pax_global_header00006660000000000000000000000064132407173260014517gustar00rootroot0000000000000052 comment=a8232337d83f519ea693f29840c59a35d3e47920 repmgr-4.0.3/000077500000000000000000000000001324071732600130175ustar00rootroot00000000000000repmgr-4.0.3/.gitignore000066400000000000000000000011111324071732600150010ustar00rootroot00000000000000# 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 /doc/Makefile # other /.lineno *.dSYM # generated binaries repmgr repmgrd repmgr4 repmgrd4 repmgr-4.0.3/CONTRIBUTING.md000066400000000000000000000024061324071732600152520ustar00rootroot00000000000000License and Contributions ========================= `repmgr` is licensed under the GPL v3. All of its code and documentation is Copyright 2010-2018, 2ndQuadrant Limited. See the files COPYRIGHT and LICENSE for details. The development of repmgr has primarily been sponsored by 2ndQuadrant customers. Additional work has been sponsored by the 4CaaST project for cloud computing, which has received funding from the European Union's Seventh Framework Programme (FP7/2007-2013) under grant agreement 258862. Contributions to `repmgr` are welcome, and will be listed in the file `CREDITS`. 2ndQuadrant Limited requires that any contributions provide a copyright assignment and a disclaimer of any work-for-hire ownership claims from the employer of the developer. This lets us make sure that all of the repmgr distribution remains free code. Please contact info@2ndQuadrant.com for a copy of the relevant Copyright Assignment Form. Code style ---------- Code in repmgr should be formatted to the same standards as the main PostgreSQL project. For more details see: https://www.postgresql.org/docs/current/static/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-4.0.3/COPYRIGHT000066400000000000000000000012641324071732600143150ustar00rootroot00000000000000Copyright (c) 2010-2018, 2ndQuadrant Limited 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-4.0.3/CREDITS000066400000000000000000000011211324071732600140320ustar00rootroot00000000000000Code 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-4.0.3/FAQ.md000066400000000000000000000004321324071732600137470ustar00rootroot00000000000000FAQ - Frequently Asked Questions about repmgr ============================================= The repmgr 4 FAQ is located here: https://repmgr.org/docs/appendix-faq.html The repmgr 3.x FAQ can be found here: https://github.com/2ndQuadrant/repmgr/blob/REL3_3_STABLE/FAQ.md repmgr-4.0.3/HISTORY000066400000000000000000000430631324071732600141110ustar00rootroot000000000000004.0.3 2018-02- 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: 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-4.0.3/LICENSE000066400000000000000000001045141324071732600140310ustar00rootroot00000000000000 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-4.0.3/Makefile.global.in000066400000000000000000000011641324071732600163250ustar00rootroot00000000000000# -*-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 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-4.0.3/Makefile.in000066400000000000000000000053641324071732600150740ustar00rootroot00000000000000# -*-makefile-*- # Makefile.in # @configure_input@ repmgr_subdir = . repmgr_top_builddir = . MODULE_big = repmgr EXTENSION = repmgr DATA = \ repmgr--unpackaged--4.0.sql \ repmgr--4.0.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) HEADERS = $(wildcard *.h) OBJS = \ repmgr.o include Makefile.global $(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-bdr.o repmgr-action-cluster.o repmgr-action-node.o \ configfile.o log.o strutil.o controldata.o dirutil.o compat.o dbutils.o REPMGRD_OBJS = repmgrd.o repmgrd-physical.o repmgrd-bdr.o configfile.o log.o dbutils.o strutil.o controldata.o compat.o DATE=$(shell date "+%Y-%m-%d") repmgr_version.h: repmgr_version.h.in sed '0,/REPMGR_VERSION_DATE/s,\(REPMGR_VERSION_DATE\).*,\1 "$(DATE)",' $< >$@ $(REPMGR_CLIENT_OBJS): repmgr-client.h repmgr_version.h repmgr: $(REPMGR_CLIENT_OBJS) $(CC) $(CFLAGS) $(REPMGR_CLIENT_OBJS) $(libpq_pgport) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X) repmgrd: $(REPMGRD_OBJS) $(CC) $(CFLAGS) $(REPMGRD_OBJS) $(libpq_pgport) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -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: $(MAKE) -C doc all install-doc: $(MAKE) -C doc install clean: additional-clean maintainer-clean: additional-maintainer-clean additional-clean: rm -f repmgr-client.o rm -f repmgr-action-primary.o rm -f repmgr-action-standby.o rm -f repmgr-action-witness.o rm -f repmgr-action-bdr.o rm -f repmgr-action-node.o rm -f repmgr-action-cluster.o rm -f repmgrd.o rm -f repmgrd-physical.o rm -f repmgrd-bdr.o rm -f compat.o rm -f configfile.o rm -f controldata.o rm -f dbutils.o rm -f dirutil.o rm -f log.o rm -f strutil.o maintainer-additional-clean: clean rm -f configure rm -f config.status config.log rm -f Makefile @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 repmgr-4.0.3/PACKAGES.md000066400000000000000000000120271324071732600145210ustar00rootroot00000000000000Packaging ========= 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-4.0.3/README.md000066400000000000000000000056641324071732600143110ustar00rootroot00000000000000repmgr: 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. `repmgr 4` is a complete rewrite of the existing `repmgr` codebase, allowing the use of all of the latest features in PostgreSQL replication. PostgreSQL 10, 9.6 and 9.5 are fully supported. PostgreSQL 9.4 and 9.3 are supported, with some restrictions. `repmgr` is distributed under the GNU GPL 3 and maintained by 2ndQuadrant. ### BDR support `repmgr 4` supports monitoring of a two-node BDR 2.0 cluster on PostgreSQL 9.6 only. Note that BDR 2.0 is not publicly available; please contact 2ndQuadrant for details. `repmgr 4` will support future public BDR releases. Documentation ------------- The main `repmgr` documentation is available here: > [repmgr 4 documentation](https://repmgr.org/docs/4.0/index.html) The `README` file for `repmgr` 3.x is available here: > https://github.com/2ndQuadrant/repmgr/blob/REL3_3_STABLE/README.md 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 ---------------------- 2ndQuadrant provides 24x7 production support for `repmgr`, including configuration assistance, installation verification and training for running a robust replication cluster. For further details see: * https://2ndquadrant.com/en/support/ 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/2ndQuadrant/repmgr Further information is available at https://www.repmgr.org/ We'd love to hear from you about how you use repmgr. Case studies and news are always welcome. Send us an email at info@2ndQuadrant.com, or send a postcard to repmgr c/o 2ndQuadrant 7200 The Quorum Oxford Business Park North Oxford OX4 2JZ United Kingdom Thanks from the repmgr core team. * Ian Barwick * Jaime Casanova * Abhijit Menon-Sen * Simon Riggs * Cedric Villemain Further reading --------------- * https://blog.2ndquadrant.com/repmgr-3-2-is-here-barman-support-brand-new-high-availability-features/ * https://blog.2ndquadrant.com/improvements-in-repmgr-3-1-4/ * https://blog.2ndquadrant.com/managing-useful-clusters-repmgr/ * https://blog.2ndquadrant.com/easier_postgresql_90_clusters/ repmgr-4.0.3/compat.c000066400000000000000000000053421324071732600144520ustar00rootroot00000000000000/* * * 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) 2ndQuadrant, 2010-2018 * * 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 appendPQExpBufferChar(buf, *p); } appendPQExpBufferChar(buf, '\''); } repmgr-4.0.3/compat.h000066400000000000000000000020151324071732600144510ustar00rootroot00000000000000/* * compat.h * Copyright (c) 2ndQuadrant, 2010-2018 * * 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); #endif repmgr-4.0.3/config.h.in000066400000000000000000000001011324071732600150320ustar00rootroot00000000000000/* config.h.in. Generated from configure.in by autoheader. */ repmgr-4.0.3/configfile.c000066400000000000000000001376051324071732600153040ustar00rootroot00000000000000/* * config.c - parse repmgr.conf and other configuration-related functionality * * Copyright (c) 2ndQuadrant, 2010-2018 * * 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" 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(t_configuration_options *options, ItemList *error_list, ItemList *warning_list); static bool parse_bool(const char *s, const char *config_item, ItemList *error_list); static void _parse_line(char *buf, char *name, char *value); static void parse_event_notifications_list(t_configuration_options *options, const char *arg); static void clear_event_notification_list(t_configuration_options *options); static void parse_time_unit_parameter(const char *name, const char *value, char *dest, ItemList *errors); static void tablespace_list_append(t_configuration_options *options, const char *arg); 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, t_configuration_options *options, 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) { appendPQExpBuffer(&fullpath, "%s", 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); } appendPQExpBuffer(&fullpath, "%s", 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: %s"), config_file, 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(options, terse); return; } void parse_config(t_configuration_options *options, bool terse) { /* Collate configuration file errors here for friendlier reporting */ static ItemList config_errors = {NULL, NULL}; static ItemList config_warnings = {NULL, NULL}; _parse_config(options, &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(t_configuration_options *options, ItemList *error_list, ItemList *warning_list) { FILE *fp; char *s = NULL, buf[MAXLINELENGTH] = ""; char name[MAXLEN] = ""; char value[MAXLEN] = ""; bool node_id_found = false; /* Initialize configuration options with sensible defaults */ /*----------------- * node information *----------------- */ options->node_id = UNKNOWN_NODE_ID; memset(options->node_name, 0, sizeof(options->node_name)); memset(options->conninfo, 0, sizeof(options->conninfo)); memset(options->data_directory, 0, sizeof(options->data_directory)); memset(options->pg_bindir, 0, sizeof(options->pg_bindir)); options->replication_type = REPLICATION_TYPE_PHYSICAL; /*------------- * log settings * * note: the default for "log_level" is set in log.c and does not need * to be initialised here *------------- */ memset(options->log_facility, 0, sizeof(options->log_facility)); memset(options->log_file, 0, sizeof(options->log_file)); options->log_status_interval = DEFAULT_LOG_STATUS_INTERVAL; /*----------------------- * standby action settings *------------------------ */ options->use_replication_slots = false; memset(options->replication_user, 0, sizeof(options->replication_user)); memset(options->pg_basebackup_options, 0, sizeof(options->pg_basebackup_options)); memset(options->restore_command, 0, sizeof(options->restore_command)); options->tablespace_mapping.head = NULL; options->tablespace_mapping.tail = NULL; memset(options->recovery_min_apply_delay, 0, sizeof(options->recovery_min_apply_delay)); options->recovery_min_apply_delay_provided = false; options->use_primary_conninfo_password = false; memset(options->passfile, 0, sizeof(options->passfile)); /*----------------- * repmgrd settings *----------------- */ options->failover = FAILOVER_MANUAL; options->priority = DEFAULT_PRIORITY; memset(options->location, 0, sizeof(options->location)); strncpy(options->location, DEFAULT_LOCATION, MAXLEN); memset(options->promote_command, 0, sizeof(options->promote_command)); memset(options->follow_command, 0, sizeof(options->follow_command)); options->monitor_interval_secs = DEFAULT_MONITORING_INTERVAL; /* default to 6 reconnection attempts at intervals of 10 seconds */ options->reconnect_attempts = DEFAULT_RECONNECTION_ATTEMPTS; options->reconnect_interval = DEFAULT_RECONNECTION_INTERVAL; options->monitoring_history = false; /* new in 4.0, replaces * --monitoring-history */ options->degraded_monitoring_timeout = -1; options->async_query_timeout = DEFAULT_ASYNC_QUERY_TIMEOUT; options->primary_notification_timeout = DEFAULT_PRIMARY_NOTIFICATION_TIMEOUT; options->primary_follow_timeout = DEFAULT_PRIMARY_FOLLOW_TIMEOUT; /*------------- * witness settings *------------- */ options->witness_sync_interval = DEFAULT_WITNESS_SYNC_INTERVAL; /*------------- * BDR settings *------------- */ options->bdr_local_monitoring_only = false; options->bdr_recovery_timeout = DEFAULT_BDR_RECOVERY_TIMEOUT; /*----------------- * service settings *----------------- */ memset(options->pg_ctl_options, 0, sizeof(options->pg_ctl_options)); memset(options->service_stop_command, 0, sizeof(options->service_stop_command)); memset(options->service_start_command, 0, sizeof(options->service_start_command)); memset(options->service_restart_command, 0, sizeof(options->service_restart_command)); memset(options->service_reload_command, 0, sizeof(options->service_reload_command)); memset(options->service_promote_command, 0, sizeof(options->service_promote_command)); /*---------------------------- * event notification settings *---------------------------- */ memset(options->event_notification_command, 0, sizeof(options->event_notification_command)); options->event_notifications.head = NULL; options->event_notifications.tail = NULL; /*---------------- * barman settings * --------------- */ memset(options->barman_host, 0, sizeof(options->barman_host)); memset(options->barman_server, 0, sizeof(options->barman_server)); memset(options->barman_config, 0, sizeof(options->barman_config)); /*------------------- * rsync/ssh settings * ------------------ */ memset(options->rsync_options, 0, sizeof(options->rsync_options)); memset(options->ssh_options, 0, sizeof(options->ssh_options)); strncpy(options->ssh_options, "-q -o ConnectTimeout=10", sizeof(options->ssh_options)); /*--------------------------- * undocumented test settings *--------------------------- */ options->promote_delay = 0; /* * 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; } fp = fopen(config_file_path, "r"); /* * A configuration file has been found, either provided by the user or * found in one of the default locations. If we can't open it, fail with * an error. */ if (fp == NULL) { if (config_file_provided) { log_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); } /* Read file */ while ((s = fgets(buf, sizeof buf, fp)) != NULL) { bool known_parameter = true; /* Parse name/value pair from line */ _parse_line(buf, name, value); /* Skip blank lines */ if (!strlen(name)) continue; /* Skip comments */ if (name[0] == '#') continue; /* Copy into correct entry in parameters struct */ if (strcmp(name, "node_id") == 0) { options->node_id = repmgr_atoi(value, name, error_list, 1); node_id_found = true; } else if (strcmp(name, "node_name") == 0) strncpy(options->node_name, value, MAXLEN); else if (strcmp(name, "conninfo") == 0) strncpy(options->conninfo, value, MAXLEN); else if (strcmp(name, "data_directory") == 0) strncpy(options->data_directory, value, MAXPGPATH); else if (strcmp(name, "replication_user") == 0) { if (strlen(value) < NAMEDATALEN) strncpy(options->replication_user, value, NAMEDATALEN); else item_list_append(error_list, _("value for \"replication_user\" must contain fewer than " STR(NAMEDATALEN) " characters")); } else if (strcmp(name, "pg_bindir") == 0) strncpy(options->pg_bindir, value, MAXPGPATH); else if (strcmp(name, "replication_type") == 0) { if (strcmp(value, "physical") == 0) options->replication_type = REPLICATION_TYPE_PHYSICAL; else if (strcmp(value, "bdr") == 0) options->replication_type = REPLICATION_TYPE_BDR; else item_list_append(error_list, _("value for \"replication_type\" must be \"physical\" or \"bdr\"")); } /* log settings */ else if (strcmp(name, "log_file") == 0) strncpy(options->log_file, value, MAXLEN); else if (strcmp(name, "log_level") == 0) strncpy(options->log_level, value, MAXLEN); else if (strcmp(name, "log_facility") == 0) strncpy(options->log_facility, value, MAXLEN); else if (strcmp(name, "log_status_interval") == 0) options->log_status_interval = repmgr_atoi(value, name, error_list, 0); /* standby clone settings */ else if (strcmp(name, "use_replication_slots") == 0) options->use_replication_slots = parse_bool(value, name, error_list); else if (strcmp(name, "pg_basebackup_options") == 0) strncpy(options->pg_basebackup_options, value, MAXLEN); else if (strcmp(name, "tablespace_mapping") == 0) tablespace_list_append(options, value); else if (strcmp(name, "restore_command") == 0) strncpy(options->restore_command, value, MAXLEN); else if (strcmp(name, "recovery_min_apply_delay") == 0) { parse_time_unit_parameter(name, value, options->recovery_min_apply_delay, error_list); options->recovery_min_apply_delay_provided = true; } else if (strcmp(name, "use_primary_conninfo_password") == 0) options->use_primary_conninfo_password = parse_bool(value, name, error_list); else if (strcmp(name, "passfile") == 0) strncpy(options->passfile, value, sizeof(options->passfile)); /* node check settings */ else if (strcmp(name, "archive_ready_warning") == 0) options->archive_ready_warning = repmgr_atoi(value, name, error_list, 1); else if (strcmp(name, "archive_ready_critcial") == 0) options->archive_ready_critical = repmgr_atoi(value, name, error_list, 1); else if (strcmp(name, "replication_lag_warning") == 0) options->replication_lag_warning = repmgr_atoi(value, name, error_list, 1); else if (strcmp(name, "replication_lag_critical") == 0) options->replication_lag_critical = repmgr_atoi(value, name, error_list, 1); /* repmgrd settings */ else if (strcmp(name, "failover") == 0) { if (strcmp(value, "manual") == 0) { options->failover = FAILOVER_MANUAL; } else if (strcmp(value, "automatic") == 0) { options->failover = FAILOVER_AUTOMATIC; } else { item_list_append(error_list, _("value for \"failover\" must be \"automatic\" or \"manual\"\n")); } } else if (strcmp(name, "priority") == 0) options->priority = repmgr_atoi(value, name, error_list, 0); else if (strcmp(name, "location") == 0) strncpy(options->location, value, MAXLEN); else if (strcmp(name, "promote_command") == 0) strncpy(options->promote_command, value, MAXLEN); else if (strcmp(name, "follow_command") == 0) strncpy(options->follow_command, value, MAXLEN); else if (strcmp(name, "reconnect_attempts") == 0) options->reconnect_attempts = repmgr_atoi(value, name, error_list, 0); else if (strcmp(name, "reconnect_interval") == 0) options->reconnect_interval = repmgr_atoi(value, name, error_list, 0); else if (strcmp(name, "monitor_interval_secs") == 0) options->monitor_interval_secs = repmgr_atoi(value, name, error_list, 1); else if (strcmp(name, "monitoring_history") == 0) options->monitoring_history = parse_bool(value, name, error_list); else if (strcmp(name, "degraded_monitoring_timeout") == 0) options->degraded_monitoring_timeout = repmgr_atoi(value, name, error_list, 1); else if (strcmp(name, "async_query_timeout") == 0) options->async_query_timeout = repmgr_atoi(value, name, error_list, 0); else if (strcmp(name, "primary_notification_timeout") == 0) options->primary_notification_timeout = repmgr_atoi(value, name, error_list, 0); else if (strcmp(name, "primary_follow_timeout") == 0) options->primary_follow_timeout = repmgr_atoi(value, name, error_list, 0); /* witness settings */ else if (strcmp(name, "witness_sync_interval") == 0) options->witness_sync_interval = repmgr_atoi(value, name, error_list, 1); /* BDR settings */ else if (strcmp(name, "bdr_local_monitoring_only") == 0) options->bdr_local_monitoring_only = parse_bool(value, name, error_list); else if (strcmp(name, "bdr_recovery_timeout") == 0) options->bdr_recovery_timeout = repmgr_atoi(value, name, error_list, 0); /* service settings */ else if (strcmp(name, "pg_ctl_options") == 0) strncpy(options->pg_ctl_options, value, MAXLEN); else if (strcmp(name, "service_stop_command") == 0) strncpy(options->service_stop_command, value, MAXLEN); else if (strcmp(name, "service_start_command") == 0) strncpy(options->service_start_command, value, MAXLEN); else if (strcmp(name, "service_restart_command") == 0) strncpy(options->service_restart_command, value, MAXLEN); else if (strcmp(name, "service_reload_command") == 0) strncpy(options->service_reload_command, value, MAXLEN); else if (strcmp(name, "service_promote_command") == 0) strncpy(options->service_promote_command, value, MAXLEN); /* event notification settings */ else if (strcmp(name, "event_notification_command") == 0) strncpy(options->event_notification_command, value, MAXLEN); else if (strcmp(name, "event_notifications") == 0) { /* store unparsed value for comparison when reloading config */ strncpy(options->event_notifications_orig, value, MAXLEN); parse_event_notifications_list(options, value); } /* barman settings */ else if (strcmp(name, "barman_host") == 0) strncpy(options->barman_host, value, MAXLEN); else if (strcmp(name, "barman_server") == 0) strncpy(options->barman_server, value, MAXLEN); else if (strcmp(name, "barman_config") == 0) strncpy(options->barman_config, value, MAXLEN); /* rsync/ssh settings */ else if (strcmp(name, "rsync_options") == 0) strncpy(options->rsync_options, value, MAXLEN); else if (strcmp(name, "ssh_options") == 0) strncpy(options->ssh_options, value, MAXLEN); /* undocumented settings for testing */ else if (strcmp(name, "promote_delay") == 0) options->promote_delay = repmgr_atoi(value, name, error_list, 1); /* * Following parameters have been deprecated or renamed from 3.x - * issue a warning */ else if (strcmp(name, "cluster") == 0) { item_list_append(warning_list, _("parameter \"cluster\" is deprecated and will be ignored")); known_parameter = false; } else if (strcmp(name, "node") == 0) { item_list_append(warning_list, _("parameter \"node\" has been renamed to \"node_id\"")); known_parameter = false; } 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")); known_parameter = false; } else if (strcmp(name, "loglevel") == 0) { item_list_append(warning_list, _("parameter \"loglevel\" has been renamed to \"log_level\"")); known_parameter = false; } else if (strcmp(name, "logfacility") == 0) { item_list_append(warning_list, _("parameter \"logfacility\" has been renamed to \"log_facility\"")); known_parameter = false; } else if (strcmp(name, "logfile") == 0) { item_list_append(warning_list, _("parameter \"logfile\" has been renamed to \"log_file\"")); known_parameter = false; } 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")); known_parameter = false; } 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")); known_parameter = false; } else { known_parameter = false; log_warning(_("%s/%s: unknown name/value pair provided; ignoring"), name, value); } /* * Raise an error if a known parameter is provided with an empty * value. Currently there's no reason why empty parameters are needed; * if we want to accept those, we'd need to add stricter default * checking, as currently e.g. an empty `node_id` value will be converted * to '0'. */ if (known_parameter == true && !strlen(value)) { char error_message_buf[MAXLEN] = ""; maxlen_snprintf(error_message_buf, _("\"%s\": no value provided"), name); item_list_append(error_list, error_message_buf); } } fclose(fp); /* check required parameters */ if (node_id_found == false) { item_list_append(error_list, _("\"node_id\": required parameter was not found")); } if (!strlen(options->node_name)) { item_list_append(error_list, _("\"node_name\": required parameter was not found")); } if (!strlen(options->data_directory)) { item_list_append(error_list, _("\"data_directory\": required parameter was not found")); } if (!strlen(options->conninfo)) { item_list_append(error_list, _("\"conninfo\": required parameter was not found")); } else { /* * Sanity check the provided conninfo string * * NOTE: PQconninfoParse() verifies the string format and checks for * valid options but does not sanity check values */ PQconninfoOption *conninfo_options = NULL; char *conninfo_errmsg = NULL; conninfo_options = PQconninfoParse(options->conninfo, &conninfo_errmsg); if (conninfo_options == NULL) { char error_message_buf[MAXLEN] = ""; snprintf(error_message_buf, MAXLEN, _("\"conninfo\": %s (provided: \"%s\")"), conninfo_errmsg, options->conninfo); item_list_append(error_list, error_message_buf); } PQconninfoFree(conninfo_options); } /* add warning about changed "barman_" parameter meanings */ if ((options->barman_host[0] == '\0' && options->barman_server[0] != '\0') || (options->barman_host[0] != '\0' && 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 (options->archive_ready_warning >= options->archive_ready_critical) { item_list_append(error_list, _("\archive_ready_critical\" must be greater than \"archive_ready_warning\"")); } if (options->replication_lag_warning >= options->replication_lag_critical) { item_list_append(error_list, _("\replication_lag_critical\" must be greater than \"replication_lag_warning\"")); } } 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); } static 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 < 1) { if (errors != NULL) { item_list_append_format( errors, _("invalid value provided for \"%s\""), 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 provided for \"%s\" must be one of ms/s/min/h/d"), name); 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: * - async_query_timeout * - bdr_local_monitoring_only * - bdr_recovery_timeout * - conninfo * - degraded_monitoring_timeout * - event_notification_command * - event_notifications * - failover * - follow_command * - log_facility * - log_file * - log_level * - log_status_interval * - monitor_interval_secs * - monitoring_history * - promote_command * - promote_delay * - reconnect_attempts * - reconnect_interval * - retry_promote_interval_secs * * non-changeable options * * - node_id * - node_name * - data_directory * - 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 */ bool reload_config(t_configuration_options *orig_options) { PGconn *conn; t_configuration_options new_options = T_CONFIGURATION_OPTIONS_INITIALIZER; bool config_changed = false; bool log_config_changed = false; static ItemList config_errors = {NULL, NULL}; static ItemList config_warnings = {NULL, NULL}; log_info(_("reloading configuration file")); _parse_config(&new_options, &config_errors, &config_warnings); if (config_errors.head != NULL) { /* XXX dump errors to log */ log_warning(_("unable to parse new configuration, retaining current configuration")); return false; } /* The following options cannot be changed */ if (new_options.node_id != orig_options->node_id) { log_warning(_("\"node_id\" cannot be changed, retaining current configuration")); return false; } if (strcmp(new_options.node_name, orig_options->node_name) != 0) { log_warning(_("\"node_name\" cannot be changed, keeping current configuration")); return false; } /* * No configuration problems detected - copy 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 (orig_options->async_query_timeout != new_options.async_query_timeout) { orig_options->async_query_timeout = new_options.async_query_timeout; log_info(_("\"async_query_timeout\" is now \"%i\""), new_options.async_query_timeout); config_changed = true; } /* bdr_local_monitoring_only */ if (orig_options->bdr_local_monitoring_only != new_options.bdr_local_monitoring_only) { orig_options->bdr_local_monitoring_only = new_options.bdr_local_monitoring_only; log_info(_("\"bdr_local_monitoring_only\" is now \"%s\""), new_options.bdr_local_monitoring_only == true ? "TRUE" : "FALSE"); config_changed = true; } /* bdr_recovery_timeout */ if (orig_options->bdr_recovery_timeout != new_options.bdr_recovery_timeout) { orig_options->bdr_recovery_timeout = new_options.bdr_recovery_timeout; log_info(_("\"bdr_recovery_timeout\" is now \"%i\""), new_options.bdr_recovery_timeout); config_changed = true; } /* conninfo */ if (strcmp(orig_options->conninfo, new_options.conninfo) != 0) { /* Test conninfo string works */ conn = establish_db_connection(new_options.conninfo, false); if (!conn || (PQstatus(conn) != CONNECTION_OK)) { log_warning(_("\"conninfo\" string is not valid, retaining current configuration")); } else { strncpy(orig_options->conninfo, new_options.conninfo, MAXLEN); log_info(_("\"conninfo\" is now \"%s\""), new_options.conninfo); } PQfinish(conn); } /* degraded_monitoring_timeout */ if (orig_options->degraded_monitoring_timeout != new_options.degraded_monitoring_timeout) { orig_options->degraded_monitoring_timeout = new_options.degraded_monitoring_timeout; log_info(_("\"degraded_monitoring_timeout\" is now \"%i\""), new_options.degraded_monitoring_timeout); config_changed = true; } /* event_notification_command */ if (strcmp(orig_options->event_notification_command, new_options.event_notification_command) != 0) { strncpy(orig_options->event_notification_command, new_options.event_notification_command, MAXLEN); log_info(_("\"event_notification_command\" is now \"%s\""), new_options.event_notification_command); config_changed = true; } /* event_notifications */ if (strcmp(orig_options->event_notifications_orig, new_options.event_notifications_orig) != 0) { strncpy(orig_options->event_notifications_orig, new_options.event_notifications_orig, MAXLEN); log_info(_("\"event_notifications\" is now \"%s\""), new_options.event_notifications_orig); clear_event_notification_list(orig_options); orig_options->event_notifications = new_options.event_notifications; config_changed = true; } /* failover */ if (orig_options->failover != new_options.failover) { orig_options->failover = new_options.failover; log_info(_("\"failover\" is now \"%s\""), new_options.failover == true ? "TRUE" : "FALSE"); config_changed = true; } /* follow_command */ if (strcmp(orig_options->follow_command, new_options.follow_command) != 0) { strncpy(orig_options->follow_command, new_options.follow_command, MAXLEN); log_info(_("\"follow_command\" is now \"%s\""), new_options.follow_command); config_changed = true; } /* monitor_interval_secs */ if (orig_options->monitor_interval_secs != new_options.monitor_interval_secs) { orig_options->monitor_interval_secs = new_options.monitor_interval_secs; log_info(_("\"monitor_interval_secs\" is now \"%i\""), new_options.monitor_interval_secs); config_changed = true; } /* monitoring_history */ if (orig_options->monitoring_history != new_options.monitoring_history) { orig_options->monitoring_history = new_options.monitoring_history; log_info(_("\"monitoring_history\" is now \"%s\""), new_options.monitoring_history == true ? "TRUE" : "FALSE"); config_changed = true; } /* primary_notification_timeout */ if (orig_options->primary_notification_timeout != new_options.primary_notification_timeout) { orig_options->primary_notification_timeout = new_options.primary_notification_timeout; log_info(_("\"primary_notification_timeout\" is now \"%i\""), new_options.primary_notification_timeout); config_changed = true; } /* promote_command */ if (strcmp(orig_options->promote_command, new_options.promote_command) != 0) { strncpy(orig_options->promote_command, new_options.promote_command, MAXLEN); log_info(_("\"promote_command\" is now \"%s\""), new_options.promote_command); config_changed = true; } /* promote_delay */ if (orig_options->promote_delay != new_options.promote_delay) { orig_options->promote_delay = new_options.promote_delay; log_info(_("\"promote_delay\" is now \"%i\""), new_options.promote_delay); config_changed = true; } /* reconnect_attempts */ if (orig_options->reconnect_attempts != new_options.reconnect_attempts) { orig_options->reconnect_attempts = new_options.reconnect_attempts; log_info(_("\"reconnect_attempts\" is now \"%i\""), new_options.reconnect_attempts); config_changed = true; } /* reconnect_interval */ if (orig_options->reconnect_interval != new_options.reconnect_interval) { orig_options->reconnect_interval = new_options.reconnect_interval; log_info(_("\"reconnect_interval\" is now \"%i\""), new_options.reconnect_interval); config_changed = true; } /* * Handle changes to logging configuration */ /* log_facility */ if (strcmp(orig_options->log_facility, new_options.log_facility) != 0) { strcpy(orig_options->log_facility, new_options.log_facility); log_info(_("\"log_facility\" is now \"%s\""), new_options.log_facility); log_config_changed = true; } /* log_file */ if (strcmp(orig_options->log_file, new_options.log_file) != 0) { strcpy(orig_options->log_file, new_options.log_file); log_info(_("\"log_file\" is now \"%s\""), new_options.log_file); log_config_changed = true; } /* log_level */ if (strcmp(orig_options->log_level, new_options.log_level) != 0) { strcpy(orig_options->log_level, new_options.log_level); log_info(_("\"log_level\" is now \"%s\""), new_options.log_level); log_config_changed = true; } /* log_status_interval */ if (orig_options->log_status_interval != new_options.log_status_interval) { orig_options->log_status_interval = new_options.log_status_interval; log_info(_("\"log_status_interval\" is now \"%i\""), new_options.log_status_interval); config_changed = true; } if (log_config_changed == true) { log_notice(_("restarting logging with changed parameters")); logger_shutdown(); logger_init(orig_options, progname()); log_notice(_("configuration file reloaded with changed parameters")); } if (config_changed == true) { log_info(_("configuration has changed")); } /* * neither logging nor other configuration has changed */ if (log_config_changed == false && config_changed == false) { log_info(_("configuration has not changed")); } return config_changed; } 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) { fprintf(stderr, _("The following command line errors were encountered:\n")); print_item_list(error_list); 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; } /* * 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/static/config-setting.html */ static bool parse_bool(const char *s, const char *config_item, ItemList *error_list) { PQExpBufferData errors; 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; } /* * Split argument into old_dir and new_dir and append to tablespace mapping * list. * * Adapted from pg_basebackup.c */ static void tablespace_list_append(t_configuration_options *options, 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 (options->tablespace_mapping.tail) options->tablespace_mapping.tail->next = cell; else options->tablespace_mapping.head = cell; options->tablespace_mapping.tail = cell; } /* * parse_event_notifications_list() * * */ static void parse_event_notifications_list(t_configuration_options *options, 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 (options->event_notifications.tail) { options->event_notifications.tail->next = cell; } else { options->event_notifications.head = cell; } options->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(t_configuration_options *options) { if (options->event_notifications.head != NULL) { EventNotificationListCell *cell; EventNotificationListCell *next_cell; cell = options->event_notifications.head; while (cell != NULL) { next_cell = cell->next; pfree(cell); cell = next_cell; } } } 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_9[] = { {"slot", required_argument, NULL, 'S'}, {"xlog-method", required_argument, NULL, 'X'}, {NULL, 0, NULL, 0} }; /* * From PostgreSQL 10, --xlog-method is renamed --wal-method and there's * also --no-slot, which we'll want to consider. */ static struct option long_options_10[] = { {"slot", required_argument, NULL, 'S'}, {"wal-method", required_argument, NULL, 'X'}, {"no-slot", no_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_9; 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->xlog_method, optarg, MAXLEN); break; case 1: backup_options->no_slot = true; break; case '?': if (server_version_num >= 100000 && optopt == 1) { 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; } free_parsed_argv(&argv_array); return backup_options_ok; } repmgr-4.0.3/configfile.h000066400000000000000000000164761324071732600153130ustar00rootroot00000000000000/* * configfile.h * * Copyright (c) 2ndQuadrant, 2010-2018 * * * 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 extern bool config_file_found; extern char config_file_path[MAXPGPATH]; typedef enum { FAILOVER_MANUAL, FAILOVER_AUTOMATIC } failover_mode_opt; 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 struct { /* node information */ int node_id; char node_name[MAXLEN]; char conninfo[MAXLEN]; char replication_user[NAMEDATALEN]; char data_directory[MAXPGPATH]; char pg_bindir[MAXPGPATH]; int replication_type; /* log settings */ char log_level[MAXLEN]; char log_facility[MAXLEN]; char log_file[MAXLEN]; int log_status_interval; /* standby action 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; bool use_primary_conninfo_password; char passfile[MAXPGPATH]; /* 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 primary_follow_timeout; /* BDR settings */ bool bdr_local_monitoring_only; bool bdr_recovery_timeout; /* service settings */ char pg_ctl_options[MAXLEN]; char service_stop_command[MAXLEN]; char service_start_command[MAXLEN]; char service_restart_command[MAXLEN]; char service_reload_command[MAXLEN]; char service_promote_command[MAXLEN]; /* event notification settings */ char event_notification_command[MAXLEN]; 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 test settings */ int promote_delay; } t_configuration_options; /* * The following will initialize the structure with a minimal set of options; * actual defaults are set in parse_config() before parsing the configuration file */ #define T_CONFIGURATION_OPTIONS_INITIALIZER { \ /* node information */ \ UNKNOWN_NODE_ID, "", "", "", "", "", REPLICATION_TYPE_PHYSICAL, \ /* log settings */ \ "", "", "", DEFAULT_LOG_STATUS_INTERVAL, \ /* standby action settings */ \ false, "", "", { NULL, NULL }, "", false, false, "", \ /* node check settings */ \ DEFAULT_ARCHIVE_READY_WARNING, DEFAULT_ARCHIVE_READY_CRITICAL, \ DEFAULT_REPLICATION_LAG_WARNING, DEFAULT_REPLICATION_LAG_CRITICAL, \ /* witness settings */ \ DEFAULT_WITNESS_SYNC_INTERVAL, \ /* repmgrd settings */ \ FAILOVER_MANUAL, DEFAULT_LOCATION, DEFAULT_PRIORITY, "", "", \ DEFAULT_MONITORING_INTERVAL, \ DEFAULT_RECONNECTION_ATTEMPTS, \ DEFAULT_RECONNECTION_INTERVAL, \ false, -1, \ DEFAULT_ASYNC_QUERY_TIMEOUT, \ DEFAULT_PRIMARY_NOTIFICATION_TIMEOUT, \ DEFAULT_PRIMARY_FOLLOW_TIMEOUT, \ /* BDR settings */ \ false, DEFAULT_BDR_RECOVERY_TIMEOUT, \ /* service settings */ \ "", "", "", "", "", "", \ /* event notification settings */ \ "", "", { NULL, NULL }, \ /* barman settings */ \ "", "", "", \ /* rsync/ssh settings */ \ "", "", \ /* undocumented test settings */ \ 0 \ } typedef struct { char slot[MAXLEN]; char xlog_method[MAXLEN]; 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, \ "", "", "", "" \ } void set_progname(const char *argv0); const char *progname(void); void load_config(const char *config_file, bool verbose, bool terse, t_configuration_options *options, char *argv0); void parse_config(t_configuration_options *options, bool terse); bool reload_config(t_configuration_options *orig_options); bool parse_recovery_conf(const char *data_dir, t_recovery_conf *conf); int repmgr_atoi(const char *s, const char *config_item, ItemList *error_list, int minval); 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); /* called by repmgr-client and repmgrd */ void exit_with_cli_errors(ItemList *error_list); void print_item_list(ItemList *item_list); #endif /* _REPMGR_CONFIGFILE_H_ */ repmgr-4.0.3/configure000077500000000000000000002653721324071732600147450ustar00rootroot00000000000000#! /bin/sh # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.69 for repmgr 4.0.3. # # 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-2018, 2ndQuadrant Ltd. ## -------------------- ## ## 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: pgsql-bugs@postgresql.org 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='4.0.3' PACKAGE_STRING='repmgr 4.0.3' PACKAGE_BUGREPORT='pgsql-bugs@postgresql.org' PACKAGE_URL='https://2ndquadrant.com/en/resources/repmgr/' ac_subst_vars='LTLIBOBJS LIBOBJS 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 4.0.3 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 4.0.3:";; 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 4.0.3 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-2018, 2ndQuadrant Ltd. _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 4.0.3, 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/^PostgreSQL \([0-9]\{1,2\}\).*$/\1/') if test "$major_version_num" -lt '10'; then version_num=$(echo "$pgac_pg_config_version"| $SED -e 's/^PostgreSQL \([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 '93'; 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/^PostgreSQL \(.\+\)$/\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 ac_config_files="$ac_config_files Makefile" ac_config_files="$ac_config_files Makefile.global" ac_config_files="$ac_config_files doc/Makefile" 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 4.0.3, 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 4.0.3 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" ;; "doc/Makefile") CONFIG_FILES="$CONFIG_FILES doc/Makefile" ;; *) 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-4.0.3/configure.in000066400000000000000000000036201324071732600153310ustar00rootroot00000000000000AC_INIT([repmgr], [4.0.3], [pgsql-bugs@postgresql.org], [repmgr], [https://2ndquadrant.com/en/resources/repmgr/]) AC_COPYRIGHT([Copyright (c) 2010-2018, 2ndQuadrant Ltd.]) 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/^PostgreSQL \([[0-9]]\{1,2\}\).*$/\1/') if test "$major_version_num" -lt '10'; then version_num=$(echo "$pgac_pg_config_version"| $SED -e 's/^PostgreSQL \([[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 '93'; 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/^PostgreSQL \(.\+\)$/\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_CONFIG_FILES([Makefile]) AC_CONFIG_FILES([Makefile.global]) AC_CONFIG_FILES([doc/Makefile]) AC_OUTPUT repmgr-4.0.3/contrib/000077500000000000000000000000001324071732600144575ustar00rootroot00000000000000repmgr-4.0.3/contrib/convert-config.pl000077500000000000000000000032621324071732600177450ustar00rootroot00000000000000#!/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; } push @outp, $line; } } close $fh; print join("\n", @outp); print "\n"; if ($data_directory_found == 0) { print "data_directory=\n"; } repmgr-4.0.3/controldata.c000066400000000000000000000113731324071732600155020ustar00rootroot00000000000000/* * controldata.c * Copyright (c) 2ndQuadrant, 2010-2018 * * 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); 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->control_file->system_identifier; else system_identifier = UNKNOWN_SYSTEM_IDENTIFIER; pfree(control_file_info->control_file); pfree(control_file_info); return system_identifier; } DBState get_db_state(const char *data_directory) { ControlFileInfo *control_file_info = NULL; DBState state; control_file_info = get_controlfile(data_directory); if (control_file_info->control_file_processed == true) state = control_file_info->control_file->state; else /* if we were unable to parse the control file, assume DB is shut down */ state = DB_SHUTDOWNED; pfree(control_file_info->control_file); pfree(control_file_info); return state; } extern 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 == false) return InvalidXLogRecPtr; checkPoint = control_file_info->control_file->checkPoint; pfree(control_file_info->control_file); pfree(control_file_info); return checkPoint; } int get_data_checksum_version(const char *data_directory) { ControlFileInfo *control_file_info = NULL; int data_checksum_version = -1; control_file_info = get_controlfile(data_directory); if (control_file_info->control_file_processed == false) { data_checksum_version = -1; } else { data_checksum_version = (int) control_file_info->control_file->data_checksum_version; } pfree(control_file_info->control_file); 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"); } /* * 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) { ControlFileInfo *control_file_info; int fd; char ControlFilePath[MAXPGPATH] = ""; control_file_info = palloc0(sizeof(ControlFileInfo)); control_file_info->control_file_processed = false; control_file_info->control_file = palloc0(sizeof(ControlFileData)); snprintf(ControlFilePath, MAXPGPATH, "%s/global/pg_control", DataDir); if ((fd = open(ControlFilePath, O_RDONLY | PG_BINARY, 0)) == -1) { log_debug("could not open file \"%s\" for reading: %s", ControlFilePath, strerror(errno)); return control_file_info; } if (read(fd, control_file_info->control_file, sizeof(ControlFileData)) != sizeof(ControlFileData)) { log_debug("could not read file \"%s\": %s", ControlFilePath, strerror(errno)); return control_file_info; } close(fd); control_file_info->control_file_processed = true; /* * 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. However we're only interested in the first few fields, which * should be constant across supported versions * */ return control_file_info; } repmgr-4.0.3/controldata.h000066400000000000000000000014071324071732600155040ustar00rootroot00000000000000/* * controldata.h * Copyright (c) 2ndQuadrant, 2010-2018 * * 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" typedef struct { bool control_file_processed; ControlFileData *control_file; } ControlFileInfo; extern DBState get_db_state(const char *data_directory); 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); #endif /* _CONTROLDATA_H_ */ repmgr-4.0.3/dbutils.c000066400000000000000000003234171324071732600146430ustar00rootroot00000000000000/* * dbutils.c - Database connection/management functions * * Copyright (c) 2ndQuadrant, 2010-2018 * * * 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 "repmgr.h" #include "dbutils.h" #include "controldata.h" #include "dirutil.h" /* mainly for use by repmgrd */ int server_version_num = UNKNOWN_SERVER_VERSION_NUM; static PGconn *_establish_db_connection(const char *conninfo, const bool exit_on_error, const bool log_notice, const bool verbose_only); 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 RecordStatus _get_node_record(PGconn *conn, char *sqlquery, t_node_info *node_info); static void _populate_node_record(PGresult *res, t_node_info *node_info, int row); 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 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); static bool _is_bdr_db(PGconn *conn, PQExpBufferData *output, bool quiet); static void _populate_bdr_node_record(PGresult *res, t_bdr_node_info *node_info, int row); static void _populate_bdr_node_records(PGresult *res, BdrNodeInfoList *node_list); /* ================= */ /* 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; } /* * Wrap query with appropriate DDL function, if required. */ void wrap_ddl_query(PQExpBufferData *query_buf, int replication_type, const char *fmt,...) { va_list arglist; char buf[MAXLEN]; if (replication_type == REPLICATION_TYPE_BDR) { appendPQExpBuffer(query_buf, "SELECT bdr.bdr_replicate_ddl_command($repmgr$"); } va_start(arglist, fmt); vsnprintf(buf, MAXLEN, fmt, arglist); va_end(arglist); appendPQExpBuffer(query_buf, "%s", buf); if (replication_type == REPLICATION_TYPE_BDR) { appendPQExpBuffer(query_buf, "$repmgr$)"); } } /* ==================== */ /* 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 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 pass provided conninfo string:\n %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"); 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:\n %s"), PQerrorMessage(conn)); } else { log_error(_("connection to database failed:\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 (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_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_as_user(const char *conninfo, const char *user, const bool exit_on_error) { PGconn *conn = NULL; t_conninfo_param_list conninfo_params = T_CONNINFO_PARAM_LIST_INITIALIZER; bool parse_success = false; char *errmsg = NULL; initialize_conninfo_params(&conninfo_params, false); parse_success = parse_conninfo_string(conninfo, &conninfo_params, errmsg, true); if (parse_success == false) { log_error(_("unable to pass provided conninfo string:\n %s"), errmsg); return NULL; } param_set(&conninfo_params, "user", user); conn = establish_db_connection_by_params(&conninfo_params, false); return conn; } 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"); /* 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:\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; } bool is_superuser_connection(PGconn *conn, t_connection_user *userinfo) { char *current_user = NULL; const char *superuser_status = NULL; bool is_superuser = false; current_user = PQuser(conn); superuser_status = PQparameterStatus(conn, "is_superuser"); is_superuser = (strcmp(superuser_status, "on") == 0) ? true : false; if (userinfo != NULL) { strncpy(userinfo->username, current_user, MAXLEN); userinfo->is_superuser = is_superuser; } return is_superuser; } /* =============================== */ /* 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; } 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; /* * 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; } } /* * Parameter not in array - add it and its associated value */ if (c < param_list->size) { int 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); } /* * It's theoretically possible a parameter couldn't be added as the array * is full, but it's highly improbable so we won't handle it at the * moment. */ } /* * 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; /* * 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; } } /* * Parameter not in array - add it and its associated value */ if (c < param_list->size) { int 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; } /* * Parse a conninfo string into a t_conninfo_param_list * * See conn_to_param_list() to do the same for a PQconn * * "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 != 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 PQconn into a t_conninfo_param_list * * See parse_conninfo_string() to do the same for a conninfo string */ 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 != NULL && option->val[0] == '\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; } /* * 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:\n %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:\n %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:\n %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) { PGresult *res = NULL; res = PQexec(conn, sqlquery); if (PQresultStatus(res) != PGRES_COMMAND_OK) { log_error("unable to set \"%s\": %s", config_param, PQerrorMessage(conn)); PQclear(res); return false; } PQclear(res); return true; } 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); termPQExpBuffer(&query); pfree(escaped_parameter); pfree(escaped_value); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_error(_("guc_set(): unable to execute query\n%s"), PQerrorMessage(conn)); retval = -1; } else if (PQntuples(res) == 0) { retval = 0; } PQclear(res); return retval; } /** * Just like guc_set except with an extra parameter containing the name of * the pg datatype so that the comparison can be done properly. */ int guc_set_typed(PGconn *conn, const char *parameter, const char *op, const char *value, const char *datatype) { 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 '%s'::%s", parameter, datatype, op, value, datatype); log_verbose(LOG_DEBUG, "guc_set_typed():\n%s", query.data); res = PQexec(conn, query.data); termPQExpBuffer(&query); pfree(escaped_parameter); pfree(escaped_value); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_error(_("guc_set_typed(): unable to execute query\n %s"), PQerrorMessage(conn)); retval = -1; } else if (PQntuples(res) == 0) { retval = 0; } PQclear(res); return retval; } bool get_pg_setting(PGconn *conn, const char *setting, char *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); termPQExpBuffer(&query); pfree(escaped_setting); if (res == NULL || PQresultStatus(res) != PGRES_TUPLES_OK) { log_error(_("get_pg_setting() - PQexec failed: %s"), PQerrorMessage(conn)); PQclear(res); return false; } for (i = 0; i < PQntuples(res); i++) { if (strcmp(PQgetvalue(res, i, 0), setting) == 0) { strncpy(output, PQgetvalue(res, i, 1), MAXLEN); success = true; break; } else { /* XXX highly unlikely this would ever happen */ log_error(_("get_pg_setting(): unknown parameter \"%s\""), PQgetvalue(res, i, 0)); } } if (success == true) { log_verbose(LOG_DEBUG, _("get_pg_setting(): returned value is \"%s\""), output); } PQclear(res); return success; } /* ============================ */ /* Server information functions */ /* ============================ */ bool get_cluster_size(PGconn *conn, char *size) { PQExpBufferData query; PGresult *res = NULL; initPQExpBuffer(&query); appendPQExpBuffer(&query, "SELECT pg_catalog.pg_size_pretty(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); termPQExpBuffer(&query); if (res == NULL || PQresultStatus(res) != PGRES_TUPLES_OK) { log_error(_("get_cluster_size(): unable to execute query\n%s"), PQerrorMessage(conn)); PQclear(res); return false; } strncpy(size, PQgetvalue(res, 0, 0), MAXLEN); PQclear(res); return true; } /* * Return the server version number for the connection provided */ int get_server_version(PGconn *conn, char *server_version) { PGresult *res = NULL; int server_version_num; res = PQexec(conn, "SELECT pg_catalog.current_setting('server_version_num'), " " pg_catalog.current_setting('server_version')"); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_error(_("unable to determine server version number:\n%s"), PQerrorMessage(conn)); PQclear(res); return -1; } if (server_version != NULL) strcpy(server_version, PQgetvalue(res, 0, 1)); server_version_num = atoi(PQgetvalue(res, 0, 0)); PQclear(res); return server_version_num; } RecoveryType get_recovery_type(PGconn *conn) { PGresult *res = NULL; RecoveryType recovery_type = RECTYPE_UNKNOWN; 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_error(_("unable to determine if server is in recovery:\n %s"), PQerrorMessage(conn)); 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); appendPQExpBuffer(&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_error(_("unable to retrieve node records:\n %s"), PQerrorMessage(conn)); 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)); strncpy(remote_conninfo, PQgetvalue(res, i, 1), MAXCONNINFO); 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_error(_("unable to retrieve recovery state from node %i:\n %s"), node_id, PQerrorMessage(remote_conn)); 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; } 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); } /* * 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); appendPQExpBuffer(&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); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_error(_("get_primary_node_id(): query failed\n %s"), PQerrorMessage(conn)); retval = NODE_NOT_FOUND; } 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)); } PQclear(res); return retval; } bool get_replication_info(PGconn *conn, ReplInfo *replication_info) { PQExpBufferData query; PGresult *res = NULL; if (server_version_num == UNKNOWN_SERVER_VERSION_NUM) server_version_num = get_server_version(conn, NULL); initPQExpBuffer(&query); appendPQExpBuffer(&query, " SELECT ts, " " 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 " " EXTRACT(epoch FROM (pg_catalog.clock_timestamp() - last_xact_replay_timestamp))::INT " " END AS replication_lag_time, " " COALESCE(last_wal_receive_lsn, '0/0') >= last_wal_replay_lsn AS receiving_streamed_wal " " FROM ( "); if (server_version_num >= 100000) { appendPQExpBuffer(&query, " SELECT CURRENT_TIMESTAMP AS ts, " " pg_catalog.pg_last_wal_receive_lsn() AS last_wal_receive_lsn, " " pg_catalog.pg_last_wal_replay_lsn() AS last_wal_replay_lsn, " " pg_catalog.pg_last_xact_replay_timestamp() AS last_xact_replay_timestamp "); } else { appendPQExpBuffer(&query, " SELECT CURRENT_TIMESTAMP AS ts, " " pg_catalog.pg_last_xlog_receive_location() AS last_wal_receive_lsn, " " pg_catalog.pg_last_xlog_replay_location() AS last_wal_replay_lsn, " " pg_catalog.pg_last_xact_replay_timestamp() AS last_xact_replay_timestamp "); } appendPQExpBuffer(&query, " ) q "); log_verbose(LOG_DEBUG, "get_replication_info():\n%s", query.data); res = PQexec(conn, query.data); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_TUPLES_OK || !PQntuples(res)) { log_error(_("unable to execute replication info query:\n %s"), PQerrorMessage(conn)); PQclear(res); return false; } strncpy(replication_info->current_timestamp, PQgetvalue(res, 0, 0), MAXLEN); replication_info->last_wal_receive_lsn = parse_lsn(PQgetvalue(res, 0, 1)); replication_info->last_wal_replay_lsn = parse_lsn(PQgetvalue(res, 0, 2)); strncpy(replication_info->last_xact_replay_timestamp, PQgetvalue(res, 0, 3), MAXLEN); replication_info->replication_lag_time = atoi(PQgetvalue(res, 0, 4)); replication_info->receiving_streamed_wal = atobool(PQgetvalue(res, 0, 5)); PQclear(res); return true; } bool can_use_pg_rewind(PGconn *conn, const char *data_directory, PQExpBufferData *reason) { bool can_use = true; if (server_version_num == UNKNOWN_SERVER_VERSION_NUM) server_version_num = get_server_version(conn, NULL); if (server_version_num < 90500) { appendPQExpBuffer(reason, _("pg_rewind available from PostgreSQL 9.5")); return false; } if (guc_set(conn, "full_page_writes", "=", "off")) { if (can_use == false) appendPQExpBuffer(reason, "; "); 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 < 0) { if (can_use == false) appendPQExpBuffer(reason, "; "); appendPQExpBuffer(reason, _("\"wal_log_hints\" is set to \"off\" but unable to determine 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 checksums are disabled")); can_use = false; } } return can_use; } 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 (server_version_num == UNKNOWN_SERVER_VERSION_NUM) server_version_num = get_server_version(conn, NULL); if (server_version_num >= 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)); /* XXX magic number */ return -1; } arcdir = opendir(archive_status_dir); if (arcdir == NULL) { log_error(_("unable to open archive directory \"%s\""), archive_status_dir); log_detail("%s", strerror(errno)); /* XXX magic number */ return -1; } while ((arcdir_ent = readdir(arcdir)) != NULL) { struct stat statbuf; char file_path[MAXPGPATH] = ""; int basenamelen = 0; snprintf(file_path, MAXPGPATH, "%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; } int get_replication_lag_seconds(PGconn *conn) { PQExpBufferData query; PGresult *res = NULL; int lag_seconds = 0; if (server_version_num == UNKNOWN_SERVER_VERSION_NUM) server_version_num = get_server_version(conn, NULL); initPQExpBuffer(&query); if (server_version_num >= 100000) { appendPQExpBuffer(&query, " SELECT CASE WHEN (pg_catalog.pg_last_wal_receive_lsn() = pg_catalog.pg_last_wal_replay_lsn()) "); } else { appendPQExpBuffer(&query, " SELECT CASE WHEN (pg_catalog.pg_last_xlog_receive_location() = pg_catalog.pg_last_xlog_replay_location()) "); } appendPQExpBuffer(&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); /* XXX magic number */ return -1; } if (!PQntuples(res)) { return -1; } lag_seconds = atoi(PQgetvalue(res, 0, 0)); PQclear(res); return lag_seconds; } bool identify_system(PGconn *repl_conn, t_system_identification *identification) { PGresult *res = NULL; res = PQexec(repl_conn, "IDENTIFY_SYSTEM;"); if (PQresultStatus(res) != PGRES_TUPLES_OK || !PQntuples(res)) { PQclear(res); return false; } identification->system_identifier = atol(PQgetvalue(res, 0, 0)); identification->timeline = atoi(PQgetvalue(res, 0, 1)); identification->xlogpos = parse_lsn(PQgetvalue(res, 0, 2)); PQclear(res); return true; } bool repmgrd_set_local_node_id(PGconn *conn, int local_node_id) { PQExpBufferData query; PGresult *res = NULL; initPQExpBuffer(&query); appendPQExpBuffer(&query, "SELECT repmgr.set_local_node_id(%i)", local_node_id); res = PQexec(conn, query.data); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_TUPLES_OK) { PQclear(res); return false; } PQclear(res); return true; } int repmgrd_get_local_node_id(PGconn *conn) { PGresult *res = NULL; int local_node_id = UNKNOWN_NODE_ID; res = PQexec(conn, "SELECT repmgr.get_local_node_id()"); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_error(_("unable to execute \"SELECT repmgr.get_local_node_id()\"")); log_detail("%s", PQerrorMessage(conn)); } else if (!PQgetisnull(res, 0, 0)) { local_node_id = atoi(PQgetvalue(res, 0, 0)); } PQclear(res); return local_node_id; } /* ================ */ /* result functions */ /* ================ */ bool atobool(const char *value) { return (strcmp(value, "t") == 0) ? true : false; } /* =================== */ /* extension functions */ /* =================== */ ExtensionStatus get_repmgr_extension_status(PGconn *conn) { PQExpBufferData query; PGresult *res = NULL; ExtensionStatus status = REPMGR_UNKNOWN; /* TODO: check version */ initPQExpBuffer(&query); appendPQExpBuffer(&query, " SELECT ae.name, e.extname " " 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); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_error(_("unable to execute extension query:\n %s"), PQerrorMessage(conn)); 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) { status = REPMGR_INSTALLED; } else { status = REPMGR_AVAILABLE; } 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_error(_("unable to execute CHECKPOINT")); log_detail("%s", PQerrorMessage(conn)); } PQclear(res); return; } /* assumes superuser connection */ 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); termPQExpBuffer(&query); log_debug("%i", (int) PQresultStatus(res)); if (PQresultStatus(res) != PGRES_COMMAND_OK) { success = false; } PQclear(res); return success; } /* ===================== */ /* Node record functions */ /* ===================== */ static RecordStatus _get_node_record(PGconn *conn, char *sqlquery, t_node_info *node_info) { int ntuples = 0; PGresult *res = NULL; res = PQexec(conn, sqlquery); if (PQresultStatus(res) != PGRES_TUPLES_OK) { PQclear(res); return RECORD_ERROR; } ntuples = PQntuples(res); if (ntuples == 0) { PQclear(res); return RECORD_NOT_FOUND; } _populate_node_record(res, node_info, 0); PQclear(res); return RECORD_FOUND; } static void _populate_node_record(PGresult *res, t_node_info *node_info, int row) { 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)); } strncpy(node_info->node_name, PQgetvalue(res, row, 3), MAXLEN); strncpy(node_info->conninfo, PQgetvalue(res, row, 4), MAXLEN); strncpy(node_info->repluser, PQgetvalue(res, row, 5), NAMEDATALEN); strncpy(node_info->slot_name, PQgetvalue(res, row, 6), MAXLEN); strncpy(node_info->location, PQgetvalue(res, row, 7), MAXLEN); node_info->priority = atoi(PQgetvalue(res, row, 8)); node_info->active = atobool(PQgetvalue(res, row, 9)); strncpy(node_info->config_file, PQgetvalue(res, row, 10), MAXLEN); /* This won't normally be set */ strncpy(node_info->upstream_node_name, PQgetvalue(res, row, 10), MAXLEN); /* Set remaining struct fields with default values */ 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; } else if (strcmp(type, "bdr") == 0) { return BDR; } 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"; case BDR: return "bdr"; /* 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); 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); 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); if (node_list->tail) node_list->tail->next = cell; else node_list->head = cell; node_list->tail = cell; node_list->node_count++; } return; } void get_all_node_records(PGconn *conn, NodeInfoList *node_list) { PQExpBufferData query; PGresult *res = NULL; initPQExpBuffer(&query); appendPQExpBuffer(&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); termPQExpBuffer(&query); _populate_node_records(res, node_list); PQclear(res); return; } 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); termPQExpBuffer(&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); termPQExpBuffer(&query); _populate_node_records(res, node_list); PQclear(res); return; } void get_node_records_by_priority(PGconn *conn, NodeInfoList *node_list) { PQExpBufferData query; PGresult *res = NULL; initPQExpBuffer(&query); appendPQExpBuffer(&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); termPQExpBuffer(&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; 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, un.node_name AS upstream_node_name " " 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); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_error(_("unable to retrieve node records")); log_detail("%s", PQerrorMessage(conn)); PQclear(res); return false; } _populate_node_records(res, node_list); PQclear(res); return true; } bool get_downsteam_nodes_with_missing_slot(PGconn *conn, int this_node_id, NodeInfoList *node_list) { PQExpBufferData query; PGresult *res = NULL; 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.node_name " " WHERE rs.slot_name IS NULL " " AND n.node_id != %i ", this_node_id); log_verbose(LOG_DEBUG, "get_all_node_records_with_missing_slot():\n%s", query.data); res = PQexec(conn, query.data); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_error(_("unable to retrieve node records")); log_detail("%s", PQerrorMessage(conn)); PQclear(res); return false; } _populate_node_records(res, node_list); PQclear(res); return true; } 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 = 11; const char *param_values[param_count]; PGresult *res; 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) { appendPQExpBuffer(&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 { appendPQExpBuffer(&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); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_COMMAND_OK) { log_error(_("unable to %s node record for node \"%s\" (ID: %i)"), action, node_info->node_name, node_info->node_id); log_detail("%s", PQerrorMessage(conn)); PQclear(res); return false; } PQclear(res); return true; } bool update_node_record_set_active(PGconn *conn, int this_node_id, bool active) { PQExpBufferData query; PGresult *res = NULL; 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); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_COMMAND_OK) { log_error(_("unable to update node record:\n %s"), PQerrorMessage(conn)); PQclear(res); return false; } PQclear(res); return true; } 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 "); res = PQexec(conn, query.data); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_COMMAND_OK) { log_error(_("unable to set old primary node as inactive:\n %s"), PQerrorMessage(conn)); PQclear(res); rollback_transaction(conn); return false; } PQclear(res); initPQExpBuffer(&query); appendPQExpBuffer(&query, " UPDATE repmgr.nodes" " SET type = 'primary', " " upstream_node_id = NULL " " WHERE node_id = %i ", this_node_id); res = PQexec(conn, query.data); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_COMMAND_OK) { log_error(_("unable to set current node %i as active primary:\n %s"), this_node_id, PQerrorMessage(conn)); PQclear(res); rollback_transaction(conn); return false; } 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; 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_error(_("unable to set new upstream node id:\n %s"), PQerrorMessage(conn)); PQclear(res); return false; } PQclear(res); return true; } /* * 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; 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); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_COMMAND_OK) { log_error(_("unable to update node record:\n %s"), PQerrorMessage(conn)); PQclear(res); return false; } PQclear(res); return true; } /* * 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; 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); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_COMMAND_OK) { PQclear(res); return false; } PQclear(res); return true; } /* * 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_error(_("unable to defer constraints:\n %s"), PQerrorMessage(witness_conn)); rollback_transaction(witness_conn); return false; } /* truncate existing records */ if (truncate_node_records(witness_conn) == false) { rollback_transaction(witness_conn); return false; } get_all_node_records(primary_conn, &nodes); for (cell = nodes.head; cell; cell = cell->next) { create_node_record(witness_conn, NULL, cell->node_info); } /* and done */ commit_transaction(witness_conn); return true; } bool delete_node_record(PGconn *conn, int node) { PQExpBufferData query; PGresult *res = NULL; initPQExpBuffer(&query); appendPQExpBuffer(&query, "DELETE FROM repmgr.nodes " " WHERE node_id = %d", node); log_verbose(LOG_DEBUG, "delete_node_record():\n %s", query.data); res = PQexec(conn, query.data); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_COMMAND_OK) { log_error(_("unable to delete node record:\n %s"), PQerrorMessage(conn)); PQclear(res); return false; } PQclear(res); return true; } bool truncate_node_records(PGconn *conn) { PGresult *res = NULL; res = PQexec(conn, "TRUNCATE TABLE repmgr.nodes"); if (PQresultStatus(res) != PGRES_COMMAND_OK) { log_error(_("unable to truncate node record table:\n %s"), PQerrorMessage(conn)); PQclear(res); return false; } PQclear(res); return true; } bool update_node_record_slot_name(PGconn *primary_conn, int node_id, char *slot_name) { PQExpBufferData query; PGresult *res = NULL; 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); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_COMMAND_OK) { log_error(_("unable to set node record slot name:\n %s"), PQerrorMessage(primary_conn)); PQclear(res); return false; } PQclear(res); return true; } void get_node_replication_stats(PGconn *conn, int server_version_num, t_node_info *node_info) { PQExpBufferData query; PGresult *res = NULL; if (server_version_num == UNKNOWN_SERVER_VERSION_NUM) server_version_num = get_server_version(conn, NULL); Assert(server_version_num != UNKNOWN_SERVER_VERSION_NUM); initPQExpBuffer(&query); appendPQExpBuffer(&query, " SELECT current_setting('max_wal_senders')::INT AS max_wal_senders, " " (SELECT COUNT(*) FROM pg_catalog.pg_stat_replication) AS attached_wal_receivers, "); /* no replication slots in PostgreSQL 9.3 */ if (server_version_num < 90400) { appendPQExpBuffer(&query, " 0 AS max_replication_slots, " " 0 AS total_replication_slots, " " 0 AS active_replication_slots, " " 0 AS inactive_replication_slots, "); } else { appendPQExpBuffer(&query, " current_setting('max_replication_slots')::INT AS max_replication_slots, " " (SELECT COUNT(*) FROM pg_catalog.pg_replication_slots) AS total_replication_slots, " " (SELECT COUNT(*) FROM pg_catalog.pg_replication_slots WHERE active IS TRUE) AS active_replication_slots, " " (SELECT COUNT(*) FROM pg_catalog.pg_replication_slots WHERE active IS FALSE) AS inactive_replication_slots, "); } appendPQExpBuffer(&query, " pg_catalog.pg_is_in_recovery() AS in_recovery"); res = PQexec(conn, query.data); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_warning(_("unable to retrieve node replication statistics")); log_detail("%s", PQerrorMessage(conn)); 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; PQclear(res); return; } bool is_downstream_node_attached(PGconn *conn, char *node_name) { PQExpBufferData query; PGresult *res = NULL; int c = 0; initPQExpBuffer(&query); appendPQExpBuffer(&query, " SELECT COUNT(*) FROM pg_catalog.pg_stat_replication " " WHERE application_name = '%s'", node_name); res = PQexec(conn, query.data); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_verbose(LOG_WARNING, _("unable to query pg_stat_replication")); log_detail("%s", PQerrorMessage(conn)); PQclear(res); return false; } if (PQntuples(res) != 1) { log_verbose(LOG_WARNING, _("unexpected number of tuples (%i) returned"), PQntuples(res)); PQclear(res); return false; } c = atoi(PQgetvalue(res, 0, 0)); PQclear(res); if (c == 0) { log_verbose(LOG_WARNING, _("node \"%s\" not found in \"pg_stat_replication\""), node_name); return false; } if (c > 1) log_verbose(LOG_WARNING, _("multiple entries with \"application_name\" set to \"%s\" found in \"pg_stat_replication\""), node_name); return true; } 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; 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; initPQExpBuffer(&query); appendPQExpBuffer(&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, " " regexp_replace(config_file, '^.*\\/','') AS filename " " FROM files " "ORDER BY config_file"); res = PQexec(conn, query.data); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_error(_("unable to retrieve configuration file information")); log_detail("%s", PQerrorMessage(conn)); PQclear(res); return false; } for (i = 0; i < PQntuples(res); i++) { key_value_list_set( list, PQgetvalue(res, i, 1), PQgetvalue(res, i, 0)); } PQclear(res); return true; } bool get_configuration_file_locations(PGconn *conn, t_configfile_list *list) { PQExpBufferData query; PGresult *res = NULL; int i; initPQExpBuffer(&query); appendPQExpBuffer(&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); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_error(_("unable to retrieve configuration file locations")); log_detail("%s", PQerrorMessage(conn)); 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))); } PQclear(res); /* Fetch locations of pg_hba.conf and pg_ident.conf */ initPQExpBuffer(&query); appendPQExpBuffer(&query, " WITH dd AS ( " " SELECT setting AS data_directory" " FROM pg_catalog.pg_settings " " WHERE name = 'data_directory' " " ) " " SELECT ps.setting, " " 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); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_error(_("unable to retrieve configuration file locations")); log_detail("%s", PQerrorMessage(conn)); 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))); } 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(_("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(_("unable to allocate memory; terminating")); exit(ERR_OUT_OF_MEMORY); } strncpy(list->files[list->entries]->filepath, file, MAXPGPATH); canonicalize_path(list->files[list->entries]->filepath); strncpy(list->files[list->entries]->filename, filename, MAXPGPATH); 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; /* * Only attempt to write a record if a connection handle was provided. * Also check that the repmgr schema has been properly initialised - if * not it means no configuration file was provided, which can happen with * e.g. `repmgr standby clone`, and we won't know which schema to write * to. */ if (conn != NULL && PQstatus(conn) == CONNECTION_OK) { 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); appendPQExpBuffer(&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); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_TUPLES_OK) { /* we don't treat this as a fatal error */ log_warning(_("unable to create event record:\n %s"), PQerrorMessage(conn)); success = false; } else { /* Store timestamp to send to the notification command */ strncpy(event_timestamp, PQgetvalue(res, 0, 0), MAXLEN); } 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": 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_info(_("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 */ appendPQExpBuffer(&query, " SELECT e.node_id, n.node_name, e.event, e.successful, " " 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); appendPQExpBuffer(&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); } bool create_replication_slot(PGconn *conn, char *slot_name, int server_version_num, PQExpBufferData *error_msg) { PQExpBufferData query; RecordStatus record_status = RECORD_NOT_FOUND; PGresult *res = NULL; 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) { appendPQExpBuffer(error_msg, _("slot \"%s\" exists and is not a physical slot\n"), slot_name); return false; } if (slot_info.active == false) { /* XXX is this a good idea? */ log_debug("replication slot \"%s\" exists but is inactive; reusing", slot_name); return true; } appendPQExpBuffer(error_msg, _("slot \"%s\" already exists as an active slot\n"), slot_name); return false; } initPQExpBuffer(&query); /* In 9.6 and later, reserve the LSN straight away */ if (server_version_num >= 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(): creating slot \"%s\" on upstream"), slot_name); log_verbose(LOG_DEBUG, "create_replication_slot():\n%s", query.data); res = PQexec(conn, query.data); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_TUPLES_OK) { appendPQExpBuffer(error_msg, _("unable to create slot \"%s\" on the upstream node: %s\n"), slot_name, PQerrorMessage(conn)); PQclear(res); return false; } PQclear(res); return true; } bool drop_replication_slot(PGconn *conn, char *slot_name) { PQExpBufferData query; PGresult *res = NULL; initPQExpBuffer(&query); appendPQExpBuffer(&query, "SELECT pg_catalog.pg_drop_replication_slot('%s')", slot_name); log_verbose(LOG_DEBUG, "drop_replication_slot():\n %s", query.data); res = PQexec(conn, query.data); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_error(_("unable to drop replication slot \"%s\":\n %s"), slot_name, PQerrorMessage(conn)); PQclear(res); return false; } log_verbose(LOG_DEBUG, "replication slot \"%s\" successfully dropped", slot_name); PQclear(res); return true; } RecordStatus get_slot_record(PGconn *conn, char *slot_name, t_replication_slot *record) { PQExpBufferData query; PGresult *res = NULL; 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); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_error(_("unable to query pg_replication_slots:\n %s"), PQerrorMessage(conn)); PQclear(res); return RECORD_ERROR; } if (!PQntuples(res)) { PQclear(res); return RECORD_NOT_FOUND; } strncpy(record->slot_name, PQgetvalue(res, 0, 0), MAXLEN); strncpy(record->slot_type, PQgetvalue(res, 0, 1), MAXLEN); record->active = atobool(PQgetvalue(res, 0, 2)); PQclear(res); return RECORD_FOUND; } int get_free_replication_slots(PGconn *conn) { PQExpBufferData query; PGresult *res = NULL; int free_slots = 0; initPQExpBuffer(&query); appendPQExpBuffer(&query, " SELECT pg_catalog.current_setting('max_replication_slots')::INT - " " COUNT(*) AS free_slots" " FROM pg_catalog.pg_replication_slots"); res = PQexec(conn, query.data); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_error(_("unable to execute replication slot query")); log_detail("%s", PQerrorMessage(conn)); PQclear(res); return -1; } if (PQntuples(res) == 0) { PQclear(res); return -1; } free_slots = atoi(PQgetvalue(res, 0, 0)); PQclear(res); return free_slots; } /* ==================== */ /* tablespace functions */ /* ==================== */ bool get_tablespace_name_by_location(PGconn *conn, const char *location, char *name) { PQExpBufferData query; PGresult *res = NULL; 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); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_error(_("unable to execute tablespace query")); log_detail("%s", PQerrorMessage(conn)); PQclear(res); return false; } if (PQntuples(res) == 0) { PQclear(res); return false; } strncpy(name, PQgetvalue(res, 0, 0), MAXLEN); PQclear(res); return true; } /* ============================ */ /* 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 stop current query:\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 ocurred; -1 if timeout reached. */ int wait_connection_availability(PGconn *conn, long long timeout) { PGresult *res = NULL; fd_set read_set; int sock = PQsocket(conn); struct timeval tmout, before, after; struct timezone tz; /* recalc to microseconds */ timeout *= 1000000; while (timeout > 0) { if (PQconsumeInput(conn) == 0) { log_warning(_("wait_connection_availability(): could not receive data from connection:\n %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:\n %s"), strerror(errno)); return -1; } gettimeofday(&after, &tz); timeout -= (after.tv_sec * 1000000 + after.tv_usec) - (before.tv_sec * 1000000 + before.tv_usec); } if (timeout >= 0) { return 1; } log_warning(_("wait_connection_availability(): timeout reached")); return -1; } /* =========================== */ /* node availability functions */ /* =========================== */ bool is_server_available(const char *conninfo) { PGPing status = PQping(conninfo); log_verbose(LOG_DEBUG, "ping status for %s is %i", conninfo, (int)status); if (status == PQPING_OK) return true; return false; } /* ==================== */ /* 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 = NULL; 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(_("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) { PQExpBufferData query; int record_count = -1; PGresult *res = NULL; initPQExpBuffer(&query); appendPQExpBuffer(&query, "SELECT COUNT(*) " " FROM repmgr.monitoring_history " " WHERE age(now(), last_monitor_time) >= '%d days'::interval", keep_history); res = PQexec(primary_conn, query.data); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_error(_("unable to query number of monitoring records to clean up")); log_detail("%s", PQerrorMessage(primary_conn)); PQclear(res); PQfinish(primary_conn); exit(ERR_DB_QUERY); } else { record_count = atoi(PQgetvalue(res, 0, 0)); } PQclear(res); return record_count; } bool delete_monitoring_records(PGconn *primary_conn, int keep_history) { PQExpBufferData query; bool success = true; PGresult *res = NULL; initPQExpBuffer(&query); if (keep_history > 0) { appendPQExpBuffer(&query, "DELETE FROM repmgr.monitoring_history " " WHERE age(now(), last_monitor_time) >= '%d days'::interval ", keep_history); } else { appendPQExpBuffer(&query, "TRUNCATE TABLE repmgr.monitoring_history"); } res = PQexec(primary_conn, query.data); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_COMMAND_OK) { success = false; } 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"); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_error(_("unable to query repmgr.voting_term:\n %s"), PQerrorMessage(conn)); PQclear(res); return -1; } 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_error(_("unable to initialize repmgr.voting_term:\n %s"), PQerrorMessage(conn)); } 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_error(_("unable to increment repmgr.voting_term:\n %s"), PQerrorMessage(conn)); } 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); termPQExpBuffer(&query); retval = atobool(PQgetvalue(res, 0, 0)); 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); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_error(_("unable to execute repmgr.notify_follow_primary():\n %s"), PQerrorMessage(conn)); PQclear(res); return; } if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_error(_("unable to execute repmgr.notify_follow_primary():\n %s"), PQerrorMessage(conn)); } PQclear(res); return; } bool get_new_primary(PGconn *conn, int *primary_node_id) { PQExpBufferData query; PGresult *res = NULL; int new_primary_node_id = UNKNOWN_NODE_ID; initPQExpBuffer(&query); appendPQExpBuffer(&query, "SELECT repmgr.get_new_primary()"); res = PQexec(conn, query.data); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_error(_("unable to execute repmgr.reset_voting_status():\n %s"), PQerrorMessage(conn)); PQclear(res); return false; } if (PQgetisnull(res, 0, 0)) { *primary_node_id = UNKNOWN_NODE_ID; PQclear(res); return false; } new_primary_node_id = atoi(PQgetvalue(res, 0, 0)); PQclear(res); *primary_node_id = new_primary_node_id; return true; } void reset_voting_status(PGconn *conn) { PQExpBufferData query; PGresult *res = NULL; initPQExpBuffer(&query); appendPQExpBuffer(&query, "SELECT repmgr.reset_voting_status()"); res = PQexec(conn, query.data); termPQExpBuffer(&query); /* COMMAND_OK? */ if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_error(_("unable to execute repmgr.reset_voting_status():\n %s"), PQerrorMessage(conn)); } PQclear(res); return; } /* ============================ */ /* replication status functions */ /* ============================ */ XLogRecPtr get_current_wal_lsn(PGconn *conn) { PGresult *res = NULL; XLogRecPtr ptr = InvalidXLogRecPtr; if (server_version_num >= 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)); } PQclear(res); return ptr; } XLogRecPtr get_last_wal_receive_location(PGconn *conn) { PGresult *res = NULL; XLogRecPtr ptr = InvalidXLogRecPtr; if (server_version_num >= 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)); } PQclear(res); return ptr; } /* ============= */ /* BDR functions */ /* ============= */ static bool _is_bdr_db(PGconn *conn, PQExpBufferData *output, bool quiet) { PQExpBufferData query; PGresult *res = NULL; bool is_bdr_db = false; initPQExpBuffer(&query); appendPQExpBuffer(&query, "SELECT COUNT(*) FROM pg_catalog.pg_extension WHERE extname='bdr'"); res = PQexec(conn, query.data); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_TUPLES_OK || PQntuples(res) == 0) { is_bdr_db = false; } else { is_bdr_db = atoi(PQgetvalue(res, 0, 0)) == 1 ? true : false; } PQclear(res); if (is_bdr_db == false) { const char *warning = _("BDR extension is not available for this database"); if (output != NULL) appendPQExpBuffer(output, "%s", warning); else if (quiet == false) log_warning("%s", warning); return is_bdr_db; } initPQExpBuffer(&query); appendPQExpBuffer(&query, "SELECT bdr.bdr_is_active_in_db()"); res = PQexec(conn, query.data); termPQExpBuffer(&query); is_bdr_db = atobool(PQgetvalue(res, 0, 0)); if (is_bdr_db == false) { const char *warning = _("BDR extension available for this database, but the database is not configured for BDR"); if (output != NULL) appendPQExpBuffer(output, "%s", warning); else if (quiet == false) log_warning("%s", warning); } PQclear(res); return is_bdr_db; } bool is_bdr_db(PGconn *conn, PQExpBufferData *output) { return _is_bdr_db(conn, output, false); } bool is_bdr_db_quiet(PGconn *conn) { return _is_bdr_db(conn, NULL, true); } bool is_active_bdr_node(PGconn *conn, const char *node_name) { PQExpBufferData query; PGresult *res = NULL; bool is_active_bdr_node = false; initPQExpBuffer(&query); appendPQExpBuffer(&query, " SELECT COALESCE(s.active, TRUE) AS active" " FROM bdr.bdr_nodes n " " LEFT JOIN pg_catalog.pg_replication_slots s " " ON s.slot_name=bdr.bdr_format_slot_name(n.node_sysid, n.node_timeline, n.node_dboid, (SELECT oid FROM pg_catalog.pg_database WHERE datname = pg_catalog.current_database())) " " WHERE n.node_name='%s' ", node_name); log_verbose(LOG_DEBUG, "is_active_bdr_node():\n %s", query.data); res = PQexec(conn, query.data); termPQExpBuffer(&query); /* we don't care if the query fails */ if (PQresultStatus(res) != PGRES_TUPLES_OK || PQntuples(res) == 0) { is_active_bdr_node = false; } else { is_active_bdr_node = atobool(PQgetvalue(res, 0, 0)); } PQclear(res); return is_active_bdr_node; } bool is_bdr_repmgr(PGconn *conn) { PQExpBufferData query; PGresult *res = NULL; int non_bdr_nodes = 0; initPQExpBuffer(&query); appendPQExpBuffer(&query, "SELECT COUNT(*)" " FROM repmgr.nodes n" " WHERE n.type != 'bdr' "); res = PQexec(conn, query.data); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_TUPLES_OK || PQntuples(res) == 0) { PQclear(res); return false; } non_bdr_nodes = atoi(PQgetvalue(res, 0, 0)); PQclear(res); return (non_bdr_nodes == 0) ? true : false; } bool is_table_in_bdr_replication_set(PGconn *conn, const char *tablename, const char *set) { PQExpBufferData query; PGresult *res = NULL; bool in_replication_set = false; initPQExpBuffer(&query); appendPQExpBuffer(&query, "SELECT COUNT(*) " " FROM UNNEST(bdr.table_get_replication_sets('repmgr.%s')) AS repset " " WHERE repset='%s' ", tablename, set); res = PQexec(conn, query.data); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_TUPLES_OK || PQntuples(res) == 0) { in_replication_set = false; } else { in_replication_set = atoi(PQgetvalue(res, 0, 0)) == 1 ? true : false; } PQclear(res); return in_replication_set; } bool add_table_to_bdr_replication_set(PGconn *conn, const char *tablename, const char *set) { PQExpBufferData query; PGresult *res = NULL; initPQExpBuffer(&query); appendPQExpBuffer(&query, "SELECT bdr.table_set_replication_sets('repmgr.%s', '{%s}')", tablename, set); res = PQexec(conn, query.data); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_error(_("unable to add table \"repmgr.%s\" to replication set \"%s\":\n %s"), tablename, set, PQerrorMessage(conn)); if (res != NULL) PQclear(res); return false; } PQclear(res); return true; } bool bdr_node_name_matches(PGconn *conn, const char *node_name, PQExpBufferData *bdr_local_node_name) { PQExpBufferData query; PGresult *res = NULL; bool node_exists = false; initPQExpBuffer(&query); appendPQExpBuffer(&query, "SELECT bdr.bdr_get_local_node_name() AS node_name"); res = PQexec(conn, query.data); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_TUPLES_OK) { node_exists = false; } else { node_exists = true; appendPQExpBuffer(bdr_local_node_name, "%s", PQgetvalue(res, 0, 0)); } PQclear(res); return node_exists; } ReplSlotStatus get_bdr_node_replication_slot_status(PGconn *conn, const char *node_name) { PQExpBufferData query; PGresult *res = NULL; ReplSlotStatus status = SLOT_UNKNOWN; initPQExpBuffer(&query); appendPQExpBuffer(&query, " SELECT s.active " " FROM pg_catalog.pg_replication_slots s " " WHERE slot_name = " " (SELECT bdr.bdr_format_slot_name(node_sysid, node_timeline, node_dboid, datoid) " " FROM bdr.bdr_nodes " " WHERE node_name = '%s') ", node_name); log_verbose(LOG_DEBUG, "get_bdr_node_replication_slot_status():\n %s", query.data); res = PQexec(conn, query.data); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_TUPLES_OK) { status = SLOT_UNKNOWN; } else { status = (atobool(PQgetvalue(res, 0, 0)) == true) ? SLOT_ACTIVE : SLOT_INACTIVE; } PQclear(res); return status; } void get_bdr_other_node_name(PGconn *conn, int node_id, char *node_name) { PQExpBufferData query; PGresult *res = NULL; initPQExpBuffer(&query); appendPQExpBuffer(&query, " SELECT n.node_name " " FROM repmgr.nodes n " " WHERE n.node_id != %i", node_id); log_verbose(LOG_DEBUG, "get_bdr_other_node_name():\n %s", query.data); res = PQexec(conn, query.data); termPQExpBuffer(&query); if (PQresultStatus(res) == PGRES_TUPLES_OK) { strncpy(node_name, PQgetvalue(res, 0, 0), MAXLEN); } else { log_warning(_("get_bdr_other_node_name(): unable to execute query\n %s"), PQerrorMessage(conn)); } PQclear(res); return; } void add_extension_tables_to_bdr_replication_set(PGconn *conn) { PQExpBufferData query; PGresult *res = NULL; initPQExpBuffer(&query); appendPQExpBuffer(&query, " SELECT c.relname " " FROM pg_class c " "INNER JOIN pg_namespace n " " ON c.relnamespace = n.oid " " WHERE n.nspname = 'repmgr' " " AND c.relkind = 'r' "); res = PQexec(conn, query.data); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_TUPLES_OK) { /* */ } else { int i; for (i = 0; i < PQntuples(res); i++) { add_table_to_bdr_replication_set( conn, PQgetvalue(res, i, 0), "repmgr"); } } PQclear(res); return; } void get_all_bdr_node_records(PGconn *conn, BdrNodeInfoList *node_list) { PQExpBufferData query; PGresult *res = NULL; initPQExpBuffer(&query); appendPQExpBuffer(&query, " SELECT " BDR_NODES_COLUMNS " FROM bdr.bdr_nodes " "ORDER BY node_seq_id "); log_verbose(LOG_DEBUG, "get_all_node_records():\n%s", query.data); res = PQexec(conn, query.data); termPQExpBuffer(&query); _populate_bdr_node_records(res, node_list); PQclear(res); return; } RecordStatus get_bdr_node_record_by_name(PGconn *conn, const char *node_name, t_bdr_node_info *node_info) { PQExpBufferData query; PGresult *res = NULL; initPQExpBuffer(&query); appendPQExpBuffer(&query, " SELECT " BDR_NODES_COLUMNS " FROM bdr.bdr_nodes " " WHERE node_name = '%s'", node_name); log_verbose(LOG_DEBUG, "get_bdr_node_record_by_name():\n%s", query.data); res = PQexec(conn, query.data); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_error(_("unable to retrieve BDR node record for \"%s\":\n %s"), node_name, PQerrorMessage(conn)); PQclear(res); return RECORD_ERROR; } if (PQntuples(res) == 0) { PQclear(res); return RECORD_NOT_FOUND; } _populate_bdr_node_record(res, node_info, 0); PQclear(res); return RECORD_FOUND; } static void _populate_bdr_node_records(PGresult *res, BdrNodeInfoList *node_list) { int i; clear_node_info_list((NodeInfoList *) node_list); if (PQresultStatus(res) != PGRES_TUPLES_OK) { return; } for (i = 0; i < PQntuples(res); i++) { BdrNodeInfoListCell *cell; cell = (BdrNodeInfoListCell *) pg_malloc0(sizeof(BdrNodeInfoListCell)); cell->node_info = pg_malloc0(sizeof(t_bdr_node_info)); _populate_bdr_node_record(res, cell->node_info, i); if (node_list->tail) node_list->tail->next = cell; else node_list->head = cell; node_list->tail = cell; node_list->node_count++; } return; } static void _populate_bdr_node_record(PGresult *res, t_bdr_node_info *node_info, int row) { char buf[MAXLEN] = ""; strncpy(node_info->node_sysid, PQgetvalue(res, row, 0), MAXLEN); node_info->node_timeline = atoi(PQgetvalue(res, row, 1)); node_info->node_dboid = atoi(PQgetvalue(res, row, 2)); strncpy(buf, PQgetvalue(res, row, 3), MAXLEN); node_info->node_status = buf[0]; strncpy(node_info->node_name, PQgetvalue(res, row, 4), MAXLEN); strncpy(node_info->node_local_dsn, PQgetvalue(res, row, 5), MAXLEN); strncpy(node_info->node_init_from_dsn, PQgetvalue(res, row, 6), MAXLEN); } bool am_bdr_failover_handler(PGconn *conn, int node_id) { PQExpBufferData query; PGresult *res = NULL; bool am_handler = false; initPQExpBuffer(&query); appendPQExpBuffer(&query, "SELECT repmgr.am_bdr_failover_handler(%i)", node_id); res = PQexec(conn, query.data); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_TUPLES_OK) { log_error(_("unable to execute function repmgr.am_bdr_failover_handler():\n %s"), PQerrorMessage(conn)); PQclear(res); return false; } am_handler = atobool(PQgetvalue(res, 0, 0)); PQclear(res); return am_handler; } void unset_bdr_failover_handler(PGconn *conn) { PGresult *res = NULL; res = PQexec(conn, "SELECT repmgr.unset_bdr_failover_handler()"); PQclear(res); return; } bool bdr_node_has_repmgr_set(PGconn *conn, const char *node_name) { PQExpBufferData query; PGresult *res = NULL; bool has_repmgr_set = false; initPQExpBuffer(&query); appendPQExpBuffer(&query, " SELECT COUNT(*) " " FROM UNNEST(bdr.connection_get_replication_sets('%s') AS repset " " WHERE repset = 'repmgr'", node_name); res = PQexec(conn, query.data); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_TUPLES_OK || PQntuples(res) == 0) { has_repmgr_set = false; } else { has_repmgr_set = atoi(PQgetvalue(res, 0, 0)) == 1 ? true : false; } PQclear(res); return has_repmgr_set; } bool bdr_node_set_repmgr_set(PGconn *conn, const char *node_name) { PQExpBufferData query; PGresult *res = NULL; bool success = true; initPQExpBuffer(&query); appendPQExpBuffer(&query, " SELECT bdr.connection_set_replication_sets( " " ARRAY( " " SELECT repset::TEXT " " FROM UNNEST(bdr.connection_get_replication_sets('%s')) AS repset " " UNION " " SELECT 'repmgr'::TEXT " " ), " " '%s' " " ) ", node_name, node_name); res = PQexec(conn, query.data); termPQExpBuffer(&query); if (PQresultStatus(res) != PGRES_TUPLES_OK) { success = false; } PQclear(res); return success; } /* 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 "DOWN"; case NODE_STATUS_UNCLEAN_SHUTDOWN: return "UNCLEAN_SHUTDOWN"; } 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-4.0.3/dbutils.h000066400000000000000000000401511324071732600146370ustar00rootroot00000000000000/* * dbutils.h * * Copyright (c) 2ndQuadrant, 2010-2018 * * 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/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 " #define BDR_NODES_COLUMNS "node_sysid, node_timeline, node_dboid, node_status, node_name, node_local_dsn, node_init_from_dsn, node_read_only, node_seq_id" #define ERRBUFF_SIZE 512 typedef enum { UNKNOWN = 0, PRIMARY, STANDBY, WITNESS, BDR } t_server_type; typedef enum { REPMGR_INSTALLED = 0, 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 } NodeStatus; typedef enum { CONN_UNKNOWN = -1, CONN_OK, CONN_BAD, CONN_ERROR } ConnectionStatus; typedef enum { SLOT_UNKNOWN = -1, SLOT_INACTIVE, SLOT_ACTIVE } ReplSlotStatus; /* * Struct to store node information */ typedef struct s_node_info { /* contents of "repmgr.nodes" */ int node_id; int upstream_node_id; t_server_type type; char node_name[MAXLEN]; char upstream_node_name[MAXLEN]; 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; bool 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; } 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 \ } /* 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 } /* represents an entry in bdr.bdr_nodes */ typedef struct s_bdr_node_info { char node_sysid[MAXLEN]; uint32 node_timeline; uint32 node_dboid; char node_status; char node_name[MAXLEN]; char node_local_dsn[MAXLEN]; char node_init_from_dsn[MAXLEN]; bool read_only; uint32 node_seq_id; } t_bdr_node_info; #define T_BDR_NODE_INFO_INITIALIZER { \ "", InvalidOid, InvalidOid, \ '?', "", "", "", \ false, -1 \ } /* structs to store a list of BDR node records */ typedef struct BdrNodeInfoListCell { struct BdrNodeInfoListCell *next; t_bdr_node_info *node_info; } BdrNodeInfoListCell; typedef struct BdrNodeInfoList { BdrNodeInfoListCell *head; BdrNodeInfoListCell *tail; int node_count; } BdrNodeInfoList; #define T_BDR_NODE_INFO_LIST_INITIALIZER { \ NULL, \ NULL, \ 0 \ } typedef struct { char current_timestamp[MAXLEN]; uint64 last_wal_receive_lsn; uint64 last_wal_replay_lsn; char last_xact_replay_timestamp[MAXLEN]; int replication_lag_time; bool receiving_streamed_wal; } ReplInfo; #define T_REPLINFO_INTIALIZER { \ "", \ InvalidXLogRecPtr, \ InvalidXLogRecPtr, \ "", \ 0 \ } 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 \ } /* global variables */ extern int server_version_num; /* 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); extern void wrap_ddl_query(PQExpBufferData *query_buf, int replication_type, const char *fmt,...) __attribute__((format(PG_PRINTF_ATTRIBUTE, 3, 4))); 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_as_user(const char *conninfo, const char *user, const bool exit_on_error); PGconn *establish_db_connection_by_params(t_conninfo_param_list *param_list, const bool exit_on_error); 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); bool is_superuser_connection(PGconn *conn, t_connection_user *userinfo); /* conninfo manipulation functions */ bool get_conninfo_value(const char *conninfo, const char *keyword, char *output); 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 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); bool has_passfile(void); /* transaction functions */ bool begin_transaction(PGconn *conn); bool commit_transaction(PGconn *conn); bool rollback_transaction(PGconn *conn); bool check_cluster_schema(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); int guc_set_typed(PGconn *conn, const char *parameter, const char *op, const char *value, const char *datatype); bool get_pg_setting(PGconn *conn, const char *setting, char *output); /* server information functions */ bool get_cluster_size(PGconn *conn, char *size); int get_server_version(PGconn *conn, char *server_version); RecoveryType get_recovery_type(PGconn *conn); int get_primary_node_id(PGconn *conn); bool can_use_pg_rewind(PGconn *conn, const char *data_directory, PQExpBufferData *reason); int get_ready_archive_files(PGconn *conn, const char *data_directory); bool identify_system(PGconn *repl_conn, t_system_identification *identification); bool repmgrd_set_local_node_id(PGconn *conn, int local_node_id); int repmgrd_get_local_node_id(PGconn *conn); /* extension functions */ ExtensionStatus get_repmgr_extension_status(PGconn *conn); /* node management functions */ void checkpoint(PGconn *conn); bool vacuum_table(PGconn *conn, const char *table); /* 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 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); void get_all_node_records(PGconn *conn, NodeInfoList *node_list); 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); 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_downsteam_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_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(PGconn *conn, char *slot_name, int server_version_num, PQExpBufferData *error_msg); bool drop_replication_slot(PGconn *conn, char *slot_name); RecordStatus get_slot_record(PGconn *conn, char *slot_name, t_replication_slot *record); int get_free_replication_slots(PGconn *conn); /* 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, long long timeout); /* node availability functions */ bool is_server_available(const char *conninfo); /* 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); bool delete_monitoring_records(PGconn *primary_conn, int keep_history); /* 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_current_wal_lsn(PGconn *conn); XLogRecPtr get_last_wal_receive_location(PGconn *conn); bool get_replication_info(PGconn *conn, ReplInfo *replication_info); int get_replication_lag_seconds(PGconn *conn); void get_node_replication_stats(PGconn *conn, int server_version_num, t_node_info *node_info); bool is_downstream_node_attached(PGconn *conn, char *node_name); /* BDR functions */ void get_all_bdr_node_records(PGconn *conn, BdrNodeInfoList *node_list); RecordStatus get_bdr_node_record_by_name(PGconn *conn, const char *node_name, t_bdr_node_info *node_info); bool is_bdr_db(PGconn *conn, PQExpBufferData *output); bool is_bdr_db_quiet(PGconn *conn); bool is_active_bdr_node(PGconn *conn, const char *node_name); bool is_bdr_repmgr(PGconn *conn); bool is_table_in_bdr_replication_set(PGconn *conn, const char *tablename, const char *set); bool add_table_to_bdr_replication_set(PGconn *conn, const char *tablename, const char *set); void add_extension_tables_to_bdr_replication_set(PGconn *conn); bool bdr_node_name_matches(PGconn *conn, const char *node_name, PQExpBufferData *bdr_local_node_name); ReplSlotStatus get_bdr_node_replication_slot_status(PGconn *conn, const char *node_name); void get_bdr_other_node_name(PGconn *conn, int node_id, char *name_buf); bool am_bdr_failover_handler(PGconn *conn, int node_id); void unset_bdr_failover_handler(PGconn *conn); bool bdr_node_has_repmgr_set(PGconn *conn, const char *node_name); bool bdr_node_set_repmgr_set(PGconn *conn, const char *node_name); /* miscellaneous debugging functions */ const char *print_node_status(NodeStatus node_status); const char *print_pqping_status(PGPing ping_status); #endif /* _REPMGR_DBUTILS_H_ */ repmgr-4.0.3/dirutil.c000066400000000000000000000174431324071732600146500ustar00rootroot00000000000000/* * * dirmod.c * directory handling functions * * Copyright (c) 2ndQuadrant, 2010-2018 * * 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(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(char *path) { if (mkdir_p(path, 0700) == 0) return true; log_error(_("unable to create directory \"%s\""), path); log_detail("%s", strerror(errno)); return false; } bool set_dir_permissions(char *path) { return (chmod(path, 0700) != 0) ? false : 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 occcur: * * 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(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(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 * earliesty 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); } 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(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: /* exists but empty, fix permissions and use it */ log_info(_("checking and correcting permissions on existing directory \"%s\""), path); if (!set_dir_permissions(path)) { log_error(_("unable to change permissions of directory \"%s\""), path); log_detail("%s", strerror(errno)); 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); 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); return true; } return false; } break; case DIR_ERROR: log_error(_("could not access directory \"%s\": %s"), path, strerror(errno)); return false; } return true; } int rmdir_recursive(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-4.0.3/dirutil.h000066400000000000000000000023741324071732600146520ustar00rootroot00000000000000/* * dirutil.h * Copyright (c) 2ndQuadrant, 2010-2018 * * 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(char *path); extern DataDirState check_dir(char *path); extern bool create_dir(char *path); extern bool is_pg_dir(char *path); extern PgDirState is_pg_running(char *path); extern bool create_pg_dir(char *path, bool force); extern int rmdir_recursive(char *path); #endif repmgr-4.0.3/doc/000077500000000000000000000000001324071732600135645ustar00rootroot00000000000000repmgr-4.0.3/doc/.gitignore000066400000000000000000000001211324071732600155460ustar00rootroot00000000000000HTML.index bookindex.sgml html-stamp html/ nochunks.dsl repmgr.html version.sgml repmgr-4.0.3/doc/Makefile.in000066400000000000000000000041371324071732600156360ustar00rootroot00000000000000repmgr_subdir = doc repmgr_top_builddir = .. include $(repmgr_top_builddir)/Makefile.global ifndef JADE JADE = $(missing) jade endif SGMLINCLUDE = -D . -D ${srcdir} SPFLAGS += -wall -wno-unused-param -wno-empty -wfully-tagged JADE.html.call = $(JADE) $(JADEFLAGS) $(SPFLAGS) $(SGMLINCLUDE) $(CATALOG) -t sgml -i output-html ALLSGML := $(wildcard $(srcdir)/*.sgml) # to build bookindex ALMOSTALLSGML := $(filter-out %bookindex.sgml,$(ALLSGML)) GENERATED_SGML = version.sgml bookindex.sgml Makefile: Makefile.in cd $(repmgr_top_builddir) && ./config.status doc/Makefile all: html html: html-stamp html-stamp: repmgr.sgml $(ALLSGML) $(GENERATED_SGML) stylesheet.dsl website-docs.css $(MKDIR_P) html $(JADE.html.call) -d stylesheet.dsl -i include-index $< cp $(srcdir)/stylesheet.css $(srcdir)/website-docs.css html/ touch $@ repmgr.html: repmgr.sgml $(ALLSGML) $(GENERATED_SGML) stylesheet.dsl website-docs.css sed '/html-index-filename/a\ (define nochunks #t)' nochunks.dsl $(JADE.html.call) -d nochunks.dsl -i include-index $< >repmgr.html version.sgml: ${repmgr_top_builddir}/repmgr_version.h { \ echo ""; \ } > $@ HTML.index: repmgr.sgml $(ALMOSTALLSGML) stylesheet.dsl @$(MKDIR_P) html $(JADE.html.call) -d stylesheet.dsl -V html-index $< website-docs.css: @$(MKDIR_P) html curl http://www.postgresql.org/media/css/docs.css > ${srcdir}/website-docs.css bookindex.sgml: HTML.index ifdef COLLATEINDEX LC_ALL=C $(PERL) $(COLLATEINDEX) -f -g -i 'bookindex' -o $@ $< else @$(missing) collateindex.pl $< $@ endif clean: rm -f html-stamp rm -f HTML.index $(GENERATED_SGML) maintainer-clean: rm -rf html rm -rf Makefile 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) 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 .PHONY: html all repmgr-4.0.3/doc/appendix-faq.sgml000066400000000000000000000265431324071732600170370ustar00rootroot00000000000000 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 existing &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; 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 will no longer be actively maintained. repmgr 2.x supports PostgreSQL 9.0 ~ 9.3. While it is compatible with PostgreSQL 9.3, we recommend using repmgr 4.x. 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 makes WAL file management much easier, and if used `repmgr` will no longer insist on a fixed minimum number (default: 5000) of WAL files being retained. 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. 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. <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;. 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. In PostgreSQL 9.5 and later, 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. &repmgr; provides the command repmgr node rejoin which can optionally execute pg_rewind; see the documentation for details. If pg_rewind cannot be used, then the data directory will have 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 <application>repmgrd</application>? 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>/<application>repmgrd</application> 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 . 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 repmrg.nodes table. <application>repmgrd</application> How can I prevent a node from ever being promoted to primary? In `repmgr.conf`, set its priority to a value of 0 or less; 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 <application>repmgrd</application> 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 <application>repmgrd</application> to rotate its logfile? Configure your system's logrotate service to do this; see . I've recloned a failed primary as a standby, but <application>repmgrd</application> 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. repmgr-4.0.3/doc/appendix-packages.sgml000066400000000000000000000101131324071732600200300ustar00rootroot00000000000000 packages &repmgr; package details This section provides technical details about various &repmgr; binary packages, such as location of the installed binaries and configuration files. CentOS, RHEL, Scientific Linux etc. Currently packages are provided for versions 6.x and 7.x of CentOS et al. 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; 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. CentOS 7 packages Repository URL: https://yum.postgresql.org/repopackages.php Repository documentation: https://yum.postgresql.org/ Package name example: repmgr10-4.0.0-1.rhel7.x86_64 Metapackage: (none) Installation command: yum install -y repmgr10 Binary location: /usr/pgsql-10/bin In default path: NO Configuration file location: /etc/repmgr/10/repmgr.conf repmgrd service command: service repmgr10 repmgrd service file location: /usr/lib/systemd/system/repmgr10.service repmgrd log file location: (not specified)
CentOS 6 packages Repository URL: https://yum.postgresql.org/repopackages.php Repository documentation: https://yum.postgresql.org/ Package name example: repmgr96-4.0.0-1.rhel6.x86_64 Metapackage: NO Installation command: yum install -y repmgr96 Binary location: /usr/pgsql-9.6/bin In default path: NO Configuration file location: /etc/repmgr/9.6/repmgr.conf repmgrd service command: service repmgr-9.6 repmgrd service file location: /etc/init.d/repmgr-9.6 repmgrd log file location: /var/log/repmgr/repmgrd-9.6.log
repmgr-4.0.3/doc/appendix-release-notes.sgml000066400000000000000000000535571324071732600210430ustar00rootroot00000000000000 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 4.0.3 ??? Feb ??, 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. 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 #369) 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 standy (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-4.0.3/doc/appendix-signatures.sgml000066400000000000000000000051571324071732600204520ustar00rootroot00000000000000 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 http://packages.2ndquadrant.com/repmgr/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 RPM signing key The signing key ID used for repmgr source code bundles is: 0x702D883A. To download the repmgr source key to your computer: curl -s http://packages.2ndquadrant.com/repmgr/RPM-GPG-KEY-repmgr | gpg --import gpg --fingerprint 0x702D883A then verify that the fingerprint is the expected value: AE4E 390E A58E 0037 6148 3F29 888D 018B 702D 883A To check a repository RPM, use rpmkeys to load the packaging signing key into the RPM database then use rpm -K, e.g.: sudo rpmkeys --import http://packages.2ndquadrant.com/repmgr/RPM-GPG-KEY-repmgr rpm -K postgresql-bdr94-2ndquadrant-redhat-1.0-2.noarch.rpm repmgr-4.0.3/doc/bdr-failover.md000066400000000000000000000003431324071732600164620ustar00rootroot00000000000000BDR failover with repmgrd ========================= This document has been integrated into the main `repmgr` documentation and is now located here: > [BDR failover with repmgrd](https://repmgr.org/docs/4.0/repmgrd-bdr.html) repmgr-4.0.3/doc/changes-in-repmgr4.md000066400000000000000000000003121324071732600174740ustar00rootroot00000000000000Changes in repmgr 4 =================== This document has been integrated into the main `repmgr` documentation and is now located here: > [Release notes](https://repmgr.org/docs/4.0/release-4.0.html) repmgr-4.0.3/doc/cloning-standbys.sgml000066400000000000000000000505011324071732600177270ustar00rootroot00000000000000 Cloning standbys cloning from Barman Barman cloning a standby Cloning a standby from Barman 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 does not need to be set. Prerequisites for cloning from Barman In order to enable Barman support for repmgr standby clone, following prerequisites must be met: the barman_server setting in repmgr.conf is the same as the server configured in Barman; the barman_host setting in repmgr.conf is set to the SSH hostname of the Barman server; the restore_command setting in repmgr.conf is configured to use a copy of the barman-wal-restore script shipped with the barman-cli package (see section below). the Barman catalogue includes at least one valid backup for this server. 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. 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 ofbarman_host in repmgr.conf. See the Host section in man 5 ssh_config for more details. It's now possible to clone a standby from Barman, e.g.: NOTICE: using configuration file "/etc/repmgr.conf" 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 upstream node INFO: connected to source node, checking its state INFO: successfully connected to source node DETAIL: current installation size is 29 MB NOTICE: retrieving backup from Barman... receiving file list ... (...) 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 fetching archived WAL Using Barman as a WAL file source 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 and later; for Barman 1.x the script is provided separately as barman-wal-restore.py) which performs this function for Barman. To use barman-wal-restore with &repmgr; and assuming Barman is located on the barmansrv host 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=barmansrv barman_server=somedb restore_command=/usr/bin/barman-wal-restore barmansrv somedb %f %p barman-wal-restore supports command line switches to control parallelism (--parallel=N) and compression ( --bzip2, --gzip). 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 cloning replication slots replication slots cloning Cloning and replication slots 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. 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, negating the need to use replication slots to reserve WAL. See section for more details on using &repmgr; together with Barman. cloning cascading replication Cloning and 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 bandwith 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 explictly set the upstream's primary_conninfo string in recovery.conf. cloning advanced options Advanced cloning 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 the -c/--fast-checkpoint option. However 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. 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. See the PostgreSQL pg_basebackup documentation for more details of available options. Managing 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. It's also possible to store the password in the environment variable PGPASSWORD, however this is not recommended for security reasons. For more details see the PostgreSQL password file documentation. 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. . It is of course also possible to include the password value in the conninfo string for each node, but this is obviously a security risk and should be avoided. From PostgreSQL 9.6, libpq supports the passfile parameter in connection strings, which can be used to specify a password file other than the default ~/.pgpass. To have &repmgr; write a custom password file in primary_conninfo, specify its location in passfile in repmgr.conf. 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 . repmgr-4.0.3/doc/configuration-file-settings.sgml000066400000000000000000000100721324071732600220720ustar00rootroot00000000000000 repmgr.conf settings Configuration file 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. 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. For a full list of annotated configuration items, see the file repmgr.conf.sample. 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 repmgr-4.0.3/doc/configuration-file.sgml000066400000000000000000000056511324071732600202430ustar00rootroot00000000000000 repmgr.conf location configuration repmgr.conf location Configuration file location 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. 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 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 configuraton 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). repmgr-4.0.3/doc/configuration.sgml000066400000000000000000000017471324071732600173300ustar00rootroot00000000000000 repmgr configuration &configuration-file; &configuration-file-settings; configuration user permissions repmgr user permissions &repmgr; will create an extension database containing objects for administering &repmgr; metadata. The user defined in the conninfo setting must be able to access all objects. Additionally, superuser permissions are required to install the &repmgr; extension. The easiest way to do this is create the &repmgr; user as a superuser, however if this is not desirable, the &repmgr; user can be created as a normal user and a superuser specified with --superuser when registering a &repmgr; node. repmgr-4.0.3/doc/configuring-witness-server.sgml000066400000000000000000000063561324071732600217720ustar00rootroot00000000000000 witness server Using a witness server with repmgrd Using a witness server 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 the primary server itself is unavailable. 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 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 seen 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). 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; for more details see . To unregister a witness server, use . repmgr-4.0.3/doc/event-notifications.sgml000066400000000000000000000166721324071732600204540ustar00rootroot00000000000000 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 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"' The following parameters are provided for a subset of event notifications: node ID of the current primary ( and ) node ID of the demoted primary ( only) conninfo string of the primary node ( and ) conninfo string of the next available node (bdr_failover and bdr_recovery) name of the current primary node ( and ) name of the next available node (bdr_failover and bdr_recovery) The values provided for %c and %a will probably 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: primary_register primary_unregister standby_register standby_register_sync standby_unregister standby_clone standby_promote standby_follow standby_disconnect_manual standby_failure standby_recovery witness_register witness_unregister node_rejoin repmgrd_start repmgrd_shutdown repmgrd_failover_promote repmgrd_failover_follow repmgrd_upstream_disconnect repmgrd_upstream_reconnect repmgrd_promote_error repmgrd_failover_promote bdr_failover bdr_reconnect bdr_recovery bdr_register bdr_unregister 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-4.0.3/doc/filelist.sgml000066400000000000000000000076031324071732600162710ustar00rootroot00000000000000 repmgr-4.0.3/doc/follow-new-primary.sgml000066400000000000000000000041661324071732600202310ustar00rootroot00000000000000 Following a new primary repmgr standby follow Following a new primary 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 repmgr 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-4.0.3/doc/install-packages.sgml000066400000000000000000000145761324071732600177070ustar00rootroot00000000000000 Installing &repmgr; from packages We recommend installing &repmgr; using the available packages for your system. installation on Redhat/CentOS/Fedora etc. RedHat/Fedora/CentOS RPM packages for &repmgr; are available via Yum through the PostgreSQL Global Development Group RPM repository (http://yum.postgresql.org/). Follow the instructions for your distribution (RedHat, CentOS, Fedora, etc.) and architecture as detailed there. 2ndQuadrant also provides its own RPM packages which are made available at the same time as each &repmgr; release, as it can take some days for them to become available via the main PGDG repository. See following section for details: 2ndQuadrant repmgr yum repository Beginning with repmgr 3.1.3, 2ndQuadrant provides a dedicated yum repository for &repmgr; releases. This repository complements the main PGDG community repository, but enables repmgr users to access the latest &repmgr; packages before they are available via the PGDG repository, which can take several days to be updated following a fresh &repmgr; release. Installation Import the repository public key (optional but recommended): rpm --import http://packages.2ndquadrant.com/repmgr/RPM-GPG-KEY-repmgr Install the repository RPM for your distribution (this enables the 2ndQuadrant repository as a source of repmgr packages): Fedora: http://packages.2ndquadrant.com/repmgr/yum-repo-rpms/repmgr-fedora-1.0-1.noarch.rpm RHEL, CentOS etc: http://packages.2ndquadrant.com/repmgr/yum-repo-rpms/repmgr-rhel-1.0-1.noarch.rpm e.g.: $ yum install http://packages.2ndquadrant.com/repmgr/yum-repo-rpms/repmgr-rhel-1.0-1.noarch.rpm Install the repmgr version appropriate for your PostgreSQL version (e.g. repmgr96), e.g.: $ yum install repmgr96 Compatibility with PGDG Repositories The 2ndQuadrant &repmgr; yum repository uses exactly the same package definitions as the main PGDG repository and is effectively a selective mirror for &repmgr; packages only. Normally yum should 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 repmgr96 Loaded plugins: fastestmirror Loading mirror speeds from cached hostfile * base: ftp.iij.ad.jp * extras: ftp.iij.ad.jp * updates: ftp.iij.ad.jp Available Packages repmgr96.x86_64 3.2-1.el6 2ndquadrant-repmgr repmgr96.x86_64 3.2.1-1.el6 2ndquadrant-repmgr repmgr96.x86_64 3.3-1.el6 2ndquadrant-repmgr repmgr96.x86_64 3.3.1-1.el6 2ndquadrant-repmgr repmgr96.x86_64 3.3.2-1.el6 2ndquadrant-repmgr repmgr96.x86_64 3.3.2-1.rhel6 pgdg96 repmgr96.x86_64 4.0.0-1.el6 2ndquadrant-repmgr repmgr96.x86_64 4.0.0-1.rhel6 pgdg96 then append the appropriate version number to the package name with a hyphen, e.g.: [root@localhost ~]# yum install repmgr96-3.3.2-1.el6 installation on Debian/Ubuntu etc. Debian/Ubuntu .deb packages for &repmgr; are available from the PostgreSQL Community APT repository (http://apt.postgresql.org/). Instructions can be found in the APT section of the PostgreSQL Wiki (https://wiki.postgresql.org/wiki/Apt). repmgr-4.0.3/doc/install-requirements.sgml000066400000000000000000000053601324071732600206430ustar00rootroot00000000000000 installation requirements Requirements for installing repmgr 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. From version 4.0, repmgr is compatible with all PostgreSQL versions from 9.3, including PostgreSQL 10. Note that some &repmgr; functionality is not available in PostgreSQL 9.3 and PostgreSQL 9.4. 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. 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. 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 (in which case rsync is also required) to perform switchover operations when executing repmgr cluster matrix and repmgr cluster crosscheck 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-4.0.3/doc/install-source.sgml000066400000000000000000000133601324071732600174170ustar00rootroot00000000000000 installation from source Installing &repmgr; 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. Then install the pre-requisites for building PostgreSQL with: sudo apt-get update sudo apt-get build-dep postgresql-9.6 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 versions for your target repmgr version. 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/2ndQuadrant/repmgr. There are also tags for each &repmgr; release, e.g. REL4_0_STABLE. Clone the source code using git: git clone https://github.com/2ndQuadrant/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 format. To build it locally as HTML, you'll need to install the required packages as described in the PostgreSQL documentation then execute: ./configure && make install-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, execute: cd doc/ && make repmgr.html Due to changes in PostgreSQL's documentation build system from PostgreSQL 10, the documentation can currently only be built agains PostgreSQL 9.6 or earlier. This limitation will be fixed when time and resources permit. repmgr-4.0.3/doc/install.sgml000066400000000000000000000014631324071732600161220ustar00rootroot00000000000000 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-4.0.3/doc/legal.sgml000066400000000000000000000021101324071732600155260ustar00rootroot00000000000000 2017 2010-2018 2ndQuadrant, Ltd. Legal Notice repmgr is Copyright © 2010-2018 by 2ndQuadrant, Ltd. 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-4.0.3/doc/overview.sgml000066400000000000000000000211331324071732600163160ustar00rootroot00000000000000 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-4.0.3/doc/promoting-standby.sgml000066400000000000000000000100001324071732600201170ustar00rootroot00000000000000 promoting a standby repmgr standby promote Promoting a standby server with repmgr 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-4.0.3/doc/quickstart.sgml000066400000000000000000000455121324071732600166510ustar00rootroot00000000000000 Quick-start guide 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 figure 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) max_wal_senders = 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/static/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) hot_standby = on # Enable WAL file archiving archive_mode = on # Set archive command to a script or application that will safely store # you WALs in a secure place. /bin/true is an example of a command that # ignores archiving. Use something more sensible. archive_command = '/bin/true' # If you have configured "pg_basebackup_options" # in "repmgr.conf" to include the setting "--xlog-method=fetch" (from # PostgreSQL 10 "--wal-method=fetch"), *and* you have not set # "restore_command" in "repmgr.conf"to fetch WAL files from another # source such as Barman, you'll need to set "wal_keep_segments" to a # high enough value to ensure that all WAL files generated while # the standby is being cloned are retained until the standby starts up. # # wal_keep_segments = 5000 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 . 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 requiredl; 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, 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------). 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 on and for further details about repmgr.conf. For Debian-based distributions we recommend explictly setting pg_bindir 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/. 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 | 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 | 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 Both nodes are now registered with &repmgr; and the records have been copied to the standby server. repmgr-4.0.3/doc/repmgr-cluster-cleanup.sgml000066400000000000000000000023471324071732600210560ustar00rootroot00000000000000 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. Use the -k/--keep-history to specify the number of days of monitoring history to retain. This command can be used 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. repmgr-4.0.3/doc/repmgr-cluster-crosscheck.sgml000066400000000000000000000031251324071732600215510ustar00rootroot00000000000000 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. repmgr-4.0.3/doc/repmgr-cluster-event.sgml000066400000000000000000000042161324071732600205450ustar00rootroot00000000000000 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 --terse. Example $ repmgr -f /etc/repmgr.conf cluster event --event=standby_register Node ID | Name | Event | OK | Timestamp | Details ---------+-------+------------------+----+---------------------+-------------------------------- 3 | node3 | standby_register | t | 2017-08-17 10:28:55 | standby registration succeeded 2 | node2 | standby_register | t | 2017-08-17 10:28:53 | standby registration succeeded repmgr-4.0.3/doc/repmgr-cluster-matrix.sgml000066400000000000000000000071131324071732600207270ustar00rootroot00000000000000 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. repmgr-4.0.3/doc/repmgr-cluster-show.sgml000066400000000000000000000106751324071732600204120ustar00rootroot00000000000000 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 / bdr) 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. 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 | Connection string ----+-------+---------+-----------+----------+----------+----------------------------------------- 1 | node1 | primary | * running | | default | host=db_node1 dbname=repmgr user=repmgr 2 | node2 | standby | running | node1 | default | host=db_node2 dbname=repmgr user=repmgr 3 | node3 | standby | running | node1 | default | host=db_node3 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, e.g.: $ repmgr -f /etc/repmgr.conf cluster show ID | Name | Role | Status | Upstream | Location | Connection string ----+-------+---------+----------------------+----------+----------+----------------------------------------- 1 | node1 | primary | ? unreachable | | default | host=db_node1 dbname=repmgr user=repmgr 2 | node2 | standby | ! running as primary | node1 | default | host=db_node2 dbname=repmgr user=repmgr 3 | node3 | standby | running | node1 | default | host=db_node3 dbname=repmgr user=repmgr WARNING: following issues were detected 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 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 a better overviews of connections between nodes. 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: $ 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) repmgr-4.0.3/doc/repmgr-node-check.sgml000066400000000000000000000047351324071732600177530ustar00rootroot00000000000000 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. Example $ 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) Downstream servers: OK (2 of 2 downstream nodes attached) Replication slots: OK (node has no replication slots) 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: --role: checks if the node has the expected role --replication-lag: checks if the node is lagging by more than replication_lag_warning or replication_lag_critical --archive-ready: checks for WAL files which have not yet been archived --downstream: checks that the expected downstream nodes are attached --slots: checks there are no inactive replication slots Individual checks can also be output in a Nagios-compatible format by additionally providing the option --nagios. repmgr-4.0.3/doc/repmgr-node-rejoin.sgml000066400000000000000000000143301324071732600201540ustar00rootroot00000000000000 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. If the node is running and needs to be attached to the current primary, use . Usage repmgr node rejoin -d '$conninfo' where $conninfo is the conninfo string of any reachable node in the cluster. repmgr.conf for the stopped node *must* be supplied explicitly if not otherwise available. Event notifications A node_rejoin event notification will be generated. Notes Currently repmgr node rejoin can only be used to attach a standby to the current primary, not another standby. The node must have been shut down cleanly; if this was not the case, it will need to be manually started (remove any existing recovery.conf file first) until it has reached a consistent recovery point, then shut down cleanly. 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 Using <command>pg_rewind</command> 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. 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. To have repmgr node rejoin use pg_rewind if required, pass the command line option --force-rewind, which will tell &repmgr; to execute pg_rewind to ensure the node can be rejoined successfully. 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 --config-file command line option; the specified files will be archived in a temporary directory (whose parent directory can be specified with --config-archive-dir) and restored once the rewind operation is complete. Example, first using --dry-run, then actually executing the node rejoin command. $ repmgr node rejoin -f /etc/repmgr.conf -d 'host=node1 dbname=repmgr user=repmgr' \ --force-rewind --config-files=postgresql.local.conf,postgresql.conf --verbose --dry-run NOTICE: using provided configuration file "/etc/repmgr.conf" INFO: prerequisites for using pg_rewind are met INFO: file "postgresql.local.conf" would be copied to "/tmp/repmgr-config-archive-node1/postgresql.local.conf" INFO: file "postgresql.conf" would be copied to "/tmp/repmgr-config-archive-node1/postgresql.local.conf" INFO: 2 files would have been copied to "/tmp/repmgr-config-archive-node1" INFO: directory "/tmp/repmgr-config-archive-node1" deleted INFO: pg_rewind would now be executed DETAIL: pg_rewind command is: pg_rewind -D '/var/lib/postgresql/data' --source-server='host=node1 dbname=repmgr user=repmgr' $ repmgr node rejoin -f /etc/repmgr.conf -d 'host=node1 dbname=repmgr user=repmgr' \ --force-rewind --config-files=postgresql.local.conf,postgresql.conf --verbose NOTICE: using provided configuration file "/etc/repmgr.conf" INFO: prerequisites for using pg_rewind are met INFO: 2 files copied to "/tmp/repmgr-config-archive-node1" NOTICE: executing pg_rewind NOTICE: 2 files copied to /var/lib/pgsql/data INFO: directory "/tmp/repmgr-config-archive-node1" deleted INFO: deleting "recovery.done" 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' start" waiting for server to start.... done server started NOTICE: NODE REJOIN successful DETAIL: node 1 is now attached to node 2 See also repmgr-4.0.3/doc/repmgr-node-status.sgml000066400000000000000000000024161324071732600202130ustar00rootroot00000000000000 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.comf 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 See also See to diagnose issues. repmgr-4.0.3/doc/repmgr-primary-register.sgml000066400000000000000000000040051324071732600212460ustar00rootroot00000000000000 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. Execution Execute with the --dry-run option to check what would happen without actually registering the primary. repmgr master register can be used as an alias for repmgr primary register. 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). Event notifications A primary_register event notification will be generated. repmgr-4.0.3/doc/repmgr-primary-unregister.sgml000066400000000000000000000037661324071732600216260ustar00rootroot00000000000000 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 should be run on the current primary, 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. Event notifications A primary_unregister event notification will be generated. repmgr-4.0.3/doc/repmgr-standby-clone.sgml000066400000000000000000000113401324071732600205030ustar00rootroot00000000000000 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 recovery.conf file 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 repmgr standby register must be executed to notify &repmgr; of its presence. 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 --copy-external-config-files 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. For reliable configuration file management we recommend using a configuration management tool such as Ansible, Chef, Puppet or Salt. 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 --xlog-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 --xlog-method parameter to fetch: pg_basebackup_options='--xlog-method=fetch' and ensure that wal_keep_segments is set to an appropriately high value. See the pg_basebackup documentation for details. From PostgreSQL 10, pg_basebackup's --xlog-method parameter has been renamed to --wal-method. Event notifications A standby_clone event notification will be generated. repmgr-4.0.3/doc/repmgr-standby-follow.sgml000066400000000000000000000061641324071732600207150ustar00rootroot00000000000000 repmgr standby follow repmgr standby follow repmgr standby follow attach a standby to a new primary Description Attaches the standby to a new primary. 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. This command will force a restart of the standby server, which must be running. It can only be used to attach an active standby to the current primary node (and not to another standby). To re-add an inactive node to the replication cluster, see 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 standby. This does not guarantee the standby can follow the primary; in particular, whether the primary and standby timelines have diverged, can currently only be determined by actually attempting to attach the standby to the primary. Wait for a primary to appear. Event notifications A standby_follow event notification will be generated. If provided, &repmgr; will subsitute the placeholders %p with the node ID of the primary being followed, %c with its conninfo string, and %a with its node name. See also repmgr-4.0.3/doc/repmgr-standby-promote.sgml000066400000000000000000000034671324071732600211030ustar00rootroot00000000000000 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 the standby promotion succeeds, the server will not need to be restarted. However any other standbys will need to follow the new server, by using ; if repmgrd is active, it will handle this automatically. 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 DEBUG: setting node 2 as primary and marking existing primary as failed NOTICE: STANDBY PROMOTE successful DETAIL: server "node2" (ID: 2) was successfully promoted to primary Event notifications A standby_promote event notification will be generated. repmgr-4.0.3/doc/repmgr-standby-register.sgml000066400000000000000000000122211324071732600212260ustar00rootroot00000000000000 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. In this case, by using the option and providing the connection parameters to the primary server, the standby can be registered. 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. 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 subsitute the placeholders %p with the node ID of the primary node, %c with its conninfo string, and %a with its node name. repmgr-4.0.3/doc/repmgr-standby-switchover.sgml000066400000000000000000000142571324071732600216120ustar00rootroot00000000000000 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 standbys 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 standbys attached to the demotion candidate (existing 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. Options Promote standby to primary, even if it is behind original primary (original primary will be shut down in any case). 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 (PostgreSQL 9.5 and later). System username for remote SSH operations (defaults to local system user). Have standbys attached to the old primary follow the new primary. Execution Execute with the --dry-run option to test the switchover as far as possible without actually changing the status of either node. repmgrd should not be active on any nodes while a switchover is being executed. This restriction may be lifted in a later version. 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 Following exit codes can be emitted by repmgr standby switchover: The switchover completed successfully. 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. See also For more details see the section . repmgr-4.0.3/doc/repmgr-standby-unregister.sgml000066400000000000000000000034251324071732600215770ustar00rootroot00000000000000 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 Event notifications A standby_unregister event notification will be generated. repmgr-4.0.3/doc/repmgr-witness-register.sgml000066400000000000000000000042161324071732600212630ustar00rootroot00000000000000 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, connection information for the cluster primary server must also be provided. &repmgr; will automatically use the user and dbname values defined in the conninfo string defined in the witness node's repmgr.conf, if these are not explicitly provided. Execute with the --dry-run 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 Event notifications A witness_register event notification will be generated. repmgr-4.0.3/doc/repmgr-witness-unregister.sgml000066400000000000000000000051641324071732600216310ustar00rootroot00000000000000 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 connection information for the primary server must be provided. 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 id 3 (conninfo: host=node3 dbname=repmgr user=repmgr port=5499) successfully unregistered Unregistering a non-running witness node: $ repmgr -f /etc/repmgr.conf witness unregister -h node1 -p 5501 -F INFO: connecting to witness node "node3" (ID: 3) NOTICE: unable to connect to witness 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 3 (conninfo: host=node3 dbname=repmgr user=repmgr port=5499) 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. Event notifications A witness_unregister event notification will be generated. repmgr-4.0.3/doc/repmgr.sgml000066400000000000000000000063721324071732600157540ustar00rootroot00000000000000 %version; %filelist; repmgr"> PostgreSQL"> ]> repmgr &repmgrversion; Documentation 2ndQuadrant Ltd repmgr &repmgrversion; &legal; This is the official documentation of &repmgr; &repmgrversion; for use with PostgreSQL 9.3 - PostgreSQL 10. It describes the functionality supported by the current version of &repmgr;. &repmgr; was developed by 2ndQuadrant along with contributions from other individuals and companies. Contributions from the community are appreciated and welcome - get in touch via github or the mailing list/forum. Multiple 2ndQuadrant customers contribute funding to make repmgr development possible. 2ndQuadrant, a Platinum sponsor of the PostgreSQL project, continues to develop repmgr to meet internal needs and those of customers. Other companies as well as individual developers are welcome to participate in the efforts. repmgr PostgreSQL replication asynchronous HA high-availability Getting started &overview; &install; &quickstart; repmgr administration manual &configuration; &cloning-standbys; &promoting-standby; &follow-new-primary; &switchover; &configuring-witness-server; &event-notifications; &upgrading-repmgr; Using repmgrd &repmgrd-automatic-failover; &repmgrd-configuration; &repmgrd-demonstration; &repmgrd-cascading-replication; &repmgrd-network-split; &repmgrd-witness-server; &repmgrd-degraded-monitoring; &repmgrd-monitoring; &repmgrd-bdr; 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-cluster-show; &repmgr-cluster-matrix; &repmgr-cluster-crosscheck; &repmgr-cluster-event; &repmgr-cluster-cleanup; &appendix-release-notes; &appendix-signatures; &appendix-faq; &appendix-packages; ]]> repmgr-4.0.3/doc/repmgrd-automatic-failover.sgml000066400000000000000000000010661324071732600217040ustar00rootroot00000000000000 repmgrd automatic failover Automatic failover with repmgrd 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. repmgr-4.0.3/doc/repmgrd-bdr.sgml000066400000000000000000000454601324071732600166660ustar00rootroot00000000000000 repmgrd BDR BDR BDR failover with repmgrd &repmgr; 4.x provides support for monitoring BDR nodes and taking action in case one of the nodes fails. Due to the nature of BDR, it's only safe to use this solution for a two-node scenario. Introducing additional nodes will create an inherent risk of node desynchronisation if a node goes down without being cleanly removed from the cluster. In contrast to streaming replication, there's no concept of "promoting" a new primary node with BDR. Instead, "failover" involves monitoring both nodes with repmgrd and redirecting queries from the failed node to the remaining active node. This can be done by using an event notification script which is called by repmgrd to dynamically reconfigure a proxy server/connection pooler such as PgBouncer. Prerequisites &repmgr; 4 requires PostgreSQL 9.4 or 9.6 with the BDR 2 extension enabled and configured for a two-node BDR network. &repmgr; 4 packages must be installed on each node before attempting to configure repmgr. &repmgr; 4 will refuse to install if it detects more than two BDR nodes. Application database connections *must* be passed through a proxy server/ connection pooler such as PgBouncer, and it must be possible to dynamically reconfigure that from repmgrd. The example demonstrated in this document will use PgBouncer The proxy server / connection poolers must not be installed on the database servers. For this example, it's assumed password-less SSH connections are available from the PostgreSQL servers to the servers where PgBouncer runs, and that the user on those servers has permission to alter the PgBouncer configuration files. PostgreSQL connections must be possible between each node, and each node must be able to connect to each PgBouncer instance. Configuration A sample configuration for repmgr.conf on each BDR node would look like this: # Node information node_id=1 node_name='node1' conninfo='host=node1 dbname=bdrtest user=repmgr connect_timeout=2' data_directory='/var/lib/postgresql/data' replication_type='bdr' # Event notification configuration event_notifications=bdr_failover event_notification_command='/path/to/bdr-pgbouncer.sh %n %e %s "%c" "%a" >> /tmp/bdr-failover.log 2>&1' # repmgrd options monitor_interval_secs=5 reconnect_attempts=6 reconnect_interval=5 Adjust settings as appropriate; copy and adjust for the second node (particularly the values node_id, node_name and conninfo). Note that the values provided for the conninfo string must be valid for connections from both nodes in the replication cluster. The database must be the BDR-enabled database. If defined, the evenr event_notifications parameter will restrict execution of event_notification_command to the specified event(s). event_notification_command is the script which does the actual "heavy lifting" of reconfiguring the proxy server/ connection pooler. It is fully user-definable; a reference implementation is documented below. repmgr setup Register both nodes; example on node1: $ repmgr -f /etc/repmgr.conf bdr register NOTICE: attempting to install extension "repmgr" NOTICE: "repmgr" extension successfully installed NOTICE: node record created for node 'node1' (ID: 1) NOTICE: BDR node 1 registered (conninfo: host=node1 dbname=bdrtest user=repmgr) and on node1: $ repmgr -f /etc/repmgr.conf bdr register NOTICE: node record created for node 'node2' (ID: 2) NOTICE: BDR node 2 registered (conninfo: host=node2 dbname=bdrtest user=repmgr) The repmgr extension will be automatically created when the first node is registered, and will be propagated to the second node. Ensure the &repmgr; package is available on both nodes before attempting to register the first node. At this point the meta data for both nodes has been created; executing (on either node) should produce output like this: $ repmgr -f /etc/repmgr.conf cluster show ID | Name | Role | Status | Upstream | Location | Connection string ----+-------+------+-----------+----------+-------------------------------------------------------- 1 | node1 | bdr | * running | | default | host=node1 dbname=bdrtest user=repmgr connect_timeout=2 2 | node2 | bdr | * running | | default | host=node2 dbname=bdrtest user=repmgr connect_timeout=2 Additionally it's possible to display log of significant events; executing (on either node) should produce output like this: $ repmgr -f /etc/repmgr.conf cluster event Node ID | Event | OK | Timestamp | Details ---------+--------------+----+---------------------+---------------------------------------------- 2 | bdr_register | t | 2017-07-27 17:51:48 | node record created for node 'node2' (ID: 2) 1 | bdr_register | t | 2017-07-27 17:51:00 | node record created for node 'node1' (ID: 1) At this point there will only be records for the two node registrations (displayed here in reverse chronological order). Defining the "event_notification_command" Key to "failover" execution is the event_notification_command, which is a user-definable script specified in repmpgr.conf and which can use a &repmgr; event notification to reconfigure the proxy server / connection pooler so it points to the other, still-active node. Details of the event will be passed as parameters to the script. Following parameter placeholders are available for the script definition in repmpgr.conf; these will be replaced with the appropriate value when the script is executed: node ID event type success (1 or 0) timestamp details conninfo string of the next available node (bdr_failover and bdr_recovery) name of the next available node (bdr_failover and bdr_recovery) Note that %c and %a are only provided with particular failover events, in this case bdr_failover. The provided sample script (scripts/bdr-pgbouncer.sh) is configured as follows: event_notification_command='/path/to/bdr-pgbouncer.sh %n %e %s "%c" "%a"' and parses the placeholder parameters like this: NODE_ID=$1 EVENT_TYPE=$2 SUCCESS=$3 NEXT_CONNINFO=$4 NEXT_NODE_NAME=$5 The sample script also contains some hard-coded values for the PgBouncer configuration for both nodes; these will need to be adjusted for your local environment (ideally the scripts would be maintained as templates and generated by some kind of provisioning system). The script performs following steps: pauses PgBouncer on all nodes recreates the PgBouncer configuration file on each node using the information provided by repmgrd (primarily the conninfo string) to configure PgBouncer reloads the PgBouncer configuration executes the RESUME command (in PgBouncer) Following successful script execution, any connections to PgBouncer on the failed BDR node will be redirected to the active node. Node monitoring and failover At the intervals specified by monitor_interval_secs in repmgr.conf, repmgrd will ping each node to check if it's available. If a node isn't available, repmgrd will enter failover mode and check reconnect_attempts times at intervals of reconnect_interval to confirm the node is definitely unreachable. This buffer period is necessary to avoid false positives caused by transient network outages. If the node is still unavailable, repmgrd will enter failover mode and execute the script defined in event_notification_command; an entry will be logged in the repmgr.events table and repmgrd will (unless otherwise configured) resume monitoring of the node in "degraded" mode until it reappears. repmgrd logfile output during a failover event will look something like this on one node (usually the node which has failed, here node2): ... [2017-07-27 21:08:39] [INFO] starting continuous BDR node monitoring [2017-07-27 21:08:39] [INFO] monitoring BDR replication status on node "node2" (ID: 2) [2017-07-27 21:08:55] [INFO] monitoring BDR replication status on node "node2" (ID: 2) [2017-07-27 21:09:11] [INFO] monitoring BDR replication status on node "node2" (ID: 2) [2017-07-27 21:09:23] [WARNING] unable to connect to node node2 (ID 2) [2017-07-27 21:09:23] [INFO] checking state of node 2, 0 of 5 attempts [2017-07-27 21:09:23] [INFO] sleeping 1 seconds until next reconnection attempt [2017-07-27 21:09:24] [INFO] checking state of node 2, 1 of 5 attempts [2017-07-27 21:09:24] [INFO] sleeping 1 seconds until next reconnection attempt [2017-07-27 21:09:25] [INFO] checking state of node 2, 2 of 5 attempts [2017-07-27 21:09:25] [INFO] sleeping 1 seconds until next reconnection attempt [2017-07-27 21:09:26] [INFO] checking state of node 2, 3 of 5 attempts [2017-07-27 21:09:26] [INFO] sleeping 1 seconds until next reconnection attempt [2017-07-27 21:09:27] [INFO] checking state of node 2, 4 of 5 attempts [2017-07-27 21:09:27] [INFO] sleeping 1 seconds until next reconnection attempt [2017-07-27 21:09:28] [WARNING] unable to reconnect to node 2 after 5 attempts [2017-07-27 21:09:28] [NOTICE] setting node record for node 2 to inactive [2017-07-27 21:09:28] [INFO] executing notification command for event "bdr_failover" [2017-07-27 21:09:28] [DETAIL] command is: /path/to/bdr-pgbouncer.sh 2 bdr_failover 1 "host=host=node1 dbname=bdrtest user=repmgr connect_timeout=2" "node1" [2017-07-27 21:09:28] [INFO] node 'node2' (ID: 2) detected as failed; next available node is 'node1' (ID: 1) [2017-07-27 21:09:28] [INFO] monitoring BDR replication status on node "node2" (ID: 2) [2017-07-27 21:09:28] [DETAIL] monitoring node "node2" (ID: 2) in degraded mode ... Output on the other node (node1) during the same event will look like this: ... [2017-07-27 21:08:35] [INFO] starting continuous BDR node monitoring [2017-07-27 21:08:35] [INFO] monitoring BDR replication status on node "node1" (ID: 1) [2017-07-27 21:08:51] [INFO] monitoring BDR replication status on node "node1" (ID: 1) [2017-07-27 21:09:07] [INFO] monitoring BDR replication status on node "node1" (ID: 1) [2017-07-27 21:09:23] [WARNING] unable to connect to node node2 (ID 2) [2017-07-27 21:09:23] [INFO] checking state of node 2, 0 of 5 attempts [2017-07-27 21:09:23] [INFO] sleeping 1 seconds until next reconnection attempt [2017-07-27 21:09:24] [INFO] checking state of node 2, 1 of 5 attempts [2017-07-27 21:09:24] [INFO] sleeping 1 seconds until next reconnection attempt [2017-07-27 21:09:25] [INFO] checking state of node 2, 2 of 5 attempts [2017-07-27 21:09:25] [INFO] sleeping 1 seconds until next reconnection attempt [2017-07-27 21:09:26] [INFO] checking state of node 2, 3 of 5 attempts [2017-07-27 21:09:26] [INFO] sleeping 1 seconds until next reconnection attempt [2017-07-27 21:09:27] [INFO] checking state of node 2, 4 of 5 attempts [2017-07-27 21:09:27] [INFO] sleeping 1 seconds until next reconnection attempt [2017-07-27 21:09:28] [WARNING] unable to reconnect to node 2 after 5 attempts [2017-07-27 21:09:28] [NOTICE] other node's repmgrd is handling failover [2017-07-27 21:09:28] [INFO] monitoring BDR replication status on node "node1" (ID: 1) [2017-07-27 21:09:28] [DETAIL] monitoring node "node2" (ID: 2) in degraded mode ... This assumes only the PostgreSQL instance on node2 has failed. In this case the repmgrd instance running on node2 has performed the failover. However if the entire server becomes unavailable, repmgrd on node1 will perform the failover. Node recovery Following failure of a BDR node, if the node subsequently becomes available again, a bdr_recovery event will be generated. This could potentially be used to reconfigure PgBouncer automatically to bring the node back into the available pool, however it would be prudent to manually verify the node's status before exposing it to the application. If the failed node comes back up and connects correctly, output similar to this will be visible in the repmgrd log: [2017-07-27 21:25:30] [DETAIL] monitoring node "node2" (ID: 2) in degraded mode [2017-07-27 21:25:46] [INFO] monitoring BDR replication status on node "node2" (ID: 2) [2017-07-27 21:25:46] [DETAIL] monitoring node "node2" (ID: 2) in degraded mode [2017-07-27 21:25:55] [INFO] active replication slot for node "node1" found after 1 seconds [2017-07-27 21:25:55] [NOTICE] node "node2" (ID: 2) has recovered after 986 seconds Shutdown of both nodes If both PostgreSQL instances are shut down, repmgrd will try and handle the situation as gracefully as possible, though with no failover candidates available there's not much it can do. Should this case ever occur, we recommend shutting down repmgrd on both nodes and restarting it once the PostgreSQL instances are running properly. repmgr-4.0.3/doc/repmgrd-cascading-replication.sgml000066400000000000000000000020171324071732600223310ustar00rootroot00000000000000 repmgrd cascading replication repmgrd and cascading replication 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. repmgr-4.0.3/doc/repmgrd-configuration.sgml000066400000000000000000000112071324071732600207560ustar00rootroot00000000000000 repmgrd configuration repmgrd configuration To use repmgrd, its associated function library must be included in postgresql.conf with: shared_preload_libraries = 'repmgr' Changing this setting requires a restart of PostgreSQL; for more details see the PostgreSQL documentation. Additionally the following repmgrd options *must* be set in repmgr.conf (adjust configuration file locations as appropriate): failover=automatic promote_command='repmgr standby promote -f /etc/repmgr.conf --log-to-file' follow_command='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. See repmgr.conf.sample for further repmgrd-specific settings. When failover is set to automatic, upon detecting failure of the current primary, repmgrd will execute one of promote_command or follow_command, depending on whether the current server is to become the new primary, or needs to follow another server which has become the new primary. Note that these commands can be any valid shell script which results in one of these two actions happening, but if &repmgr;'s standby follow or standby promote commands are not executed (either directly as shown here, or from a script which performs other actions), the &repmgr; metadata will not be updated and &repmgr; will no longer function reliably. The follow_command 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; 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. 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 To ensure the current repmgrd logfile 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/postgresql/repmgr-9.6.log { missingok compress rotate 52 maxsize 100M weekly create 0600 postgres postgres } repmgr-4.0.3/doc/repmgrd-degraded-monitoring.sgml000066400000000000000000000067021324071732600220350ustar00rootroot00000000000000 repmgrd degraded monitoring "degraded monitoring" mode In certain circumstances, repmgrd is not able to fulfill its primary mission of monitoring the nodes' 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 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" (node ID: 2) monitoring upstream node "node1" (node ID: 1) in normal state (automatic failover disabled) [2017-08-29 10:59:33] [WARNING] unable to connect to upstream node "node1" (node 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" (node ID: 2) monitoring upstream node "node1" (node ID: 1) in degraded state (automatic failover disabled) [2017-08-29 10:59:53] [INFO] node "node2" (node ID: 2) monitoring upstream node "node1" (node ID: 1) in degraded state (automatic failover disabled) [2017-08-29 11:00:45] [NOTICE] reconnected to upstream node 1 after 68 seconds, resuming monitoring [2017-08-29 11:00:57] [INFO] node "node2" (node ID: 2) monitoring upstream node "node1" (node 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. repmgr-4.0.3/doc/repmgrd-demonstration.sgml000066400000000000000000000144221324071732600207770ustar00rootroot00000000000000 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 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 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: [2017-08-24 17:31:00] [NOTICE] using configuration file "/etc/repmgr.conf" [2017-08-24 17:31:00] [INFO] connecting to database "host=node2 dbname=repmgr user=repmgr" [2017-08-24 17:31:00] [NOTICE] starting monitoring of node node2 (ID: 2) [2017-08-24 17:31:00] [INFO] monitoring connection to upstream node "node1" (node 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 | 2017-08-24 17:35:54 | monitoring connection to upstream node "node1" (node ID: 1) 2 | node2 | repmgrd_start | t | 2017-08-24 17:35:50 | monitoring connection to upstream node "node1" (node ID: 1) 1 | node1 | repmgrd_start | t | 2017-08-24 17:35:46 | monitoring cluster primary "node1" (node 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). [2017-08-24 23:32:01] [INFO] node "node2" (node ID: 2) monitoring upstream node "node1" (node ID: 1) in normal state [2017-08-24 23:32:08] [WARNING] unable to connect to upstream node "node1" (node ID: 1) [2017-08-24 23:32:08] [INFO] checking state of node 1, 1 of 5 attempts [2017-08-24 23:32:08] [INFO] sleeping 1 seconds until next reconnection attempt [2017-08-24 23:32:09] [INFO] checking state of node 1, 2 of 5 attempts [2017-08-24 23:32:09] [INFO] sleeping 1 seconds until next reconnection attempt [2017-08-24 23:32:10] [INFO] checking state of node 1, 3 of 5 attempts [2017-08-24 23:32:10] [INFO] sleeping 1 seconds until next reconnection attempt [2017-08-24 23:32:11] [INFO] checking state of node 1, 4 of 5 attempts [2017-08-24 23:32:11] [INFO] sleeping 1 seconds until next reconnection attempt [2017-08-24 23:32:12] [INFO] checking state of node 1, 5 of 5 attempts [2017-08-24 23:32:12] [WARNING] unable to reconnect to node 1 after 5 attempts INFO: setting voting term to 1 INFO: node 2 is candidate INFO: node 3 has received request from node 2 for electoral term 1 (our term: 0) [2017-08-24 23:32:12] [NOTICE] this node is the winner, will now promote self and inform other nodes INFO: connecting to standby database NOTICE: promoting standby DETAIL: promoting server using 'pg_ctl -l /var/log/postgres/startup.log -w -D '/var/lib/pgsql/data' promote' INFO: reconnecting to promoted server NOTICE: STANDBY PROMOTE successful DETAIL: node 2 was successfully promoted to primary INFO: node 3 received notification to follow node 2 [2017-08-24 23:32:13] [INFO] switching to primary monitoring mode 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 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 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 | 2017-08-24 23:32:16 | node 3 now following new upstream node 2 3 | node3 | standby_follow | t | 2017-08-24 23:32:16 | node 3 is now attached to node 2 2 | node2 | repmgrd_failover_promote | t | 2017-08-24 23:32:13 | node 2 promoted to primary; old primary 1 marked as failed 2 | node2 | standby_promote | t | 2017-08-24 23:32:13 | node 2 was successfully promoted to primary repmgr-4.0.3/doc/repmgrd-monitoring.sgml000066400000000000000000000063601324071732600203000ustar00rootroot00000000000000 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-4.0.3/doc/repmgrd-network-split.sgml000066400000000000000000000044271324071732600207370ustar00rootroot00000000000000 repmgrd network splits Handling network splits with repmgrd 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 mode until a primary becomes visible). repmgr-4.0.3/doc/repmgrd-node-fencing.md000066400000000000000000000116021324071732600201000ustar00rootroot00000000000000Fencing 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 # 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-4.0.3/doc/repmgrd-witness-server.sgml000066400000000000000000000027651324071732600211200ustar00rootroot00000000000000 repmgrd witness server Using a witness server with repmgrd In a situation caused e.g. by a network interruption between two data centres, it's important to avoid a "split-brain" situation where both sides of the network assume they are the active segment and the side without an active primary unilaterally promotes one of its standbys. To prevent this situation happening, it's essential to ensure that one network segment has a "voting majority", so other segments will know they're in the minority and not attempt to promote a new primary. Where an odd number of servers exists, this is not an issue. However, if each network has an even number of nodes, it's necessary to provide some way of ensuring a majority, which is where the witness server becomes useful. This is not a fully-fledged standby node and is not integrated into replication, but it effectively represents the "casting vote" when deciding which network segment has a majority. A witness server can be set up using . Note that it only makes sense to create a witness server in conjunction with running repmgrd; the witness server will require its own repmgrd instance. repmgr-4.0.3/doc/stylesheet.css000066400000000000000000000030021324071732600164620ustar00rootroot00000000000000/* doc/src/sgml/stylesheet.css */ /* color scheme similar to www.postgresql.org */ BODY { color: #000000; background: #FFFFFF; font-family: verdana, sans-serif; } A:link { color:#0066A2; } A:visited { color:#004E66; } A:active { color:#0066A2; } A:hover { color:#000000; } H1 { font-size: 1.4em; font-weight: bold; margin-top: 0em; margin-bottom: 0em; color: #EC5800; } H2 { font-size: 1.2em; margin: 1.2em 0em 1.2em 0em; font-weight: bold; color: #666; } H3 { font-size: 1.1em; margin: 1.2em 0em 1.2em 0em; font-weight: bold; color: #666; } H4 { font-size: 0.95em; margin: 1.2em 0em 1.2em 0em; font-weight: normal; color: #666; } H5 { font-size: 0.9em; margin: 1.2em 0em 1.2em 0em; font-weight: normal; } H6 { font-size: 0.85em; margin: 1.2em 0em 1.2em 0em; font-weight: normal; } /* center some titles */ .BOOK .TITLE, .BOOK .CORPAUTHOR, .BOOK .COPYRIGHT { text-align: center; } /* decoration for formal examples */ DIV.EXAMPLE { padding-left: 15px; border-style: solid; border-width: 0px; border-left-width: 2px; border-color: black; margin: 0.5ex; } /* less dense spacing of TOC */ .BOOK .TOC DL DT { padding-top: 1.5ex; padding-bottom: 1.5ex; } .BOOK .TOC DL DL DT { padding-top: 0ex; padding-bottom: 0ex; } /* miscellaneous */ PRE.LITERALLAYOUT, .SCREEN, .SYNOPSIS, .PROGRAMLISTING { margin-left: 4ex; } .COMMENT { color: red; } VAR { font-family: monospace; font-style: italic; } /* Konqueror's standard style for ACRONYM is italic. */ ACRONYM { font-style: inherit; } repmgr-4.0.3/doc/stylesheet.dsl000066400000000000000000000763121324071732600164720ustar00rootroot00000000000000 ]]> ]]> ]]> ]> (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-4.0.3/doc/switchover.sgml000066400000000000000000000312601324071732600166470ustar00rootroot00000000000000 switchover Performing a switchover with repmgr 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 another server (the demotion candidate), which means passwordless SSH access is required to that server 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. switchover preparation Preparing for switchover 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 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 standbys attached to the demotion candidate. Double-check which commands will be used to stop/start/restart the current primary; on the primary execute: repmgr -f /etc/repmgr.conf node service --list --action=stop repmgr -f /etc/repmgr.conf node service --list --action=start repmgr -f /etc/repmgr.conf node service --list --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. 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. Check that access from applications is minimalized or preferably blocked completely, so applications are not unexpectedly interrupted. 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. Ensure that repmgrd is *not* running anywhere to prevent it unintentionally promoting a node. 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" 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). Note that following parameters in repmgr.conf are relevant to the switchover operation: reconnect_attempts: number of times to check the original primary for a clean shutdown after executing the shutdown command, before aborting reconnect_interval: interval (in seconds) to check the original primary for a clean shutdown after executing the shutdown command (up to a maximum of reconnect_attempts tries) replication_lag_critical: if replication lag (in seconds) on the standby exceeds this value, the switchover will be aborted (unless the -F/--force option is provided) switchover execution Executing the switchover command 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 switchover caveats Caveats If using PostgreSQL 9.3 or 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. repmgrd should not be running with setting failover=automatic in repmgr.conf when a switchover is carried out, otherwise the repmgrd daemon may try and promote a standby by itself. We hope to remove some of these restrictions in future versions of &repmgr;. repmgr-4.0.3/doc/upgrading-from-repmgr3.md000066400000000000000000000003541324071732600204060ustar00rootroot00000000000000Upgrading 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/4.0/upgrading-from-repmgr-3.html) repmgr-4.0.3/doc/upgrading-repmgr.sgml000066400000000000000000000301721324071732600177250ustar00rootroot00000000000000 upgrading Upgrading repmgr &repmgr; is updated regularly with point 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 feature release (e.g. 4.0.x to 4.1.x). upgrading repmgr 4.x and later Upgrading repmgr 4.x and later &repmgr; 4.x is implemented as a PostgreSQL extension; normally the upgrade consists of the two following steps: Install the updated package (or compile the updated source) In the database where the &repmgr; extension is installed, execute ALTER EXTENSION repmgr UPDATE. Always check the release notes for every release as they may contain upgrade instructions particular to individual versions. If the repmgrd daemon is in use, we recommend stopping it before upgrading &repmgr;. Note that it may be necessary to restart the PostgreSQL server if the upgrade contains changes to the shared object file used by repmgrd; check the release notes for details. upgrading pg_upgrade pg_upgrade pg_upgrade and repmgr 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. 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 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 in repmgr4 and which must be provided.
Upgrading the repmgr schema Ensure repmgrd is not running, or any cron jobs which execute the repmgr binary. Install repmgr 4 packages; 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 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. 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 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. If you don't care about any data from the existing &repmgr; installation, (e.g. the contents of the events and monitoring tables), the manual CREATE EXTENSION step can be skipped; just re-register each node, starting with the primary node, and the repmgr extension will be automatically created.
repmgr-4.0.3/doc/version.sgml000066400000000000000000000000401324071732600161270ustar00rootroot00000000000000 repmgr-4.0.3/doc/website-docs.css000066400000000000000000000152111324071732600166660ustar00rootroot00000000000000/* PostgreSQL.org Documentation Style */ /* 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-4.0.3/errcode.h000066400000000000000000000026351324071732600146210ustar00rootroot00000000000000/* * errcode.h * Copyright (c) 2ndQuadrant, 2010-2018 * * 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 #endif /* _ERRCODE_H_ */ repmgr-4.0.3/expected/000077500000000000000000000000001324071732600146205ustar00rootroot00000000000000repmgr-4.0.3/expected/repmgr_extension.out000066400000000000000000000055211324071732600207440ustar00rootroot00000000000000-- 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.am_bdr_failover_handler(-1); am_bdr_failover_handler ------------------------- (1 row) SELECT repmgr.am_bdr_failover_handler(NULL); am_bdr_failover_handler ------------------------- (1 row) SELECT repmgr.get_new_primary(); get_new_primary ----------------- (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) SELECT repmgr.unset_bdr_failover_handler(); unset_bdr_failover_handler ---------------------------- (1 row) repmgr-4.0.3/log.c000066400000000000000000000201341324071732600137440ustar00rootroot00000000000000/* * log.c - Logging methods * Copyright (c) 2ndQuadrant, 2010-2018 * * 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_NOTICE; 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 appropriate. */ 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, 100, "[%Y-%m-%d %H:%M:%S]", tm); fprintf(stderr, "%s [%s] ", buf, level_name); } else { fprintf(stderr, "%s: ", level_name); } vfprintf(stderr, fmt, ap); 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; } 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-4.0.3/log.h000066400000000000000000000106001324071732600137460ustar00rootroot00000000000000/* * log.h * Copyright (c) 2ndQuadrant, 2010-2018 * * 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 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-4.0.3/repmgr--4.0.sql000066400000000000000000000125201324071732600154100ustar00rootroot00000000000000-- 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; /* XXX update upgrade scripts! */ 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-4.0.3/repmgr--unpackaged--4.0.sql000066400000000000000000000170051324071732600175700ustar00rootroot00000000000000-- 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-4.0.3/repmgr-action-bdr.c000066400000000000000000000324121324071732600165010ustar00rootroot00000000000000/* * repmgr-action-bdr.c * * Implements BDR-related actions for the repmgr command line utility * * Copyright (c) 2ndQuadrant, 2010-2018 * * 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-bdr.h" /* * do_bdr_register() * * As each BDR node is its own primary, registering a BDR node * will create the repmgr metadata schema if necessary. */ void do_bdr_register(void) { PGconn *conn = NULL; BdrNodeInfoList bdr_nodes = T_BDR_NODE_INFO_LIST_INITIALIZER; ExtensionStatus extension_status = REPMGR_UNKNOWN; t_node_info node_info = T_NODE_INFO_INITIALIZER; RecordStatus record_status = RECORD_NOT_FOUND; PQExpBufferData event_details; bool success = true; char *dbname = NULL; /* sanity-check configuration for BDR-compatability */ if (config_file_options.replication_type != REPLICATION_TYPE_BDR) { log_error(_("cannot run BDR REGISTER on a non-BDR node")); exit(ERR_BAD_CONFIG); } dbname = pg_malloc0(MAXLEN); if (dbname == NULL) { log_error(_("unable to allocate memory; terminating.")); exit(ERR_OUT_OF_MEMORY); } /* store the database name for future reference */ get_conninfo_value(config_file_options.conninfo, "dbname", dbname); conn = establish_db_connection(config_file_options.conninfo, true); if (!is_bdr_db(conn, NULL)) { log_error(_("database \"%s\" is not BDR-enabled"), dbname); log_hint(_("when using repmgr with BDR, the repmgr schema must be stored in the BDR database")); PQfinish(conn); pfree(dbname); exit(ERR_BAD_CONFIG); } /* Check that there are at most 2 BDR nodes */ get_all_bdr_node_records(conn, &bdr_nodes); if (bdr_nodes.node_count == 0) { log_error(_("database \"%s\" is BDR-enabled but no BDR nodes were found"), dbname); PQfinish(conn); pfree(dbname); exit(ERR_BAD_CONFIG); } if (bdr_nodes.node_count > 2) { log_error(_("repmgr can only support BDR clusters with 2 nodes")); log_detail(_("this BDR cluster has %i nodes"), bdr_nodes.node_count); PQfinish(conn); pfree(dbname); exit(ERR_BAD_CONFIG); } /* check for a matching BDR node */ { PQExpBufferData bdr_local_node_name; bool node_match = false; initPQExpBuffer(&bdr_local_node_name); node_match = bdr_node_name_matches(conn, config_file_options.node_name, &bdr_local_node_name); if (node_match == false) { if (strlen(bdr_local_node_name.data)) { log_error(_("local node BDR node name is \"%s\", expected: \"%s\""), bdr_local_node_name.data, config_file_options.node_name); log_hint(_("\"node_name\" in repmgr.conf must match \"node_name\" in bdr.bdr_nodes")); } else { log_error(_("local node does not report BDR node name")); log_hint(_("ensure this is an active BDR node")); } PQfinish(conn); pfree(dbname); termPQExpBuffer(&bdr_local_node_name); exit(ERR_BAD_CONFIG); } termPQExpBuffer(&bdr_local_node_name); } /* check whether repmgr extension exists, and there are no non-BDR nodes registered */ extension_status = get_repmgr_extension_status(conn); if (extension_status == REPMGR_UNKNOWN) { log_error(_("unable to determine status of \"repmgr\" extension in database \"%s\""), dbname); PQfinish(conn); pfree(dbname); exit(ERR_BAD_CONFIG); } if (extension_status == REPMGR_UNAVAILABLE) { log_error(_("\"repmgr\" extension is not available")); PQfinish(conn); pfree(dbname); exit(ERR_BAD_CONFIG); } if (extension_status == REPMGR_INSTALLED) { if (!is_bdr_repmgr(conn)) { log_error(_("repmgr metadatabase contains records for non-BDR nodes")); PQfinish(conn); pfree(dbname); exit(ERR_BAD_CONFIG); } } else { log_debug("creating repmgr extension in database \"%s\"", dbname); begin_transaction(conn); if (!create_repmgr_extension(conn)) { log_error(_("unable to create repmgr extension - see preceding error message(s); aborting")); rollback_transaction(conn); pfree(dbname); PQfinish(conn); exit(ERR_BAD_CONFIG); } commit_transaction(conn); } pfree(dbname); if (bdr_node_has_repmgr_set(conn, config_file_options.node_name) == false) { bdr_node_set_repmgr_set(conn, config_file_options.node_name); } /* * before adding the extension tables to the replication set, if any other * BDR nodes exist, populate repmgr.nodes with a copy of existing entries * * currently we won't copy the contents of any other tables * */ { NodeInfoList local_node_records = T_NODE_INFO_LIST_INITIALIZER; get_all_node_records(conn, &local_node_records); if (local_node_records.node_count == 0) { BdrNodeInfoList bdr_nodes = T_BDR_NODE_INFO_LIST_INITIALIZER; BdrNodeInfoListCell *bdr_cell = NULL; get_all_bdr_node_records(conn, &bdr_nodes); if (bdr_nodes.node_count == 0) { log_error(_("unable to retrieve any BDR node records")); PQfinish(conn); exit(ERR_BAD_CONFIG); } for (bdr_cell = bdr_nodes.head; bdr_cell; bdr_cell = bdr_cell->next) { PGconn *bdr_node_conn = NULL; NodeInfoList existing_nodes = T_NODE_INFO_LIST_INITIALIZER; NodeInfoListCell *cell = NULL; ExtensionStatus other_node_extension_status = REPMGR_UNKNOWN; /* skip the local node */ if (strncmp(node_info.node_name, bdr_cell->node_info->node_name, MAXLEN) == 0) { continue; } log_debug("connecting to BDR node \"%s\" (conninfo: \"%s\")", bdr_cell->node_info->node_name, bdr_cell->node_info->node_local_dsn); bdr_node_conn = establish_db_connection_quiet(bdr_cell->node_info->node_local_dsn); if (PQstatus(bdr_node_conn) != CONNECTION_OK) { continue; } /* check repmgr schema exists, skip if not */ other_node_extension_status = get_repmgr_extension_status(bdr_node_conn); if (other_node_extension_status != REPMGR_INSTALLED) { continue; } get_all_node_records(bdr_node_conn, &existing_nodes); for (cell = existing_nodes.head; cell; cell = cell->next) { log_debug("creating record for node \"%s\" (ID: %i)", cell->node_info->node_name, cell->node_info->node_id); create_node_record(conn, "bdr register", cell->node_info); } PQfinish(bdr_node_conn); break; } } } /* Add the repmgr extension tables to a replication set */ add_extension_tables_to_bdr_replication_set(conn); initPQExpBuffer(&event_details); begin_transaction(conn); /* * we'll check if a record exists (even if the schema was just created), * as there's a faint chance of a race condition */ record_status = get_node_record(conn, config_file_options.node_id, &node_info); /* Update internal node record */ node_info.type = BDR; node_info.node_id = config_file_options.node_id; node_info.upstream_node_id = NO_UPSTREAM_NODE; node_info.active = true; node_info.priority = config_file_options.priority; strncpy(node_info.node_name, config_file_options.node_name, MAXLEN); strncpy(node_info.location, config_file_options.location, MAXLEN); strncpy(node_info.conninfo, config_file_options.conninfo, MAXLEN); if (record_status == RECORD_FOUND) { bool node_updated = false; /* * At this point we will have established there are no non-BDR * records, so no need to verify the node type */ 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); } /* * don't permit changing the node name - this must match the BDR node * name set when the node was registered. */ if (strncmp(node_info.node_name, config_file_options.node_name, MAXLEN) != 0) { log_error(_("a record for node %i is already registered with node_name \"%s\""), config_file_options.node_id, node_info.node_name); log_hint(_("node_name configured in repmgr.conf is \"%s\""), config_file_options.node_name); rollback_transaction(conn); PQfinish(conn); exit(ERR_BAD_CONFIG); } node_updated = update_node_record(conn, "bdr register", &node_info); if (node_updated == true) { appendPQExpBuffer(&event_details, _("node record updated for node \"%s\" (%i)"), config_file_options.node_name, config_file_options.node_id); log_verbose(LOG_NOTICE, "%s", event_details.data); } else { success = false; } } else { /* create new node record */ bool node_created = create_node_record(conn, "bdr register", &node_info); if (node_created == true) { appendPQExpBuffer(&event_details, _("node record created for node \"%s\" (ID: %i)"), config_file_options.node_name, config_file_options.node_id); log_notice("%s", event_details.data); } else { success = false; } } if (success == false) { rollback_transaction(conn); PQfinish(conn); exit(ERR_DB_QUERY); } commit_transaction(conn); /* Log the event */ create_event_notification( conn, &config_file_options, config_file_options.node_id, "bdr_register", true, event_details.data); termPQExpBuffer(&event_details); PQfinish(conn); log_notice(_("BDR node %i registered (conninfo: %s)"), config_file_options.node_id, config_file_options.conninfo); return; } void do_bdr_unregister(void) { PGconn *conn = NULL; ExtensionStatus extension_status = REPMGR_UNKNOWN; int target_node_id = UNKNOWN_NODE_ID; t_node_info node_info = T_NODE_INFO_INITIALIZER; RecordStatus record_status = RECORD_NOT_FOUND; bool node_record_deleted = false; PQExpBufferData event_details; char *dbname; /* sanity-check configuration for BDR-compatability */ if (config_file_options.replication_type != REPLICATION_TYPE_BDR) { log_error(_("cannot run BDR UNREGISTER on a non-BDR node")); exit(ERR_BAD_CONFIG); } dbname = pg_malloc0(MAXLEN); if (dbname == NULL) { log_error(_("unable to allocate memory; terminating.")); exit(ERR_OUT_OF_MEMORY); } /* store the database name for future reference */ get_conninfo_value(config_file_options.conninfo, "dbname", dbname); conn = establish_db_connection(config_file_options.conninfo, true); if (!is_bdr_db(conn, NULL)) { log_error(_("database \"%s\" is not BDR-enabled"), dbname); PQfinish(conn); pfree(dbname); exit(ERR_BAD_CONFIG); } extension_status = get_repmgr_extension_status(conn); if (extension_status != REPMGR_INSTALLED) { log_error(_("repmgr is not installed on database \"%s\""), dbname); PQfinish(conn); pfree(dbname); exit(ERR_BAD_CONFIG); } pfree(dbname); if (!is_bdr_repmgr(conn)) { log_error(_("repmgr metadatabase contains records for non-BDR nodes")); PQfinish(conn); exit(ERR_BAD_CONFIG); } initPQExpBuffer(&event_details); 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 BDR node */ record_status = get_node_record(conn, target_node_id, &node_info); if (record_status != RECORD_FOUND) { log_error(_("no record found for node %i"), target_node_id); PQfinish(conn); exit(ERR_BAD_CONFIG); } begin_transaction(conn); log_debug("unregistering node %i", target_node_id); node_record_deleted = delete_node_record(conn, target_node_id); if (node_record_deleted == false) { appendPQExpBuffer(&event_details, "unable to delete node record for node \"%s\" (ID: %i)", node_info.node_name, target_node_id); rollback_transaction(conn); } else { appendPQExpBuffer(&event_details, "node record deleted for node \"%s\" (ID: %i)", node_info.node_name, target_node_id); commit_transaction(conn); } /* Log the event */ create_event_notification( conn, &config_file_options, config_file_options.node_id, "bdr_unregister", true, event_details.data); PQfinish(conn); log_notice(_("bdr node \"%s\" (ID: %i) successfully unregistered"), node_info.node_name, target_node_id); termPQExpBuffer(&event_details); return; } void do_bdr_help(void) { print_help_header(); printf(_("Usage:\n")); printf(_(" %s [OPTIONS] bdr register\n"), progname()); printf(_(" %s [OPTIONS] bdr unregister\n"), progname()); puts(""); printf(_("BDR REGISTER\n")); puts(""); printf(_(" \"bdr register\" initialises the repmgr cluster and registers the initial bdr node.\n")); puts(""); printf(_(" -F, --force overwrite an existing node record\n")); puts(""); printf(_("BDR UNREGISTER\n")); puts(""); printf(_(" \"bdr unregister\" unregisters an inactive BDR node.\n")); puts(""); printf(_(" --node-id ID node to unregister (optional, used when the node to unregister\n" \ " is offline)\n")); puts(""); } repmgr-4.0.3/repmgr-action-bdr.h000066400000000000000000000016401324071732600165050ustar00rootroot00000000000000/* * repmgr-action-bdr.h * Copyright (c) 2ndQuadrant, 2010-2018 * * 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_BDR_H_ #define _REPMGR_ACTION_BDR_H_ extern void do_bdr_register(void); extern void do_bdr_unregister(void); extern void do_bdr_help(void); #endif /* _REPMGR_ACTION_BDR_H_ */ repmgr-4.0.3/repmgr-action-cluster.c000066400000000000000000001025331324071732600174150ustar00rootroot00000000000000/* * repmgr-action-cluster.c * * Implements cluster information actions for the repmgr command line utility * * Copyright (c) 2ndQuadrant, 2010-2018 * * 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 7 typedef enum { SHOW_ID = 0, SHOW_NAME, SHOW_ROLE, SHOW_STATUS, SHOW_UPSTREAM_NAME, SHOW_LOCATION, 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 { char title[MAXLEN]; int max_length; int cur_length; }; 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, int *name_length); static int build_cluster_crosscheck(t_node_status_cube ***cube_dest, int *name_length); 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: * --csv */ 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; /* 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); } 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); 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].max_length = strlen(headers_show[i].title); } for (cell = nodes.head; cell; cell = cell->next) { PQExpBufferData details; cell->node_info->conn = establish_db_connection_quiet(cell->node_info->conninfo); if (PQstatus(cell->node_info->conn) == CONNECTION_OK) { cell->node_info->node_status = NODE_STATUS_UP; cell->node_info->recovery_type = get_recovery_type(cell->node_info->conn); } else { char error[MAXLEN]; strncpy(error, PQerrorMessage(cell->node_info->conn), MAXLEN); cell->node_info->node_status = NODE_STATUS_DOWN; cell->node_info->recovery_type = RECTYPE_UNKNOWN; 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)); } initPQExpBuffer(&details); /* * TODO: count nodes marked as "? unreachable" and add a hint about * the other cluster commands for better determining whether * unreachable. */ switch (cell->node_info->type) { case PRIMARY: { /* node is reachable */ if (cell->node_info->node_status == NODE_STATUS_UP) { if (cell->node_info->active == true) { switch (cell->node_info->recovery_type) { case RECTYPE_PRIMARY: appendPQExpBuffer(&details, "* running"); break; case RECTYPE_STANDBY: appendPQExpBuffer(&details, "! running as standby"); item_list_append_format(&warnings, "node \"%s\" (ID: %i) is registered as primary but running as standby", cell->node_info->node_name, cell->node_info->node_id); break; case RECTYPE_UNKNOWN: appendPQExpBuffer(&details, "! unknown"); item_list_append_format(&warnings, "node \"%s\" (ID: %i) has unknown replication status", cell->node_info->node_name, cell->node_info->node_id); break; } } else { if (cell->node_info->recovery_type == RECTYPE_PRIMARY) { appendPQExpBuffer(&details, "! running"); item_list_append_format(&warnings, "node \"%s\" (ID: %i) is running but the repmgr node record is inactive", cell->node_info->node_name, cell->node_info->node_id); } else { appendPQExpBuffer(&details, "! running as standby"); item_list_append_format(&warnings, "node \"%s\" (ID: %i) is registered as an inactive primary but running as standby", cell->node_info->node_name, cell->node_info->node_id); } } } /* node is unreachable */ else { /* node is unreachable but marked active */ if (cell->node_info->active == true) { appendPQExpBuffer(&details, "? unreachable"); item_list_append_format(&warnings, "node \"%s\" (ID: %i) is registered as an active primary but is unreachable", cell->node_info->node_name, cell->node_info->node_id); } /* node is unreachable and marked as inactive */ else { appendPQExpBuffer(&details, "- failed"); } } } break; case STANDBY: { /* node is reachable */ if (cell->node_info->node_status == NODE_STATUS_UP) { if (cell->node_info->active == true) { switch (cell->node_info->recovery_type) { case RECTYPE_STANDBY: appendPQExpBuffer(&details, " running"); break; case RECTYPE_PRIMARY: appendPQExpBuffer(&details, "! running as primary"); item_list_append_format(&warnings, "node \"%s\" (ID: %i) is registered as standby but running as primary", cell->node_info->node_name, cell->node_info->node_id); break; case RECTYPE_UNKNOWN: appendPQExpBuffer(&details, "! unknown"); item_list_append_format( &warnings, "node \"%s\" (ID: %i) has unknown replication status", cell->node_info->node_name, cell->node_info->node_id); break; } } else { if (cell->node_info->recovery_type == RECTYPE_STANDBY) { appendPQExpBuffer(&details, "! running"); item_list_append_format(&warnings, "node \"%s\" (ID: %i) is running but the repmgr node record is inactive", cell->node_info->node_name, cell->node_info->node_id); } else { appendPQExpBuffer(&details, "! running as primary"); item_list_append_format(&warnings, "node \"%s\" (ID: %i) is running as primary but the repmgr node record is inactive", cell->node_info->node_name, cell->node_info->node_id); } } } /* node is unreachable */ else { /* node is unreachable but marked active */ if (cell->node_info->active == true) { appendPQExpBuffer(&details, "? unreachable"); item_list_append_format(&warnings, "node \"%s\" (ID: %i) is registered as an active standby but is unreachable", cell->node_info->node_name, cell->node_info->node_id); } else { appendPQExpBuffer(&details, "- failed"); } } } break; case WITNESS: case BDR: { /* node is reachable */ if (cell->node_info->node_status == NODE_STATUS_UP) { if (cell->node_info->active == true) appendPQExpBuffer(&details, "* running"); else appendPQExpBuffer(&details, "! running"); } /* node is unreachable */ else { if (cell->node_info->active == true) appendPQExpBuffer(&details, "? unreachable"); else appendPQExpBuffer(&details, "- failed"); } } break; case UNKNOWN: { /* this should never happen */ appendPQExpBuffer(&details, "? unknown node type"); } break; } strncpy(cell->node_info->details, details.data, MAXLEN); termPQExpBuffer(&details); PQfinish(cell->node_info->conn); cell->node_info->conn = NULL; 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); headers_show[SHOW_LOCATION].cur_length = strlen(cell->node_info->location); headers_show[SHOW_CONNINFO].cur_length = strlen(cell->node_info->conninfo); for (i = 0; i < SHOW_HEADER_COUNT; i++) { if (headers_show[i].cur_length > headers_show[i].max_length) { headers_show[i].max_length = headers_show[i].cur_length; } } } if (runtime_options.output_mode == OM_TEXT) { for (i = 0; i < SHOW_HEADER_COUNT; i++) { if (i == 0) printf(" "); else printf(" | "); printf("%-*s", headers_show[i].max_length, headers_show[i].title); } printf("\n"); printf("-"); for (i = 0; i < SHOW_HEADER_COUNT; i++) { int j; for (j = 0; j < headers_show[i].max_length; j++) printf("-"); if (i < (SHOW_HEADER_COUNT - 1)) printf("-+-"); else printf("-"); } printf("\n"); } 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("| %-*s\n", headers_show[SHOW_CONNINFO].max_length, cell->node_info->conninfo); } } 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; printf(_("\nWARNING: following issues were detected\n")); for (cell = warnings.head; cell; cell = cell->next) { printf(_(" - %s\n"), cell->string); } } } /* * CLUSTER EVENT * * Parameters: * --limit[=20] * --all * --node-[id|name] * --event */ 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 --terse provided, simply omit the "Details" column */ if (runtime_options.terse == true) 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; } } } 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; 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); puts(""); } void do_cluster_crosscheck(void) { int i = 0, n = 0; char c; const char *node_header = "Name"; int name_length = strlen(node_header); t_node_status_cube **cube; n = build_cluster_crosscheck(&cube, &name_length); 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); } } } else { printf("%*s | Id ", name_length, node_header); for (i = 0; i < n; i++) printf("| %2d ", cube[i]->node_id); printf("\n"); for (i = 0; i < name_length; i++) printf("-"); printf("-+----"); for (i = 0; i < n; i++) printf("+----"); printf("\n"); for (i = 0; i < n; i++) { int column_node_ix; printf("%*s | %2d ", name_length, cube[i]->node_name, 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; /* * 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'; break; case 0: c = '*'; break; default: exit(ERR_INTERNAL); } printf("| %c ", c); } printf("\n"); } } /* 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); } } void do_cluster_matrix() { int i = 0, j = 0, n = 0; const char *node_header = "Name"; int name_length = strlen(node_header); t_node_matrix_rec **matrix_rec_list; n = build_cluster_matrix(&matrix_rec_list, &name_length); 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); } else { char c; printf("%*s | Id ", name_length, node_header); for (i = 0; i < n; i++) printf("| %2d ", matrix_rec_list[i]->node_id); printf("\n"); for (i = 0; i < name_length; i++) printf("-"); printf("-+----"); for (i = 0; i < n; i++) printf("+----"); printf("\n"); for (i = 0; i < n; i++) { printf("%*s | %2d ", name_length, matrix_rec_list[i]->node_name, matrix_rec_list[i]->node_id); for (j = 0; j < n; j++) { switch (matrix_rec_list[i]->node_status_list[j]->node_status) { case -2: c = '?'; break; case -1: c = 'x'; break; case 0: c = '*'; break; default: exit(ERR_INTERNAL); } printf("| %c ", c); } printf("\n"); } } 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); } 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, int *name_length) { 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; } get_all_node_records(conn, &nodes); 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) { int name_length_cur; 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, MAXLEN); /* * Find the maximum length of a node name */ name_length_cur = strlen(matrix_rec_list[i]->node_name); if (name_length_cur > *name_length) *name_length = name_length_cur; 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(cell->node_info->conninfo, false); 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 */ appendPQExpBuffer(&command, "\"%s -d '%s' ", make_pg_path(progname()), cell->node_info->conninfo); if (strlen(pg_bindir)) { appendPQExpBuffer(&command, "--pg_bindir="); appendShellString(&command, pg_bindir); appendPQExpBuffer(&command, " "); } appendPQExpBuffer(&command, " cluster show --csv\""); 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, &command_output); p = command_output.data; termPQExpBuffer(&command); for (j = 0; j < nodes.node_count; j++) { if (sscanf(p, "%d,%d", &x, &y) != 2) { fprintf(stderr, _("cannot parse --csv output: %s\n"), p); PQfinish(node_conn); exit(ERR_INTERNAL); } 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); node_conn = NULL; } *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, int *name_length) { 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); get_all_node_records(conn, &nodes); 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) { int name_length_cur = 0; 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, MAXLEN); /* * Find the maximum length of a node name */ name_length_cur = strlen(cube[h]->node_name); if (name_length_cur > *name_length) *name_length = name_length_cur; 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); appendPQExpBuffer(&command, "%s -d '%s' --node-id=%i ", make_pg_path(progname()), cell->node_info->conninfo, remote_node_id); if (strlen(pg_bindir)) { appendPQExpBuffer(&command, "--pg_bindir="); appendShellString(&command, pg_bindir); appendPQExpBuffer(&command, " "); } appendPQExpBuffer(&command, "cluster matrix --csv 2>/dev/null"); initPQExpBuffer(&command_output); /* fix to work with --node-id */ if (cube[i]->node_id == config_file_options.node_id) { (void) local_command( 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, &command_output); free_conninfo_params(&remote_conninfo); termPQExpBuffer("ed_command); } termPQExpBuffer(&command); p = command_output.data; if (!strlen(command_output.data)) { termPQExpBuffer(&command_output); 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) { fprintf(stderr, _("cannot parse --csv output: %s\n"), p); exit(ERR_INTERNAL); } 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; 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); 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); if (delete_monitoring_records(primary_conn, runtime_options.keep_history) == false) { log_error(_("unable to delete monitoring records")); log_detail("%s", PQerrorMessage(primary_conn)); 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)); } PQfinish(primary_conn); if (runtime_options.keep_history > 0) { log_notice(_("monitoring records older than %i day(s) deleted"), runtime_options.keep_history); } else { log_info(_("all monitoring records deleted")); } 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()); 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")); 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")); puts(""); printf(_("CLUSTER CLEANUP\n")); puts(""); printf(_(" \"cluster cleanup\" purges records from the \"repmgr.monitor\" table.\n")); puts(""); printf(_(" -k, --keep-history=VALUE retain indicated number of days of history (default: 0)\n")); puts(""); } repmgr-4.0.3/repmgr-action-cluster.h000066400000000000000000000024771324071732600174300ustar00rootroot00000000000000/* * repmgr-action-cluster.h * Copyright (c) 2ndQuadrant, 2010-2018 * * 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[MAXLEN]; t_node_status_rec **node_status_list; } t_node_matrix_rec; typedef struct { int node_id; char node_name[MAXLEN]; t_node_matrix_rec **matrix_list_rec; } t_node_status_cube; extern 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-4.0.3/repmgr-action-node.c000066400000000000000000001634311324071732600166650ustar00rootroot00000000000000/* * repmgr-action-node.c * * Implements actions available for any kind of node * * Copyright (c) 2ndQuadrant, 2010-2018 * * 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 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, 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); /* * 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 server_version[MAXLEN]; char cluster_size[MAXLEN]; PQExpBufferData output; KeyValueList node_status = {NULL, NULL}; KeyValueListCell *cell = NULL; ItemList warnings = {NULL, NULL}; RecoveryType recovery_type = RECTYPE_UNKNOWN; ReplInfo replication_info = T_REPLINFO_INTIALIZER; t_recovery_conf recovery_conf = T_RECOVERY_CONF_INITIALIZER; char data_dir[MAXPGPATH] = ""; if (runtime_options.is_shutdown_cleanly == true) { return _do_node_status_is_shutdown_cleanly(); } /* 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); server_version_num = get_server_version(conn, NULL); /* Check node exists and is really a standby */ 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); } (void) get_server_version(conn, server_version); if (get_cluster_size(conn, cluster_size) == false) strncpy(cluster_size, _("unknown"), MAXLEN); recovery_type = get_recovery_type(conn); get_node_replication_stats(conn, server_version_num, &node_info); key_value_list_set( &node_status, "PostgreSQL version", server_version); 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 = UNKNOWN_SYSTEM_IDENTIFIER; local_system_identifier = get_system_identifier(config_file_options.data_directory); 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; case BDR: 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 { bool enabled = true; PQExpBufferData archiving_status; char archive_command[MAXLEN] = ""; initPQExpBuffer(&archiving_status); if (recovery_type == RECTYPE_STANDBY) { if (guc_set(conn, "archive_mode", "=", "on")) enabled = false; } if (enabled == true) { appendPQExpBuffer(&archiving_status, "enabled"); } else { appendPQExpBuffer(&archiving_status, "disabled"); } if (enabled == false && recovery_type == RECTYPE_STANDBY) { appendPQExpBuffer(&archiving_status, " (on standbys \"archive_mode\" must be set to \"always\" to be effective)"); } 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 (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"); } if (server_version_num < 90400) { key_value_list_set(&node_status, "Replication slots", "not available"); } else if (node_info.max_replication_slots > 0) { PQExpBufferData slotinfo; initPQExpBuffer(&slotinfo); appendPQExpBuffer(&slotinfo, "%i (of maximal %i)", node_info.active_replication_slots + node_info.inactive_replication_slots, node_info.max_replication_slots); if (node_info.inactive_replication_slots > 0) { appendPQExpBuffer(&slotinfo, "; %i inactive", node_info.inactive_replication_slots); item_list_append_format(&warnings, _("- node has %i inactive replication slots"), node_info.inactive_replication_slots); } key_value_list_set(&node_status, "Replication slots", slotinfo.data); termPQExpBuffer(&slotinfo); } else if (node_info.max_replication_slots == 0) { key_value_list_set(&node_status, "Replication slots", "disabled"); } /* * check for missing replication slots - we do this regardless of * what "max_replication_slots" is set to */ { NodeInfoList missing_slots = T_NODE_INFO_LIST_INITIALIZER; get_downsteam_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); } } } 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, &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); appendPQExpBuffer(&output, "\"inactive_replaction_slots\",%i\n", node_info.inactive_replication_slots); } 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) { log_warning(_("following issue(s) were detected:")); print_item_list(&warnings); /* add this when functionality implemented */ /* log_hint(_("execute \"repmgr node check\" for more details")); */ } key_value_list_free(&node_status); item_list_free(&warnings); PQfinish(conn); } /* * 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); appendPQExpBuffer(&output, "--state="); /* sanity-check we're dealing with a PostgreSQL directory */ if (is_pg_dir(config_file_options.data_directory) == false) { appendPQExpBuffer(&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_controldata says */ db_state = get_db_state(config_file_options.data_directory); 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); /* unable to read pg_control, don't know what's happening */ if (checkPoint == InvalidXLogRecPtr) { node_status = NODE_STATUS_UNKNOWN; } /* * if still "UNKNOWN" at this point, then the node must be cleanly shut * down */ else if (node_status == NODE_STATUS_UNKNOWN) { node_status = NODE_STATUS_DOWN; } log_verbose(LOG_DEBUG, "node status determined as: %s", print_node_status(node_status)); switch (node_status) { case NODE_STATUS_UP: appendPQExpBuffer(&output, "RUNNING"); break; case NODE_STATUS_SHUTTING_DOWN: appendPQExpBuffer(&output, "SHUTTING_DOWN"); break; case NODE_STATUS_DOWN: appendPQExpBuffer(&output, "SHUTDOWN --last-checkpoint-lsn=%X/%X", format_lsn(checkPoint)); break; case NODE_STATUS_UNCLEAN_SHUTDOWN: appendPQExpBuffer(&output, "UNCLEAN_SHUTDOWN"); break; case NODE_STATUS_UNKNOWN: appendPQExpBuffer(&output, "UNKNOWN"); break; } printf("%s\n", output.data); termPQExpBuffer(&output); return; } /* * 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; /* internal */ if (runtime_options.has_passfile == true) { return_code = has_passfile() ? 0 : 1; exit(return_code); } if (runtime_options.replication_connection == true) { do_node_check_replication_connection(); exit(SUCCESS); } 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_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); } server_version_num = get_server_version(conn, NULL); /* add replication statistics to node record */ get_node_replication_stats(conn, server_version_num, &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.downstream == true) { return_code = do_node_check_downstream(conn, runtime_options.output_mode, 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.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); return; } /* output general overview */ initPQExpBuffer(&output); /* order functions are called is also output order */ (void) do_node_check_role(conn, runtime_options.output_mode, &node_info, &status_list); (void) do_node_check_replication_lag(conn, runtime_options.output_mode, &node_info, &status_list); (void) do_node_check_archive_ready(conn, runtime_options.output_mode, &status_list); (void) do_node_check_downstream(conn, runtime_options.output_mode, &status_list); (void) do_node_check_slots(conn, runtime_options.output_mode, &node_info, &status_list); if (runtime_options.output_mode == OM_CSV) { /* TODO */ } 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); } appendPQExpBuffer(&output, "\n"); } } printf("%s", output.data); termPQExpBuffer(&output); check_status_list_free(&status_list); PQfinish(conn); } 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) { 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; appendPQExpBuffer(&details, _("node is registered as primary but running as standby")); } else { appendPQExpBuffer(&details, _("node is primary")); } break; case STANDBY: if (recovery_type == RECTYPE_PRIMARY) { status = CHECK_STATUS_CRITICAL; appendPQExpBuffer(&details, _("node is registered as standby but running as primary")); } else { appendPQExpBuffer(&details, _("node is standby")); } break; case BDR: { PQExpBufferData output; initPQExpBuffer(&output); if (is_bdr_db(conn, &output) == false) { status = CHECK_STATUS_CRITICAL; appendPQExpBuffer(&details, "%s", output.data); } termPQExpBuffer(&output); if (status == CHECK_STATUS_OK) { if (is_active_bdr_node(conn, node_info->node_name) == false) { status = CHECK_STATUS_CRITICAL; appendPQExpBuffer(&details, _("node is not an active BDR node")); } else { appendPQExpBuffer(&details, _("node is an active BDR node")); } } } default: break; } switch (mode) { case OM_NAGIOS: printf("REPMGR_SERVER_ROLE %s: %s\n", output_check_status(status), details.data); break; 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; initPQExpBuffer(&details); if (server_version_num < 90400) { appendPQExpBuffer(&details, _("replication slots not available for this PostgreSQL version")); } else if (node_info->total_replication_slots == 0) { appendPQExpBuffer(&details, _("node has no replication slots")); } else if (node_info->inactive_replication_slots == 0) { appendPQExpBuffer(&details, _("%i of %i 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 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_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 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; t_conninfo_param_list remote_conninfo = T_CONNINFO_PARAM_LIST_INITIALIZER; PQExpBufferData output; initPQExpBuffer(&output); appendPQExpBuffer(&output, "--connection="); if (runtime_options.remote_node_id == UNKNOWN_NODE_ID) { appendPQExpBuffer(&output, "UNKNOWN"); printf("%s\n", output.data); termPQExpBuffer(&output); return; } local_conn = establish_db_connection(config_file_options.conninfo, true); record_status = get_node_record(local_conn, runtime_options.remote_node_id, &node_record); PQfinish(local_conn); if (record_status != RECORD_FOUND) { appendPQExpBuffer(&output, "UNKNOWN"); printf("%s\n", output.data); termPQExpBuffer(&output); return; } initialize_conninfo_params(&remote_conninfo, false); parse_conninfo_string(node_record.conninfo, &remote_conninfo, NULL, false); param_set(&remote_conninfo, "replication", "1"); param_set(&remote_conninfo, "user", node_record.repluser); repl_conn = establish_db_connection_by_params(&remote_conninfo, false); if (PQstatus(repl_conn) != CONNECTION_OK) { appendPQExpBuffer(&output, "BAD"); printf("%s\n", output.data); termPQExpBuffer(&output); return; } PQfinish(repl_conn); appendPQExpBuffer(&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) { 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: appendPQExpBuffer( &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_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_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) { 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: appendPQExpBuffer( &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: appendPQExpBuffer( &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 < 0) { status = CHECK_STATUS_UNKNOWN; switch (mode) { case OM_OPTFORMAT: break; case OM_NAGIOS: case OM_TEXT: appendPQExpBuffer( &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_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; } /* TODO: ensure only runs on streaming replication nodes */ static CheckStatus do_node_check_downstream(PGconn *conn, OutputMode mode, CheckStatusList *list_output) { NodeInfoList downstream_nodes = T_NODE_INFO_LIST_INITIALIZER; NodeInfoListCell *cell = NULL; int missing_nodes_count = 0; CheckStatus status = CHECK_STATUS_OK; ItemList missing_nodes = {NULL, NULL}; ItemList attached_nodes = {NULL, NULL}; PQExpBufferData details; initPQExpBuffer(&details); get_downstream_node_records(conn, config_file_options.node_id, &downstream_nodes); for (cell = downstream_nodes.head; cell; cell = cell->next) { if (is_downstream_node_attached(conn, cell->node_info->node_name) == false) { 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 (missing_nodes_count == 0) { if (downstream_nodes.node_count == 0) appendPQExpBuffer( &details, "this node has no downstream nodes"); else appendPQExpBuffer( &details, "%i of %i downstream nodes attached", downstream_nodes.node_count, downstream_nodes.node_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, downstream_nodes.node_count); if (mode != OM_NAGIOS) { appendPQExpBuffer( &details, "; missing: "); for (missing_cell = missing_nodes.head; missing_cell; missing_cell = missing_cell->next) { if (first == false) appendPQExpBuffer( &details, ", "); else first = false; if (first == false) appendPQExpBuffer( &details, "%s", 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 (downstream_nodes.node_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_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; } 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_data_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) { if (runtime_options.dry_run == true) { log_info(_("a CHECKPOINT would be issued here")); } else { PGconn *conn = NULL; 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); log_notice(_("issuing CHECKPOINT")); /* check superuser conn! */ 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_data_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. */ void do_node_rejoin(void) { PGconn *upstream_conn = NULL; RecoveryType upstream_recovery_type = RECTYPE_UNKNOWN; DBState db_state; PGPing status; bool is_shutdown = true; PQExpBufferData command; PQExpBufferData command_output; PQExpBufferData follow_output; struct stat statbuf; t_node_info primary_node_record = T_NODE_INFO_INITIALIZER; bool success = true; int server_version_num = UNKNOWN_SERVER_VERSION_NUM; 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; } db_state = get_db_state(config_file_options.data_directory); 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_BAD_CONFIG); } /* 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 { log_error(_("database is not shut down cleanly")); if (runtime_options.force_rewind == 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_BAD_CONFIG); } } /* check provided upstream connection */ upstream_conn = establish_db_connection_by_params(&source_conninfo, true); /* sanity checks for 9.3 */ server_version_num = get_server_version(upstream_conn, NULL); if (server_version_num < 90400) check_93_config(); 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); } PQfinish(upstream_conn); /* connect to registered primary and check it's not in recovery */ upstream_conn = establish_db_connection(primary_node_record.conninfo, true); upstream_recovery_type = get_recovery_type(upstream_conn); if (upstream_recovery_type != RECTYPE_PRIMARY) { log_error(_("primary server is registered 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(upstream_conn); exit(ERR_BAD_CONFIG); } /* * 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 == true) { PQExpBufferData reason; PQExpBufferData msg; initPQExpBuffer(&reason); if (can_use_pg_rewind(upstream_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(upstream_conn); exit(ERR_BAD_CONFIG); } termPQExpBuffer(&reason); initPQExpBuffer(&msg); appendPQExpBuffer(&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); } /* * Forcibly rewind node if requested (this is mainly for use when this * action is being executed by "repmgr standby switchover") */ if (runtime_options.force_rewind == true) { int ret; PQExpBufferData filebuf; _do_node_archive_config(); /* execute pg_rewind */ initPQExpBuffer(&command); appendPQExpBuffer(&command, "%s -D ", make_pg_path("pg_rewind")); 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); PQfinish(upstream_conn); exit(SUCCESS); } log_notice(_("executing pg_rewind")); log_debug("pg_rewind command is:\n %s", command.data); initPQExpBuffer(&command_output); ret = local_command( command.data, &command_output); termPQExpBuffer(&command); if (ret == false) { log_error(_("unable to execute pg_rewind")); log_detail("%s", command_output.data); termPQExpBuffer(&command_output); exit(ERR_BAD_CONFIG); } 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 */ { 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); } } termPQExpBuffer(&slotdir_path); } } initPQExpBuffer(&follow_output); success = do_standby_follow_internal(upstream_conn, &primary_node_record, &follow_output, &follow_error_code); if (success == false) { log_notice(_("NODE REJOIN failed")); log_detail("%s", follow_output.data); create_event_notification(upstream_conn, &config_file_options, config_file_options.node_id, "node_rejoin", success, follow_output.data); PQfinish(upstream_conn); termPQExpBuffer(&follow_output); exit(follow_error_code); } /* * XXX add checks that node actually started and connected to primary, * if not exit with ERR_REJOIN_FAIL */ create_event_notification(upstream_conn, &config_file_options, config_file_options.node_id, "node_rejoin", success, follow_output.data); PQfinish(upstream_conn); log_notice(_("NODE REJOIN successful")); log_detail("%s", follow_output.data); termPQExpBuffer(&follow_output); return; } /* * 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); } } 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); 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) { strncpy(filenamebuf, runtime_options.config_files + i, config_file_len - i); 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, "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"); ptr_new = fopen(dest_file, "w"); if (ptr_old == NULL) return false; 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; } 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(_(" --csv emit output as CSV\n")); printf(_(" --nagios emit output in Nagios format (individual status 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(_(" --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")); 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 execute \"pg_rewind\" if necessary\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")); 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")); puts(""); } repmgr-4.0.3/repmgr-action-node.h000066400000000000000000000017451324071732600166710ustar00rootroot00000000000000/* * repmgr-action-node.h * Copyright (c) 2ndQuadrant, 2010-2018 * * 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_help(void); #endif /* _REPMGR_ACTION_NODE_H_ */ repmgr-4.0.3/repmgr-action-primary.c000066400000000000000000000357541324071732600174310ustar00rootroot00000000000000/* * repmgr-action-primary.c * * Implements primary actions for the repmgr command line utility * * Copyright (c) 2ndQuadrant, 2010-2018 * * 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); } else { log_error(_("connection to node lost")); 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); /* Ensure there isn't another registered node which is primary */ primary_conn = get_primary_connection(conn, ¤t_primary_id, NULL); if (primary_conn != NULL) { if (current_primary_id != config_file_options.node_id) { /* * it's impossible to add a second primary to a streaming * replication cluster */ log_error(_("there is already an active registered primary (node ID: %i) in this cluster"), current_primary_id); PQfinish(primary_conn); PQfinish(conn); exit(ERR_BAD_CONFIG); } /* we've probably connected to ourselves */ PQfinish(primary_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_error(_("another node with id %i is already registered as primary"), current_primary_id); log_detail(_("a streaming replication cluster can have only one primary node")); rollback_transaction(conn); PQfinish(conn); exit(ERR_BAD_CONFIG); } /* * 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) { appendPQExpBuffer(&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 *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) { t_node_info primary_node_info = T_NODE_INFO_INITIALIZER; 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); /* Target node is local node? */ if (target_node_info.node_id == UNKNOWN_NODE_ID || 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; } /* * 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 */ if (primary_node_info.node_id == target_node_info_ptr->node_id) { 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(""); } repmgr-4.0.3/repmgr-action-primary.h000066400000000000000000000016241324071732600174230ustar00rootroot00000000000000/* * repmgr-action-primary.h * Copyright (c) 2ndQuadrant, 2010-2018 * * 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-4.0.3/repmgr-action-standby.c000066400000000000000000004613151324071732600174060ustar00rootroot00000000000000/* * repmgr-action-standby.c * * Implements standby actions for the repmgr command line utility * * Copyright (c) 2ndQuadrant, 2010-2018 * * 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-standby.h" typedef struct TablespaceDataListCell { struct TablespaceDataListCell *next; char *name; char *oid; char *location; /* optional payload */ FILE *f; } TablespaceDataListCell; typedef struct TablespaceDataList { TablespaceDataListCell *head; TablespaceDataListCell *tail; } TablespaceDataList; static PGconn *primary_conn = NULL; static PGconn *source_conn = NULL; static char local_data_directory[MAXPGPATH] = ""; static bool local_data_directory_provided = false; static bool upstream_conninfo_found = false; static int upstream_node_id = UNKNOWN_NODE_ID; static char upstream_data_directory[MAXPGPATH]; static t_conninfo_param_list recovery_conninfo = T_CONNINFO_PARAM_LIST_INITIALIZER; static char recovery_conninfo_str[MAXLEN] = ""; static char upstream_repluser[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] = ""; static void _do_standby_promote_internal(PGconn *conn, const char *data_dir); 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 *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(void); static void drop_replication_slot_if_exists(PGconn *conn, int node_id, char *slot_name); 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 *recovery_conninfo, const char *data_dir); static void write_primary_conninfo(char *line, t_conninfo_param_list *param_list); static bool write_recovery_file_line(FILE *recovery_file, char *recovery_file_path, char *line); 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); static ConnectionStatus parse_remote_node_replication_connection(const char *node_check_output); /* * STANDBY CLONE * * Event(s): * - standby_clone * * Parameters: * --upstream-conninfo * --no-upstream-connection * -F/--force * --dry-run * -c/--fast-checkpoint * --copy-external-config-files * --recovery-min-apply-delay * --replication-user (only required if no upstream record) * --without-barman */ void do_standby_clone(void) { PQExpBufferData event_details; int r = 0; /* dummy node record */ t_node_info node_record = T_NODE_INFO_INITIALIZER; /* * 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(); /* * Copy the provided data directory; if a configuration file was provided, * use the (mandatory) value from that; if -D/--pgdata was provided, use * that; otherwise repmgr will default to using the same directory path as * on the source host. The last case will only ever occur when executing * "repmgr standby clone" with no configuration file. * * Note that barman mode requires -D/--pgdata. * * If no data directory is explicitly provided, and we're not cloning from * barman, the source host's data directory will be fetched later, after * we've connected to it, in check_source_server(). * */ 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 (mode == barman) { /* * XXX in Barman mode it's still possible to connect to the upstream, * so only fail if that's not available. */ log_error(_("Barman mode requires a data directory")); log_hint(_("use -D/--pgdata to explicitly specify a data directory")); exit(ERR_BAD_CONFIG); } /* Sanity-check barman connection and installation */ if (mode == barman) { /* this will exit with ERR_BARMAN if problems found */ check_barman_config(); } init_node_record(&node_record); node_record.type = STANDBY; /* * Initialise list of conninfo parameters which will later be used to * create the `primary_conninfo` string in recovery.conf . * * 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) */ initialize_conninfo_params(&recovery_conninfo, false); 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, MAXLEN) != 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 (runtime_options.no_upstream_connection == false) { /* * This connects to the source node and performs sanity checks, also * sets "recovery_conninfo_str", "upstream_repluser" and * "upstream_node_id". * * Will error out if source connection not possible and not in * "barman" mode. */ check_source_server(); } 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:\n %s"), recovery_conninfo_str, 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 */ 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); /* TODO: check all files are readable */ } if (superuser_conn != NULL) PQfinish(superuser_conn); } if (runtime_options.dry_run == true) { 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")); } log_info(_("all prerequisites for \"standby clone\" are met")); PQfinish(source_conn); exit(SUCCESS); } if (mode != barman) { initialise_direct_clone(&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")); } } if (mode == pg_basebackup) { r = run_basebackup(&node_record); } else { r = run_file_backup(&node_record); } /* 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) { drop_replication_slot(source_conn, node_record.slot_name); } log_error(_("unable to take a base backup of the primary server")); log_hint(_("data directory (\"%s\") may need to be cleaned up manually"), local_data_directory); PQfinish(source_conn); exit(r); } /* * 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) { copy_configuration_files(); } /* Write the recovery.conf file */ if (create_recovery_file(&node_record, &recovery_conninfo, local_data_directory) == false) { /* create_recovery_file() will log an error */ 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; } /* * 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); appendPQExpBuffer(&event_details, _("; backup method: ")); switch (mode) { case pg_basebackup: appendPQExpBuffer(&event_details, "pg_basebackup"); break; case barman: appendPQExpBuffer(&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) { char command[MAXLEN]; 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); maxlen_snprintf(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, 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_hint(_("refer to the Barman documentation for more information")); exit(ERR_BARMAN); } 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")); maxlen_snprintf(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, NULL); if (command_ok == false) { log_error(_("unable to fetch server parameters from Barman server")); exit(ERR_BARMAN); } } /* * 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; 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 --force provided, 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 (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("%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 an inactive standby, additionally provide the primary connection parameters")); exit(ERR_BAD_CONFIG); } } if (PQstatus(conn) == CONNECTION_OK) { 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 */ 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; 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) == true) { log_verbose(LOG_INFO, _("local node is attached to upstream")); } else { if (!runtime_options.force) { log_error(_("this node does not appear to be attached to upstream node \"%s\" (ID: %i)"), config_file_options.node_name, config_file_options.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)"), config_file_options.node_name, config_file_options.node_id); } PQfinish(upstream_conn); } } } if (runtime_options.dry_run == true) { log_info(_("all prerequisites for \"standby register\" are met")); PQfinish(primary_conn); if (PQstatus(conn) == CONNECTION_OK) PQfinish(conn); exit(SUCCESS); } /* * populate node record structure with current values (this will overwrite * any existing values, which is what we want when updating the record */ init_node_record(&node_record); node_record.type = STANDBY; /* * 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"); if (runtime_options.force == true) appendPQExpBuffer( &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"); if (runtime_options.force == true) appendPQExpBuffer(&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 */ 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 (runtime_options.wait_register_sync_seconds && 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 (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("%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 *conn = NULL; PGconn *current_primary_conn = NULL; RecoveryType recovery_type = RECTYPE_UNKNOWN; int existing_primary_id = UNKNOWN_NODE_ID; 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 */ check_server_version(conn, "standby", true, NULL); /* Check we are in a standby node */ recovery_type = get_recovery_type(conn); if (recovery_type != RECTYPE_STANDBY) { if (recovery_type == RECTYPE_PRIMARY) { log_error(_("STANDBY PROMOTE can only be executed on a standby node")); PQfinish(conn); exit(ERR_BAD_CONFIG); } else { log_error(_("connection to node lost")); PQfinish(conn); exit(ERR_DB_CONN); } } /* check that there's no existing primary */ current_primary_conn = get_primary_connection_quiet(conn, &existing_primary_id, NULL); if (PQstatus(current_primary_conn) == CONNECTION_OK) { log_error(_("this cluster already has an active primary server")); if (existing_primary_id != UNKNOWN_NODE_ID) { t_node_info primary_rec; get_node_record(conn, existing_primary_id, &primary_rec); log_detail(_("current primary is %s (node_id: %i)"), primary_rec.node_name, existing_primary_id); } PQfinish(current_primary_conn); PQfinish(conn); exit(ERR_PROMOTION_FAIL); } PQfinish(current_primary_conn); _do_standby_promote_internal(conn, config_file_options.data_directory); } static void _do_standby_promote_internal(PGconn *conn, const char *data_dir) { char script[MAXLEN]; int r; int i, promote_check_timeout = 60, promote_check_interval = 2; 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; /* 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) */ get_server_action(ACTION_PROMOTE, script, (char *) data_dir); log_notice(_("promoting standby to primary")); 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); } /* TODO: make these values configurable */ for (i = 0; i < promote_check_timeout; i += promote_check_interval) { recovery_type = get_recovery_type(conn); if (recovery_type == RECTYPE_PRIMARY) { promote_success = true; break; } sleep(promote_check_interval); } if (promote_success == false) { if (recovery_type == RECTYPE_STANDBY) { log_error(_("STANDBY PROMOTE failed, node is still a standby")); PQfinish(conn); exit(ERR_PROMOTION_FAIL); } else { log_error(_("connection to node lost")); PQfinish(conn); exit(ERR_DB_CONN); } } /* * Execute a CHECKPOINT as soon as possible after promotion. The primary * reason for this is to ensure that "pg_control" has the latest timeline * before it's read by "pg_rewind", typically during a switchover operation. */ checkpoint(conn); /* 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; PGconn *primary_conn = NULL; int primary_node_id = UNKNOWN_NODE_ID; t_node_info primary_node_record = T_NODE_INFO_INITIALIZER; 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; int server_version_num = UNKNOWN_SERVER_VERSION_NUM; PQExpBufferData follow_output; bool success = false; int follow_error_code = SUCCESS; uint64 local_system_identifier = UNKNOWN_SYSTEM_IDENTIFIER; t_conninfo_param_list repl_conninfo = T_CONNINFO_PARAM_LIST_INITIALIZER; PGconn *repl_conn = NULL; t_system_identification primary_identification = T_SYSTEM_IDENTIFICATION_INITIALIZER; log_verbose(LOG_DEBUG, "do_standby_follow()"); local_conn = establish_db_connection(config_file_options.conninfo, true); log_verbose(LOG_INFO, _("connected to local node")); /* check this is a standby */ check_recovery_type(local_conn); /* sanity-checks for 9.3 */ server_version_num = get_server_version(local_conn, NULL); if (server_version_num < 90400) check_93_config(); /* * Attempt to connect to primary. * * If --wait provided, loop for up `primary_follow_timeout` seconds * before giving up */ for (timer = 0; timer < config_file_options.primary_follow_timeout; timer++) { primary_conn = get_primary_connection_quiet(local_conn, &primary_node_id, NULL); if (PQstatus(primary_conn) == CONNECTION_OK || runtime_options.wait == false) { break; } sleep(1); } PQfinish(local_conn); if (PQstatus(primary_conn) != CONNECTION_OK) { log_error(_("unable to determine primary node")); if (runtime_options.wait == true) { log_detail(_("no primary appeared after %i seconds"), config_file_options.primary_follow_timeout); log_hint(_("alter \"primary_follow_timeout\" in \"repmgr.conf\" to change this value")); } exit(ERR_BAD_CONFIG); } if (runtime_options.dry_run == true) { log_info(_("connected to node %i, checking for current primary"), primary_node_id); } else { log_verbose(LOG_INFO, _("connected to node %i, checking for current primary"), primary_node_id); } record_status = get_node_record(primary_conn, primary_node_id, &primary_node_record); if (record_status != RECORD_FOUND) { log_error(_("unable to find record for new upstream node %i"), runtime_options.upstream_node_id); PQfinish(primary_conn); exit(ERR_BAD_CONFIG); } /* * Populate "event_info" with info about the primary for event notifications */ event_info.node_id = primary_node_id; event_info.node_name = primary_node_record.node_name; event_info.conninfo_str = primary_node_record.conninfo; if (runtime_options.dry_run == true) { log_info(_("primary node is \"%s\" (ID: %i)"), primary_node_record.node_name, primary_node_id); } else { log_verbose(LOG_INFO, ("primary node is \"%s\" (ID: %i)"), primary_node_record.node_name, primary_node_id); } /* if replication slots in use, check at least one free slot is available */ if (config_file_options.use_replication_slots) { int free_slots = get_free_replication_slots(primary_conn); if (free_slots < 0) { log_error(_("unable to determine number of free replication slots on the primary")); PQfinish(primary_conn); exit(ERR_BAD_CONFIG); } if (free_slots == 0) { log_error(_("no free replication slots available on the primary")); log_hint(_("consider increasing \"max_replication_slots\"")); PQfinish(primary_conn); exit(ERR_BAD_CONFIG); } else if (runtime_options.dry_run == true) { log_info(_("replication slots in use, %i free slots on the primary"), free_slots); } } /* XXX check this is not current upstream anyway */ /* check replication connection */ initialize_conninfo_params(&repl_conninfo, false); conn_to_param_list(primary_conn, &repl_conninfo); param_set(&repl_conninfo, "replication", "1"); param_set(&repl_conninfo, "user", primary_node_record.repluser); repl_conn = establish_db_connection_by_params(&repl_conninfo, false); if (PQstatus(repl_conn) != CONNECTION_OK) { log_error(_("unable to establish a replication connection to the primary node")); PQfinish(primary_conn); exit(ERR_BAD_CONFIG); } else if (runtime_options.dry_run == true) { log_info(_("replication connection to primary node was successful")); } /* check system_identifiers match */ local_system_identifier = get_system_identifier(config_file_options.data_directory); success = identify_system(repl_conn, &primary_identification); if (success == false) { log_error(_("unable to query the primary node's system identification")); PQfinish(primary_conn); PQfinish(repl_conn); exit(ERR_BAD_CONFIG); } if (primary_identification.system_identifier != local_system_identifier) { log_error(_("this node is not part of the primary node's replication cluster")); log_detail(_("this node's system identifier is %lu, primary node's system identifier is %lu"), local_system_identifier, primary_identification.system_identifier); PQfinish(primary_conn); PQfinish(repl_conn); exit(ERR_BAD_CONFIG); } else if (runtime_options.dry_run == true) { log_info(_("local and primary system identifiers match")); log_detail(_("system identifier is %lu"), local_system_identifier); } /* TODO: check timelines */ PQfinish(repl_conn); free_conninfo_params(&repl_conninfo); 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, &primary_node_record, &follow_output, &follow_error_code); create_event_notification_extended( primary_conn, &config_file_options, config_file_options.node_id, "standby_follow", success, follow_output.data, &event_info); PQfinish(primary_conn); if (success == false) { log_notice(_("STANDBY FOLLOW failed")); log_detail("%s", follow_output.data); termPQExpBuffer(&follow_output); exit(follow_error_code); } log_notice(_("STANDBY FOLLOW successful")); log_detail("%s", follow_output.data); termPQExpBuffer(&follow_output); return; } /* * Perform the actuall "follow" operation; this is executed by * "node rejoin" too. * * For PostgreSQL 9.3, ensure check_93_config() was called before calling * this function. */ bool do_standby_follow_internal(PGconn *primary_conn, t_node_info *primary_node_record, PQExpBufferData *output, 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 upstream node ID, which we'll need to know if replication slots * are in use and we want to delete the old slot. */ 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 new * primary */ if (config_file_options.use_replication_slots) { int primary_server_version_num = get_server_version(primary_conn, NULL); /* * 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(primary_conn, local_node_record.slot_name, primary_server_version_num, output) == false) { log_error("%s", output->data); return false; } } /* 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(primary_node_record->conninfo, &recovery_conninfo, errmsg, true); { t_conninfo_param_list local_node_conninfo = T_CONNINFO_PARAM_LIST_INITIALIZER; bool parse_success; initialize_conninfo_params(&local_node_conninfo, false); parse_success = parse_conninfo_string(local_node_record.conninfo, &local_node_conninfo, errmsg, false); if (parse_success == false) { /* * this shouldn't happen, but if it does we'll plough on * regardless */ log_warning(_("unable to parse conninfo string \"%s\":\n %s"), local_node_record.conninfo, errmsg); } else { char *application_name = param_get(&local_node_conninfo, "application_name"); if (application_name != NULL && strlen(application_name)) param_set(&recovery_conninfo, "application_name", application_name); } free_conninfo_params(&local_node_conninfo); /* * store the original upstream node id so we can delete the * replication slot, if 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 = primary_node_record->node_id; } if (config_file_options.use_replication_slots && runtime_options.host_param_provided == false && original_upstream_node_id != UNKNOWN_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")); } } /* 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 primary node record */ param_set(&recovery_conninfo, "user", primary_node_record->repluser); log_notice(_("setting node %i's primary to node %i"), config_file_options.node_id, primary_node_record->node_id); if (!create_recovery_file(&local_node_record, &recovery_conninfo, config_file_options.data_directory)) { /* XXX ERR_RECOVERY_FILE ??? */ *error_code = ERR_BAD_CONFIG; 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) { /* no "service_restart_command" defined - stop and start using pg_ctl*/ if (config_file_options.service_restart_command[0] == '\0') { 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; } } } /* * If replication slots are in use, and an inactive one for this node * exists on the former upstream, drop it. * * XXX check if former upstream is current primary? */ 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", primary_node_record->node_id, true) == false) { appendPQExpBuffer(output, _("unable to update upstream node")); return false; } appendPQExpBuffer(output, _("node %i is now attached to node %i"), config_file_options.node_id, primary_node_record->node_id); return true; } /* * Perform a switchover by: * - stopping current primary node * - promoting this standby node to primary * - forcing previous primary node to follow this node * * Caveat: * - repmgrd must not be running, otherwise it may * attempt a failover * (TODO: find some way of notifying repmgrd of planned * activity like this) * * TODO: * - make connection test timeouts/intervals configurable (see below) */ void do_standby_switchover(void) { PGconn *local_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; RecordStatus record_status = RECORD_NOT_FOUND; RecoveryType recovery_type = RECTYPE_UNKNOWN; PQExpBufferData remote_command_str; PQExpBufferData command_output; PQExpBufferData node_rejoin_options; int r, i; bool command_success = false; bool shutdown_success = false; /* this flag will use to generate the final message generated */ bool switchover_success = true; XLogRecPtr remote_last_checkpoint_lsn = InvalidXLogRecPtr; ReplInfo replication_info = T_REPLINFO_INTIALIZER; /* store list of configuration files on the demotion candidate */ KeyValueList remote_config_files = {NULL, NULL}; /* store list of sibling nodes if --siblings-follow specified */ NodeInfoList sibling_nodes = T_NODE_INFO_LIST_INITIALIZER; int reachable_sibling_node_count = 0; int reachable_sibling_nodes_with_slot_count = 0; int unreachable_sibling_node_count = 0; /* number of free replication slots required on promotion candidate */ int min_required_free_slots = 0; t_event_info event_info = T_EVENT_INFO_INITIALIZER; /* * SANITY CHECKS * * We'll be doing a bunch of operations on the remote server (primary to * be demoted) - careful checks needed before proceding. */ local_conn = establish_db_connection(config_file_options.conninfo, true); 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); } /* 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 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 node %i"), remote_node_id); PQfinish(local_conn); PQfinish(remote_conn); exit(ERR_DB_QUERY); } /* * Check this standby is attached to the demotion candidate * TODO: * - check application_name in pg_stat_replication */ if (local_node_record.upstream_node_id != remote_node_record.node_id) { log_error(_("local node %i is not a downstream of demotion candidate primary %i"), local_node_record.node_id, remote_node_record.node_id); PQfinish(local_conn); PQfinish(remote_conn); exit(ERR_BAD_CONFIG); } log_verbose(LOG_DEBUG, "remote node name is \"%s\"", remote_node_record.node_name); /* 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') { 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 == 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); appendPQExpBuffer(&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); appendPQExpBuffer(&remote_command_str, "--version 2>/dev/null && echo \"1\" || echo \"0\""); initPQExpBuffer(&command_output); command_success = remote_command(remote_host, runtime_options.remote_user, remote_command_str.data, &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); appendPQExpBuffer(&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 { appendPQExpBuffer(&hint, "(not set)"); } log_hint("%s", hint.data); termPQExpBuffer(&hint); PQfinish(remote_conn); PQfinish(local_conn); exit(ERR_BAD_CONFIG); } if (runtime_options.dry_run == true) { log_info(_("able to execute \"%s\" on remote host \"localhost\""), progname()); } termPQExpBuffer(&command_output); /* 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, &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 deterimine whether 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; initPQExpBuffer(&remote_command_str); make_remote_repmgr_path(&remote_command_str, &remote_node_record); appendPQExpBuffer(&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, &command_output); termPQExpBuffer(&remote_command_str); if (command_success == true) { status = parse_node_check_archiver(command_output.data, &files, &threshold); } termPQExpBuffer(&command_output); switch (status) { case CHECK_STATUS_UNKNOWN: { if (runtime_options.force == false) { log_error(_("unable to check number of pending archive files on demotion candidate \"%s\""), remote_node_record.node_name); 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\" is critical"), 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\" is warning"), 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 < 0) { 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); /* * 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, server_version_num, &local_node_record); /* * If --siblings-follow specified, get list and check they're reachable * (if not just issue a warning) */ get_active_sibling_node_records(local_conn, local_node_record.node_id, local_node_record.upstream_node_id, &sibling_nodes); if (runtime_options.siblings_follow == false) { if (sibling_nodes.node_count > 0) { 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")); } } else { char host[MAXLEN] = ""; NodeInfoListCell *cell; 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")); } else { /* include walsender for promotion candidate in total */ int min_required_wal_senders = 1; int available_wal_senders = local_node_record.max_wal_senders - local_node_record.attached_wal_receivers; 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; unreachable_sibling_node_count++; } else { cell->node_info->reachable = true; reachable_sibling_node_count++; min_required_wal_senders++; if (cell->node_info->slot_name[0] != '\0') { reachable_sibling_nodes_with_slot_count++; min_required_free_slots++; } } } if (unreachable_sibling_node_count > 0) { if (runtime_options.force == false) { log_error(_("%i of %i sibling nodes unreachable via SSH:"), unreachable_sibling_node_count, sibling_nodes.node_count); } else { log_warning(_("%i of %i sibling nodes unreachable via SSH:"), 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")); PQfinish(local_conn); exit(ERR_BAD_CONFIG); } 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); } } /* * 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. */ if (available_wal_senders < min_required_wal_senders) { if (runtime_options.force == false || runtime_options.dry_run == true) { log_error(_("insufficient free walsenders to attach all sibling nodes")); log_detail(_("at least %i walsenders required but only %i free walsenders on promotion candidate"), 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 == false) { PQfinish(local_conn); exit(ERR_BAD_CONFIG); } } else { log_warning(_("insufficient free walsenders to attach all sibling nodes")); log_detail(_("at least %i walsenders required but only %i free walsender(s) on promotion candidate"), min_required_wal_senders, available_wal_senders); } } else { if (runtime_options.dry_run == true) { log_info(_("%i walsenders required, %i available"), min_required_wal_senders, available_wal_senders); } } } } /* * if replication slots are required by demotion candidate and/or siblings, * check the promotion candidate has sufficient free slots */ if (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", min_required_free_slots, reachable_sibling_nodes_with_slot_count , available_slots); if (available_slots < 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"), 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 == false) { PQfinish(local_conn); exit(ERR_BAD_CONFIG); } } } else { if (runtime_options.dry_run == true) { log_info(_("%i replication slots required, %i available"), min_required_free_slots, available_slots); } } } /* * 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) { appendPQExpBuffer(&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); appendPQExpBuffer(&remote_command_str, "node service --action=stop --checkpoint"); } /* XXX handle failure */ (void) remote_command(remote_host, runtime_options.remote_user, remote_command_str.data, &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) { 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); clear_node_info_list(&sibling_nodes); key_value_list_free(&remote_config_files); return; } termPQExpBuffer(&command_output); shutdown_success = false; /* loop for timeout waiting for current primary to stop */ for (i = 0; i < config_file_options.reconnect_attempts; i++) { /* Check whether primary is available */ PGPing ping_res; log_info(_("checking primary status; %i of %i attempts"), i + 1, config_file_options.reconnect_attempts); 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); appendPQExpBuffer(&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, &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 %i seconds (\"reconnect_interval\") until next check", config_file_options.reconnect_interval); sleep(config_file_options.reconnect_interval); } 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...")); 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")); } get_replication_info(local_conn, &replication_info); 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); } } /* promote standby (local node) */ _do_standby_promote_internal(local_conn, config_file_options.data_directory); /* * 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); if (replication_info.last_wal_receive_lsn < remote_last_checkpoint_lsn) { KeyValueListCell *cell = NULL; bool first_entry = true; if (runtime_options.force_rewind == false) { log_error(_("new primary diverges from former primary and --force-rewind not provided")); /* TODO: "repmgr node rejoin" example, when available */ log_hint(_("the former primary will need to be restored manually")); termPQExpBuffer(&node_rejoin_options); PQfinish(local_conn); exit(ERR_SWITCHOVER_FAIL); } appendPQExpBuffer(&node_rejoin_options, " --force-rewind --config-files="); for (cell = remote_config_files.head; cell; cell = cell->next) { if (first_entry == false) appendPQExpBuffer(&node_rejoin_options, ","); else first_entry = false; appendPQExpBuffer(&node_rejoin_options, "%s", cell->key); } appendPQExpBuffer(&node_rejoin_options, " "); } key_value_list_free(&remote_config_files); initPQExpBuffer(&remote_command_str); make_remote_repmgr_path(&remote_command_str, &remote_node_record); appendPQExpBuffer(&remote_command_str, "%s-d \\'%s\\' node rejoin", node_rejoin_options.data, local_node_record.conninfo); 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, &command_output); termPQExpBuffer(&remote_command_str); /* TODO: verify this node's record was updated correctly */ if (command_success == false) { log_error(_("rejoin failed %i"), r); create_event_notification_extended(local_conn, &config_file_options, config_file_options.node_id, "standby_switchover", false, command_output.data, &event_info); } else { PQExpBufferData event_details; initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, "node %i promoted to primary, node %i demoted to standby", config_file_options.node_id, remote_node_record.node_id); create_event_notification_extended(local_conn, &config_file_options, config_file_options.node_id, "standby_switchover", true, event_details.data, &event_info); termPQExpBuffer(&event_details); } termPQExpBuffer(&command_output); /* clean up remote node */ remote_conn = establish_db_connection(remote_node_record.conninfo, false); /* 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\" is now primary but node \"%s\" is not reachable"), local_node_record.node_name, remote_node_record.node_name); } else { 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); } /* TODO warn about any inactive replication slots */ 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); } PQfinish(remote_conn); /* * If --siblings-follow specified, attempt to make them follow the new * primary */ if (runtime_options.siblings_follow == true && sibling_nodes.node_count > 0) { int failed_follow_count = 0; char host[MAXLEN] = ""; NodeInfoListCell *cell = NULL; log_notice(_("executing STANDBY FOLLOW on %i of %i siblings"), sibling_nodes.node_count - unreachable_sibling_node_count, sibling_nodes.node_count); for (cell = sibling_nodes.head; cell; cell = cell->next) { bool success = false; t_node_info sibling_node_record = T_NODE_INFO_INITIALIZER; /* skip nodes previously determined as unreachable */ if (cell->node_info->reachable == false) continue; record_status = get_node_record(local_conn, cell->node_info->node_id, &sibling_node_record); initPQExpBuffer(&remote_command_str); make_remote_repmgr_path(&remote_command_str, &sibling_node_record); appendPQExpBuffer(&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, &command_output); termPQExpBuffer(&remote_command_str); if (success == false || command_output.data[0] == '0') { 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 */ } clear_node_info_list(&sibling_nodes); PQfinish(local_conn); 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() { PGconn *superuser_conn = NULL; PGconn *privileged_conn = NULL; char cluster_size[MAXLEN]; t_node_info node_record = T_NODE_INFO_INITIALIZER; RecordStatus record_status = RECORD_NOT_FOUND; ExtensionStatus extension_status = REPMGR_UNKNOWN; /* Attempt to connect to the upstream server to verify its configuration */ log_verbose(LOG_DEBUG, "check_source_server()"); log_info(_("connecting to source node")); 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; else 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); 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); 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); } /* schema doesn't exist */ log_error(_("repmgr extension not found on source node")); if (extension_status == REPMGR_AVAILABLE) { log_detail(_("repmgr extension is available but not installed in database \"%s\""), param_get(&source_conninfo, "dbname")); } else if (extension_status == REPMGR_UNAVAILABLE) { log_detail(_("repmgr extension is not available on the upstream node")); } log_hint(_("check that the upstream node is part of a repmgr cluster")); PQfinish(source_conn); exit(ERR_BAD_CONFIG); } log_warning(_("repmgr extension not found on source node")); } /* Fetch the source's data directory */ get_superuser_connection(&source_conn, &superuser_conn, &privileged_conn); if (get_pg_setting(privileged_conn, "data_directory", upstream_data_directory) == false) { log_error(_("unable to retrieve source node's data directory")); log_detail(_("STANDBY CLONE must be run with database superuser permissions")); log_hint(_("provide a database superuser name with -S/--superuser")); PQfinish(source_conn); source_conn = NULL; if (superuser_conn != NULL) PQfinish(superuser_conn); exit(ERR_BAD_CONFIG); } if (superuser_conn != NULL) PQfinish(superuser_conn); /* * If no target data directory was explicitly provided, we'll default to * the source host's data directory. */ if (local_data_directory_provided == false) { strncpy(local_data_directory, upstream_data_directory, MAXPGPATH); log_notice(_("setting data directory to: \"%s\""), local_data_directory); log_hint(_("use -D/--pgdata to explicitly specify a data directory")); } /* * In the default pg_basebackup mode, we'll cowardly refuse to overwrite * an existing data directory */ if (mode == pg_basebackup) { if (is_pg_dir(local_data_directory) && runtime_options.force != true) { log_error(_("target data directory appears to be a PostgreSQL data directory")); log_detail(_("target data directory is \"%s\""), local_data_directory); log_hint(_("use -F/--force to overwrite the existing data directory")); PQfinish(source_conn); exit(ERR_BAD_CONFIG); } } /* * 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) { record_status = get_node_record(source_conn, upstream_node_id, &node_record); if (record_status == RECORD_FOUND) { upstream_conninfo_found = true; strncpy(recovery_conninfo_str, node_record.conninfo, MAXLEN); strncpy(upstream_repluser, node_record.repluser, NAMEDATALEN); } /* * 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, &node_record); if (record_status == RECORD_FOUND && node_record.node_id != config_file_options.node_id) { log_error(_("another node (node_id: %i) already exists with node_name \"%s\""), node_record.node_id, config_file_options.node_name); PQfinish(source_conn); exit(ERR_BAD_CONFIG); } } /* disable configuration file options incompatible with 9.3 */ if (source_server_version_num < 90400) check_93_config(); check_upstream_config(source_conn, source_server_version_num, &node_record, 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, "ssh %s \"psql -Aqt \\\"%s\\\" -c \\\"" " SELECT conninfo" " FROM repmgr.nodes" " WHERE %s" " AND active IS TRUE" "\\\"\"", config_file_options.barman_host, repmgr_conninfo_buf.data, where_condition); termPQExpBuffer(&repmgr_conninfo_buf); command_success = local_command(buf, &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 * * For PostreSQL 9.3, ensure check_93_config() is called before calling this. * * TODO: * - check user is qualified to perform base backup */ static bool check_upstream_config(PGconn *conn, int server_version_num, t_node_info *node_info, 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 xlog_stream = true; standy_clone_mode mode; /* * Detecting the intended cloning mode */ mode = get_standby_clone_mode(); /* * Parse `pg_basebackup_options`, if set, to detect whether --xlog-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.xlog_method) && strcmp(backup_options.xlog_method, "stream") != 0) xlog_stream = false; /* Check that WAL level is set correctly */ if (server_version_num < 90400) { i = guc_set(conn, "wal_level", "=", "hot_standby"); wal_error_message = _("parameter \"wal_level\" must be set to \"hot_standby\""); } else { 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) { i = guc_set_typed(conn, "max_replication_slots", ">", "0", "integer"); if (i == 0 || i == -1) { if (i == 0) { log_error(_("parameter \"max_replication_slots\" must be set to at least 1 to enable replication slots")); 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; } } } /* * 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 (xlog_stream == false && strcmp(config_file_options.restore_command, "") == 0) { check_wal_keep_segments = true; } if (check_wal_keep_segments == true) { i = guc_set_typed(conn, "wal_keep_segments", ">", "0", "integer"); if (i == 0 || i == -1) { if (i == 0) { log_error(_("parameter \"wal_keep_segments\" on the upstream server must be be set to a non-zero value")); 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.")); if (server_version_num >= 90400) { log_hint(_("In PostgreSQL 9.4 and later, replication slots can be used, which " "do not require \"wal_keep_segments\" to be set " "(set parameter \"use_replication_slots\" in repmgr.conf to enable)\n" )); } } if (exit_on_error == true) { PQfinish(conn); exit(ERR_BAD_CONFIG); } config_ok = false; } } } /* * 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; } i = guc_set_typed(conn, "max_wal_senders", ">", "0", "integer"); if (i == 0 || i == -1) { if (i == 0) { log_error(_("parameter \"max_wal_senders\" must be set to be at least 1")); 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; } /* * 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. */ if (mode == pg_basebackup) { PGconn **connections; int i; int min_replication_connections = 1, possible_replication_connections = 0; t_conninfo_param_list repl_conninfo = T_CONNINFO_PARAM_LIST_INITIALIZER; /* * 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) { param_set(&repl_conninfo, "user", runtime_options.replication_user); } else if (node_info->repluser[0] != '\0') { param_set(&repl_conninfo, "user", node_info->repluser); } /* * work out how many replication connections are required (1 or 2) */ if (xlog_stream == true) min_replication_connections += 1; log_verbose(LOG_NOTICE, "checking for available walsenders on source node (%i required)", min_replication_connections); 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; /* * XXX at this point we could check * current_setting('max_wal_senders) - COUNT(*) FROM * pg_stat_replication; if >= min_replication_connections we could * infer possible authentication error. * * Alternatively call PQconnectStart() and poll for * presence/absence of CONNECTION_AUTH_OK ? */ log_error(_("unable to establish necessary replication connections")); log_hint(_("increase \"max_wal_senders\" by at least %i"), min_replication_connections - possible_replication_connections); if (exit_on_error == true) { PQfinish(conn); exit(ERR_BAD_CONFIG); } } log_verbose(LOG_INFO, "sufficient walsenders available on source node (%i required)", min_replication_connections); } 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 *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) { if (source_server_version_num < 90400) { log_error(_("tablespace mapping not supported in PostgreSQL 9.3, ignoring")); } else { TablespaceListCell *cell = false; 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) { PGconn *superuser_conn = NULL; PGconn *privileged_conn = NULL; PQExpBufferData event_details; initPQExpBuffer(&event_details); get_superuser_connection(&source_conn, &superuser_conn, &privileged_conn); if (create_replication_slot(privileged_conn, node_record->slot_name, source_server_version_num, &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); if (superuser_conn != NULL) PQfinish(superuser_conn); exit(ERR_DB_QUERY); } termPQExpBuffer(&event_details); log_verbose(LOG_INFO, _("replication slot \"%s\" created on source node"), node_record->slot_name); if (superuser_conn != NULL) PQfinish(superuser_conn); } return; } static int run_basebackup(t_node_info *node_record) { char script[MAXLEN] = ""; int r = SUCCESS; PQExpBufferData params; 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) { param_set(&conninfo, "user", runtime_options.replication_user); } 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(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) { appendPQExpBuffer(¶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.xlog_method)) { appendPQExpBuffer(¶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.xlog_method) && strcmp(backup_options.xlog_method, "stream") != 0)) { slot_add = false; } if (slot_add == true) { appendPQExpBuffer(¶ms, " -S %s", node_record->slot_name); } } maxlen_snprintf(script, "%s -l \"repmgr base backup\" %s %s", make_pg_path("pg_basebackup"), params.data, config_file_options.pg_basebackup_options); termPQExpBuffer(¶ms); log_info(_("executing:\n %s"), script); /* * As of 9.4, pg_basebackup only ever returns 0 or 1 */ r = system(script); if (r != 0) return ERR_BAD_BASEBACKUP; /* * 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) { PGconn *superuser_conn = NULL; PGconn *privileged_conn = NULL; 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, true); 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\" aleady 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); get_superuser_connection(&upstream_conn, &superuser_conn, &privileged_conn); initPQExpBuffer(&event_details); if (create_replication_slot(privileged_conn, node_record->slot_name, source_server_version_num, &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); if (superuser_conn != NULL) PQfinish(superuser_conn); exit(ERR_DB_QUERY); } if (superuser_conn != NULL) PQfinish(superuser_conn); termPQExpBuffer(&event_details); } PQfinish(upstream_conn); } get_superuser_connection(&source_conn, &superuser_conn, &privileged_conn); if (slot_info.active == false) { if (slot_exists_on_upstream == false) { if (drop_replication_slot(source_conn, 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); } if (superuser_conn != NULL) PQfinish(superuser_conn); } return SUCCESS; } static int run_file_backup(t_node_info *node_record) { int r = SUCCESS, i; char command[MAXLEN] = ""; char filename[MAXLEN] = ""; char buf[MAXLEN] = ""; char basebackups_directory[MAXLEN] = ""; char backup_id[MAXLEN] = ""; char *p = NULL, *q = NULL; TablespaceDataList tablespace_list = {NULL, NULL}; TablespaceDataListCell *cell_t = NULL; PQExpBufferData tablespace_map; bool tablespace_map_rewrite = false; if (mode == barman) { /* * 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; 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\": %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); 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->f == NULL) { maxlen_snprintf(filename, "%s/%s.txt", local_repmgr_tmp_directory, cell_t->oid); cell_t->f = fopen(filename, "w"); if (cell_t->f == NULL) { log_error("cannot open file: %s", filename); exit(ERR_INTERNAL); } } fputs(q + 1, cell_t->f); 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); /* * 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", /* Already in 9.3 */ "pg_notify", "pg_serial", "pg_snapshots", "pg_stat", "pg_stat_tmp", "pg_subtrans", "pg_tblspc", "pg_twophase", "pg_xlog", 0 }; const int vers[] = { 100000, 90500, 90400, 90400, 90400, 90400, 90400, 0, 0, 0, 0, 0, 0, 0, -100000, 0 }; 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 (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(cell_t->location, false); if (cell_t->f != NULL) /* cell_t->f == NULL iff the tablespace is * empty */ { 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); fclose(cell_t->f); maxlen_snprintf(filename, "%s/%s.txt", local_repmgr_tmp_directory, cell_t->oid); unlink(filename); } } /* * If a valid mapping was provide 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); 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: %s"), tablespace_map_filename.data, 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) { 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); } stop_backup: if (mode == barman) { /* In Barman mode, remove local_repmgr_directory */ rmtree(local_repmgr_tmp_directory, true); } 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 \"^\t%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(void) { 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); termPQExpBuffer(&dest_path); if (WEXITSTATUS(r)) { log_error(_("standby clone: unable to copy config file \"%s\""), file->filename); } } return; } static void tablespace_data_append(TablespaceDataList *list, const char *name, const char *oid, const char *location) { TablespaceDataListCell *cell = NULL; cell = (TablespaceDataListCell *) pg_malloc0(sizeof(TablespaceDataListCell)); if (cell == NULL) { log_error(_("unable to allocate memory; terminating")); exit(ERR_OUT_OF_MEMORY); } cell->oid = pg_malloc(1 + strlen(oid)); cell->name = pg_malloc(1 + strlen(name)); cell->location = pg_malloc(1 + strlen(location)); strncpy(cell->oid, oid, 1 + strlen(oid)); strncpy(cell->name, name, 1 + strlen(name)); strncpy(cell->location, location, 1 + strlen(location)); 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); } } } static void drop_replication_slot_if_exists(PGconn *conn, int node_id, char *slot_name) { t_replication_slot slot_info = T_REPLICATION_SLOT_INITIALIZER; RecordStatus 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) { log_info(_("no slot record found for slot \"%s\" on node %i"), slot_name, node_id); } else { if (slot_info.active == false) { if (drop_replication_slot(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); } } /* * if 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); } } } /* * Creates a recovery.conf file for a standby * * A database connection pointer is required for escaping primary_conninfo * parameters. When cloning from Barman and --no-upstream-connection ) this * might not be available. */ bool create_recovery_file(t_node_info *node_record, t_conninfo_param_list *recovery_conninfo, const char *data_dir) { FILE *recovery_file; char recovery_file_path[MAXPGPATH] = ""; char line[MAXLEN] = ""; mode_t um; maxpath_snprintf(recovery_file_path, "%s/%s", data_dir, RECOVERY_COMMAND_FILE); /* 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; } log_debug("create_recovery_file(): creating \"%s\"...", recovery_file_path); /* standby_mode = 'on' */ maxlen_snprintf(line, "standby_mode = 'on'\n"); if (write_recovery_file_line(recovery_file, recovery_file_path, line) == false) return false; trim(line); log_debug("recovery.conf: %s", line); /* primary_conninfo = '...' */ /* * the user specified --upstream-conninfo string - copy that */ if (strlen(runtime_options.upstream_conninfo)) { char *escaped = escape_recovery_conf_value(runtime_options.upstream_conninfo); maxlen_snprintf(line, "primary_conninfo = '%s'\n", escaped); free(escaped); } /* * otherwise use the conninfo inferred from the upstream connection and/or * node record */ else { write_primary_conninfo(line, recovery_conninfo); } if (write_recovery_file_line(recovery_file, recovery_file_path, line) == false) return false; trim(line); log_debug("recovery.conf: %s", line); /* recovery_target_timeline = 'latest' */ maxlen_snprintf(line, "recovery_target_timeline = 'latest'\n"); if (write_recovery_file_line(recovery_file, recovery_file_path, line) == false) return false; trim(line); log_debug("recovery.conf: %s", line); /* recovery_min_apply_delay = ... (optional) */ if (config_file_options.recovery_min_apply_delay_provided == true) { maxlen_snprintf(line, "recovery_min_apply_delay = %s\n", config_file_options.recovery_min_apply_delay); if (write_recovery_file_line(recovery_file, recovery_file_path, line) == false) return false; trim(line); log_debug("recovery.conf: %s", line); } /* primary_slot_name = '...' (optional, for 9.4 and later) */ if (config_file_options.use_replication_slots) { maxlen_snprintf(line, "primary_slot_name = %s\n", node_record->slot_name); if (write_recovery_file_line(recovery_file, recovery_file_path, line) == false) return false; trim(line); log_debug("recovery.conf: %s", line); } /* * If restore_command is set, we use it as restore_command in * recovery.conf */ if (strcmp(config_file_options.restore_command, "") != 0) { maxlen_snprintf(line, "restore_command = '%s'\n", config_file_options.restore_command); if (write_recovery_file_line(recovery_file, recovery_file_path, line) == false) return false; trim(line); log_debug("recovery.conf: %s", line); } fclose(recovery_file); return true; } static bool write_recovery_file_line(FILE *recovery_file, char *recovery_file_path, char *line) { if (fputs(line, recovery_file) == EOF) { log_error(_("unable to write to recovery file at \"%s\""), recovery_file_path); fclose(recovery_file); return false; } return true; } static void write_primary_conninfo(char *line, 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)) { appendPQExpBuffer(&conninfo_buf, " application_name="); appendConnStrVal(&conninfo_buf, config_file_options.node_name); } else { appendPQExpBuffer(&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) { appendPQExpBuffer(&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) { appendPQExpBuffer(&conninfo_buf, " passfile="); appendConnStrVal(&conninfo_buf, config_file_options.passfile); } } escaped = escape_recovery_conf_value(conninfo_buf.data); maxlen_snprintf(line, "primary_conninfo = '%s'\n", escaped); free(escaped); free_conninfo_params(&env_conninfo); termPQExpBuffer(&conninfo_buf); } 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) { 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'}, {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': { if (strncmp(optarg, "OK", MAXLEN) == 0) { status = CHECK_STATUS_OK; } else if (strncmp(optarg, "WARNING", MAXLEN) == 0) { status = CHECK_STATUS_WARNING; } else if (strncmp(optarg, "CRITICAL", MAXLEN) == 0) { status = CHECK_STATUS_CRITICAL; } else if (strncmp(optarg, "UNKNOWN", MAXLEN) == 0) { status = CHECK_STATUS_UNKNOWN; } else { status = CHECK_STATUS_UNKNOWN; } } 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(_(" -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(_(" --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")); printf(_(" --without-barman do not use Barman even if configured\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(_("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(_(" -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 9.5 and later - use pg_rewind to reintegrate the old primary if necessary\n")); printf(_(" -R, --remote-user=USERNAME database server username for SSH operations (default: \"%s\")\n"), runtime_options.username); printf(_(" --siblings-follow have other standbys follow new primary\n")); puts(""); } repmgr-4.0.3/repmgr-action-standby.h000066400000000000000000000023401324071732600174000ustar00rootroot00000000000000/* * repmgr-action-standby.h * Copyright (c) 2ndQuadrant, 2010-2018 * * 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, t_node_info *primary_node_record, PQExpBufferData *output, int *error_code); #endif /* _REPMGR_ACTION_STANDBY_H_ */ repmgr-4.0.3/repmgr-action-witness.c000066400000000000000000000305611324071732600174310ustar00rootroot00000000000000/* * repmgr-action-witness.c * * Implements witness actions for the repmgr command line utility * * Copyright (c) 2ndQuadrant, 2010-2018 * * 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; RecoveryType recovery_type = RECTYPE_UNKNOWN; NodeInfoList nodes = T_NODE_INFO_LIST_INITIALIZER; t_node_info 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("%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_error(_("a witness node must run on an independent primary server")); PQfinish(witness_conn); exit(ERR_BAD_CONFIG); } /* check that witness node is not a BDR node */ if (is_bdr_db_quiet(witness_conn) == true) { log_error(_("witness node is a BDR node")); log_hint(_("a witness node cannot be configured for a BDR cluster")); 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 that primary node is not a BDR node */ if (is_bdr_db_quiet(primary_conn) == true) { log_error(_("primary node is a BDR node")); log_hint(_("a witness node cannot be configured for a BDR cluster")); PQfinish(witness_conn); PQfinish(primary_conn); exit(ERR_BAD_CONFIG); } /* XXX sanity check witness node is not part of main cluster */ /* 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); } } // XXX check other node with same name does not exist /* * if repmgr.nodes contains entries, delete if -F/--force provided, * otherwise exit with error */ get_all_node_records(witness_conn, &nodes); 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 = get_primary_node_id(primary_conn); 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); } /* create event */ create_event_record(primary_conn, &config_file_options, config_file_options.node_id, "witness_register", true, NULL); 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 *witness_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 witness_available = true; 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) { if (!runtime_options.force) { log_error(_("unable to connect to witness node \"%s\" (ID: %i)"), config_file_options.node_name, config_file_options.node_id); log_detail("%s", PQerrorMessage(witness_conn)); log_hint(_("provide -F/--force to remove the witness record if the server is not running")); 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); witness_available = false; } if (witness_available == true) { primary_conn = get_primary_connection_quiet(witness_conn, NULL, NULL); } else { /* * 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); 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("%s", PQerrorMessage(primary_conn)); if (witness_available == true) { PQfinish(witness_conn); } else { log_hint(_("provide connection details to primary server")); } exit(ERR_BAD_CONFIG); } /* Check node exists and is really a witness */ record_status = get_node_record(primary_conn, config_file_options.node_id, &node_record); if (record_status != RECORD_FOUND) { log_error(_("no record found for node %i"), config_file_options.node_id); if (witness_available == true) PQfinish(witness_conn); PQfinish(primary_conn); exit(ERR_BAD_CONFIG); } if (node_record.type != WITNESS) { 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 (witness_available == true) PQfinish(witness_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 (witness_available == true) PQfinish(witness_conn); PQfinish(primary_conn); exit(SUCCESS); } log_info(_("unregistering witness node %i"), config_file_options.node_id); node_record_deleted = delete_node_record(primary_conn, config_file_options.node_id); if (node_record_deleted == false) { PQfinish(primary_conn); PQfinish(witness_conn); exit(ERR_BAD_CONFIG); } /* sync records from primary */ if (witness_available == true && 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); } /* Log the event */ create_event_record(primary_conn, &config_file_options, config_file_options.node_id, "witness_unregister", true, NULL); PQfinish(primary_conn); if (witness_available == true) PQfinish(witness_conn); log_info(_("witness unregistration complete")); log_detail(_("witness node with id %i (conninfo: %s) successfully unregistered"), config_file_options.node_id, config_file_options.conninfo); 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()); printf(_("WITNESS REGISTER\n")); puts(""); printf(_(" \"witness register\" registers a witness node.\n")); puts(""); printf(_(" Requires provision of connection information for the primary\n")); puts(""); 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 register\" 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")); puts(""); return; } repmgr-4.0.3/repmgr-action-witness.h000066400000000000000000000016731324071732600174400ustar00rootroot00000000000000/* * repmgr-action-witness.h * Copyright (c) 2ndQuadrant, 2010-2018 * * 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-4.0.3/repmgr-client-global.h000066400000000000000000000141151324071732600172000ustar00rootroot00000000000000/* * repmgr-client-global.h * Copyright (c) 2ndQuadrant, 2010-2018 * * 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; /* general configuration options */ char config_file[MAXPGPATH]; bool dry_run; bool force; char pg_bindir[MAXLEN]; /* overrides setting in repmgr.conf */ bool wait; /* logging options */ char log_level[MAXLEN]; /* overrides setting in repmgr.conf */ bool log_to_file; 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[MAXLEN]; 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]; char replication_user[MAXLEN]; char upstream_conninfo[MAXLEN]; bool without_barman; /* "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; bool siblings_follow; /* "node status" options */ bool is_shutdown_cleanly; /* "node check" options */ bool archive_ready; bool downstream; bool replication_lag; bool role; bool slots; bool has_passfile; bool replication_connection; /* "node join" 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; } t_runtime_options; #define T_RUNTIME_OPTIONS_INITIALIZER { \ /* configuration metadata */ \ false, false, false, false, \ /* general configuration options */ \ "", false, false, "", false, \ /* logging options */ \ "", 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, \ /* "standby clone"/"standby follow" options */ \ NO_UPSTREAM_NODE, \ /* "standby register" options */ \ false, 0, DEFAULT_WAIT_START, \ /* "standby switchover" options */ \ false, false, false, \ /* "node status" options */ \ false, \ /* "node check" options */ \ false, false, false, false, false, false, false, \ /* "node join" 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 \ } 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; /* global configuration structures */ extern t_runtime_options runtime_options; extern t_configuration_options config_file_options; t_conninfo_param_list source_conninfo; extern bool config_file_required; extern char pg_bindir[MAXLEN]; extern t_node_info target_node_info; extern int check_server_version(PGconn *conn, char *server_type, bool exit_on_error, char *server_version_string); extern void check_93_config(void); extern bool create_repmgr_extension(PGconn *conn); extern int test_ssh_connection(char *host, char *remote_user); extern bool local_command(const char *command, PQExpBufferData *outputbuf); 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 char *make_pg_path(const char *file); extern void get_superuser_connection(PGconn **conn, PGconn **superuser_conn, PGconn **privileged_conn); extern bool remote_command(const char *host, const char *user, const char *command, PQExpBufferData *outputbuf); extern void make_remote_repmgr_path(PQExpBufferData *outputbuf, t_node_info *remote_node_record); extern void print_help_header(void); /* 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_data_directory(char *data_dir_buf); extern void init_node_record(t_node_info *node_record); #endif /* _REPMGR_CLIENT_GLOBAL_H_ */ repmgr-4.0.3/repmgr-client.c000066400000000000000000002031461324071732600157410ustar00rootroot00000000000000/* * repmgr-client.c - Command interpreter for the repmgr package * * Copyright (c) 2ndQuadrant, 2010-2018 * * 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 * * BDR REGISTER * BDR UNREGISTER * * CLUSTER SHOW * CLUSTER EVENT * CLUSTER CROSSCHECK * CLUSTER MATRIX * CLUSTER CLEANUP * * NODE STATUS * NODE CHECK * * For internal use: * NODE REJOIN * NODE SERVICE * * 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 "compat.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-bdr.h" #include "repmgr-action-node.h" #include "repmgr-action-cluster.h" #include /* for PG_TEMP_FILE_PREFIX */ /* globally available variables * * ============================ */ t_runtime_options runtime_options = T_RUNTIME_OPTIONS_INITIALIZER; t_configuration_options config_file_options = T_CONFIGURATION_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[MAXLEN] = ""; char path_buf[MAXLEN] = ""; /* * 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; /* Collate command line errors and warnings here for friendlier reporting */ static ItemList cli_errors = {NULL, NULL}; static ItemList cli_warnings = {NULL, NULL}; int main(int argc, char **argv) { t_conninfo_param_list default_conninfo = T_CONNINFO_PARAM_LIST_INITIALIZER; int optindex; 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; 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 overriden 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 = NULL; 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); } while ((c = getopt_long(argc, argv, "?Vb:f:FWd:h:p:U:R:S:D:ckL:tvC:", 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; case '?': /* Actual help option given */ if (strcmp(argv[optind - 1], "-?") == 0) { help_option = true; } break; 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); /*------------------------------ * 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 = 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). XXX * check this is same as psql */ /* -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': (void) repmgr_atoi(optarg, "-p/--port", &cli_errors, false); 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, false); break; /* --node-name */ case OPT_NODE_NAME: strncpy(runtime_options.node_name, optarg, MAXLEN); break; /* --remote-node-id */ case OPT_REMOTE_NODE_ID: runtime_options.remote_node_id = repmgr_atoi(optarg, "--remote-node-id", &cli_errors, false); break; /* * standby options * --------------- */ /* --upstream-node-id */ case OPT_UPSTREAM_NODE_ID: runtime_options.upstream_node_id = repmgr_atoi(optarg, "--upstream-node-id", &cli_errors, false); 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; /*--------------------------- * "standby register" options *--------------------------- */ case OPT_WAIT_START: runtime_options.wait_start = repmgr_atoi(optarg, "--wait-start", &cli_errors, false); 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, false); } break; /*----------------------------- * "standby switchover" options *----------------------------- */ case OPT_ALWAYS_PROMOTE: runtime_options.always_promote = true; break; case OPT_FORCE_REWIND: runtime_options.force_rewind = true; break; case OPT_SIBLINGS_FOLLOW: runtime_options.siblings_follow = 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_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_HAS_PASSFILE: runtime_options.has_passfile = true; break; case OPT_REPL_CONN: runtime_options.replication_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, false); 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, false); 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; /* --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; /*----------------------------- * options deprecated since 3.3 *----------------------------- */ case OPT_CHECK_UPSTREAM_CONFIG: item_list_append(&cli_warnings, _("--check-upstream-config is deprecated; use --dry-run instead")); break; case OPT_DATA_DIR: item_list_append(&cli_warnings, _("--data-dir is deprecated; use -D/--pgdata instead")); break; case OPT_NO_CONNINFO_PASSWORD: item_list_append(&cli_warnings, _("--no-conninfo-password is deprecated; use --use-recovery-conninfo-password to explicitly set a password")); break; /* -C/--remote-config-file */ case 'C': item_list_append(&cli_warnings, _("--remote-config-file is no longer required")); break; /* --recovery-min-apply-delay */ case OPT_RECOVERY_MIN_APPLY_DELAY: item_list_append(&cli_warnings, _("--recovery-min-apply-delay is now a configuration file parameter, \"recovery_min_apply_delay\"")); 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) { 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); } /*---------- * 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 } * BDR { REGISTER | UNREGISTER } | * NODE { STATUS | CHECK | REJOIN | SERVICE } | * CLUSTER { CROSSCHECK | MATRIX | SHOW | EVENT | CLEANUP } * * [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; 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; 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, "BDR") == 0) { if (help_option == true) { do_bdr_help(); exit(SUCCESS); } if (strcasecmp(repmgr_action, "REGISTER") == 0) action = BDR_REGISTER; else if (strcasecmp(repmgr_action, "UNREGISTER") == 0) action = BDR_UNREGISTER; 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, "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_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 { 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, &config_file_options, argv[0]); check_cli_parameters(action); /* * 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); } /* * 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 conflicts between runtime options and configuration file */ /* ================================================================== */ if (action == STANDBY_CLONE) { standy_clone_mode mode = get_standby_clone_mode(); if (mode == barman && runtime_options.without_barman == false && config_file_options.use_replication_slots == true) { log_error(_("STANDBY CLONE in Barman mode is incompatible with configuration option \"use_replication_slots\"")); log_hint(_("set \"use_replication_slots\" to \"no\" in repmgr.conf, or use --without-barman to clone directly from the upstream server")); exit(ERR_BAD_CONFIG); } } /* * Check for configuration file items which can be overriden 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(); /* * 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; /* BDR */ case BDR_REGISTER: do_bdr_register(); break; case BDR_UNREGISTER: do_bdr_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; /* 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; 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. * * XXX for each individual actions, check only required actions * for non-required actions check warn if provided */ 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 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)); } /* * XXX if -D/--pgdata provided, and also * config_file_options.pgdata, warn -D/--pgdata will be * ignored */ 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")); } } } 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 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 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.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.replication_user[0]) { switch (action) { case PRIMARY_REGISTER: case STANDBY_REGISTER: break; case STANDBY_CLONE: case STANDBY_FOLLOW: item_list_append_format(&cli_warnings, _("--replication-user ignored when executing %s)"), action_name(action)); default: item_list_append_format(&cli_warnings, _("--replication-user not required when executing %s"), action_name(action)); } } if (runtime_options.limit_provided) { switch (action) { case CLUSTER_EVENT: if (runtime_options.limit < 1) { item_list_append_format(&cli_errors, _("value for --limit must be 1 or greater (provided: %i)"), runtime_options.limit); } 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)); } } /* 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 == 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.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 WITNESS_REGISTER: case WITNESS_UNREGISTER: case NODE_REJOIN: case NODE_SERVICE: 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"); } } } 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 WITNESS_REGISTER: return "WITNESS REGISTER"; case WITNESS_UNREGISTER: return "WITNESS UNREGISTER"; case BDR_REGISTER: return "BDR REGISTER"; case BDR_UNREGISTER: return "BDR 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 CLUSTER_SHOW: return "CLUSTER SHOW"; case CLUSTER_EVENT: return "CLUSTER EVENT"; case CLUSTER_MATRIX: return "CLUSTER MATRIX"; case CLUSTER_CROSSCHECK: return "CLUSTER CROSSCHECK"; } 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_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}\n"), progname()); printf(_(" %s [OPTIONS] bdr {register|unregister}\n"), progname()); printf(_(" %s [OPTIONS] node status\n"), progname()); printf(_(" %s [OPTIONS] cluster {show|event|matrix|crosscheck}\n"), progname()); puts(""); printf(_(" Execute \"%s {primary|standby|bdr|node|cluster} --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")); 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); printf(_(" -S, --superuser=USERNAME superuser to use, if repmgr user is not superuser\n")); 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(_(" -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(""); } /* * 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 scenarious 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; extension_status = get_repmgr_extension_status(conn); 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_INSTALLED: /* TODO: check version */ 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 */ initPQExpBuffer(&query); wrap_ddl_query(&query, config_file_options.replication_type, "CREATE EXTENSION repmgr"); res = PQexec(schema_create_conn, query.data); termPQExpBuffer(&query); 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); wrap_ddl_query(&query, config_file_options.replication_type, "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); wrap_ddl_query(&query, config_file_options.replication_type, "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) { int conn_server_version_num = UNKNOWN_SERVER_VERSION_NUM; conn_server_version_num = get_server_version(conn, server_version_string); 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 ); if (exit_on_error == true) { PQfinish(conn); exit(ERR_BAD_CONFIG); } return -1; } return conn_server_version_num; } /* * check_93_config() * * Disable options not compatible with PostgreSQL 9.3 */ void check_93_config(void) { if (config_file_options.recovery_min_apply_delay_provided == true) { config_file_options.recovery_min_apply_delay_provided = false; log_warning(_("configuration file option \"recovery_min_apply_delay\" not compatible with PostgreSQL 9.3, ignoring")); } if (config_file_options.use_replication_slots == true) { config_file_options.use_replication_slots = false; log_warning(_("configuration file option \"use_replication_slots\" not compatible with PostgreSQL 9.3, ignoring")); log_hint(_("replication slots are available from PostgreSQL 9.4")); } } 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; } /* * Execute a command locally. "outputbuf" should either be an * initialised PQexpbuffer, or NULL */ bool local_command(const char *command, PQExpBufferData *outputbuf) { FILE *fp = NULL; char output[MAXLEN]; int retval = 0; bool success; log_verbose(LOG_DEBUG, "executing:\n %s", command); if (outputbuf == NULL) { retval = system(command); return (retval == 0) ? true : false; } fp = popen(command, "r"); if (fp == NULL) { log_error(_("unable to execute local command:\n%s"), command); return false; } while (fgets(output, MAXLEN, fp) != NULL) { appendPQExpBuffer(outputbuf, "%s", output); if (!feof(fp)) { break; } } retval = pclose(fp); /* */ success = (WEXITSTATUS(retval) == 0 || WEXITSTATUS(retval) == 141) ? true : false; log_verbose(LOG_DEBUG, "result of command was %i (%i)", WEXITSTATUS(retval), retval); if (outputbuf->data != NULL) 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; } void get_superuser_connection(PGconn **conn, PGconn **superuser_conn, PGconn **privileged_conn) { t_connection_user userinfo = T_CONNECTION_USER_INITIALIZER; bool is_superuser = false; /* this should never happen */ if (PQstatus(*conn) != CONNECTION_OK) { log_error(_("no database connection available")); 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); } *superuser_conn = establish_db_connection_as_user(config_file_options.conninfo, runtime_options.superuser, 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); } *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; } char * make_pg_path(const char *file) { maxlen_snprintf(path_buf, "%s%s", pg_bindir, file); return path_buf; } 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') { appendPQExpBuffer(&rsync_flags, "%s", "--archive --checksum --compress --progress --rsh=ssh"); } else { appendPQExpBuffer(&rsync_flags, "%s", config_file_options.rsync_options); } if (runtime_options.force) { appendPQExpBuffer(&rsync_flags, "%s", " --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 */ appendPQExpBuffer(&rsync_flags, "%s", " --exclude=postmaster.pid --exclude=postmaster.opts --exclude=global/pg_control"); appendPQExpBuffer(&rsync_flags, "%s", " --exclude=recovery.conf --exclude=recovery.done"); if (server_version_num >= 90400) { /* * 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, "%s", " --exclude=postgresql.auto.conf.tmp"); } /* 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) { appendPQExpBuffer(&rsync_flags, "%s", " --exclude=pg_wal/*"); } else { appendPQExpBuffer(&rsync_flags, "%s", " --exclude=pg_xlog/*"); } appendPQExpBuffer(&rsync_flags, "%s", " --exclude=pg_log/* --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; } /* * 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, PQExpBufferData *outputbuf) { FILE *fp; char ssh_command[MAXLEN] = ""; PQExpBufferData ssh_host; char output[MAXLEN] = ""; initPQExpBuffer(&ssh_host); if (*user != '\0') { appendPQExpBuffer(&ssh_host, "%s@", user); } appendPQExpBuffer(&ssh_host, "%s", host); maxlen_snprintf(ssh_command, "ssh -o Batchmode=yes %s %s %s", config_file_options.ssh_options, ssh_host.data, command); termPQExpBuffer(&ssh_host); log_debug("remote_command():\n %s", ssh_command); fp = popen(ssh_command, "r"); if (fp == NULL) { log_error(_("unable to execute remote command:\n %s"), ssh_command); return false; } if (outputbuf != NULL) { /* TODO: better error handling */ while (fgets(output, MAXLEN, fp) != NULL) { appendPQExpBuffer(outputbuf, "%s", output); } } else { while (fgets(output, MAXLEN, fp) != NULL) { if (!feof(fp)) { break; } } } pclose(fp); if (outputbuf != NULL) { if (strlen(outputbuf->data)) 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_repmgr_path(PQExpBufferData *output_buf, t_node_info *remote_node_record) { appendPQExpBuffer(output_buf, "%s -f %s ", make_pg_path(progname()), remote_node_record->config_file); } /* ======================== */ /* 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); appendPQExpBuffer(&command, "%s %s -w -D ", make_pg_path("pg_ctl"), 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); appendPQExpBuffer(&command, "%s %s -D ", make_pg_path("pg_ctl"), 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); appendPQExpBuffer(&command, "%s %s -w -D ", make_pg_path("pg_ctl"), 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); appendPQExpBuffer(&command, "%s %s -w -D ", make_pg_path("pg_ctl"), 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); appendPQExpBuffer(&command, "%s %s -w -D ", make_pg_path("pg_ctl"), 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; } 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, MAXLEN); 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 */ 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); } } repmgr-4.0.3/repmgr-client.h000066400000000000000000000157311324071732600157470ustar00rootroot00000000000000/* * repmgr-client.h * Copyright (c) 2ndQuadrant, 2010-2018 * * 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 BDR_REGISTER 11 #define BDR_UNREGISTER 12 #define NODE_STATUS 13 #define NODE_CHECK 14 #define NODE_SERVICE 15 #define NODE_REJOIN 16 #define CLUSTER_SHOW 17 #define CLUSTER_CLEANUP 18 #define CLUSTER_MATRIX 19 #define CLUSTER_CROSSCHECK 20 #define CLUSTER_EVENT 21 /* command line options without short versions */ #define OPT_HELP 1001 #define OPT_CHECK_UPSTREAM_CONFIG 1002 #define OPT_COPY_EXTERNAL_CONFIG_FILES 1003 #define OPT_CSV 1004 #define OPT_NODE 1005 #define OPT_NODE_ID 1006 #define OPT_NODE_NAME 1007 #define OPT_WITHOUT_BARMAN 1008 #define OPT_NO_UPSTREAM_CONNECTION 1009 #define OPT_WAIT_SYNC 1010 #define OPT_LOG_TO_FILE 1011 #define OPT_UPSTREAM_CONNINFO 1012 #define OPT_REPLICATION_USER 1013 #define OPT_EVENT 1014 #define OPT_LIMIT 1015 #define OPT_ALL 1016 #define OPT_DRY_RUN 1017 #define OPT_UPSTREAM_NODE_ID 1018 #define OPT_ACTION 1019 #define OPT_LIST_ACTIONS 1020 #define OPT_CHECKPOINT 1021 #define OPT_IS_SHUTDOWN_CLEANLY 1022 #define OPT_ALWAYS_PROMOTE 1023 #define OPT_FORCE_REWIND 1024 #define OPT_NAGIOS 1025 #define OPT_ARCHIVE_READY 1026 #define OPT_OPTFORMAT 1027 #define OPT_REPLICATION_LAG 1028 #define OPT_CONFIG_FILES 1029 #define OPT_SIBLINGS_FOLLOW 1030 #define OPT_ROLE 1031 #define OPT_DOWNSTREAM 1032 #define OPT_SLOTS 1033 #define OPT_CONFIG_ARCHIVE_DIR 1034 #define OPT_HAS_PASSFILE 1035 #define OPT_WAIT_START 1036 #define OPT_REPL_CONN 1037 #define OPT_REMOTE_NODE_ID 1038 /* deprecated since 3.3 */ #define OPT_DATA_DIR 999 #define OPT_NO_CONNINFO_PASSWORD 998 #define OPT_RECOVERY_MIN_APPLY_DELAY 997 static struct option long_options[] = { /* general options */ {"version", no_argument, NULL, 'V'}, {"help", no_argument, NULL, OPT_HELP}, /* 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", no_argument, NULL, 'W'}, /* 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}, {"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}, {"recovery-min-apply-delay", required_argument, NULL, OPT_RECOVERY_MIN_APPLY_DELAY}, {"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}, /* "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 join" */ {"always-promote", no_argument, NULL, OPT_ALWAYS_PROMOTE}, {"siblings-follow", no_argument, NULL, OPT_SIBLINGS_FOLLOW}, /* "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}, {"replication-lag", no_argument, NULL, OPT_REPLICATION_LAG}, {"role", no_argument, NULL, OPT_ROLE}, {"slots", no_argument, NULL, OPT_SLOTS}, {"has-passfile", no_argument, NULL, OPT_HAS_PASSFILE}, {"replication-connection", no_argument, NULL, OPT_REPL_CONN}, /* "node rejoin" options */ {"config-files", required_argument, NULL, OPT_CONFIG_FILES}, {"config-archive-dir", required_argument, NULL, OPT_CONFIG_ARCHIVE_DIR}, {"force-rewind", no_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'}, /* deprecated */ {"check-upstream-config", no_argument, NULL, OPT_CHECK_UPSTREAM_CONFIG}, {"no-conninfo-password", no_argument, NULL, OPT_NO_CONNINFO_PASSWORD}, /* previously used by "standby switchover" */ {"remote-config-file", required_argument, NULL, 'C'}, /* legacy alias for -D/--pgdata */ {"data-dir", required_argument, NULL, OPT_DATA_DIR}, /* replaced by --node-id */ {"node", required_argument, NULL, OPT_NODE}, {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-4.0.3/repmgr.c000066400000000000000000000215771324071732600144730ustar00rootroot00000000000000/* * repmgr.c - repmgr extension * * Copyright (c) 2ndQuadrant, 2010-2018 * * 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/ipc.h" #include "storage/lwlock.h" #include "storage/procarray.h" #include "storage/shmem.h" #include "storage/spin.h" #include "utils/builtins.h" #if (PG_VERSION_NUM >= 90400) #include "utils/pg_lsn.h" #endif #include "utils/timestamp.h" #include "executor/spi.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 TRANCHE_NAME "repmgrd" 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; /* streaming failover */ NodeVotingStatus voting_status; int current_electoral_term; int candidate_node_id; bool follow_new_primary; /* BDR failover */ int bdr_failover_handler; } 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); Datum set_local_node_id(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(set_local_node_id); Datum get_local_node_id(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(get_local_node_id); Datum standby_set_last_updated(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(standby_set_last_updated); Datum standby_get_last_updated(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(standby_get_last_updated); Datum notify_follow_primary(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(notify_follow_primary); Datum get_new_primary(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(get_new_primary); Datum reset_voting_status(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(reset_voting_status); Datum am_bdr_failover_handler(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(am_bdr_failover_handler); Datum unset_bdr_failover_handler(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(unset_bdr_failover_handler); /* * Module load callback */ void _PG_init(void) { elog(DEBUG1, "repmgr init"); 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, including hash table */ 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->current_electoral_term = 0; shared_state->voting_status = VS_NO_VOTE; shared_state->candidate_node_id = UNKNOWN_NODE_ID; shared_state->follow_new_primary = false; shared_state->bdr_failover_handler = UNKNOWN_NODE_ID; } LWLockRelease(AddinShmemInitLock); } /* ==================== */ /* monitoring functions */ /* ==================== */ Datum set_local_node_id(PG_FUNCTION_ARGS) { int local_node_id = UNKNOWN_NODE_ID; if (!shared_state) PG_RETURN_NULL(); if (PG_ARGISNULL(0)) PG_RETURN_NULL(); local_node_id = PG_GETARG_INT32(0); 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; } LWLockRelease(shared_state->lock); PG_RETURN_VOID(); } Datum 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 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 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); } /* ===================*/ /* failover functions */ /* ===================*/ Datum notify_follow_primary(PG_FUNCTION_ARGS) { int primary_node_id = UNKNOWN_NODE_ID; if (!shared_state) PG_RETURN_NULL(); if (PG_ARGISNULL(0)) PG_RETURN_NULL(); 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) { 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 get_new_primary(PG_FUNCTION_ARGS) { int new_primary_node_id = UNKNOWN_NODE_ID; if (!shared_state) PG_RETURN_NULL(); 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_NULL(); PG_RETURN_INT32(new_primary_node_id); } Datum 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(); } Datum am_bdr_failover_handler(PG_FUNCTION_ARGS) { int node_id = UNKNOWN_NODE_ID; bool am_handler = false; if (!shared_state) PG_RETURN_NULL(); if (PG_ARGISNULL(0)) PG_RETURN_NULL(); node_id = PG_GETARG_INT32(0); LWLockAcquire(shared_state->lock, LW_SHARED); if (shared_state->bdr_failover_handler == UNKNOWN_NODE_ID) { LWLockRelease(shared_state->lock); LWLockAcquire(shared_state->lock, LW_EXCLUSIVE); shared_state->bdr_failover_handler = node_id; am_handler = true; } else if (shared_state->bdr_failover_handler == node_id) { am_handler = true; } LWLockRelease(shared_state->lock); PG_RETURN_BOOL(am_handler); } Datum unset_bdr_failover_handler(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->bdr_failover_handler = UNKNOWN_NODE_ID; LWLockRelease(shared_state->lock); } PG_RETURN_VOID(); } repmgr-4.0.3/repmgr.conf.sample000066400000000000000000000330231324071732600164430ustar00rootroot00000000000000################################################### # 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. # ============================================================================= # 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". #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/static/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/static/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. #replication_user='repmgr' # User to make replication connections with, if not set defaults # to the user defined in "conninfo". # ============================================================================= # Optional configuration items # ============================================================================= #------------------------------------------------------------------------------ # Replication settings #------------------------------------------------------------------------------ #replication_type=physical # Must be one of 'physical' or 'bdr'. #location=default # arbitrary string defining the location of the node; this # is used during failover to check visibilty of the # current primary node. See the 'repmgrd' documentation # in README.md for further details. #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. #recovery_min_apply_delay= # If provided, "recovery_min_apply_delay" in recovery.conf # will be set to this value. #------------------------------------------------------------------------------ # 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/ #use_primary_conninfo_password=false # explicitly set "password" in recovery.conf's # "primary_conninfo" parameter 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" #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 placed in the recovery.conf # file generated by repmgr #------------------------------------------------------------------------------ # Standby follow settings #------------------------------------------------------------------------------ # These settings apply when instructing a standby to follow the new primary # ("repmgr standby follow"). #primary_follow_timeout=60 # The length of time (in seconds) to wait # for the new primary to become available #------------------------------------------------------------------------------ # 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 # (does not apply to BDR mode) #priority=100 # indicate a preferred priorty for promoting nodes; # a value of zero prevents the node being promoted to primary # (default: 100) #reconnect_attempts=6 # Number 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 to execute when promoting a new primary; use something like: # # repmgr standby promote -f /etc/repmgr.conf # #follow_command= # command to execute when instructing a standby to follow a new primary; # use something like: # # repmgr standby follow -f /etc/repmgr.conf -W --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 #monitoring_history=no # Whether to write monitoring data to the "montoring_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 being monitored is 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. #------------------------------------------------------------------------------ # service control commands #------------------------------------------------------------------------------ # # repmgr provides options to override the default pg_ctl commands # used to stop, start, restart, reload and promote the PostgreSQL cluster # # NOTE: These commands must be runnable on remote nodes as well for switchover # to function correctly. # # If you use sudo, the user repmgr runs as (usually 'postgres') must have # passwordless sudo access to execute the command. # # For example, to use systemd, you 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 # #service_start_command = '' #service_stop_command = '' #service_restart_command = '' #service_reload_command = '' #service_promote_command = '' # Note: this overrides any value contained in the setting # "promote_command". This is intended for systems which # provide a package-level promote command, such as Debian's # "pg_ctlcluster" #------------------------------------------------------------------------------ # 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. #------------------------------------------------------------------------------ # BDR monitoring options #------------------------------------------------------------------------------ #bdr_local_monitoring_only=false # Only monitor the local node; no checks will be # performed on the other node #bdr_recovery_timeout # If a BDR node was offline and has become available # maximum length of time in seconds to wait for the # node to reconnect to the cluster repmgr-4.0.3/repmgr.control000066400000000000000000000002431324071732600157140ustar00rootroot00000000000000# repmgr extension comment = 'Replication manager for PostgreSQL' default_version = '4.0' module_pathname = '$libdir/repmgr' relocatable = false schema = repmgr repmgr-4.0.3/repmgr.h000066400000000000000000000052351324071732600144710ustar00rootroot00000000000000/* * repmgr.h * Copyright (c) 2ndQuadrant, 2010-2018 * * 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 #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" #define MIN_SUPPORTED_VERSION "9.3" #define MIN_SUPPORTED_VERSION_NUM 90300 #define REPLICATION_TYPE_PHYSICAL 1 #define REPLICATION_TYPE_BDR 2 #define UNKNOWN_SERVER_VERSION_NUM -1 #define UNKNOWN_TIMELINE_ID -1 #define UNKNOWN_SYSTEM_IDENTIFIER 0 #define NODE_NOT_FOUND -1 #define NO_UPSTREAM_NODE -1 #define UNKNOWN_NODE_ID -1 #define VOTING_TERM_NOT_SET -1 /* * various default values - ensure repmgr.conf.sample is update * if any of these are changed */ #define DEFAULT_LOCATION "default" #define DEFAULT_PRIORITY 100 #define DEFAULT_RECONNECTION_ATTEMPTS 6 /* seconds */ #define DEFAULT_RECONNECTION_INTERVAL 10 /* seconds */ #define DEFAULT_MONITORING_INTERVAL 2 /* seconds */ #define DEFAULT_ASYNC_QUERY_TIMEOUT 60 /* seconds */ #define DEFAULT_PRIMARY_NOTIFICATION_TIMEOUT 60 /* seconds */ #define DEFAULT_PRIMARY_FOLLOW_TIMEOUT 60 /* seconds */ #define DEFAULT_BDR_RECOVERY_TIMEOUT 30 /* seconds */ #define DEFAULT_ARCHIVE_READY_WARNING 16 /* WAL files */ #define DEFAULT_ARCHIVE_READY_CRITICAL 128 /* WAL files */ #define DEFAULT_REPLICATION_LAG_WARNING 300 /* seconds */ #define DEFAULT_REPLICATION_LAG_CRITICAL 600 /* seconds */ #define DEFAULT_WITNESS_SYNC_INTERVAL 15 /* seconds */ #define DEFAULT_WAIT_START 30 /* seconds */ #ifndef RECOVERY_COMMAND_FILE #define RECOVERY_COMMAND_FILE "recovery.conf" #endif #ifndef TABLESPACE_MAP #define TABLESPACE_MAP "tablespace_map" #endif #endif /* _REPMGR_H_ */ repmgr-4.0.3/repmgr_version.h.in000066400000000000000000000000771324071732600166420ustar00rootroot00000000000000#define REPMGR_VERSION_DATE "" #define REPMGR_VERSION "4.0.3" repmgr-4.0.3/repmgrd-bdr.c000066400000000000000000000414161324071732600153760ustar00rootroot00000000000000/* * repmgrd-bdr.c - BDR functionality for repmgrd * * Copyright (c) 2ndQuadrant, 2010-2018 * * 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-bdr.h" #include "configfile.h" static void do_bdr_failover(NodeInfoList *nodes, t_node_info *monitored_node); static void do_bdr_recovery(NodeInfoList *nodes, t_node_info *monitored_node); void do_bdr_node_check(void) { /* nothing to do at the moment */ } void monitor_bdr(void) { NodeInfoList nodes = T_NODE_INFO_LIST_INITIALIZER; t_bdr_node_info bdr_node_info = T_BDR_NODE_INFO_INITIALIZER; RecordStatus record_status; NodeInfoListCell *cell; PQExpBufferData event_details; instr_time log_status_interval_start; /* sanity check local database */ log_info(_("connecting to local database \"%s\""), config_file_options.conninfo); local_conn = establish_db_connection(config_file_options.conninfo, true); /* * Local node must be running */ if (PQstatus(local_conn) != CONNECTION_OK) { log_error(_("unable connect to local node (ID: %i), terminating"), local_node_info.node_id); log_hint(_("local node must be running before repmgrd can start")); PQfinish(local_conn); exit(ERR_DB_CONN); } /* * Verify that database is a BDR one TODO: check if supported BDR version? */ log_info(_("connected to database, checking for BDR")); if (!is_bdr_db(local_conn, NULL)) { log_error(_("database is not BDR-enabled")); exit(ERR_BAD_CONFIG); } if (is_table_in_bdr_replication_set(local_conn, "nodes", "repmgr") == false) { log_error(_("repmgr metadata table 'repmgr.%s' is not in the 'repmgr' replication set"), "nodes"); /* * TODO: add `repmgr bdr sync` or similar for this situation, and hint * here */ exit(ERR_BAD_CONFIG); } record_status = get_bdr_node_record_by_name(local_conn, local_node_info.node_name, &bdr_node_info); if (record_status != RECORD_FOUND) { log_error(_("unable to retrieve BDR record for node %s, terminating"), local_node_info.node_name); PQfinish(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(_("unable to retrieve record for local node (ID: %i), terminating"), local_node_info.node_id); log_hint(_("check that \"repmgr bdr register\" was executed for this node")); PQfinish(local_conn); exit(ERR_BAD_CONFIG); } if (local_node_info.active == false) { log_error(_("local node (ID: %i) is marked as inactive in repmgr"), local_node_info.node_id); log_hint(_("if the node has been reactivated, run \"repmgr bdr register --force\" and restart repmgrd")); PQfinish(local_conn); exit(ERR_BAD_CONFIG); } if (is_active_bdr_node(local_conn, local_node_info.node_name) == false) { log_error(_("BDR node \"%s\" is not active, terminating"), local_node_info.node_name); PQfinish(local_conn); exit(ERR_BAD_CONFIG); } /* Log startup event */ create_event_record(local_conn, &config_file_options, config_file_options.node_id, "repmgrd_start", true, NULL); /* * retrieve list of all nodes - we'll need these if the DB connection goes * away */ get_all_node_records(local_conn, &nodes); /* we're expecting all (both) nodes to be up */ for (cell = nodes.head; cell; cell = cell->next) { cell->node_info->node_status = NODE_STATUS_UP; } log_debug("main_loop_bdr() monitoring local node %i", config_file_options.node_id); log_info(_("starting continuous BDR node monitoring")); while (true) { /* monitoring loop */ log_verbose(LOG_DEBUG, "BDR check loop..."); for (cell = nodes.head; cell; cell = cell->next) { if (config_file_options.bdr_local_monitoring_only == true && cell->node_info->node_id != local_node_info.node_id) { continue; } if (cell->node_info->node_id == local_node_info.node_id) { log_debug("checking local node %i in %s state", local_node_info.node_id, print_monitoring_state(cell->node_info->monitoring_state)); } else { log_debug("checking other node %i in %s state", cell->node_info->node_id, print_monitoring_state(cell->node_info->monitoring_state)); } switch (cell->node_info->monitoring_state) { case MS_NORMAL: { if (is_server_available(cell->node_info->conninfo) == false) { /* node is down, we were expecting it to be up */ if (cell->node_info->node_status == NODE_STATUS_UP) { instr_time node_unreachable_start; INSTR_TIME_SET_CURRENT(node_unreachable_start); cell->node_info->node_status = NODE_STATUS_DOWN; if (cell->node_info->conn != NULL) { PQfinish(cell->node_info->conn); cell->node_info->conn = NULL; } log_warning(_("unable to connect to node %s (ID %i)"), cell->node_info->node_name, cell->node_info->node_id); cell->node_info->conn = try_reconnect(cell->node_info); /* node has recovered - log and continue */ if (cell->node_info->node_status == NODE_STATUS_UP) { int node_unreachable_elapsed = calculate_elapsed(node_unreachable_start); initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("reconnected to node %i after %i seconds"), cell->node_info->node_id, node_unreachable_elapsed); log_notice("%s", event_details.data); create_event_notification(cell->node_info->conn, &config_file_options, config_file_options.node_id, "bdr_reconnect", true, event_details.data); termPQExpBuffer(&event_details); goto loop; } /* still down after reconnect attempt(s) */ if (cell->node_info->node_status == NODE_STATUS_DOWN) { do_bdr_failover(&nodes, cell->node_info); goto loop; } } } } break; case MS_DEGRADED: { /* degraded monitoring */ if (is_server_available(cell->node_info->conninfo) == true) { do_bdr_recovery(&nodes, cell->node_info); } } break; } } 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) { log_info(_("monitoring BDR replication status on node \"%s\" (ID: %i)"), local_node_info.node_name, local_node_info.node_id); for (cell = nodes.head; cell; cell = cell->next) { if (cell->node_info->monitoring_state == MS_DEGRADED) { log_detail( _("monitoring node \"%s\" (ID: %i) in degraded mode"), cell->node_info->node_name, cell->node_info->node_id); } } INSTR_TIME_SET_CURRENT(log_status_interval_start); } } if (got_SIGHUP) { /* * if we can reload, then could need to change local_conn */ if (reload_config(&config_file_options)) { PQfinish(local_conn); local_conn = establish_db_connection(config_file_options.conninfo, true); update_registration(local_conn); } got_SIGHUP = false; } if (got_SIGHUP) { log_debug("SIGHUP received"); if (reload_config(&config_file_options)) { PQfinish(local_conn); local_conn = establish_db_connection(config_file_options.conninfo, true); if (*config_file_options.log_file) { FILE *fd; 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; } log_verbose(LOG_DEBUG, "sleeping %i seconds (\"monitor_interval_secs\")", config_file_options.monitor_interval_secs); sleep(config_file_options.monitor_interval_secs); } return; } /* * do_bdr_failover() * * Here we attempt to perform a BDR "failover". * * As there's no equivalent of a physical replication failover, * we'll do the following: * * - connect to active node * - generate an event log record on that node * - optionally execute `bdr_failover_command`, passing the conninfo string * of that node to the command; this can be used for e.g. reconfiguring * pgbouncer. * */ void do_bdr_failover(NodeInfoList *nodes, t_node_info *monitored_node) { PGconn *next_node_conn = NULL; NodeInfoListCell *cell; PQExpBufferData event_details; t_event_info event_info = T_EVENT_INFO_INITIALIZER; t_node_info target_node = T_NODE_INFO_INITIALIZER; t_node_info failed_node = T_NODE_INFO_INITIALIZER; RecordStatus record_status; /* if one of the two nodes is down, cluster will be in a degraded state */ monitored_node->monitoring_state = MS_DEGRADED; INSTR_TIME_SET_CURRENT(degraded_monitoring_start); /* terminate local connection if this is the failed node */ if (monitored_node->node_id == local_node_info.node_id) { PQfinish(local_conn); local_conn = NULL; } /* get other node */ for (cell = nodes->head; cell; cell = cell->next) { log_debug("do_bdr_failover() %s", cell->node_info->node_name); /* * don't attempt to connect to the current monitored node, as that's * the one which has failed */ if (cell->node_info->node_id == monitored_node->node_id) continue; /* TODO: reuse local conn if local node is up */ next_node_conn = establish_db_connection(cell->node_info->conninfo, false); if (PQstatus(next_node_conn) == CONNECTION_OK) { record_status = get_node_record(next_node_conn, cell->node_info->node_id, &target_node); if (record_status == RECORD_FOUND) { break; } } next_node_conn = NULL; } /* shouldn't happen, and if it does, it means everything is down */ if (next_node_conn == NULL) { log_error(_("no other available node found")); /* no other nodes found - continue degraded monitoring */ return; } /* * check if the node record for the failed node is still marked as active, * if not it means the other node has done the "failover" already */ record_status = get_node_record(next_node_conn, monitored_node->node_id, &failed_node); if (record_status == RECORD_FOUND && failed_node.active == false) { PQfinish(next_node_conn); log_notice(_("record for node %i has already been set inactive"), failed_node.node_id); return; } if (am_bdr_failover_handler(next_node_conn, local_node_info.node_id) == false) { PQfinish(next_node_conn); log_notice(_("other node's repmgrd is handling failover")); return; } /* check here that the node hasn't come back up */ if (is_server_available(monitored_node->conninfo) == true) { log_notice(_("node %i has reappeared, aborting failover"), monitored_node->node_id); monitored_node->monitoring_state = MS_NORMAL; PQfinish(next_node_conn); } log_debug("this node is the failover handler"); initPQExpBuffer(&event_details); event_info.conninfo_str = target_node.conninfo; event_info.node_name = target_node.node_name; /* update node record on the active node */ update_node_record_set_active(next_node_conn, monitored_node->node_id, false); log_notice(_("setting node record for node %i to inactive"), monitored_node->node_id); appendPQExpBuffer(&event_details, _("node \"%s\" (ID: %i) detected as failed; next available node is \"%s\" (ID: %i)"), monitored_node->node_name, monitored_node->node_id, target_node.node_name, target_node.node_id); /* * Create an event record * * If we were able to connect to another node, we'll update the event log * there. * * In any case the event notification command will be triggered with the * event "bdr_failover" */ create_event_notification_extended(next_node_conn, &config_file_options, monitored_node->node_id, "bdr_failover", true, event_details.data, &event_info); log_info("%s", event_details.data); termPQExpBuffer(&event_details); unset_bdr_failover_handler(next_node_conn); PQfinish(next_node_conn); return; } static void do_bdr_recovery(NodeInfoList *nodes, t_node_info *monitored_node) { PGconn *recovered_node_conn; PQExpBufferData event_details; t_event_info event_info = T_EVENT_INFO_INITIALIZER; int i; bool slot_reactivated = false; int node_recovery_elapsed; char node_name[MAXLEN] = ""; log_debug("handling recovery for monitored node %i", monitored_node->node_id); recovered_node_conn = establish_db_connection(monitored_node->conninfo, false); if (PQstatus(recovered_node_conn) != CONNECTION_OK) { PQfinish(recovered_node_conn); return; } if (PQstatus(local_conn) != CONNECTION_OK) { log_debug("no local connection - attempting to reconnect "); local_conn = establish_db_connection(config_file_options.conninfo, false); } /* * still unable to connect - the local node is probably down, so we can't * check for reconnection */ if (PQstatus(local_conn) != CONNECTION_OK) { local_conn = NULL; log_warning(_("unable to reconnect to local node")); initPQExpBuffer(&event_details); node_recovery_elapsed = calculate_elapsed(degraded_monitoring_start); monitored_node->monitoring_state = MS_NORMAL; monitored_node->node_status = NODE_STATUS_UP; appendPQExpBuffer( &event_details, _("node \"%s\" (ID: %i) has become available after %i seconds"), monitored_node->node_name, monitored_node->node_id, node_recovery_elapsed); log_notice("%s", event_details.data); termPQExpBuffer(&event_details); PQfinish(recovered_node_conn); return; } get_bdr_other_node_name(local_conn, local_node_info.node_id, node_name); log_info(_("detected recovery on node %s (ID: %i), checking status"), monitored_node->node_name, monitored_node->node_id); for (i = 0; i < config_file_options.bdr_recovery_timeout; i++) { ReplSlotStatus slot_status; log_debug("checking for state of replication slot for node \"%s\"", node_name); slot_status = get_bdr_node_replication_slot_status( local_conn, node_name); if (slot_status == SLOT_ACTIVE) { slot_reactivated = true; break; } sleep(1); } /* mark node as up */ monitored_node->node_status = NODE_STATUS_UP; if (slot_reactivated == false) { log_warning(_("no active replication slot for node \"%s\" found after %i seconds"), node_name, config_file_options.bdr_recovery_timeout); log_detail(_("this probably means inter-node BDR connections have not been re-established")); PQfinish(recovered_node_conn); return; } log_info(_("active replication slot for node \"%s\" found after %i seconds"), node_name, i); node_recovery_elapsed = calculate_elapsed(degraded_monitoring_start); monitored_node->monitoring_state = MS_NORMAL; initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("node \"%s\" (ID: %i) has recovered after %i seconds"), monitored_node->node_name, monitored_node->node_id, node_recovery_elapsed); log_notice("%s", event_details.data); /* other node will generate the event */ if (monitored_node->node_id == local_node_info.node_id) { termPQExpBuffer(&event_details); PQfinish(recovered_node_conn); return; } /* generate the event on the currently active node only */ if (monitored_node->node_id != local_node_info.node_id) { event_info.conninfo_str = monitored_node->conninfo; event_info.node_name = monitored_node->node_name; create_event_notification_extended( local_conn, &config_file_options, config_file_options.node_id, "bdr_recovery", true, event_details.data, &event_info); } update_node_record_set_active(local_conn, monitored_node->node_id, true); termPQExpBuffer(&event_details); PQfinish(recovered_node_conn); return; } repmgr-4.0.3/repmgrd-bdr.h000066400000000000000000000015431324071732600154000ustar00rootroot00000000000000/* * repmgrd-bdr.h * Copyright (c) 2ndQuadrant, 2010-2018 * * 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_BDR_H_ #define _REPMGRD_BDR_H_ extern void do_bdr_node_check(void); extern void monitor_bdr(void); #endif /* _REPMGRD_BDR_H_ */ repmgr-4.0.3/repmgrd-physical.c000066400000000000000000002241201324071732600164360ustar00rootroot00000000000000/* * repmgrd-physical.c - physical (streaming) replication functionality for repmgrd * * Copyright (c) 2ndQuadrant, 2010-2018 * * 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_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 } FailoverState; typedef enum { ELECTION_NOT_CANDIDATE = -1, ELECTION_WON, ELECTION_LOST, ELECTION_CANCELLED } ElectionResult; 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 NodeInfoList standby_nodes = T_NODE_INFO_LIST_INITIALIZER; static ElectionResult do_election(void); 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 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); void close_connections_physical(); static bool do_primary_failover(void); static bool do_upstream_standby_failover(void); static bool do_witness_failover(void); static void update_monitoring_history(void); static const char * format_failover_state(FailoverState failover_state); /* perform some sanity checks on the node's configuration */ void do_physical_node_check(void) { /* * Check if node record is active - if not, and `failover=automatic`, the * node won't be considered as a promotion candidate; this often happens * when a failed primary is recloned and the node was not re-registered, * giving the impression failover capability is there when it's not. In * this case abort with an error and a hint about registering. * * If `failover=manual`, repmgrd can continue to passively monitor the * node, but we should nevertheless issue a warning and the same hint. */ if (local_node_info.active == false) { char *hint = "Check that \"repmgr (primary|standby) register\" was executed for this node"; 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); PQfinish(local_conn); terminate(ERR_BAD_CONFIG); 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/follow commands are defined, otherwise repmgrd * won't be able to perform any useful action */ 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') { /* * if repmgrd executes "service_promote_command" directly, * repmgr metadata won't get updated */ 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")); PQfinish(local_conn); exit(ERR_BAD_CONFIG); } } } /* * repmgrd running on the primary server */ void monitor_streaming_primary(void) { instr_time log_status_interval_start; PQExpBufferData event_details; reset_node_voting_status(); initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("monitoring cluster primary \"%s\" (node 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); local_node_info.node_status = NODE_STATUS_UP; while (true) { /* * TODO: cache node list here, refresh at `node_list_refresh_interval` * also return reason for inavailability so we can log it */ if (is_server_available(local_node_info.conninfo) == false) { /* local node is down, we were expecting it to be up */ if (local_node_info.node_status == NODE_STATUS_UP) { PQExpBufferData event_details; instr_time local_node_unreachable_start; INSTR_TIME_SET_CURRENT(local_node_unreachable_start); initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("unable to connect to local node")); log_warning("%s", event_details.data); local_node_info.node_status = NODE_STATUS_UNKNOWN; PQfinish(local_conn); /* * as we're monitoring the primary, no point in trying to * write the event to the database * * XXX possible pre-action event */ create_event_notification(NULL, &config_file_options, config_file_options.node_id, "repmgrd_local_disconnect", true, event_details.data); termPQExpBuffer(&event_details); local_conn = try_reconnect(&local_node_info); if (local_node_info.node_status == NODE_STATUS_UP) { int local_node_unreachable_elapsed = calculate_elapsed(local_node_unreachable_start); 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); goto loop; } monitoring_state = MS_DEGRADED; INSTR_TIME_SET_CURRENT(degraded_monitoring_start); } } 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) { 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_terminate", 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) { 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")); PQfinish(local_conn); } else { local_node_info.node_status = NODE_STATUS_UP; monitoring_state = MS_NORMAL; initPQExpBuffer(&event_details); /* check to see if the node has been restored as a standby */ if (get_recovery_type(local_conn) == RECTYPE_STANDBY) { PGconn *new_primary_conn; appendPQExpBuffer(&event_details, _("reconnected to node after %i seconds, node is now a standby, switching to standby monitoring"), degraded_monitoring_elapsed); 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) { PQfinish(new_primary_conn); log_warning(_("unable to connect to new primary node %i"), primary_node_id); } else { RecordStatus record_status; int i = 0; log_debug("primary node id is now %i", primary_node_id); /* * poll for a while until record type is returned as "STANDBY" - it's possible * that there's a gap between the server being restarted and the record * being updated */ for (i = 0; i < 30; i++) { /* * try and refresh the local node record from the primary, as the updated * local node record may not have been replicated yet */ record_status = get_node_record(new_primary_conn, config_file_options.node_id, &local_node_info); if (record_status == RECORD_FOUND) { log_debug("type = %s", get_node_type_string(local_node_info.type)); if (local_node_info.type == STANDBY) { PQfinish(new_primary_conn); /* XXX add event notification */ return; } } sleep(1); } PQfinish(new_primary_conn); if (record_status == RECORD_FOUND) { log_warning(_("repmgr node record is still %s"), get_node_type_string(local_node_info.type)); } else { log_error(_("no metadata record found for this node")); log_hint(_("check that 'repmgr (primary|standby) register' was executed for this node")); } } } else { 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); 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 */ } 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) { log_info(_("monitoring primary node \"%s\" (node 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 primary to reappear")); } INSTR_TIME_SET_CURRENT(log_status_interval_start); } } if (got_SIGHUP) { log_debug("SIGHUP received"); if (reload_config(&config_file_options)) { PQfinish(local_conn); local_conn = establish_db_connection(config_file_options.conninfo, true); if (*config_file_options.log_file) { FILE *fd; 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; } 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_standby(void) { RecordStatus record_status; instr_time log_status_interval_start; PQExpBufferData event_details; reset_node_voting_status(); log_debug("monitor_streaming_standby()"); /* * 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) { local_node_info.upstream_node_id = get_primary_node_id(local_conn); /* * 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")); PQfinish(local_conn); exit(ERR_BAD_CONFIG); } } 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")); PQfinish(local_conn); exit(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); PQfinish(local_conn); exit(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) { 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")); PQfinish(local_conn); exit(ERR_DB_CONN); } /* * 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) { /* * 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. */ primary_conn = establish_primary_db_connection(upstream_conn, false); if (PQstatus(primary_conn) != CONNECTION_OK) { log_error(_("unable to connect to primary node")); log_hint(_("ensure the primary node is reachable from this node")); exit(ERR_DB_CONN); } log_verbose(LOG_DEBUG, "connected to primary"); } else { primary_conn = upstream_conn; } primary_node_id = get_primary_node_id(primary_conn); /* Log startup event */ if (startup_event_logged == false) { PQExpBufferData event_details; initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("monitoring connection to upstream node \"%s\" (node 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) { log_verbose(LOG_DEBUG, "checking %s", upstream_node_info.conninfo); if (is_server_available(upstream_node_info.conninfo) == false) { /* 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); initPQExpBuffer(&event_details); upstream_node_info.node_status = NODE_STATUS_UNKNOWN; appendPQExpBuffer(&event_details, _("unable to connect to upstream node \"%s\" (node ID: %i)"), upstream_node_info.node_name, upstream_node_info.node_id); /* */ if (upstream_node_info.type == STANDBY) { /* XXX possible pre-action event */ 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); PQfinish(upstream_conn); upstream_conn = try_reconnect(&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); initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("reconnected to upstream node after %i seconds"), upstream_node_unreachable_elapsed); log_notice("%s", event_details.data); create_event_notification(upstream_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; if (upstream_node_info.type == PRIMARY) { failover_done = do_primary_failover(); } else if (upstream_node_info.type == STANDBY) { failover_done = do_upstream_standby_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 (is_server_available(upstream_node_info.conninfo) == true) { upstream_conn = establish_db_connection(upstream_node_info.conninfo, false); if (PQstatus(upstream_conn) == CONNECTION_OK) { /* 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) { primary_conn = establish_primary_db_connection(upstream_conn, false); } } initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("reconnected to upstream node %i after %i seconds, resuming monitoring"), 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 */ NodeInfoListCell *cell; int follow_node_id = UNKNOWN_NODE_ID; /* local node has been promoted */ if (get_recovery_type(local_conn) == RECTYPE_PRIMARY) { log_notice(_("local node is primary, checking local node record")); /* * 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 = get_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) { get_active_sibling_node_records(local_conn, local_node_info.node_id, local_node_info.upstream_node_id, &standby_nodes); if (standby_nodes.node_count > 0) { log_debug("scanning %i node records to detect new primary...", standby_nodes.node_count); for (cell = standby_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; } cell->node_info->conn = establish_db_connection(cell->node_info->conninfo, false); if (PQstatus(cell->node_info->conn) != CONNECTION_OK) { log_debug("unable to connect to %i ... ", cell->node_info->node_id); continue; } if (get_recovery_type(cell->node_info->conn) == RECTYPE_PRIMARY) { follow_node_id = cell->node_info->node_id; PQfinish(cell->node_info->conn); cell->node_info->conn = NULL; break; } PQfinish(cell->node_info->conn); cell->node_info->conn = NULL; } if (follow_node_id != UNKNOWN_NODE_ID) { follow_new_primary(follow_node_id); } } clear_node_info_list(&standby_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\" (node ID: %i) monitoring upstream node \"%s\" (node 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) { appendPQExpBuffer( &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) { log_detail(_("waiting for upstream or another primary to reappear")); } INSTR_TIME_SET_CURRENT(log_status_interval_start); } } /* * 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) { if (PQstatus(primary_conn) == CONNECTION_OK) { if (update_node_record_set_active(primary_conn, local_node_info.node_id, false) == 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_warning("%s", event_details.data); create_event_notification(primary_conn, &config_file_options, local_node_info.node_id, "standby_failure", false, event_details.data); termPQExpBuffer(&event_details); } } } } else { 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_warning("%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 (PQstatus(primary_conn) == CONNECTION_OK && config_file_options.monitoring_history == true) update_monitoring_history(); if (got_SIGHUP) { log_debug("SIGHUP received"); if (reload_config(&config_file_options)) { PQfinish(local_conn); local_conn = establish_db_connection(config_file_options.conninfo, true); if (*config_file_options.log_file) { FILE *fd; 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; } sleep(config_file_options.monitor_interval_secs); } } void monitor_streaming_witness(void) { instr_time log_status_interval_start; instr_time witness_sync_interval_start; PQExpBufferData event_details; RecordStatus record_status; reset_node_voting_status(); log_debug("monitor_streaming_witness()"); if (get_primary_node_record(local_conn, &upstream_node_info) == false) { log_error(_("unable to retrieve record for primary node")); log_hint(_("execute \"repmgr witness register --force\" to update the witness node ")); PQfinish(local_conn); terminate(ERR_BAD_CONFIG); } primary_conn = establish_db_connection(upstream_node_info.conninfo, false); /* * Primary 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(primary_conn) != CONNECTION_OK) { log_error(_("unable connect to upstream node (ID: %i), terminating"), upstream_node_info.node_id); log_hint(_("primary node must be running before repmgrd can start")); PQfinish(local_conn); exit(ERR_DB_CONN); } /* 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, upstream_node_info.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")); } /* Log startup event */ if (startup_event_logged == false) { PQExpBufferData event_details; initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("witness monitoring connection to primary node \"%s\" (node 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); INSTR_TIME_SET_CURRENT(witness_sync_interval_start); upstream_node_info.node_status = NODE_STATUS_UP; while (true) { if (is_server_available(upstream_node_info.conninfo) == false) { if (upstream_node_info.node_status == NODE_STATUS_UP) { instr_time upstream_node_unreachable_start; INSTR_TIME_SET_CURRENT(upstream_node_unreachable_start); initPQExpBuffer(&event_details); upstream_node_info.node_status = NODE_STATUS_UNKNOWN; appendPQExpBuffer(&event_details, _("unable to connect to primary node \"%s\" (node 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); PQfinish(primary_conn); primary_conn = try_reconnect(&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); initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("reconnected to upstream node after %i seconds"), upstream_node_unreachable_elapsed); log_notice("%s", event_details.data); create_event_notification(upstream_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 (is_server_available(upstream_node_info.conninfo) == true) { primary_conn = establish_db_connection(upstream_node_info.conninfo, false); if (PQstatus(primary_conn) == CONNECTION_OK) { upstream_node_info.node_status = NODE_STATUS_UP; monitoring_state = MS_NORMAL; initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("reconnected to upstream node %i after %i seconds, resuming monitoring"), 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 */ NodeInfoListCell *cell; int follow_node_id = UNKNOWN_NODE_ID; get_active_sibling_node_records(local_conn, local_node_info.node_id, local_node_info.upstream_node_id, &standby_nodes); if (standby_nodes.node_count > 0) { log_debug("scanning %i node records to detect new primary...", standby_nodes.node_count); for (cell = standby_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; } cell->node_info->conn = establish_db_connection(cell->node_info->conninfo, false); if (PQstatus(cell->node_info->conn) != CONNECTION_OK) { log_debug("unable to connect to %i ... ", cell->node_info->node_id); continue; } if (get_recovery_type(cell->node_info->conn) == RECTYPE_PRIMARY) { follow_node_id = cell->node_info->node_id; PQfinish(cell->node_info->conn); cell->node_info->conn = NULL; break; } PQfinish(cell->node_info->conn); cell->node_info->conn = NULL; } if (follow_node_id != UNKNOWN_NODE_ID) { witness_follow_new_primary(follow_node_id); } } clear_node_info_list(&standby_nodes); } } loop: /* refresh repmgr.nodes after "witness_sync_interval" seconds */ { int witness_sync_interval_elapsed = calculate_elapsed(witness_sync_interval_start); if (witness_sync_interval_elapsed >= config_file_options.witness_sync_interval) { log_debug("synchronising witness node records"); witness_copy_node_records(primary_conn, local_conn); INSTR_TIME_SET_CURRENT(witness_sync_interval_start); } } /* 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\" (node ID: %i) monitoring primary node \"%s\" (node 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) { log_debug("SIGHUP received"); if (reload_config(&config_file_options)) { PQfinish(local_conn); local_conn = establish_db_connection(config_file_options.conninfo, true); if (*config_file_options.log_file) { FILE *fd; 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; } sleep(config_file_options.monitor_interval_secs); } return; } static bool do_primary_failover(void) { /* attempt to initiate voting process */ ElectionResult election_result = do_election(); /* TODO add pre-event notification here */ failover_state = FAILOVER_STATE_UNKNOWN; log_debug("election result: %s", _print_election_result(election_result)); if (election_result == ELECTION_CANCELLED) { log_notice(_("election cancelled")); return false; } else if (election_result == ELECTION_WON) { if (standby_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) { log_info(_("follower node awaiting notification from the candidate node")); failover_state = FAILOVER_STATE_WAITING_NEW_PRIMARY; } /* * node has decided it is a follower, so will await notification from the * candidate that it has promoted itself and can be followed */ if (failover_state == FAILOVER_STATE_WAITING_NEW_PRIMARY) { int new_primary_id = UNKNOWN_NODE_ID; /* TODO: rerun election if new primary doesn't appear after timeout */ /* either follow 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, &standby_nodes); } 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; PGconn *new_primary_conn; 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; initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("node %i is in manual failover mode and is now disconnected from streaming replication"), 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); PQfinish(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(&standby_nodes, local_node_info.node_id); /* we no longer care about our former siblings */ clear_node_info_list(&standby_nodes); /* pass control back down to start_monitoring() */ log_info(_("switching to primary monitoring mode")); failover_state = FAILOVER_STATE_NONE; return true; case FAILOVER_STATE_PRIMARY_REAPPEARED: /* * notify siblings that they should resume following the original * primary */ notify_followers(&standby_nodes, upstream_node_info.node_id); /* we no longer care about our former siblings */ clear_node_info_list(&standby_nodes); /* pass control back down to start_monitoring() */ log_info(_("resuming standby monitoring mode")); log_detail(_("original primary \"%s\" (node 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 standby monitoring mode")); log_detail(_("following new primary \"%s\" (node 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 standby monitoring mode")); log_detail(_("following original primary \"%s\" (node id: %i)"), upstream_node_info.node_name, upstream_node_info.node_id); failover_state = FAILOVER_STATE_NONE; return true; case FAILOVER_STATE_PROMOTION_FAILED: monitoring_state = MS_DEGRADED; INSTR_TIME_SET_CURRENT(degraded_monitoring_start); return false; 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; 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); return false; case FAILOVER_STATE_NO_NEW_PRIMARY: case FAILOVER_STATE_WAITING_NEW_PRIMARY: /* pass control back down to start_monitoring() */ return false; case FAILOVER_STATE_NODE_NOTIFICATION_ERROR: case FAILOVER_STATE_LOCAL_NODE_FAILURE: case FAILOVER_STATE_UNKNOWN: case FAILOVER_STATE_NONE: return false; } /* should never reach here */ return false; } static void update_monitoring_history(void) { ReplInfo replication_info = T_REPLINFO_INTIALIZER; 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 || PQstatus(local_conn) != CONNECTION_OK) return; if (get_replication_info(local_conn, &replication_info) == false) { log_warning(_("unable to retrieve replication status information")); return; } /* * 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_current_wal_lsn(primary_conn); if (primary_last_wal_location == InvalidXLogRecPtr) { log_warning(_("unable to retrieve primary's current LSN")); return; } /* 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); } else { /* * This should never happen, but in case it does set replication lag * to zero */ log_warning("primary xlog (%X/%X) location appears less than 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); } /* * do_upstream_standby_failover() * * Attach cascaded standby to primary * * Currently we will try to attach to the cluster primary, as "repmgr * standby follow" doesn't support attaching to another node. * * If this becomes supported, it might be worth providing a selection * of reconnection strategies as different behaviour might be desirable * in different situations; * or maybe the option not to reconnect might be required? */ static bool do_upstream_standby_failover(void) { PQExpBufferData event_details; t_node_info primary_node_info = T_NODE_INFO_INITIALIZER; RecordStatus record_status = RECORD_NOT_FOUND; RecoveryType primary_type = RECTYPE_UNKNOWN; int r; char parsed_follow_command[MAXPGPATH] = ""; PQfinish(upstream_conn); upstream_conn = NULL; 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); PQfinish(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) { log_error(_("last known primary\"%s\" (ID: %i) is in recovery, not following"), primary_node_info.node_name, primary_node_info.node_id); PQfinish(primary_conn); monitoring_state = MS_DEGRADED; INSTR_TIME_SET_CURRENT(degraded_monitoring_start); return false; } /* Close the connection to this server */ PQfinish(local_conn); local_conn = NULL; initPQExpBuffer(&event_details); 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); r = system(parsed_follow_command); if (r != 0) { 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); } /* reconnect to local node */ local_conn = establish_db_connection(config_file_options.conninfo, false); /* refresh shared memory settings which will have been zapped by the restart */ repmgrd_set_local_node_id(local_conn, config_file_options.node_id); if (update_node_record_set_upstream(primary_conn, local_node_info.node_id, primary_node_info.node_id) == false) { appendPQExpBuffer(&event_details, _("unable to set node %i's new upstream ID to %i"), 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; } appendPQExpBuffer(&event_details, _("node %i is now following primary node %i"), local_node_info.node_id, 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; } static FailoverState promote_self(void) { PQExpBufferData event_details; 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); } 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 either of this command has been established already */ promote_command = config_file_options.promote_command; log_debug("promote command is:\n \"%s\"", promote_command); if (log_type == REPMGR_STDERR && *config_file_options.log_file) { fflush(stderr); } r = system(promote_command); /* connection should stay up, but check just in case */ if (PQstatus(local_conn) != CONNECTION_OK) { 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")); /* XXX handle this */ return FAILOVER_STATE_LOCAL_NODE_FAILURE; } } if (r != 0) { int primary_node_id; upstream_conn = get_primary_connection(local_conn, &primary_node_id, NULL); if (PQstatus(upstream_conn) == CONNECTION_OK && primary_node_id == failed_primary.node_id) { log_notice(_("original primary (id: %i) reappeared before this standby was promoted - no action taken"), failed_primary.node_id); initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("original primary \"%s\" (node 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; } log_error(_("promote command failed")); initPQExpBuffer(&event_details); create_event_notification( NULL, &config_file_options, local_node_info.node_id, "repmgrd_promote_error", true, event_details.data); termPQExpBuffer(&event_details); return FAILOVER_STATE_PROMOTION_FAILED; } /* bump the electoral term */ increment_current_term(local_conn); initPQExpBuffer(&event_details); /* 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 */ appendPQExpBuffer(&event_details, _("node %i promoted to primary; old primary %i marked as failed"), local_node_info.node_id, failed_primary.node_id); /* local_conn is now the primary connection */ create_event_notification(local_conn, &config_file_options, local_node_info.node_id, "repmgrd_failover_promote", true, event_details.data); 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_verbose(LOG_NOTICE, "%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_debug("reconnecting to node %i... ", cell->node_info->node_id); cell->node_info->conn = establish_db_connection(cell->node_info->conninfo, false); } if (PQstatus(cell->node_info->conn) != CONNECTION_OK) { log_debug("unable to reconnect to %i ... ", cell->node_info->node_id); continue; } log_verbose(LOG_NOTICE, "notifying node %i to follow node %i", 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", 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] = ""; PQExpBufferData event_details; int 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; 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; } 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 is not in recovery")); PQfinish(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 */ PQfinish(local_conn); local_conn = NULL; /* * 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 follow action 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 use to * follow it after it's successfully promoted itself, so that very * likely won't be the reason for the failure. * * * TODO: check the new primary too - we could have a split-brain * situation where the old primary reappeared just after the new one * promoted itself. */ old_primary_conn = establish_db_connection(failed_primary.conninfo, false); if (PQstatus(old_primary_conn) == CONNECTION_OK) { /* XXX add event notifications */ RecoveryType upstream_recovery_type = get_recovery_type(old_primary_conn); PQfinish(old_primary_conn); if (upstream_recovery_type == RECTYPE_PRIMARY) { log_notice(_("original primary reappeared - no action taken")); return FAILOVER_STATE_PRIMARY_REAPPEARED; } } 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; } local_conn = establish_db_connection(local_node_info.conninfo, false); /* refresh shared memory settings which will have been zapped by the restart */ repmgrd_set_local_node_id(local_conn, config_file_options.node_id); initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("node %i now following new upstream node %i"), local_node_info.node_id, 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) { PQExpBufferData event_details; 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); if (primary_recovery_type == RECTYPE_PRIMARY) { new_primary_ok = true; } else { new_primary_ok = false; log_warning(_("new primary is not in recovery")); PQfinish(upstream_conn); } } if (new_primary_ok == false) { 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 found for node %i"), local_node_info.node_id); return FAILOVER_STATE_FOLLOW_FAIL; } initPQExpBuffer(&event_details); appendPQExpBuffer(&event_details, _("witness node %i now following new primary node %i"), local_node_info.node_id, 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"; } /* should never reach here */ return "UNKNOWN"; } /* * NB: this function sets standby_nodes; caller (do_primary_failover) * expects to be able to read this list */ static ElectionResult do_election(void) { int electoral_term = -1; /* we're visible */ int visible_nodes = 1; NodeInfoListCell *cell = NULL; t_node_info *candidate_node = NULL; /* * 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; 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")); return ELECTION_LOST; } /* node priority is set to zero - don't ever become a candidate */ 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_NOT_CANDIDATE; } /* 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, &standby_nodes); log_debug("do_election(): primary location is %s", upstream_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 (standby_nodes.node_count == 0) { if (strncmp(upstream_node_info.location, local_node_info.location, MAXLEN) == 0) { log_debug("no other 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; } } /* get our lsn */ local_node_info.last_wal_receive_lsn = get_last_wal_receive_location(local_conn); log_debug("our 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; for (cell = standby_nodes.head; cell; cell = cell->next) { /* 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) { continue; } cell->node_info->node_status = NODE_STATUS_UP; 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) { primary_location_seen = true; } } /* 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; } /* XXX don't check 0-priority nodes */ /* get node's LSN - if "higher" than current winner, current node is candidate */ cell->node_info->last_wal_receive_lsn = get_last_wal_receive_location(cell->node_info->conn); log_verbose(LOG_DEBUG, "node %i's last receive LSN is: %X/%X", 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_verbose(LOG_DEBUG, "node %i is ahead of current candidate %i", cell->node_info->node_id, 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_verbose(LOG_DEBUG, "node %i has same LSN as current candidate %i", cell->node_info->node_id, candidate_node->node_id); if (cell->node_info->priority > candidate_node->priority) { log_verbose(LOG_DEBUG, "node %i has higher priority (%i) than current candidate %i (%i)", cell->node_info->node_id, cell->node_info->priority, 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_verbose(LOG_DEBUG, "node %i has same priority but lower node_id than current candidate %i", cell->node_info->node_id, candidate_node->node_id); candidate_node = cell->node_info; } } else { log_verbose(LOG_DEBUG, "node %i has lower priority (%i) than current candidate %i (%i)", cell->node_info->node_id, cell->node_info->priority, 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(); return ELECTION_CANCELLED; } log_debug("visible nodes: %i; total nodes: %i", visible_nodes, standby_nodes.node_count); if (visible_nodes <= (standby_nodes.node_count / 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_debug("promotion candidate is %i", candidate_node->node_id); if (candidate_node->node_id == local_node_info.node_id) 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 witness monitoring mode")); log_detail(_("original primary \"%s\" (node 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 standby monitoring mode")); log_detail(_("following new primary \"%s\" (node 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 witness monitoring mode")); log_detail(_("following original primary \"%s\" (node 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")); 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 %i lost"), node_info->node_id); } if (PQstatus(*conn) != CONNECTION_OK) { log_info(_("attempting to reconnect to node \"%s\" (ID: %i)"), node_info->node_name, node_info->node_id); *conn = establish_db_connection(node_info->conninfo, false); if (PQstatus(*conn) != CONNECTION_OK) { *conn = NULL; log_warning(_("reconnection to node \"%s\" (ID: %i) failed"), node_info->node_name, node_info->node_id); } else { log_info(_("reconnected to node \"%s\" (ID: %i)"), node_info->node_name, node_info->node_id); } } } 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_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 "ODE_NOTIFICATION_ERROR"; } /* should never reach here */ return "UNKNOWN_FAILOVER_STATE"; } void close_connections_physical() { if (PQstatus(primary_conn) == CONNECTION_OK) { /* cancel any pending queries to the primary */ if (PQisBusy(primary_conn) == 1) cancel_query(primary_conn, config_file_options.async_query_timeout); PQfinish(primary_conn); primary_conn = NULL; } if (upstream_conn != NULL && PQstatus(upstream_conn) == CONNECTION_OK) { PQfinish(upstream_conn); upstream_conn = NULL; } } repmgr-4.0.3/repmgrd-physical.h000066400000000000000000000017651324071732600164530ustar00rootroot00000000000000/* * repmgrd-physical.h * Copyright (c) 2ndQuadrant, 2010-2018 * * 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(void); void monitor_streaming_primary(void); void monitor_streaming_standby(void); void monitor_streaming_witness(void); void close_connections_physical(void); #endif /* _REPMGRD_PHYSICAL_H_ */ repmgr-4.0.3/repmgrd.c000066400000000000000000000414271324071732600146330ustar00rootroot00000000000000/* * repmgrd.c - Replication manager daemon * * Copyright (c) 2ndQuadrant, 2010-2018 * * 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 "repmgrd-bdr.h" #include "configfile.h" #include "voting.h" #define OPT_HELP 1 static char *config_file = NULL; static bool verbose = false; static char *pid_file = NULL; static bool daemonize = false; t_configuration_options config_file_options = T_CONFIGURATION_OPTIONS_INITIALIZER; 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; static void close_connections(void); void (*_close_connections) (void) = NULL; /* * 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); static void handle_sigint(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; 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", no_argument, NULL, 'd'}, {"pid-file", required_argument, NULL, 'p'}, /* 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]); srand(time(NULL)); /* 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); } while ((c = getopt_long(argc, argv, "?Vf:L:vdp:m", 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 'p': pid_file = optarg; 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); } 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 conifguration file is available, or it can't be parsed * parse_config() will abort anyway, with an appropriate message. */ load_config(config_file, verbose, false, &config_file_options, argv[0]); /* Some configuration file items can be overriden 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()); 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); /* * store the server version number - we'll need this to generate * version-dependent queries etc. */ server_version_num = get_server_version(local_conn, NULL); /* * 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. */ /* Check "repmgr" the extension is installed */ extension_status = get_repmgr_extension_status(local_conn); if (extension_status != REPMGR_INSTALLED) { /* this is unlikely to happen */ if (extension_status == REPMGR_UNKNOWN) { log_error(_("unable to determine status of \"repmgr\" extension")); log_detail("%s", PQerrorMessage(local_conn)); PQfinish(local_conn); exit(ERR_DB_QUERY); } 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")); PQfinish(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); if (record_status != RECORD_FOUND) { log_error(_("no metadata record found for this node - terminating")); log_hint(_("check that 'repmgr (primary|standby) register' was executed for this node")); PQfinish(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\"")); PQfinish(local_conn); terminate(ERR_BAD_CONFIG); } } if (config_file_options.replication_type == REPLICATION_TYPE_BDR) { log_debug("node id is %i", local_node_info.node_id); do_bdr_node_check(); } else { _close_connections = close_connections_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(); } if (daemonize == true) { daemonize_process(); } if (pid_file != NULL) { check_and_create_pid_file(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); while (true) { switch (local_node_info.type) { case PRIMARY: monitor_streaming_primary(); break; case STANDBY: monitor_streaming_standby(); break; case WITNESS: monitor_streaming_witness(); case BDR: monitor_bdr(); return; 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():\n %s"), strerror(errno)); exit(ERR_SYS_FAILURE); break; case 0: /* create independent session ID */ pid = setsid(); if (pid == (pid_t) -1) { log_error(_("error in setsid():\n %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 in fork():\n %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 static void handle_sigint(SIGNAL_ARGS) { terminate(SUCCESS); } /* 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); pqsignal(SIGINT, handle_sigint); pqsignal(SIGTERM, handle_sigint); } #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(_("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(_("General configuration options:\n")); printf(_(" -d, --daemonize detach process from foreground\n")); printf(_(" -p, --pid-file=PATH write a PID file\n")); puts(""); printf(_("%s monitors a cluster of servers and optionally performs failover.\n"), progname()); } PGconn * try_reconnect(t_node_info *node_info) { PGconn *conn; int i; int max_attempts = config_file_options.reconnect_attempts; 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(node_info->conninfo) == true) { log_notice(_("node has recovered, reconnecting")); /* * XXX we should also handle the case where node is pingable but * connection denied due to connection exhaustion - fall back to * degraded monitoring? - make that configurable */ conn = establish_db_connection(node_info->conninfo, false); if (PQstatus(conn) == CONNECTION_OK) { node_info->node_status = NODE_STATUS_UP; return conn; } PQfinish(conn); log_notice(_("unable to reconnect to node")); } 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; return NULL; } 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"; } static void close_connections() { if (_close_connections != NULL) _close_connections(); if (local_conn != NULL && PQstatus(local_conn) == CONNECTION_OK) { PQfinish(local_conn); local_conn = NULL; } } void terminate(int retval) { close_connections(); logger_shutdown(); if (pid_file) { unlink(pid_file); } log_info(_("%s terminating..."), progname()); exit(retval); } repmgr-4.0.3/repmgrd.h000066400000000000000000000013031324071732600146250ustar00rootroot00000000000000/* * repmgrd.h * Copyright (c) 2ndQuadrant, 2010-2018 */ #ifndef _REPMGRD_H_ #define _REPMGRD_H_ #include #include "portability/instr_time.h" extern volatile sig_atomic_t got_SIGHUP; extern MonitoringState monitoring_state; extern instr_time degraded_monitoring_start; extern t_configuration_options config_file_options; extern t_node_info local_node_info; extern PGconn *local_conn; extern bool startup_event_logged; PGconn *try_reconnect(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-4.0.3/scripts/000077500000000000000000000000001324071732600145065ustar00rootroot00000000000000repmgr-4.0.3/scripts/bdr-pgbouncer.sh000066400000000000000000000045671324071732600176070ustar00rootroot00000000000000#!/usr/bin/env bash set -u set -e # Process parameters passed to script # ----------------------------------- # # This assumes the repmgr "event_notification_command" is defined like this: # # event_notification_command='/path/to/bdr-pgbouncer.sh %n %e %s "%c" "%a" >> /tmp/bdr-failover.log 2>&1' # # Adjust as appropriate. NODE_ID=$1 EVENT_TYPE=$2 SUCCESS=$3 NEXT_CONNINFO=$4 NEXT_NODE_NAME=$5 if [ "$EVENT_TYPE" != "bdr_failover" ]; then echo "unable to handle event type '$EVENT_TYPE'" exit fi # Define database name here # ------------------------- # # Note: this assumes the BDR-enabled database has the same name on # both hosts BDR_DBNAME=bdr_db # Define PgBouncer hosts here # --------------------------- PGBOUNCER_HOSTS="host1 host2" PGBOUNCER_PORTS=(6432 6432) PGBOUNCER_DATABASE_INI=(/path/to/pgbouncer.database.ini /path/to/pgbouncer.database.ini) # Define local host info here # --------------------------- THIS_HOST="host1" THIS_PGBOUNCER_PORT="6432" THIS_DB_PORT="5432" # Pause all pgbouncer nodes to minimize impact on clients # ------------------------------------------------------- i=0 for HOST in $PGBOUNCER_HOSTS do PORT="${PGBOUNCER_PORTS[$i]}" psql -tc "pause" -h $HOST -p $PORT -U postgres pgbouncer i=$((i+1)) done # Copy pgbouncer database ini file to all nodes and restart pgbouncer # ------------------------------------------------------------------- i=0 THIS_HOSTPORT="$THIS_HOST$THIS_PGBOUNCER_PORT" PGBOUNCER_DATABASE_INI_NEW="/tmp/pgbouncer.database.ini.new" for HOST in $PGBOUNCER_HOSTS do PORT="${PGBOUNCER_PORTS[$i]}" # Recreate the pgbouncer config file # ---------------------------------- echo -e "[databases]\n" > $PGBOUNCER_DATABASE_INI_NEW echo -e "$BDR_DBNAME= $NEXT_CONNINFO application_name=pgbouncer_$PORT" >> $PGBOUNCER_DATABASE_INI_NEW # Copy file to host # ----------------- CONFIG="${PGBOUNCER_DATABASE_INI[$i]}" if [ "$HOST$PORT" != "$THIS_HOSTPORT" ]; then rsync $PGBOUNCER_DATABASE_INI_NEW $HOST:$CONFIG else cp $PGBOUNCER_DATABASE_INI_NEW $CONFIG fi # Reload and resume PgBouncer # --------------------------- psql -tc "reload" -h $HOST -p $PORT -U postgres pgbouncer psql -tc "resume" -h $HOST -p $PORT -U postgres pgbouncer i=$((i+1)) done # Clean up generated file rm $PGBOUNCER_DATABASE_INI_NEW echo "Reconfiguration of pgbouncer complete" repmgr-4.0.3/sql/000077500000000000000000000000001324071732600136165ustar00rootroot00000000000000repmgr-4.0.3/sql/.gitignore000066400000000000000000000000601324071732600156020ustar00rootroot00000000000000# Might be created by repmgr3 /repmgr_funcs.sql repmgr-4.0.3/sql/repmgr_extension.sql000066400000000000000000000015161324071732600177320ustar00rootroot00000000000000-- 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.am_bdr_failover_handler(-1); SELECT repmgr.am_bdr_failover_handler(NULL); 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(); SELECT repmgr.unset_bdr_failover_handler(); repmgr-4.0.3/strutil.c000066400000000000000000000213061324071732600146730ustar00rootroot00000000000000/* * strutil.c * * Copyright (c) 2ndQuadrant, 2010-2018 * * 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 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') { appendPQExpBuffer(where_clause, " WHERE "); } else { appendPQExpBuffer(where_clause, " AND "); } appendPQExpBuffer(where_clause, "%s", 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; 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_format(item_list, key, "%s", value); return; } void key_value_list_set_format(KeyValueList *item_list, const char *key, const char *value,...) { KeyValueListCell *cell = NULL; va_list arglist; int keylen = 0; cell = (KeyValueListCell *) pg_malloc0(sizeof(KeyValueListCell)); if (cell == NULL) { log_error(_("unable to allocate memory; terminating.")); exit(ERR_BAD_CONFIG); } keylen = strlen(key); cell->key = pg_malloc0(keylen + 1); cell->value = pg_malloc0(MAXLEN); cell->output_mode = OM_NOT_SET; strncpy(cell->key, key, keylen); va_start(arglist, value); (void) xvsnprintf(cell->value, MAXLEN, value, arglist); va_end(arglist); 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; } repmgr-4.0.3/strutil.h000066400000000000000000000100301324071732600146700ustar00rootroot00000000000000/* * strutil.h * Copyright (c) 2ndQuadrant, 2010-2018 * * 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_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); #endif /* _STRUTIL_H_ */ repmgr-4.0.3/voting.h000066400000000000000000000015721324071732600145030ustar00rootroot00000000000000/* * voting.h * Copyright (c) 2ndQuadrant, 2010-2018 * * 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_ */