mirror of
https://github.com/EnterpriseDB/repmgr.git
synced 2026-03-23 15:16:29 +00:00
to find the master. This was a micro optimization based on the fact that all commands that needed to detect the master were executed from the standby but now that we have CLUSTER level commands that is not true anymore
1856 lines
55 KiB
C
1856 lines
55 KiB
C
/*
|
|
* repmgr.c - Command interpreter for the repmgr
|
|
* Copyright (C) 2ndQuadrant, 2010-2011
|
|
*
|
|
* This module is a command-line utility to easily setup a cluster of
|
|
* hot standby servers for an HA environment
|
|
*
|
|
* Commands implemented are.
|
|
* MASTER REGISTER, STANDBY REGISTER, STANDBY CLONE, STANDBY FOLLOW,
|
|
* STANDBY PROMOTE, CLUSTER SHOW, CLUSTER CLEANUP
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "repmgr.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#include "log.h"
|
|
#include "config.h"
|
|
#include "check_dir.h"
|
|
#include "strutil.h"
|
|
|
|
#define RECOVERY_FILE "recovery.conf"
|
|
#define RECOVERY_DONE_FILE "recovery.done"
|
|
|
|
#define NO_ACTION 0 /* Not a real action, just to initialize */
|
|
#define MASTER_REGISTER 1
|
|
#define STANDBY_REGISTER 2
|
|
#define STANDBY_CLONE 3
|
|
#define STANDBY_PROMOTE 4
|
|
#define STANDBY_FOLLOW 5
|
|
#define CLUSTER_SHOW 6
|
|
#define CLUSTER_CLEANUP 7
|
|
|
|
static void help(const char *progname);
|
|
static bool create_recovery_file(const char *data_dir, char *master_conninfo);
|
|
static int test_ssh_connection(char *host, char *remote_user);
|
|
static int copy_remote_files(char *host, char *remote_user, char *remote_path,
|
|
char *local_path, bool is_directory);
|
|
static bool check_parameters_for_action(const int action);
|
|
static void write_primary_conninfo(char* line);
|
|
|
|
static void do_master_register(void);
|
|
static void do_standby_register(void);
|
|
static void do_standby_clone(void);
|
|
static void do_standby_promote(void);
|
|
static void do_standby_follow(void);
|
|
static void do_cluster_show(void);
|
|
static void do_cluster_cleanup(void);
|
|
|
|
static void usage(void);
|
|
|
|
/* Global variables */
|
|
static const char *progname;
|
|
static const char *keywords[6];
|
|
static const char *values[6];
|
|
char repmgr_schema[MAXLEN];
|
|
bool need_a_node = true;
|
|
|
|
/* XXX This should be mapped into a command line option */
|
|
bool require_password = false;
|
|
|
|
/* Initialization of runtime options */
|
|
t_runtime_options runtime_options = { "", "", "", "", "", "", DEFAULT_WAL_KEEP_SEGMENTS, false, false, false, "", 0 };
|
|
t_configuration_options options = { "", -1, "", "", "" };
|
|
|
|
static char *server_mode = NULL;
|
|
static char *server_cmd = NULL;
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
static struct option long_options[] =
|
|
{
|
|
{"dbname", required_argument, NULL, 'd'},
|
|
{"host", required_argument, NULL, 'h'},
|
|
{"port", required_argument, NULL, 'p'},
|
|
{"username", required_argument, NULL, 'U'},
|
|
{"dest-dir", required_argument, NULL, 'D'},
|
|
{"config-file", required_argument, NULL, 'f'},
|
|
{"remote-user", required_argument, NULL, 'R'},
|
|
{"wal-keep-segments", required_argument, NULL, 'w'},
|
|
{"keep-history", required_argument, NULL, 'k'},
|
|
{"force", no_argument, NULL, 'F'},
|
|
{"ignore-rsync-warning", no_argument, NULL, 'I'},
|
|
{"verbose", no_argument, NULL, 'v'},
|
|
{NULL, 0, NULL, 0}
|
|
};
|
|
|
|
int optindex;
|
|
int c;
|
|
int action = NO_ACTION;
|
|
|
|
progname = get_progname(argv[0]);
|
|
|
|
if (argc > 1)
|
|
{
|
|
if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
|
|
{
|
|
help(progname);
|
|
exit(SUCCESS);
|
|
}
|
|
if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
|
|
{
|
|
printf("%s (PostgreSQL) " PG_VERSION "\n", progname);
|
|
exit(SUCCESS);
|
|
}
|
|
}
|
|
|
|
|
|
while ((c = getopt_long(argc, argv, "d:h:p:U:D:f:R:w:k:F:I:v", long_options,
|
|
&optindex)) != -1)
|
|
{
|
|
switch (c)
|
|
{
|
|
case 'd':
|
|
strncpy(runtime_options.dbname, optarg, MAXLEN);
|
|
break;
|
|
case 'h':
|
|
strncpy(runtime_options.host, optarg, MAXLEN);
|
|
break;
|
|
case 'p':
|
|
if (atoi(optarg) > 0)
|
|
strncpy(runtime_options.masterport, optarg, MAXLEN);
|
|
break;
|
|
case 'U':
|
|
strncpy(runtime_options.username, optarg, MAXLEN);
|
|
break;
|
|
case 'D':
|
|
strncpy(runtime_options.dest_dir, optarg, MAXFILENAME);
|
|
break;
|
|
case 'f':
|
|
strncpy(runtime_options.config_file, optarg, MAXLEN);
|
|
break;
|
|
case 'R':
|
|
strncpy(runtime_options.remote_user, optarg, MAXLEN);
|
|
break;
|
|
case 'w':
|
|
if (atoi(optarg) > 0)
|
|
strncpy(runtime_options.wal_keep_segments, optarg, MAXLEN);
|
|
break;
|
|
case 'k':
|
|
if (atoi(optarg) > 0)
|
|
runtime_options.keep_history = atoi(optarg);
|
|
else
|
|
runtime_options.keep_history = 0;
|
|
break;
|
|
case 'F':
|
|
runtime_options.force = true;
|
|
break;
|
|
case 'I':
|
|
runtime_options.ignore_rsync_warn = true;
|
|
break;
|
|
case 'v':
|
|
runtime_options.verbose = true;
|
|
break;
|
|
default:
|
|
usage();
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Now we need to obtain the action, this comes in one of these forms:
|
|
* MASTER REGISTER |
|
|
* STANDBY {REGISTER | CLONE [node] | PROMOTE | FOLLOW [node]} |
|
|
* CLUSTER {SHOW | CLEANUP}
|
|
*
|
|
* the node part is optional, if we receive it then we shouldn't
|
|
* have received a -h option
|
|
*/
|
|
if (optind < argc)
|
|
{
|
|
server_mode = argv[optind++];
|
|
if (strcasecmp(server_mode, "STANDBY") != 0 && strcasecmp(server_mode, "MASTER") != 0 &&
|
|
strcasecmp(server_mode, "CLUSTER") != 0 )
|
|
{
|
|
usage();
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
}
|
|
|
|
if (optind < argc)
|
|
{
|
|
server_cmd = argv[optind++];
|
|
if (strcasecmp(server_cmd, "REGISTER") == 0)
|
|
{
|
|
/*
|
|
* we don't use this info in any other place so i will
|
|
* just execute the compare again instead of having an
|
|
* additional variable to hold a value that we will use
|
|
* no more
|
|
*/
|
|
if (strcasecmp(server_mode, "MASTER") == 0)
|
|
action = MASTER_REGISTER;
|
|
else if (strcasecmp(server_mode, "STANDBY") == 0)
|
|
action = STANDBY_REGISTER;
|
|
}
|
|
else if (strcasecmp(server_cmd, "CLONE") == 0)
|
|
action = STANDBY_CLONE;
|
|
else if (strcasecmp(server_cmd, "PROMOTE") == 0)
|
|
action = STANDBY_PROMOTE;
|
|
else if (strcasecmp(server_cmd, "FOLLOW") == 0)
|
|
action = STANDBY_FOLLOW;
|
|
else if (strcasecmp(server_mode, "CLUSTER") == 0)
|
|
{
|
|
if(strcasecmp(server_cmd, "SHOW") == 0)
|
|
action = CLUSTER_SHOW;
|
|
else if(strcasecmp(server_cmd, "CLEANUP") == 0)
|
|
action = CLUSTER_CLEANUP;
|
|
}
|
|
}
|
|
|
|
if (action == NO_ACTION)
|
|
{
|
|
usage();
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
/* For some actions we still can receive a last argument */
|
|
if (action == STANDBY_CLONE)
|
|
{
|
|
if (optind < argc)
|
|
{
|
|
if (runtime_options.host[0])
|
|
{
|
|
log_err(_("Conflicting parameters: you can't use -h while providing a node separately.\n"));
|
|
usage();
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
strncpy(runtime_options.host, argv[optind++], MAXLEN);
|
|
}
|
|
}
|
|
|
|
switch (optind < argc)
|
|
{
|
|
case 0:
|
|
break;
|
|
default:
|
|
log_err(_("%s: too many command-line arguments (first extra is \"%s\")\n"),
|
|
progname, argv[optind + 1]);
|
|
usage();
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
if (!check_parameters_for_action(action))
|
|
exit(ERR_BAD_CONFIG);
|
|
|
|
if (!runtime_options.dbname[0])
|
|
{
|
|
if (getenv("PGDATABASE"))
|
|
strncpy(runtime_options.dbname, getenv("PGDATABASE"), MAXLEN);
|
|
else if (getenv("PGUSER"))
|
|
strncpy(runtime_options.dbname, getenv("PGUSER"), MAXLEN);
|
|
else
|
|
strncpy(runtime_options.dbname, DEFAULT_DBNAME, MAXLEN);
|
|
}
|
|
|
|
/* Read the configuration file, normally repmgr.conf */
|
|
if (!runtime_options.config_file[0])
|
|
strncpy(runtime_options.config_file, DEFAULT_CONFIG_FILE, MAXLEN);
|
|
|
|
if (runtime_options.verbose)
|
|
printf(_("Opening configuration file: %s\n"), runtime_options.config_file);
|
|
|
|
parse_config(runtime_options.config_file, &options);
|
|
|
|
keywords[2] = "user";
|
|
values[2] = (runtime_options.username[0]) ? runtime_options.username : NULL;
|
|
keywords[3] = "dbname";
|
|
values[3] = runtime_options.dbname;
|
|
keywords[4] = "application_name";
|
|
values[4] = (char *) progname;
|
|
keywords[5] = NULL;
|
|
values[5] = NULL;
|
|
|
|
/*
|
|
* Initialize the logger. If verbose command line parameter was
|
|
* input, make sure that the log level is at least INFO. This
|
|
* is mainly useful for STANDBY CLONE. That doesn't require a
|
|
* configuration file where a logging level might be specified
|
|
* at, but it often requires detailed logging to troubleshoot
|
|
* problems.
|
|
*/
|
|
logger_init(progname, options.loglevel, options.logfacility);
|
|
if (runtime_options.verbose)
|
|
logger_min_verbose(LOG_INFO);
|
|
|
|
/*
|
|
* Node configuration information is not needed for all actions,
|
|
* with STANDBY CLONE being the main exception.
|
|
*/
|
|
if (need_a_node)
|
|
{
|
|
if (options.node == -1)
|
|
{
|
|
log_err("Node information is missing. "
|
|
"Check the configuration file.\n");
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
}
|
|
|
|
/* Prepare the repmgr schema variable */
|
|
snprintf(repmgr_schema, MAXLEN, "%s%s", DEFAULT_REPMGR_SCHEMA_PREFIX, options.cluster_name);
|
|
|
|
switch (action)
|
|
{
|
|
case MASTER_REGISTER:
|
|
do_master_register();
|
|
break;
|
|
case STANDBY_REGISTER:
|
|
do_standby_register();
|
|
break;
|
|
case STANDBY_CLONE:
|
|
do_standby_clone();
|
|
break;
|
|
case STANDBY_PROMOTE:
|
|
do_standby_promote();
|
|
break;
|
|
case STANDBY_FOLLOW:
|
|
do_standby_follow();
|
|
break;
|
|
case CLUSTER_SHOW:
|
|
do_cluster_show();
|
|
break;
|
|
case CLUSTER_CLEANUP:
|
|
do_cluster_cleanup();
|
|
break;
|
|
default:
|
|
usage();
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
logger_shutdown();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
do_cluster_show(void)
|
|
{
|
|
PGconn *conn;
|
|
PGconn *node_conn = NULL;
|
|
PGresult *res;
|
|
char sqlquery[QUERY_STR_LEN];
|
|
char node_role[MAXLEN];
|
|
int i;
|
|
|
|
/* We need to connect to check configuration */
|
|
log_info(_("%s connecting to database\n"), progname);
|
|
conn = establishDBConnection(options.conninfo, true);
|
|
|
|
sqlquery_snprintf(sqlquery, "SELECT conninfo FROM %s.repl_nodes;", repmgr_schema);
|
|
log_debug("cluster show: %s\n", sqlquery);
|
|
res = PQexec(conn, sqlquery);
|
|
|
|
if (PQresultStatus(res) != PGRES_TUPLES_OK)
|
|
{
|
|
log_err(_("Can't get nodes informations, have you regitered them?\n%s\n"), PQerrorMessage(conn));
|
|
PQclear(res);
|
|
PQfinish(conn);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
printf("Role | Connection String \n");
|
|
for (i = 0; i < PQntuples(res); i++)
|
|
{
|
|
node_conn = establishDBConnection(PQgetvalue(res, i, 0), false);
|
|
if (PQstatus(node_conn) != CONNECTION_OK)
|
|
strcpy(node_role, " FAILED");
|
|
else if (is_standby(node_conn))
|
|
strcpy(node_role, " standby");
|
|
else
|
|
strcpy(node_role, "* master");
|
|
|
|
printf("%-10s", node_role);
|
|
printf("| %s\n", PQgetvalue(res, i, 0));
|
|
|
|
PQfinish(node_conn);
|
|
}
|
|
PQclear(res);
|
|
PQfinish(conn);
|
|
}
|
|
|
|
|
|
static void
|
|
do_cluster_cleanup(void)
|
|
{
|
|
int master_id;
|
|
PGconn *conn;
|
|
PGconn *master_conn;
|
|
PGresult *res;
|
|
char sqlquery[QUERY_STR_LEN];
|
|
|
|
/* I need a connection to my local db to know what node is the master */
|
|
log_info(_("%s connecting to database\n"), progname);
|
|
conn = establishDBConnection(options.conninfo, true);
|
|
|
|
/* check if there is a master in this cluster */
|
|
log_info(_("%s connecting to master database\n"), progname);
|
|
master_conn = getMasterConnection(conn, options.cluster_name,
|
|
&master_id, NULL);
|
|
if (!master_conn)
|
|
{
|
|
log_err(_("cluster cleanup: cannot connect to master\n"));
|
|
PQfinish(conn);
|
|
exit(ERR_DB_CON);
|
|
}
|
|
|
|
/* I don't need a local connection anymore */
|
|
PQfinish(conn);
|
|
|
|
if (runtime_options.keep_history > 0)
|
|
{
|
|
sqlquery_snprintf(sqlquery, "DELETE FROM %s.repl_monitor "
|
|
" WHERE age(now(), last_monitor_time) >= '%d days'::interval;",
|
|
repmgr_schema, runtime_options.keep_history);
|
|
}
|
|
else
|
|
{
|
|
sqlquery_snprintf(sqlquery, "TRUNCATE TABLE %s.repl_monitor;", repmgr_schema);
|
|
}
|
|
|
|
log_debug("cluster cleanup: %s\n", sqlquery);
|
|
res = PQexec(master_conn, sqlquery);
|
|
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
|
{
|
|
log_err(_("cluster cleanup: Couldn't clean history\n%s\n"), PQerrorMessage(master_conn));
|
|
PQclear(res);
|
|
PQfinish(master_conn);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
PQclear(res);
|
|
|
|
/* Let's VACUUM the table to avoid autovacuum to be launched in an unexpected hour */
|
|
sqlquery_snprintf(sqlquery, "VACUUM %s.repl_monitor;", repmgr_schema);
|
|
log_debug("cluster cleanup: %s\n", sqlquery);
|
|
res = PQexec(master_conn, sqlquery);
|
|
|
|
/* XXX There is any need to check this VACUUM happens without problems? */
|
|
|
|
PQclear(res);
|
|
PQfinish(master_conn);
|
|
}
|
|
|
|
|
|
static void
|
|
do_master_register(void)
|
|
{
|
|
PGconn *conn;
|
|
PGresult *res;
|
|
char sqlquery[QUERY_STR_LEN];
|
|
|
|
bool schema_exists = false;
|
|
char schema_quoted[MAXLEN];
|
|
char master_version[MAXVERSIONSTR];
|
|
|
|
conn = establishDBConnection(options.conninfo, true);
|
|
|
|
/* master should be v9 or better */
|
|
log_info(_("%s connecting to master database\n"), progname);
|
|
pg_version(conn, master_version);
|
|
if (strcmp(master_version, "") == 0)
|
|
{
|
|
PQfinish(conn);
|
|
log_err( _("%s needs master to be PostgreSQL 9.0 or better\n"), progname);
|
|
return;
|
|
}
|
|
|
|
/* Check we are a master */
|
|
log_info(_("%s connected to master, checking its state\n"), progname);
|
|
if (is_standby(conn))
|
|
{
|
|
log_err(_("Trying to register a standby node as a master\n"));
|
|
PQfinish(conn);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
/* Assemble a quoted schema name
|
|
* XXX This is not currently used due to a merge conflict, but
|
|
* probably should be */
|
|
if (false)
|
|
{
|
|
char *identifier = PQescapeIdentifier(conn, repmgr_schema,
|
|
strlen(repmgr_schema));
|
|
|
|
maxlen_snprintf(schema_quoted, "%s", identifier);
|
|
PQfreemem(identifier);
|
|
}
|
|
|
|
/* Check if there is a schema for this cluster */
|
|
sqlquery_snprintf(sqlquery,
|
|
"SELECT 1 FROM pg_namespace "
|
|
"WHERE nspname = '%s'", repmgr_schema);
|
|
log_debug("master register: %s\n", sqlquery);
|
|
res = PQexec(conn, sqlquery);
|
|
if (PQresultStatus(res) != PGRES_TUPLES_OK)
|
|
{
|
|
log_err(_("Can't get info about schemas: %s\n"), PQerrorMessage(conn));
|
|
PQclear(res);
|
|
PQfinish(conn);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
if (PQntuples(res) > 0) /* schema exists */
|
|
{
|
|
if (!runtime_options.force) /* and we are not forcing so error */
|
|
{
|
|
log_notice(_("Schema %s already exists.\n"), repmgr_schema);
|
|
PQclear(res);
|
|
PQfinish(conn);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
schema_exists = true;
|
|
}
|
|
PQclear(res);
|
|
|
|
if (!schema_exists)
|
|
{
|
|
log_info("master register: creating database objects inside the %s schema\n", repmgr_schema);
|
|
|
|
/* ok, create the schema */
|
|
sqlquery_snprintf(sqlquery, "CREATE SCHEMA %s", repmgr_schema);
|
|
log_debug("master register: %s\n", sqlquery);
|
|
if (!PQexec(conn, sqlquery))
|
|
{
|
|
log_err(_("Cannot create the schema %s: %s\n"),
|
|
repmgr_schema, PQerrorMessage(conn));
|
|
PQfinish(conn);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
/* ... the tables */
|
|
sqlquery_snprintf(sqlquery, "CREATE TABLE %s.repl_nodes ( "
|
|
" id integer primary key, "
|
|
" cluster text not null, "
|
|
" conninfo text not null)", repmgr_schema);
|
|
log_debug("master register: %s\n", sqlquery);
|
|
if (!PQexec(conn, sqlquery))
|
|
{
|
|
log_err(_("Cannot create the table %s.repl_nodes: %s\n"),
|
|
repmgr_schema, PQerrorMessage(conn));
|
|
PQfinish(conn);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
sqlquery_snprintf(sqlquery, "CREATE TABLE %s.repl_monitor ( "
|
|
" primary_node INTEGER NOT NULL, "
|
|
" standby_node INTEGER NOT NULL, "
|
|
" last_monitor_time TIMESTAMP WITH TIME ZONE NOT NULL, "
|
|
" last_wal_primary_location TEXT NOT NULL, "
|
|
" last_wal_standby_location TEXT NOT NULL, "
|
|
" replication_lag BIGINT NOT NULL, "
|
|
" apply_lag BIGINT NOT NULL) ", repmgr_schema);
|
|
log_debug("master register: %s\n", sqlquery);
|
|
if (!PQexec(conn, sqlquery))
|
|
{
|
|
log_err(_("Cannot create the table %s.repl_monitor: %s\n"),
|
|
repmgr_schema, PQerrorMessage(conn));
|
|
PQfinish(conn);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
/* and the view */
|
|
sqlquery_snprintf(sqlquery, "CREATE VIEW %s.repl_status AS "
|
|
" SELECT primary_node, standby_node, last_monitor_time, last_wal_primary_location, "
|
|
" last_wal_standby_location, pg_size_pretty(replication_lag) replication_lag, "
|
|
" pg_size_pretty(apply_lag) apply_lag, age(now(), last_monitor_time) AS time_lag "
|
|
" FROM %s.repl_monitor "
|
|
" WHERE (standby_node, last_monitor_time) IN (SELECT standby_node, MAX(last_monitor_time) "
|
|
" FROM %s.repl_monitor GROUP BY 1)",
|
|
repmgr_schema, repmgr_schema, repmgr_schema);
|
|
log_debug("master register: %s\n", sqlquery);
|
|
if (!PQexec(conn, sqlquery))
|
|
{
|
|
log_err(_("Cannot create the view %s.repl_status: %s\n"),
|
|
repmgr_schema, PQerrorMessage(conn));
|
|
PQfinish(conn);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
/* an index to improve performance of the view */
|
|
sqlquery_snprintf(sqlquery, "CREATE INDEX idx_repl_status_sort "
|
|
" ON %s.repl_monitor (last_monitor_time, standby_node) ",
|
|
repmgr_schema);
|
|
log_debug(_("master register: %s\n"), sqlquery);
|
|
if (!PQexec(conn, sqlquery))
|
|
{
|
|
log_err(_("Cannot indexing table %s.repl_monitor: %s\n"),
|
|
repmgr_schema, PQerrorMessage(conn));
|
|
PQfinish(conn);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PGconn *master_conn;
|
|
int id;
|
|
|
|
/* Ensure there isn't any other master already registered */
|
|
master_conn = getMasterConnection(conn, options.cluster_name, &id,NULL);
|
|
if (master_conn != NULL)
|
|
{
|
|
PQfinish(master_conn);
|
|
log_warning(_("There is a master already in cluster %s\n"), options.cluster_name);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
}
|
|
|
|
/* Now register the master */
|
|
if (runtime_options.force)
|
|
{
|
|
sqlquery_snprintf(sqlquery, "DELETE FROM %s.repl_nodes "
|
|
" WHERE id = %d",
|
|
repmgr_schema, options.node);
|
|
log_debug("master register: %s\n", sqlquery);
|
|
|
|
if (!PQexec(conn, sqlquery))
|
|
{
|
|
log_warning(_("Cannot delete node details, %s\n"),
|
|
PQerrorMessage(conn));
|
|
PQfinish(conn);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
}
|
|
|
|
sqlquery_snprintf(sqlquery, "INSERT INTO %s.repl_nodes "
|
|
"VALUES (%d, '%s', '%s')",
|
|
repmgr_schema, options.node, options.cluster_name, options.conninfo);
|
|
log_debug("master register: %s\n", sqlquery);
|
|
|
|
if (!PQexec(conn, sqlquery))
|
|
{
|
|
log_warning(_("Cannot insert node details, %s\n"),
|
|
PQerrorMessage(conn));
|
|
PQfinish(conn);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
PQfinish(conn);
|
|
log_notice(_("Master node correctly registered for cluster %s with id %d (conninfo: %s)\n"),
|
|
options.cluster_name, options.node, options.conninfo);
|
|
return;
|
|
}
|
|
|
|
|
|
static void
|
|
do_standby_register(void)
|
|
{
|
|
PGconn *conn;
|
|
PGconn *master_conn;
|
|
int master_id;
|
|
|
|
PGresult *res;
|
|
char sqlquery[QUERY_STR_LEN];
|
|
char schema_quoted[MAXLEN];
|
|
|
|
char master_version[MAXVERSIONSTR];
|
|
char standby_version[MAXVERSIONSTR];
|
|
|
|
/* XXX: A lot of copied code from do_master_register! Refactor */
|
|
|
|
log_info(_("%s connecting to standby database\n"), progname);
|
|
conn = establishDBConnection(options.conninfo, true);
|
|
|
|
/* should be v9 or better */
|
|
log_info(_("%s connected to standby, checking its state\n"), progname);
|
|
pg_version(conn, standby_version);
|
|
if (strcmp(standby_version, "") == 0)
|
|
{
|
|
PQfinish(conn);
|
|
log_err(_("%s needs standby to be PostgreSQL 9.0 or better\n"), progname);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
/* Check we are a standby */
|
|
if (!is_standby(conn))
|
|
{
|
|
log_err(_("repmgr: This node should be a standby (%s)\n"), options.conninfo);
|
|
PQfinish(conn);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
/* Assemble a quoted schema name
|
|
* XXX This is not currently used due to a merge conflict, but
|
|
* probably should be */
|
|
if (false)
|
|
{
|
|
char *identifier = PQescapeIdentifier(conn, repmgr_schema,
|
|
strlen(repmgr_schema));
|
|
|
|
maxlen_snprintf(schema_quoted, "%s", identifier);
|
|
PQfreemem(identifier);
|
|
}
|
|
|
|
/* Check if there is a schema for this cluster */
|
|
sqlquery_snprintf(sqlquery, "SELECT 1 FROM pg_namespace WHERE nspname = '%s'", repmgr_schema);
|
|
log_debug("standby register: %s\n", sqlquery);
|
|
res = PQexec(conn, sqlquery);
|
|
if (PQresultStatus(res) != PGRES_TUPLES_OK)
|
|
{
|
|
log_err("Can't get info about tablespaces: %s\n", PQerrorMessage(conn));
|
|
PQclear(res);
|
|
PQfinish(conn);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
if (PQntuples(res) == 0)
|
|
{
|
|
/* schema doesn't exist */
|
|
log_err("Schema %s doesn't exists.\n", repmgr_schema);
|
|
PQclear(res);
|
|
PQfinish(conn);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
PQclear(res);
|
|
|
|
/* check if there is a master in this cluster */
|
|
log_info(_("%s connecting to master database\n"), progname);
|
|
master_conn = getMasterConnection(conn, options.cluster_name,
|
|
&master_id, NULL);
|
|
if (!master_conn)
|
|
{
|
|
log_err(_("A master must be defined before configuring a slave\n"));
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
/* master should be v9 or better */
|
|
log_info(_("%s connected to master, checking its state\n"), progname);
|
|
pg_version(master_conn, master_version);
|
|
if (strcmp(master_version, "") == 0)
|
|
{
|
|
PQfinish(conn);
|
|
PQfinish(master_conn);
|
|
log_err(_("%s needs master to be PostgreSQL 9.0 or better\n"), progname);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
/* master and standby version should match */
|
|
if (strcmp(master_version, standby_version) != 0)
|
|
{
|
|
PQfinish(conn);
|
|
PQfinish(master_conn);
|
|
log_err(_("%s needs versions of both master (%s) and standby (%s) to match.\n"),
|
|
progname, master_version, standby_version);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
/* Now register the standby */
|
|
log_info(_("%s registering the standby\n"), progname);
|
|
if (runtime_options.force)
|
|
{
|
|
sqlquery_snprintf(sqlquery, "DELETE FROM %s.repl_nodes "
|
|
" WHERE id = %d",
|
|
repmgr_schema, options.node);
|
|
log_debug("standby register: %s\n", sqlquery);
|
|
|
|
if (!PQexec(master_conn, sqlquery))
|
|
{
|
|
log_err("Cannot delete node details, %s\n",
|
|
PQerrorMessage(master_conn));
|
|
PQfinish(master_conn);
|
|
PQfinish(conn);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
}
|
|
|
|
sqlquery_snprintf(sqlquery, "INSERT INTO %s.repl_nodes "
|
|
"VALUES (%d, '%s', '%s')",
|
|
repmgr_schema, options.node, options.cluster_name, options.conninfo);
|
|
log_debug("standby register: %s\n", sqlquery);
|
|
|
|
if (!PQexec(master_conn, sqlquery))
|
|
{
|
|
log_err("Cannot insert node details, %s\n",
|
|
PQerrorMessage(master_conn));
|
|
PQfinish(master_conn);
|
|
PQfinish(conn);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
log_info(_("%s registering the standby complete\n"), progname);
|
|
PQfinish(master_conn);
|
|
PQfinish(conn);
|
|
return;
|
|
}
|
|
|
|
|
|
static void
|
|
do_standby_clone(void)
|
|
{
|
|
PGconn *conn;
|
|
PGresult *res;
|
|
char sqlquery[QUERY_STR_LEN];
|
|
|
|
int r = 0;
|
|
int i;
|
|
bool pg_dir = false;
|
|
char master_data_directory[MAXFILENAME];
|
|
char master_config_file[MAXFILENAME];
|
|
char master_hba_file[MAXFILENAME];
|
|
char master_ident_file[MAXFILENAME];
|
|
|
|
char master_control_file[MAXFILENAME];
|
|
char local_control_file[MAXFILENAME];
|
|
char tblspc_dir[MAXFILENAME];
|
|
|
|
char *first_wal_segment = NULL;
|
|
const char *last_wal_segment = NULL;
|
|
|
|
char master_version[MAXVERSIONSTR];
|
|
|
|
/* if dest_dir hasn't been provided, initialize to current directory */
|
|
if (!runtime_options.dest_dir[0])
|
|
{
|
|
strncpy(runtime_options.dest_dir, DEFAULT_DEST_DIR, MAXFILENAME);
|
|
}
|
|
|
|
/* Check this directory could be used as a PGDATA dir */
|
|
switch (check_dir(runtime_options.dest_dir))
|
|
{
|
|
case 0:
|
|
/* dest_dir not there, must create it */
|
|
log_info(_("creating directory %s ...\n"), runtime_options.dest_dir);
|
|
|
|
if (!create_directory(runtime_options.dest_dir))
|
|
{
|
|
log_err(_("%s: couldn't create directory %s ...\n"),
|
|
progname, runtime_options.dest_dir);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
break;
|
|
case 1:
|
|
/* Present but empty, fix permissions and use it */
|
|
log_info(_("checking and correcting permissions on existing directory %s ...\n"),
|
|
runtime_options.dest_dir);
|
|
|
|
if (!set_directory_permissions(runtime_options.dest_dir))
|
|
{
|
|
log_err(_("%s: could not change permissions of directory \"%s\": %s\n"),
|
|
progname, runtime_options.dest_dir, strerror(errno));
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
break;
|
|
case 2:
|
|
/* Present and not empty */
|
|
log_warning( _("%s: directory \"%s\" exists but is not empty\n"),
|
|
progname, runtime_options.dest_dir);
|
|
|
|
pg_dir = is_pg_dir(runtime_options.dest_dir);
|
|
if (pg_dir && !runtime_options.force)
|
|
{
|
|
log_warning( _("\nThis looks like a PostgreSQL directory.\n"
|
|
"If you are sure you want to clone here, "
|
|
"please check there is no PostgreSQL server "
|
|
"running and use the --force option\n"));
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
else if (pg_dir && runtime_options.force)
|
|
{
|
|
/* Let it continue */
|
|
break;
|
|
}
|
|
else
|
|
return;
|
|
default:
|
|
/* Trouble accessing directory */
|
|
log_err( _("%s: could not access directory \"%s\": %s\n"),
|
|
progname, runtime_options.dest_dir, strerror(errno));
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
/* Connection parameters for master only */
|
|
keywords[0] = "host";
|
|
values[0] = runtime_options.host;
|
|
keywords[1] = "port";
|
|
values[1] = runtime_options.masterport;
|
|
|
|
/* We need to connect to check configuration and start a backup */
|
|
log_info(_("%s connecting to master database\n"), progname);
|
|
conn=establishDBConnectionByParams(keywords,values,true);
|
|
|
|
/* primary should be v9 or better */
|
|
log_info(_("%s connected to master, checking its state\n"), progname);
|
|
pg_version(conn, master_version);
|
|
if (strcmp(master_version, "") == 0)
|
|
{
|
|
PQfinish(conn);
|
|
log_err(_("%s needs master to be PostgreSQL 9.0 or better\n"), progname);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
/* Check we are cloning a primary node */
|
|
if (is_standby(conn))
|
|
{
|
|
PQfinish(conn);
|
|
log_err(_("\nThe command should clone a primary node\n"));
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
/* And check if it is well configured */
|
|
if (!guc_setted(conn, "wal_level", "=", "hot_standby"))
|
|
{
|
|
PQfinish(conn);
|
|
log_err(_("%s needs parameter 'wal_level' to be set to 'hot_standby'\n"), progname);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
if (!guc_setted(conn, "wal_keep_segments", ">=", runtime_options.wal_keep_segments))
|
|
{
|
|
PQfinish(conn);
|
|
log_err(_("%s needs parameter 'wal_keep_segments' to be set to %s or greater (see the '-w' option)\n"), progname, runtime_options.wal_keep_segments);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
if (!guc_setted(conn, "archive_mode", "=", "on"))
|
|
{
|
|
PQfinish(conn);
|
|
log_err(_("%s needs parameter 'archive_mode' to be set to 'on'\n"), progname);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
log_info(_("Succesfully connected to primary. Current installation size is %s\n"), get_cluster_size(conn));
|
|
|
|
/*
|
|
* Check if the tablespace locations exists and that we can write to
|
|
* them.
|
|
*/
|
|
if (strcmp(master_version, "9.0") == 0 || strcmp(master_version, "9.1") == 0)
|
|
sqlquery_snprintf(sqlquery,
|
|
"SELECT spclocation "
|
|
" FROM pg_tablespace "
|
|
"WHERE spcname NOT IN ('pg_default', 'pg_global')");
|
|
else
|
|
sqlquery_snprintf(sqlquery,
|
|
"SELECT pg_tablespace_location(oid) spclocation "
|
|
" FROM pg_tablespace "
|
|
"WHERE spcname NOT IN ('pg_default', 'pg_global')");
|
|
|
|
log_debug("standby clone: %s\n", sqlquery);
|
|
|
|
res = PQexec(conn, sqlquery);
|
|
if (PQresultStatus(res) != PGRES_TUPLES_OK)
|
|
{
|
|
log_err("Can't get info about tablespaces: %s\n", PQerrorMessage(conn));
|
|
PQclear(res);
|
|
PQfinish(conn);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
for (i = 0; i < PQntuples(res); i++)
|
|
{
|
|
strncpy(tblspc_dir, PQgetvalue(res, i, 0), MAXFILENAME);
|
|
/* Check this directory could be used as a PGDATA dir */
|
|
switch (check_dir(tblspc_dir))
|
|
{
|
|
case 0:
|
|
/* tblspc_dir not there, must create it */
|
|
log_info(_("creating directory \"%s\"... "), tblspc_dir);
|
|
|
|
if (!create_directory(tblspc_dir))
|
|
{
|
|
log_err(_("%s: couldn't create directory \"%s\"...\n"),
|
|
progname, tblspc_dir);
|
|
PQclear(res);
|
|
PQfinish(conn);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
break;
|
|
case 1:
|
|
/* Present but empty, fix permissions and use it */
|
|
log_info(_("fixing permissions on existing directory \"%s\"... "),
|
|
tblspc_dir);
|
|
|
|
if (!set_directory_permissions(tblspc_dir))
|
|
{
|
|
log_err(_("%s: could not change permissions of directory \"%s\": %s\n"),
|
|
progname, tblspc_dir, strerror(errno));
|
|
PQclear(res);
|
|
PQfinish(conn);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
break;
|
|
case 2:
|
|
/* Present and not empty */
|
|
if (!runtime_options.force)
|
|
{
|
|
log_err(_("%s: directory \"%s\" exists but is not empty\n"),
|
|
progname, tblspc_dir);
|
|
PQclear(res);
|
|
PQfinish(conn);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
break;
|
|
default:
|
|
/* Trouble accessing directory */
|
|
log_err(_("%s: could not access directory \"%s\": %s\n"),
|
|
progname, tblspc_dir, strerror(errno));
|
|
PQclear(res);
|
|
PQfinish(conn);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
}
|
|
|
|
r = test_ssh_connection(runtime_options.host, runtime_options.remote_user);
|
|
if (r != 0)
|
|
{
|
|
log_err(_("%s: Aborting, remote host %s is not reachable.\n"), progname, runtime_options.host);
|
|
PQclear(res);
|
|
PQfinish(conn);
|
|
exit(ERR_DB_CON);
|
|
}
|
|
|
|
/* Get the data directory full path and the configuration files location */
|
|
sqlquery_snprintf(sqlquery,
|
|
"SELECT name, setting "
|
|
" FROM pg_settings "
|
|
" WHERE name IN ('data_directory', 'config_file', 'hba_file', 'ident_file')");
|
|
log_debug("standby clone: %s\n", sqlquery);
|
|
res = PQexec(conn, sqlquery);
|
|
if (PQresultStatus(res) != PGRES_TUPLES_OK)
|
|
{
|
|
log_err("Can't get info about data directory and configuration files: %s\n", PQerrorMessage(conn));
|
|
PQclear(res);
|
|
PQfinish(conn);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
/* We need all 4 parameters, and they can be retrieved only by superusers */
|
|
if (PQntuples(res) != 4)
|
|
{
|
|
log_err("%s: STANDBY CLONE should be run by a SUPERUSER\n", progname);
|
|
PQclear(res);
|
|
PQfinish(conn);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
for (i = 0; i < PQntuples(res); i++)
|
|
{
|
|
if (strcmp(PQgetvalue(res, i, 0), "data_directory") == 0)
|
|
strncpy(master_data_directory, PQgetvalue(res, i, 1), MAXFILENAME);
|
|
else if (strcmp(PQgetvalue(res, i, 0), "config_file") == 0)
|
|
strncpy(master_config_file, PQgetvalue(res, i, 1), MAXFILENAME);
|
|
else if (strcmp(PQgetvalue(res, i, 0), "hba_file") == 0)
|
|
strncpy(master_hba_file, PQgetvalue(res, i, 1), MAXFILENAME);
|
|
else if (strcmp(PQgetvalue(res, i, 0), "ident_file") == 0)
|
|
strncpy(master_ident_file, PQgetvalue(res, i, 1), MAXFILENAME);
|
|
else
|
|
log_warning(_("unknown parameter: %s\n"), PQgetvalue(res, i, 0));
|
|
}
|
|
PQclear(res);
|
|
|
|
log_notice("Starting backup...\n");
|
|
|
|
/*
|
|
* in pg 9.1 default is to wait for a sync standby to ack,
|
|
* avoid that by turning off sync rep for this session
|
|
*/
|
|
sqlquery_snprintf(sqlquery, "SET synchronous_commit TO OFF");
|
|
res = PQexec(conn, sqlquery);
|
|
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
|
{
|
|
log_err("Can't set synchronous_commit: %s\n", PQerrorMessage(conn));
|
|
PQclear(res);
|
|
PQfinish(conn);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
/*
|
|
* inform the master we will start a backup and get the first XLog filename
|
|
* so we can say to the user we need those files
|
|
*/
|
|
sqlquery_snprintf(
|
|
sqlquery,
|
|
"SELECT pg_xlogfile_name(pg_start_backup('repmgr_standby_clone_%ld'))",
|
|
time(NULL));
|
|
log_debug("standby clone: %s\n", sqlquery);
|
|
res = PQexec(conn, sqlquery);
|
|
if (PQresultStatus(res) != PGRES_TUPLES_OK)
|
|
{
|
|
log_err("Can't start backup: %s\n", PQerrorMessage(conn));
|
|
PQclear(res);
|
|
PQfinish(conn);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
if (runtime_options.verbose)
|
|
{
|
|
char *first_wal_seg_pq = PQgetvalue(res, 0, 0);
|
|
size_t buf_sz = strlen(first_wal_seg_pq);
|
|
|
|
first_wal_segment = malloc(buf_sz + 1);
|
|
xsnprintf(first_wal_segment, buf_sz + 1, "%s", first_wal_seg_pq);
|
|
}
|
|
|
|
PQclear(res);
|
|
|
|
/*
|
|
* 1) first move global/pg_control
|
|
*
|
|
* 2) then move data_directory ommiting the files we have already moved and
|
|
* pg_xlog content
|
|
*
|
|
* 3) finally We need to backup configuration files (that could be on other
|
|
* directories, debian like systems likes to do that), so look at
|
|
* config_file, hba_file and ident_file but we can omit
|
|
* external_pid_file ;)
|
|
*
|
|
* On error we need to return but before that execute pg_stop_backup()
|
|
*/
|
|
|
|
/* need to create the global sub directory */
|
|
maxlen_snprintf(master_control_file, "%s/global/pg_control",
|
|
master_data_directory);
|
|
maxlen_snprintf(local_control_file, "%s/global", runtime_options.dest_dir);
|
|
if (!create_directory(local_control_file))
|
|
{
|
|
log_err(_("%s: couldn't create directory %s ...\n"),
|
|
progname, runtime_options.dest_dir);
|
|
goto stop_backup;
|
|
}
|
|
|
|
log_info("standby clone: master control file '%s'\n", master_control_file);
|
|
r = copy_remote_files(runtime_options.host, runtime_options.remote_user,
|
|
master_control_file, local_control_file, false);
|
|
if (r != 0)
|
|
{
|
|
log_warning("standby clone: failed copying master control file '%s'\n", master_control_file);
|
|
goto stop_backup;
|
|
}
|
|
|
|
log_info("standby clone: master data directory '%s'\n", master_data_directory);
|
|
r = copy_remote_files(runtime_options.host, runtime_options.remote_user,
|
|
master_data_directory, runtime_options.dest_dir, true);
|
|
if (r != 0)
|
|
{
|
|
log_warning("standby clone: failed copying master data directory '%s'\n", master_data_directory);
|
|
goto stop_backup;
|
|
}
|
|
|
|
/*
|
|
* Copy tablespace locations, i'm doing this separately because i couldn't
|
|
* find and appropiate rsync option but besides we could someday make all
|
|
* these rsync happen concurrently
|
|
*/
|
|
if (strcmp(master_version, "9.0") == 0 || strcmp(master_version, "9.1") == 0)
|
|
sqlquery_snprintf(sqlquery,
|
|
"SELECT spclocation "
|
|
" FROM pg_tablespace "
|
|
" WHERE spcname NOT IN ('pg_default', 'pg_global')");
|
|
else
|
|
sqlquery_snprintf(sqlquery,
|
|
"SELECT pg_tablespace_location(oid) spclocation "
|
|
" FROM pg_tablespace "
|
|
" WHERE spcname NOT IN ('pg_default', 'pg_global')");
|
|
|
|
log_debug("standby clone: %s\n", sqlquery);
|
|
res = PQexec(conn, sqlquery);
|
|
if (PQresultStatus(res) != PGRES_TUPLES_OK)
|
|
{
|
|
log_err("Can't get info about tablespaces: %s\n", PQerrorMessage(conn));
|
|
PQclear(res);
|
|
goto stop_backup;
|
|
}
|
|
for (i = 0; i < PQntuples(res); i++)
|
|
{
|
|
strncpy(tblspc_dir, PQgetvalue(res, i, 0), MAXFILENAME);
|
|
log_info("standby clone: master tablespace '%s'\n", tblspc_dir);
|
|
r = copy_remote_files(runtime_options.host, runtime_options.remote_user,
|
|
tblspc_dir, tblspc_dir, true);
|
|
if (r != 0)
|
|
{
|
|
log_warning("standby clone: failed copying tablespace directory '%s'\n", tblspc_dir);
|
|
goto stop_backup;
|
|
}
|
|
}
|
|
|
|
log_info("standby clone: master config file '%s'\n", master_config_file);
|
|
r = copy_remote_files(runtime_options.host, runtime_options.remote_user,
|
|
master_config_file, runtime_options.dest_dir, false);
|
|
if (r != 0)
|
|
{
|
|
log_warning("standby clone: failed copying master config file '%s'\n", master_config_file);
|
|
goto stop_backup;
|
|
}
|
|
|
|
log_info("standby clone: master hba file '%s'\n", master_hba_file);
|
|
r = copy_remote_files(runtime_options.host, runtime_options.remote_user, master_hba_file, runtime_options.dest_dir, false);
|
|
if (r != 0)
|
|
{
|
|
log_warning("standby clone: failed copying master hba file '%s'\n", master_hba_file);
|
|
goto stop_backup;
|
|
}
|
|
|
|
log_info("standby clone: master ident file '%s'\n", master_ident_file);
|
|
r = copy_remote_files(runtime_options.host, runtime_options.remote_user,
|
|
master_ident_file, runtime_options.dest_dir, false);
|
|
if (r != 0)
|
|
{
|
|
log_warning("standby clone: failed copying master ident file '%s'\n", master_ident_file);
|
|
goto stop_backup;
|
|
}
|
|
|
|
stop_backup:
|
|
|
|
/*
|
|
* Inform the master that we have finished the backup.
|
|
*
|
|
* Don't have this one exit if it fails, so that a more informative
|
|
* error message will also appear about the backup not being stopped.
|
|
*/
|
|
log_notice("Finishing backup...\n");
|
|
sqlquery_snprintf(sqlquery, "SELECT pg_xlogfile_name(pg_stop_backup())");
|
|
log_debug("standby clone: %s\n", sqlquery);
|
|
|
|
res = PQexec(conn, sqlquery);
|
|
if (PQresultStatus(res) != PGRES_TUPLES_OK)
|
|
{
|
|
log_err("Can't stop backup: %s\n", PQerrorMessage(conn));
|
|
PQclear(res);
|
|
PQfinish(conn);
|
|
exit(ERR_STOP_BACKUP);
|
|
}
|
|
last_wal_segment = PQgetvalue(res, 0, 0);
|
|
|
|
/* don't show this message if rsync failed */
|
|
if (r == 0 && runtime_options.verbose)
|
|
log_info(_("%s requires primary to keep WAL files %s until at least %s\n"),
|
|
progname, first_wal_segment, last_wal_segment);
|
|
|
|
/* Finished with the database connection now */
|
|
PQclear(res);
|
|
PQfinish(conn);
|
|
|
|
/*
|
|
* Only free the first_wal_segment since it was copied out of the
|
|
* pqresult.
|
|
*/
|
|
free(first_wal_segment);
|
|
first_wal_segment = NULL;
|
|
|
|
/* If the rsync failed then exit */
|
|
if (r != 0)
|
|
exit(ERR_BAD_RSYNC);
|
|
|
|
/*
|
|
* We need to create the pg_xlog sub directory too, I'm reusing a variable
|
|
* here.
|
|
*/
|
|
maxlen_snprintf(local_control_file, "%s/pg_xlog", runtime_options.dest_dir);
|
|
if (!create_directory(local_control_file))
|
|
{
|
|
log_err(_("%s: couldn't create directory %s, you will need to do it manually...\n"),
|
|
progname, runtime_options.dest_dir);
|
|
r = ERR_NEEDS_XLOG; /* continue, but eventually exit returning error */
|
|
}
|
|
|
|
/* Finally, write the recovery.conf file */
|
|
create_recovery_file(runtime_options.dest_dir, NULL);
|
|
|
|
/*
|
|
* We don't start the service yet because we still may want to
|
|
* move the directory
|
|
*/
|
|
log_info(_("%s standby clone complete\n"), progname);
|
|
exit(r);
|
|
}
|
|
|
|
|
|
static void
|
|
do_standby_promote(void)
|
|
{
|
|
PGconn *conn;
|
|
PGresult *res;
|
|
char sqlquery[QUERY_STR_LEN];
|
|
char script[MAXLEN];
|
|
|
|
PGconn *old_master_conn;
|
|
int old_master_id;
|
|
|
|
int r;
|
|
char data_dir[MAXLEN];
|
|
char recovery_file_path[MAXFILENAME];
|
|
char recovery_done_path[MAXFILENAME];
|
|
|
|
char standby_version[MAXVERSIONSTR];
|
|
|
|
/* We need to connect to check configuration */
|
|
log_info(_("%s connecting to master database\n"), progname);
|
|
conn = establishDBConnection(options.conninfo, true);
|
|
|
|
/* we need v9 or better */
|
|
log_info(_("%s connected to master, checking its state\n"), progname);
|
|
pg_version(conn, standby_version);
|
|
if (strcmp(standby_version, "") == 0)
|
|
{
|
|
PQfinish(conn);
|
|
log_err(_("%s needs standby to be PostgreSQL 9.0 or better\n"), progname);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
/* Check we are in a standby node */
|
|
if (!is_standby(conn))
|
|
{
|
|
log_err("repmgr: The command should be executed on a standby node\n");
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
/* we also need to check if there isn't any master already */
|
|
old_master_conn = getMasterConnection(conn, options.cluster_name,
|
|
&old_master_id, NULL);
|
|
if (old_master_conn != NULL)
|
|
{
|
|
PQfinish(old_master_conn);
|
|
log_err("There is a master already in this cluster\n");
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
log_notice(_("%s: Promoting standby\n"), progname);
|
|
|
|
/* Get the data directory full path and the last subdirectory */
|
|
sqlquery_snprintf(sqlquery, "SELECT setting "
|
|
" FROM pg_settings WHERE name = 'data_directory'");
|
|
log_debug("standby promote: %s\n", sqlquery);
|
|
res = PQexec(conn, sqlquery);
|
|
if (PQresultStatus(res) != PGRES_TUPLES_OK)
|
|
{
|
|
log_err("Can't get info about data directory: %s\n", PQerrorMessage(conn));
|
|
PQclear(res);
|
|
PQfinish(conn);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
strcpy(data_dir, PQgetvalue(res, 0, 0));
|
|
PQclear(res);
|
|
PQfinish(conn);
|
|
|
|
log_info(_("%s: Marking recovery done\n"), progname);
|
|
maxlen_snprintf(recovery_file_path, "%s/%s", data_dir, RECOVERY_FILE);
|
|
maxlen_snprintf(recovery_done_path, "%s/%s", data_dir, RECOVERY_DONE_FILE);
|
|
rename(recovery_file_path, recovery_done_path);
|
|
|
|
/*
|
|
* We assume the pg_ctl script is in the PATH. Restart and wait for
|
|
* the server to finish starting, so that the check below will
|
|
* find an active server rather than one starting up. This may
|
|
* hang for up the default timeout (60 seconds).
|
|
*/
|
|
log_notice(_("%s: restarting server using pg_ctl\n"), progname);
|
|
maxlen_snprintf(script, "pg_ctl -D %s -w -m fast restart", data_dir);
|
|
r = system(script);
|
|
if (r != 0)
|
|
{
|
|
log_err("Can't restart PostgreSQL server\n");
|
|
exit(ERR_NO_RESTART);
|
|
}
|
|
|
|
/* reconnect to check we got promoted */
|
|
log_info(_("%s connecting to now restarted database\n"), progname);
|
|
conn = establishDBConnection(options.conninfo, true);
|
|
if (is_standby(conn))
|
|
{
|
|
log_err("\n%s: STANDBY PROMOTE failed, this is still a standby node.\n", progname);
|
|
}
|
|
else
|
|
{
|
|
log_err("\n%s: STANDBY PROMOTE successful. You should REINDEX any hash indexes you have.\n", progname);
|
|
}
|
|
PQfinish(conn);
|
|
return;
|
|
}
|
|
|
|
|
|
static void
|
|
do_standby_follow(void)
|
|
{
|
|
PGconn *conn;
|
|
PGresult *res;
|
|
char sqlquery[QUERY_STR_LEN];
|
|
char script[MAXLEN];
|
|
char master_conninfo[MAXLEN];
|
|
PGconn *master_conn;
|
|
int master_id;
|
|
|
|
int r;
|
|
char data_dir[MAXLEN];
|
|
|
|
char master_version[MAXVERSIONSTR];
|
|
char standby_version[MAXVERSIONSTR];
|
|
|
|
/* We need to connect to check configuration */
|
|
log_info(_("%s connecting to standby database\n"), progname);
|
|
conn = establishDBConnection(options.conninfo, true);
|
|
|
|
/* Check we are in a standby node */
|
|
log_info(_("%s connected to standby, checking its state\n"), progname);
|
|
if (!is_standby(conn))
|
|
{
|
|
log_err("\n%s: The command should be executed in a standby node\n", progname);
|
|
return;
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
/* should be v9 or better */
|
|
pg_version(conn, standby_version);
|
|
if (strcmp(standby_version, "") == 0)
|
|
{
|
|
PQfinish(conn);
|
|
log_err(_("\n%s needs standby to be PostgreSQL 9.0 or better\n"), progname);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
/* we also need to check if there is any master in the cluster */
|
|
log_info(_("%s connecting to master database\n"), progname);
|
|
master_conn = getMasterConnection(conn, options.cluster_name, &master_id,(char *) &master_conninfo);
|
|
if (master_conn == NULL)
|
|
{
|
|
PQfinish(conn);
|
|
log_err("There isn't a master to follow in this cluster\n");
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
/* Check we are going to point to a master */
|
|
if (is_standby(master_conn))
|
|
{
|
|
PQfinish(conn);
|
|
log_err("%s: The node to follow should be a master\n", progname);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
/* should be v9 or better */
|
|
log_info(_("%s connected to master, checking its state\n"), progname);
|
|
pg_version(master_conn, master_version);
|
|
if (strcmp(master_version, "") == 0)
|
|
{
|
|
PQfinish(conn);
|
|
PQfinish(master_conn);
|
|
log_err(_("%s needs master to be PostgreSQL 9.0 or better\n"), progname);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
/* master and standby version should match */
|
|
if (strcmp(master_version, standby_version) != 0)
|
|
{
|
|
PQfinish(conn);
|
|
PQfinish(master_conn);
|
|
log_err(_("%s needs versions of both master (%s) and standby (%s) to match.\n"),
|
|
progname, master_version, standby_version);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
|
|
/*
|
|
* set the host and masterport variables with the master ones
|
|
* before closing the connection because we will need them to
|
|
* recreate the recovery.conf file
|
|
*/
|
|
strncpy(runtime_options.host, PQhost(master_conn), MAXLEN);
|
|
strncpy(runtime_options.masterport, PQport(master_conn), MAXLEN);
|
|
PQfinish(master_conn);
|
|
|
|
log_info(_("%s Changing standby's master"),progname);
|
|
|
|
/* Get the data directory full path */
|
|
sqlquery_snprintf(sqlquery, "SELECT setting "
|
|
" FROM pg_settings WHERE name = 'data_directory'");
|
|
log_debug("standby follow: %s\n", sqlquery);
|
|
res = PQexec(conn, sqlquery);
|
|
if (PQresultStatus(res) != PGRES_TUPLES_OK)
|
|
{
|
|
log_err("Can't get info about data directory: %s\n", PQerrorMessage(conn));
|
|
PQclear(res);
|
|
PQfinish(conn);
|
|
exit(ERR_BAD_CONFIG);
|
|
}
|
|
strcpy(data_dir, PQgetvalue(res, 0, 0));
|
|
PQclear(res);
|
|
PQfinish(conn);
|
|
|
|
/* write the recovery.conf file */
|
|
if (!create_recovery_file(data_dir,NULL))
|
|
exit(ERR_BAD_CONFIG);
|
|
|
|
/* Finally, restart the service */
|
|
/* We assume the pg_ctl script is in the PATH */
|
|
maxlen_snprintf(script, "pg_ctl -D %s -m fast restart", data_dir);
|
|
r = system(script);
|
|
if (r != 0)
|
|
{
|
|
log_err("Can't restart service\n");
|
|
return;
|
|
exit(ERR_NO_RESTART);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
void usage(void)
|
|
{
|
|
log_err(_("\n\n%s: Replicator manager \n"), progname);
|
|
log_err(_("Try \"%s --help\" for more information.\n"), progname);
|
|
}
|
|
|
|
void help(const char *progname)
|
|
{
|
|
printf(_("\n%s: Replicator manager \n"), progname);
|
|
printf(_("Usage:\n"));
|
|
printf(_(" %s [OPTIONS] master {register}\n"), progname);
|
|
printf(_(" %s [OPTIONS] standby {register|clone|promote|follow}\n"),
|
|
progname);
|
|
printf(_(" %s [OPTIONS] cluster {show|cleanup}\n"), progname);
|
|
printf(_("\nGeneral options:\n"));
|
|
printf(_(" --help show this help, then exit\n"));
|
|
printf(_(" --version output version information, then exit\n"));
|
|
printf(_(" --verbose output verbose activity information\n"));
|
|
printf(_("\nConnection options:\n"));
|
|
printf(_(" -d, --dbname=DBNAME database to connect to\n"));
|
|
printf(_(" -h, --host=HOSTNAME database server host or socket directory\n"));
|
|
printf(_(" -p, --port=PORT database server port\n"));
|
|
printf(_(" -U, --username=USERNAME database user name to connect as\n"));
|
|
printf(_("\nConfiguration options:\n"));
|
|
printf(_(" -D, --data-dir=DIR local directory where the files will be copied to\n"));
|
|
printf(_(" -f, --config_file=PATH path to the configuration file\n"));
|
|
printf(_(" -R, --remote-user=USERNAME database server username for rsync\n"));
|
|
printf(_(" -w, --wal-keep-segments=VALUE minimum value for the GUC wal_keep_segments (default: 5000)\n"));
|
|
printf(_(" -F, --force force potentially dangerous operations to happen\n"));
|
|
printf(_(" -I, --ignore-rsync-warning Ignore partial transfert warning\n"));
|
|
printf(_(" -k, --keep-history=VALUE keeps indicated number of days of history\n"));
|
|
|
|
printf(_("\n%s performs some tasks like clone a node, promote it "), progname);
|
|
printf(_("or making follow another node and then exits.\n"));
|
|
printf(_("COMMANDS:\n"));
|
|
printf(_(" master register - registers the master in a cluster\n"));
|
|
printf(_(" standby register - registers a standby in a cluster\n"));
|
|
printf(_(" standby clone [node] - allows creation of a new standby\n"));
|
|
printf(_(" standby promote - allows manual promotion of a specific standby into a "));
|
|
printf(_("new master in the event of a failover\n"));
|
|
printf(_(" standby follow - allows the standby to re-point itself to a new master\n"));
|
|
printf(_(" cluster show - print node informations\n"));
|
|
printf(_(" cluster cleanup - cleans monitor's history\n"));
|
|
}
|
|
|
|
|
|
/*
|
|
* Creates a recovery file for a standby.
|
|
*
|
|
* Writes master_conninfo to recovery.conf if is non-NULL
|
|
*/
|
|
static bool
|
|
create_recovery_file(const char *data_dir, char *master_conninfo)
|
|
{
|
|
FILE *recovery_file;
|
|
char recovery_file_path[MAXLEN];
|
|
char line[MAXLEN];
|
|
|
|
maxlen_snprintf(recovery_file_path, "%s/%s", data_dir, RECOVERY_FILE);
|
|
|
|
recovery_file = fopen(recovery_file_path, "w");
|
|
if (recovery_file == NULL)
|
|
{
|
|
log_err("could not create recovery.conf file, it could be necessary to create it manually\n");
|
|
return false;
|
|
}
|
|
|
|
maxlen_snprintf(line, "standby_mode = 'on'\n");
|
|
if (fputs(line, recovery_file) == EOF)
|
|
{
|
|
log_err("recovery file could not be written, it could be necessary to create it manually\n");
|
|
fclose(recovery_file);
|
|
return false;
|
|
}
|
|
|
|
write_primary_conninfo(line);
|
|
|
|
if (fputs(line, recovery_file) == EOF)
|
|
{
|
|
log_err("recovery file could not be written, it could be necessary to create it manually\n");
|
|
fclose(recovery_file);
|
|
return false;
|
|
}
|
|
|
|
/*FreeFile(recovery_file);*/
|
|
fclose(recovery_file);
|
|
|
|
return true;
|
|
}
|
|
|
|
static int
|
|
test_ssh_connection(char *host, char *remote_user)
|
|
{
|
|
char script[MAXLEN];
|
|
int r;
|
|
|
|
/* Check if we have ssh connectivity to host before trying to rsync */
|
|
if (!remote_user[0])
|
|
maxlen_snprintf(script, "ssh -o Batchmode=yes %s /bin/true", host);
|
|
else
|
|
maxlen_snprintf(script, "ssh -o Batchmode=yes %s -l %s /bin/true", host, remote_user);
|
|
|
|
log_debug(_("command is: %s"), script);
|
|
r = system(script);
|
|
if (r != 0)
|
|
log_info(_("Cannot connect to the remote host (%s)\n"), host);
|
|
return r;
|
|
}
|
|
|
|
static int
|
|
copy_remote_files(char *host, char *remote_user, char *remote_path,
|
|
char *local_path, bool is_directory)
|
|
{
|
|
char script[MAXLEN];
|
|
char rsync_flags[MAXLEN];
|
|
char host_string[MAXLEN];
|
|
int r;
|
|
|
|
if (strnlen(options.rsync_options, MAXLEN) == 0)
|
|
maxlen_snprintf(
|
|
rsync_flags, "%s",
|
|
"--archive --checksum --compress --progress --rsh=ssh");
|
|
else
|
|
maxlen_snprintf(rsync_flags, "%s", options.rsync_options);
|
|
|
|
if (runtime_options.force)
|
|
strcat(rsync_flags, " --delete");
|
|
|
|
if (!remote_user[0])
|
|
{
|
|
maxlen_snprintf(host_string, "%s", host);
|
|
}
|
|
else
|
|
{
|
|
maxlen_snprintf(host_string,"%s@%s",remote_user,host);
|
|
}
|
|
|
|
if (is_directory)
|
|
{
|
|
strcat(rsync_flags, " --exclude=pg_xlog* --exclude=pg_control --exclude=*.pid");
|
|
maxlen_snprintf(script, "rsync %s %s:%s/* %s",
|
|
rsync_flags, host_string, remote_path, local_path);
|
|
}
|
|
else
|
|
{
|
|
maxlen_snprintf(script, "rsync %s %s:%s %s/.",
|
|
rsync_flags, host_string, remote_path, local_path);
|
|
}
|
|
|
|
log_info("rsync command line: '%s'\n", script);
|
|
|
|
r = system(script);
|
|
|
|
/*
|
|
* If we are transfering a directory (ie: data directory, tablespace directories)
|
|
* then we can ignore some rsync warning, so if we get some of those errors we
|
|
* treat them as 0 if we have --ignore-rsync-warning commandline option set
|
|
* List of ignorable rsync errors:
|
|
* 24 Partial transfer due to vanished source files
|
|
*/
|
|
if ((WEXITSTATUS(r) == 24) && is_directory)
|
|
{
|
|
if (!runtime_options.ignore_rsync_warn)
|
|
{
|
|
log_warning( _("\nrsync completed with return code 24 "
|
|
"\"Partial transfer due to vanished source files\".\n"
|
|
"This can happen because of normal operation "
|
|
"on the master server, but it may indicate an "
|
|
"issue during cloning. If you are certain no "
|
|
"changes were made to the master, try cloning "
|
|
"again using \"repmgr --force --ignore-rsync-warning\"."));
|
|
exit(ERR_BAD_RSYNC);
|
|
}
|
|
else
|
|
r = 0;
|
|
}
|
|
if (r != 0)
|
|
log_err(_("Can't rsync from remote file or directory (%s:%s)\n"),
|
|
host_string, remote_path);
|
|
|
|
return r;
|
|
}
|
|
|
|
|
|
/*
|
|
* Tries to avoid useless or conflicting parameters
|
|
*/
|
|
static bool
|
|
check_parameters_for_action(const int action)
|
|
{
|
|
bool ok = true;
|
|
|
|
switch (action)
|
|
{
|
|
case MASTER_REGISTER:
|
|
/*
|
|
* To register a master we only need the repmgr.conf
|
|
* all other parameters are at least useless and could be
|
|
* confusing so reject them
|
|
*/
|
|
if (runtime_options.host[0] || runtime_options.masterport[0] || runtime_options.username[0] ||
|
|
runtime_options.dbname[0])
|
|
{
|
|
log_err("You can't use connection parameters to the master when issuing a MASTER REGISTER command.\n");
|
|
usage();
|
|
ok = false;
|
|
}
|
|
if (runtime_options.dest_dir[0])
|
|
{
|
|
log_err("You don't need a destination directory for MASTER REGISTER command\n");
|
|
usage();
|
|
ok = false;
|
|
}
|
|
break;
|
|
case STANDBY_REGISTER:
|
|
/*
|
|
* To register a standby we only need the repmgr.conf
|
|
* we don't need connection parameters to the master
|
|
* because we can detect the master in repl_nodes
|
|
*/
|
|
if (runtime_options.host[0] || runtime_options.masterport[0] || runtime_options.username[0] ||
|
|
runtime_options.dbname[0])
|
|
{
|
|
log_err("You can't use connection parameters to the master when issuing a STANDBY REGISTER command.\n");
|
|
usage();
|
|
ok = false;
|
|
}
|
|
if (runtime_options.dest_dir[0])
|
|
{
|
|
log_err("You don't need a destination directory for STANDBY REGISTER command\n");
|
|
usage();
|
|
ok = false;
|
|
}
|
|
break;
|
|
case STANDBY_PROMOTE:
|
|
/*
|
|
* To promote a standby we only need the repmgr.conf
|
|
* we don't want connection parameters to the master
|
|
* because we will try to detect the master in repl_nodes
|
|
* if we can't find it then the promote action will be cancelled
|
|
*/
|
|
if (runtime_options.host[0] || runtime_options.masterport[0] || runtime_options.username[0] ||
|
|
runtime_options.dbname[0])
|
|
{
|
|
log_err("You can't use connection parameters to the master when issuing a STANDBY PROMOTE command.\n");
|
|
usage();
|
|
ok = false;
|
|
}
|
|
if (runtime_options.dest_dir[0])
|
|
{
|
|
log_err("You don't need a destination directory for STANDBY PROMOTE command\n");
|
|
usage();
|
|
ok = false;
|
|
}
|
|
break;
|
|
case STANDBY_FOLLOW:
|
|
/*
|
|
* To make a standby follow a master we only need the repmgr.conf
|
|
* we don't want connection parameters to the new master
|
|
* because we will try to detect the master in repl_nodes
|
|
* if we can't find it then the follow action will be cancelled
|
|
*/
|
|
if (runtime_options.host[0] || runtime_options.masterport[0] || runtime_options.username[0] ||
|
|
runtime_options.dbname[0])
|
|
{
|
|
log_err("You can't use connection parameters to the master when issuing a STANDBY FOLLOW command.\n");
|
|
usage();
|
|
ok = false;
|
|
}
|
|
if (runtime_options.dest_dir[0])
|
|
{
|
|
log_err("You don't need a destination directory for STANDBY FOLLOW command\n");
|
|
usage();
|
|
ok = false;
|
|
}
|
|
break;
|
|
case STANDBY_CLONE:
|
|
/*
|
|
* Issue a friendly notice that the configuration file is not
|
|
* necessary nor read at all in when performing a STANDBY CLONE
|
|
* action.
|
|
*/
|
|
if (runtime_options.config_file[0])
|
|
{
|
|
log_notice("Only command line parameters for the connection "
|
|
"to the master are used when issuing a STANDBY CLONE command. "
|
|
"The passed configuration file is neither required nor used for "
|
|
"its node configuration portions\n\n");
|
|
}
|
|
/*
|
|
* To clone a master into a standby we need connection parameters
|
|
* repmgr.conf is useless because we don't have a server running in
|
|
* the standby; warn the user, but keep going.
|
|
*/
|
|
if (runtime_options.host == NULL)
|
|
{
|
|
log_notice("You need to use connection parameters to "
|
|
"the master when issuing a STANDBY CLONE command.");
|
|
ok = false;
|
|
}
|
|
need_a_node = false;
|
|
break;
|
|
case CLUSTER_SHOW:
|
|
/* allow all parameters to be supplied */
|
|
break;
|
|
case CLUSTER_CLEANUP:
|
|
/* allow all parameters to be supplied */
|
|
break;
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
/* This function uses global variables to determine connection settings. Special
|
|
* usage of the PGPASSWORD variable is handled, but strongly discouraged */
|
|
static void
|
|
write_primary_conninfo(char* line)
|
|
{
|
|
char host_buf[MAXLEN] = "";
|
|
char conn_buf[MAXLEN] = "";
|
|
char user_buf[MAXLEN] = "";
|
|
char password_buf[MAXLEN] = "";
|
|
|
|
/* Environment variable for password (UGLY, please use .pgpass!) */
|
|
const char *password = getenv("PGPASSWORD");
|
|
if (password != NULL) {
|
|
maxlen_snprintf(password_buf, " password=%s", password);
|
|
}
|
|
else if (require_password) {
|
|
log_err(_("%s: PGPASSWORD not set, but having one is required\n"),
|
|
progname);
|
|
exit(ERR_BAD_PASSWORD);
|
|
}
|
|
|
|
if (runtime_options.host[0]) {
|
|
maxlen_snprintf(host_buf, " host=%s", runtime_options.host);
|
|
}
|
|
|
|
if (runtime_options.username[0]) {
|
|
maxlen_snprintf(user_buf, " user=%s", runtime_options.username);
|
|
}
|
|
|
|
maxlen_snprintf(conn_buf, "port=%s%s%s%s",
|
|
(runtime_options.masterport[0]) ? runtime_options.masterport : "5432", host_buf, user_buf, password_buf);
|
|
|
|
maxlen_snprintf(line, "primary_conninfo = '%s'", conn_buf);
|
|
}
|