diff --git a/Makefile.in b/Makefile.in index 8815eca1..8b443b50 100644 --- a/Makefile.in +++ b/Makefile.in @@ -27,7 +27,7 @@ include Makefile.global $(info Building against PostgreSQL $(MAJORVERSION)) REPMGR_CLIENT_OBJS = repmgr-client.o repmgr-action-master.o repmgr-action-standby.o repmgr-action-cluster.o \ - config.o log.o strutil.o dbutils.o dirutil.o + config.o log.o strutil.o dbutils.o dirutil.o compat.o REPMGRD_OBJS = repmgrd.o $(REPMGR_CLIENT_OBJS): repmgr-client.h @@ -56,6 +56,7 @@ additional-clean: rm -f repmgr-action-master.o rm -f repmgr-action-standby.o rm -f repmgrd.o + rm -f compat.o rm -f config.o rm -f dbutils.o rm -f dirutil.o diff --git a/compat.c b/compat.c new file mode 100644 index 00000000..e3d3ee31 --- /dev/null +++ b/compat.c @@ -0,0 +1,107 @@ +/* + * + * 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-2017 + * + * 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, '\''); +} + diff --git a/compat.h b/compat.h new file mode 100644 index 00000000..5d355af8 --- /dev/null +++ b/compat.h @@ -0,0 +1,32 @@ +/* + * compat.h + * Copyright (c) 2ndQuadrant, 2010-2017 + * + * 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 diff --git a/repmgr-action-standby.c b/repmgr-action-standby.c index 26eec735..0345e25e 100644 --- a/repmgr-action-standby.c +++ b/repmgr-action-standby.c @@ -8,6 +8,7 @@ #include "repmgr.h" #include "dirutil.h" +#include "compat.h" #include "repmgr-client-global.h" #include "repmgr-action-standby.h" @@ -33,8 +34,11 @@ static char local_repmgr_tmp_directory[MAXPGPATH]; static void check_barman_config(void); -static char *make_barman_ssh_command(char *buf); static void check_source_server(void); +static void check_source_server_via_barman(void); + +static void get_barman_property(char *dst, char *name, char *local_repmgr_directory); +static char *make_barman_ssh_command(char *buf); void @@ -152,7 +156,7 @@ do_standby_clone(void) */ if (*runtime_options.upstream_conninfo) - runtime_options.no_upstream_connection = false; + runtime_options.no_upstream_connection = true; /* By default attempt to connect to the source server */ if (runtime_options.no_upstream_connection == false) @@ -160,6 +164,19 @@ do_standby_clone(void) check_source_server(); } + if (mode == barman && PQstatus(source_conn) != CONNECTION_OK) + { + /* + * Here we don't have a connection to the upstream node, and are 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. + */ + check_source_server_via_barman(); + } } @@ -242,24 +259,6 @@ check_barman_config(void) } -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_server, - config_opt); - - return buf; -} - static void check_source_server() @@ -267,7 +266,7 @@ check_source_server() int server_version_num = UNKNOWN_SERVER_VERSION_NUM; char cluster_size[MAXLEN]; t_node_info node_record = T_NODE_INFO_INITIALIZER; - int query_result; + int query_result; t_extension_status extension_status; /* Attempt to connect to the upstream server to verify its configuration */ @@ -283,15 +282,12 @@ check_source_server() */ if (PQstatus(source_conn) != CONNECTION_OK) { + PQfinish(source_conn); + if (mode == barman) - { return; - } else - { - PQfinish(source_conn); exit(ERR_DB_CON); - } } /* @@ -301,7 +297,7 @@ check_source_server() /* Verify that upstream node is a supported server version */ - log_verbose(LOG_INFO, _("connected to upstream node, checking its state")); + log_verbose(LOG_INFO, _("connected to source node, checking its state")); server_version_num = check_server_version(source_conn, "master", true, NULL); @@ -357,19 +353,19 @@ check_source_server() if (!runtime_options.force) { /* schema doesn't exist */ - log_error(_("repmgr extension not found on upstream server")); + log_error(_("repmgr extension not found on source node")); log_hint(_("check that the upstream server is part of a repmgr cluster")); PQfinish(source_conn); exit(ERR_BAD_CONFIG); } - log_warning(_("repmgr extension not found on upstream server")); + log_warning(_("repmgr extension not found on source node")); } /* Fetch the source's data directory */ if (get_pg_setting(source_conn, "data_directory", upstream_data_directory) == false) { - log_error(_("unable to retrieve upstream node's data directory")); + log_error(_("unable to retrieve source node's data directory")); log_hint(_("STANDBY CLONE must be run as a database superuser")); PQfinish(source_conn); exit(ERR_BAD_CONFIG); @@ -430,3 +426,147 @@ check_source_server() } +static void +check_source_server_via_barman() +{ + char buf[MAXLEN]; + char barman_conninfo_str[MAXLEN]; + t_conninfo_param_list barman_conninfo; + char *errmsg = NULL; + bool parse_success, + command_success; + char where_condition[MAXLEN]; + PQExpBufferData command_output; + PQExpBufferData repmgr_conninfo_buf; + + int c; + + 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); + + switch(config_file_options.upstream_node_id) + { + case NO_UPSTREAM_NODE: + // XXX did we get the upstream node ID earlier? + maxlen_snprintf(where_condition, "type='master'"); + break; + default: + maxlen_snprintf(where_condition, "node_id=%d", config_file_options.upstream_node_id); + break; + } + + 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_record_found = true; + log_verbose(LOG_DEBUG, + "upstream node conninfo string extracted via barman server: %s", + recovery_conninfo_str); + + termPQExpBuffer(&command_output); +} + + +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_server, + config_opt); + + return buf; +} + + +void +get_barman_property(char *dst, char *name, char *local_repmgr_directory) +{ + PQExpBufferData command_output; + char buf[MAXLEN]; + char command[MAXLEN]; + char *p; + + 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); +} + diff --git a/strutil.c b/strutil.c index 0e07d0cf..583731cb 100644 --- a/strutil.c +++ b/strutil.c @@ -159,3 +159,31 @@ escape_string(PGconn *conn, const char *string) return escaped_string; } + + + +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; +} diff --git a/strutil.h b/strutil.h index ed739518..cfa2ea2b 100644 --- a/strutil.h +++ b/strutil.h @@ -57,5 +57,11 @@ 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); + #endif /* _STRUTIL_H_ */