mirror of
https://github.com/EnterpriseDB/repmgr.git
synced 2026-03-25 16:16:29 +00:00
Additional "standby clone" code
We'll break up the unwieldy "do_standby_clone()" function into discrete unit for easier maintenance.
This commit is contained in:
10
Makefile.in
10
Makefile.in
@@ -27,7 +27,7 @@ include Makefile.global
|
|||||||
$(info Building against PostgreSQL $(MAJORVERSION))
|
$(info Building against PostgreSQL $(MAJORVERSION))
|
||||||
|
|
||||||
REPMGR_CLIENT_OBJS = repmgr-client.o repmgr-action-master.o repmgr-action-standby.o repmgr-action-cluster.o \
|
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
|
config.o log.o strutil.o dbutils.o dirutil.o
|
||||||
REPMGRD_OBJS = repmgrd.o
|
REPMGRD_OBJS = repmgrd.o
|
||||||
|
|
||||||
$(REPMGR_CLIENT_OBJS): repmgr-client.h
|
$(REPMGR_CLIENT_OBJS): repmgr-client.h
|
||||||
@@ -52,7 +52,15 @@ maintainer-clean: additional-maintainer-clean
|
|||||||
|
|
||||||
additional-clean:
|
additional-clean:
|
||||||
rm -f repmgr-client.o
|
rm -f repmgr-client.o
|
||||||
|
rm -f repmgr-action-cluster.o
|
||||||
|
rm -f repmgr-action-master.o
|
||||||
|
rm -f repmgr-action-standby.o
|
||||||
rm -f repmgrd.o
|
rm -f repmgrd.o
|
||||||
|
rm -f config.o
|
||||||
|
rm -f dbutils.o
|
||||||
|
rm -f dirutil.o
|
||||||
|
rm -f log.o
|
||||||
|
rm -f strutil.o
|
||||||
|
|
||||||
maintainer-additional-clean: clean
|
maintainer-additional-clean: clean
|
||||||
rm -f configure
|
rm -f configure
|
||||||
|
|||||||
12
config.c
12
config.c
@@ -441,6 +441,8 @@ _parse_config(t_configuration_options *options, ItemList *error_list, ItemList *
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* barman settings */
|
/* barman settings */
|
||||||
|
else if (strcmp(name, "barman_host") == 0)
|
||||||
|
strncpy(options->barman_host, value, MAXLEN);
|
||||||
else if (strcmp(name, "barman_server") == 0)
|
else if (strcmp(name, "barman_server") == 0)
|
||||||
strncpy(options->barman_server, value, MAXLEN);
|
strncpy(options->barman_server, value, MAXLEN);
|
||||||
else if (strcmp(name, "barman_config") == 0)
|
else if (strcmp(name, "barman_config") == 0)
|
||||||
@@ -535,6 +537,16 @@ _parse_config(t_configuration_options *options, ItemList *error_list, ItemList *
|
|||||||
|
|
||||||
PQconninfoFree(conninfo_options);
|
PQconninfoFree(conninfo_options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* add warning about changed "barman_" parameter meanings */
|
||||||
|
if (options->barman_server[0] == '\0' && options->barman_server[0] != '\0')
|
||||||
|
{
|
||||||
|
item_list_append(warning_list,
|
||||||
|
_("use \"barman_host\" for the hostname of the Barman server"));
|
||||||
|
item_list_append(warning_list,
|
||||||
|
_("use \"barman_server\" for the name of the [server] section in the Barman configururation file"));
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
3
config.h
3
config.h
@@ -95,6 +95,7 @@ typedef struct
|
|||||||
int bdr_monitoring_mode;
|
int bdr_monitoring_mode;
|
||||||
|
|
||||||
/* barman settings */
|
/* barman settings */
|
||||||
|
char barman_host[MAXLEN];
|
||||||
char barman_server[MAXLEN];
|
char barman_server[MAXLEN];
|
||||||
char barman_config[MAXLEN];
|
char barman_config[MAXLEN];
|
||||||
} t_configuration_options;
|
} t_configuration_options;
|
||||||
@@ -122,7 +123,7 @@ typedef struct
|
|||||||
/* bdr settings */ \
|
/* bdr settings */ \
|
||||||
BDR_MONITORING_LOCAL, \
|
BDR_MONITORING_LOCAL, \
|
||||||
/* barman settings */ \
|
/* barman settings */ \
|
||||||
"", "" }
|
"", "", "" }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
48
dbutils.c
48
dbutils.c
@@ -195,6 +195,48 @@ establish_db_connection_by_params(const char *keywords[], const char *values[],
|
|||||||
/* =============================== */
|
/* =============================== */
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
PQconninfoOption *conninfo_option;
|
||||||
|
|
||||||
|
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
|
void
|
||||||
initialize_conninfo_params(t_conninfo_param_list *param_list, bool set_defaults)
|
initialize_conninfo_params(t_conninfo_param_list *param_list, bool set_defaults)
|
||||||
{
|
{
|
||||||
@@ -383,7 +425,7 @@ begin_transaction(PGconn *conn)
|
|||||||
|
|
||||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||||
{
|
{
|
||||||
log_error(_("Unable to begin transaction:\n %s"),
|
log_error(_("unable to begin transaction:\n %s"),
|
||||||
PQerrorMessage(conn));
|
PQerrorMessage(conn));
|
||||||
|
|
||||||
PQclear(res);
|
PQclear(res);
|
||||||
@@ -407,7 +449,7 @@ commit_transaction(PGconn *conn)
|
|||||||
|
|
||||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||||
{
|
{
|
||||||
log_error(_("Unable to commit transaction:\n %s"),
|
log_error(_("unable to commit transaction:\n %s"),
|
||||||
PQerrorMessage(conn));
|
PQerrorMessage(conn));
|
||||||
PQclear(res);
|
PQclear(res);
|
||||||
|
|
||||||
@@ -431,7 +473,7 @@ rollback_transaction(PGconn *conn)
|
|||||||
|
|
||||||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||||
{
|
{
|
||||||
log_error(_("Unable to rollback transaction:\n %s"),
|
log_error(_("unable to rollback transaction:\n %s"),
|
||||||
PQerrorMessage(conn));
|
PQerrorMessage(conn));
|
||||||
PQclear(res);
|
PQclear(res);
|
||||||
|
|
||||||
|
|||||||
@@ -121,6 +121,8 @@ PGconn *establish_db_connection_by_params(const char *keywords[],
|
|||||||
|
|
||||||
|
|
||||||
/* conninfo manipulation functions */
|
/* 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 initialize_conninfo_params(t_conninfo_param_list *param_list, bool set_defaults);
|
||||||
void copy_conninfo_params(t_conninfo_param_list *dest_list, t_conninfo_param_list *source_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 conn_to_param_list(PGconn *conn, t_conninfo_param_list *param_list);
|
||||||
|
|||||||
341
dirutil.c
Normal file
341
dirutil.c
Normal file
@@ -0,0 +1,341 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* dirmod.c
|
||||||
|
* directory handling functions
|
||||||
|
*
|
||||||
|
* Copyright (c) 2ndQuadrant, 2010-2017
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <ftw.h>
|
||||||
|
|
||||||
|
/* NB: postgres_fe must be included BEFORE check_dir */
|
||||||
|
#include <libpq-fe.h>
|
||||||
|
#include <postgres_fe.h>
|
||||||
|
|
||||||
|
#include "dirutil.h"
|
||||||
|
#include "strutil.h"
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
|
||||||
|
static bool _create_pg_dir(char *dir, bool force, bool for_witness);
|
||||||
|
static int unlink_dir_callback(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* make sure the directory either doesn't exist or is empty
|
||||||
|
* we use this function to check the new data directory and
|
||||||
|
* the directories for tablespaces
|
||||||
|
*
|
||||||
|
* This is the same check initdb does on the new PGDATA dir
|
||||||
|
*
|
||||||
|
* Returns 0 if nonexistent, 1 if exists and empty, 2 if not empty,
|
||||||
|
* or -1 if trouble accessing directory
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
check_dir(char *path)
|
||||||
|
{
|
||||||
|
DIR *chkdir;
|
||||||
|
struct dirent *file;
|
||||||
|
int result = 1;
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
|
||||||
|
chkdir = opendir(path);
|
||||||
|
|
||||||
|
if (!chkdir)
|
||||||
|
return (errno == ENOENT) ? 0 : -1;
|
||||||
|
|
||||||
|
while ((file = readdir(chkdir)) != NULL)
|
||||||
|
{
|
||||||
|
if (strcmp(".", file->d_name) == 0 ||
|
||||||
|
strcmp("..", file->d_name) == 0)
|
||||||
|
{
|
||||||
|
/* skip this and parent directory */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = 2; /* not empty */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef WIN32
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This fix is in mingw cvs (runtime/mingwex/dirent.c rev 1.4), but not in
|
||||||
|
* released version
|
||||||
|
*/
|
||||||
|
if (GetLastError() == ERROR_NO_MORE_FILES)
|
||||||
|
errno = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
closedir(chkdir);
|
||||||
|
|
||||||
|
if (errno != 0)
|
||||||
|
return -1; /* 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\": %s"),
|
||||||
|
path, 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;
|
||||||
|
|
||||||
|
#ifdef WIN32
|
||||||
|
/* skip network and drive specifiers for win32 */
|
||||||
|
if (strlen(p) >= 2)
|
||||||
|
{
|
||||||
|
if (p[0] == '/' && p[1] == '/')
|
||||||
|
{
|
||||||
|
/* network drive */
|
||||||
|
p = strstr(p + 2, "/");
|
||||||
|
if (p == NULL)
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else if (p[1] == ':' &&
|
||||||
|
((p[0] >= 'a' && p[0] <= 'z') ||
|
||||||
|
(p[0] >= 'A' && p[0] <= 'Z')))
|
||||||
|
{
|
||||||
|
/* local drive */
|
||||||
|
p += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
const size_t buf_sz = 8192;
|
||||||
|
char dirpath[buf_sz];
|
||||||
|
struct stat sb;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
/* test pgdata */
|
||||||
|
snprintf(dirpath, buf_sz, "%s/PG_VERSION", path);
|
||||||
|
if (stat(dirpath, &sb) == 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
/* test tablespace dir */
|
||||||
|
sprintf(dirpath, "ls %s/PG_*/ -I*", path);
|
||||||
|
r = system(dirpath);
|
||||||
|
if (r == 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool
|
||||||
|
create_pg_dir(char *path, bool force)
|
||||||
|
{
|
||||||
|
return _create_pg_dir(path, force, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
create_witness_pg_dir(char *path, bool force)
|
||||||
|
{
|
||||||
|
return _create_pg_dir(path, force, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool
|
||||||
|
_create_pg_dir(char *path, bool force, bool for_witness)
|
||||||
|
{
|
||||||
|
bool pg_dir = false;
|
||||||
|
|
||||||
|
/* Check this directory could be used as a PGDATA dir */
|
||||||
|
switch (check_dir(path))
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
/* dir not there, must create it */
|
||||||
|
log_info(_("creating directory \"%s\"...\n"), path);
|
||||||
|
|
||||||
|
if (!create_dir(path))
|
||||||
|
{
|
||||||
|
log_error(_("unable to create directory \"%s\"..."),
|
||||||
|
path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
/* Present but empty, fix permissions and use it */
|
||||||
|
log_info(_("checking and correcting permissions on existing directory %s ...\n"),
|
||||||
|
path);
|
||||||
|
|
||||||
|
if (!set_dir_permissions(path))
|
||||||
|
{
|
||||||
|
log_error(_("unable to change permissions of directory \"%s\": %s"),
|
||||||
|
path, strerror(errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
/* Present and not empty */
|
||||||
|
log_warning(_("directory \"%s\" exists but is not empty\n"),
|
||||||
|
path);
|
||||||
|
|
||||||
|
pg_dir = is_pg_dir(path);
|
||||||
|
|
||||||
|
|
||||||
|
if (pg_dir && force)
|
||||||
|
{
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The witness server does not store any data other than a copy of the
|
||||||
|
* repmgr metadata, so in --force mode we can simply overwrite the
|
||||||
|
* directory.
|
||||||
|
*
|
||||||
|
* For non-witness servers, we'll leave the data in place, both to reduce
|
||||||
|
* the risk of unintentional data loss and to make it possible for the
|
||||||
|
* data directory to be brought up-to-date with rsync.
|
||||||
|
*/
|
||||||
|
if (for_witness)
|
||||||
|
{
|
||||||
|
log_notice(_("deleting existing data directory \"%s\""), path);
|
||||||
|
nftw(path, unlink_dir_callback, 64, FTW_DEPTH | FTW_PHYS);
|
||||||
|
}
|
||||||
|
/* Let it continue */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (pg_dir && !force)
|
||||||
|
{
|
||||||
|
log_hint(_("This 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 -F/--force option\n"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
log_error(_("could not access directory \"%s\": %s"),
|
||||||
|
path, strerror(errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
19
dirutil.h
Normal file
19
dirutil.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* dirutil.h
|
||||||
|
* Copyright (c) 2ndQuadrant, 2010-2017
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _DIRUTIL_H_
|
||||||
|
#define _DIRUTIL_H_
|
||||||
|
|
||||||
|
extern int mkdir_p(char *path, mode_t omode);
|
||||||
|
extern bool set_dir_permissions(char *path);
|
||||||
|
|
||||||
|
extern int check_dir(char *path);
|
||||||
|
extern bool create_dir(char *path);
|
||||||
|
extern bool is_pg_dir(char *path);
|
||||||
|
extern bool create_pg_dir(char *path, bool force);
|
||||||
|
extern bool create_witness_pg_dir(char *path, bool force);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -7,10 +7,19 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "repmgr.h"
|
#include "repmgr.h"
|
||||||
|
#include "dirutil.h"
|
||||||
|
|
||||||
#include "repmgr-client-global.h"
|
#include "repmgr-client-global.h"
|
||||||
#include "repmgr-action-standby.h"
|
#include "repmgr-action-standby.h"
|
||||||
|
|
||||||
|
static char local_data_directory[MAXPGPATH];
|
||||||
|
|
||||||
|
/* used by barman mode */
|
||||||
|
static char local_repmgr_tmp_directory[MAXPGPATH];
|
||||||
|
|
||||||
|
|
||||||
|
static void check_barman_config(void);
|
||||||
|
static char *make_barman_ssh_command(char *buf);
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -20,7 +29,7 @@ do_standby_clone(void)
|
|||||||
PGconn *source_conn = NULL;
|
PGconn *source_conn = NULL;
|
||||||
PGresult *res;
|
PGresult *res;
|
||||||
|
|
||||||
int server_version_num = -1;
|
int server_version_num = UNKNOWN_SERVER_VERSION_NUM;
|
||||||
char cluster_size[MAXLEN];
|
char cluster_size[MAXLEN];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -32,6 +41,8 @@ do_standby_clone(void)
|
|||||||
bool upstream_record_found = false;
|
bool upstream_record_found = false;
|
||||||
int upstream_node_id = UNKNOWN_NODE_ID;
|
int upstream_node_id = UNKNOWN_NODE_ID;
|
||||||
|
|
||||||
|
char upstream_data_directory[MAXPGPATH];
|
||||||
|
bool local_data_directory_provided = false;
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
barman,
|
barman,
|
||||||
@@ -39,19 +50,13 @@ do_standby_clone(void)
|
|||||||
pg_basebackup
|
pg_basebackup
|
||||||
} mode;
|
} mode;
|
||||||
|
|
||||||
/* used by barman mode */
|
|
||||||
char datadir_list_filename[MAXLEN];
|
|
||||||
char local_repmgr_tmp_directory[MAXPGPATH];
|
|
||||||
|
|
||||||
puts("standby clone");
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* detecting the cloning mode
|
* detecting the cloning mode
|
||||||
*/
|
*/
|
||||||
if (runtime_options.rsync_only)
|
if (runtime_options.rsync_only)
|
||||||
mode = rsync;
|
mode = rsync;
|
||||||
else if (strcmp(config_file_options.barman_server, "") != 0 && ! runtime_options.without_barman)
|
else if (strcmp(config_file_options.barman_host, "") != 0 && ! runtime_options.without_barman)
|
||||||
mode = barman;
|
mode = barman;
|
||||||
else
|
else
|
||||||
mode = pg_basebackup;
|
mode = pg_basebackup;
|
||||||
@@ -72,4 +77,179 @@ do_standby_clone(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If dest_dir (-D/--pgdata) was provided, this will become the new data
|
||||||
|
* directory (otherwise repmgr will default to using the same directory
|
||||||
|
* path as on the source host).
|
||||||
|
*
|
||||||
|
* Note that barman mode requires -D/--pgdata.
|
||||||
|
*
|
||||||
|
* If -D/--pgdata is not supplied, and we're not cloning from barman,
|
||||||
|
* the source host's data directory will be fetched later, after
|
||||||
|
* we've connected to it.
|
||||||
|
*/
|
||||||
|
if (runtime_options.data_dir[0])
|
||||||
|
{
|
||||||
|
local_data_directory_provided = true;
|
||||||
|
log_notice(_("destination directory '%s' provided"),
|
||||||
|
runtime_options.data_dir);
|
||||||
|
}
|
||||||
|
else if (mode == barman)
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* target directory (-D/--pgdata) provided - use that as new data directory
|
||||||
|
* (useful when executing backup on local machine only or creating the backup
|
||||||
|
* in a different local directory when backup source is a remote host)
|
||||||
|
*/
|
||||||
|
if (local_data_directory_provided == true)
|
||||||
|
{
|
||||||
|
strncpy(local_data_directory, runtime_options.data_dir, MAXPGPATH);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 default values as seen by libpq,
|
||||||
|
* and overwrite them 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, true);
|
||||||
|
|
||||||
|
copy_conninfo_params(&recovery_conninfo, &source_conninfo);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If application_name is set in repmgr.conf's conninfo parameter, use
|
||||||
|
* this value (if the source host was provided as a conninfo string, any
|
||||||
|
* application_name values set there will be overridden; we assume the only
|
||||||
|
* reason to pass an application_name via the command line is in the
|
||||||
|
* rare corner case where a user wishes to clone a server without
|
||||||
|
* providing repmgr.conf)
|
||||||
|
*/
|
||||||
|
if (strlen(config_file_options.conninfo))
|
||||||
|
{
|
||||||
|
char application_name[MAXLEN] = "";
|
||||||
|
|
||||||
|
get_conninfo_value(config_file_options.conninfo, "application_name", application_name);
|
||||||
|
if (strlen(application_name))
|
||||||
|
{
|
||||||
|
param_set(&recovery_conninfo, "application_name", application_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
check_barman_config(void)
|
||||||
|
{
|
||||||
|
char datadir_list_filename[MAXLEN];
|
||||||
|
char barman_command_buf[MAXLEN] = "";
|
||||||
|
|
||||||
|
char command[MAXLEN];
|
||||||
|
bool command_ok;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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\n"));
|
||||||
|
|
||||||
|
exit(ERR_BARMAN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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_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\n"));
|
||||||
|
exit(ERR_BAD_CONFIG);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ 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 int check_server_version(PGconn *conn, char *server_type, bool exit_on_error, char *server_version_string);
|
||||||
extern bool create_repmgr_extension(PGconn *conn);
|
extern bool create_repmgr_extension(PGconn *conn);
|
||||||
extern int test_ssh_connection(char *host, char *remote_user);
|
extern int test_ssh_connection(char *host, char *remote_user);
|
||||||
|
extern bool local_command(const char *command, PQExpBufferData *outputbuf);
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1047,3 +1047,47 @@ test_ssh_connection(char *host, char *remote_user)
|
|||||||
|
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Execute a command locally. If outputbuf == NULL, discard the
|
||||||
|
* output.
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
local_command(const char *command, PQExpBufferData *outputbuf)
|
||||||
|
{
|
||||||
|
FILE *fp;
|
||||||
|
char output[MAXLEN];
|
||||||
|
int retval;
|
||||||
|
|
||||||
|
if (outputbuf == NULL)
|
||||||
|
{
|
||||||
|
retval = system(command);
|
||||||
|
return (retval == 0) ? true : false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fp = popen(command, "r");
|
||||||
|
|
||||||
|
if (fp == NULL)
|
||||||
|
{
|
||||||
|
log_error(_("unable to execute local command:\n%s"), command);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO: better error handling */
|
||||||
|
while (fgets(output, MAXLEN, fp) != NULL)
|
||||||
|
{
|
||||||
|
appendPQExpBuffer(outputbuf, "%s", output);
|
||||||
|
}
|
||||||
|
|
||||||
|
pclose(fp);
|
||||||
|
|
||||||
|
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 true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -130,7 +130,6 @@ static struct option long_options[] =
|
|||||||
|
|
||||||
static void do_help(void);
|
static void do_help(void);
|
||||||
|
|
||||||
static void do_standby_clone(void);
|
|
||||||
|
|
||||||
|
|
||||||
static const char *action_name(const int action);
|
static const char *action_name(const int action);
|
||||||
|
|||||||
1
repmgr.h
1
repmgr.h
@@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
#define MIN_SUPPORTED_VERSION "9.3"
|
#define MIN_SUPPORTED_VERSION "9.3"
|
||||||
#define MIN_SUPPORTED_VERSION_NUM 90300
|
#define MIN_SUPPORTED_VERSION_NUM 90300
|
||||||
|
#define UNKNOWN_SERVER_VERSION_NUM -1
|
||||||
|
|
||||||
#define NODE_NOT_FOUND -1
|
#define NODE_NOT_FOUND -1
|
||||||
#define NO_UPSTREAM_NODE -1
|
#define NO_UPSTREAM_NODE -1
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "strutil.h"
|
#include "strutil.h"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user