Merge pull request #191 from gciolli/feature-barman-support

Add Barman support to repmgr standby clone
This commit is contained in:
Ian Barwick
2016-08-18 15:04:46 +09:00
5 changed files with 980 additions and 322 deletions

View File

@@ -522,6 +522,78 @@ standby's upstream server is the replication cluster master. While of limited
use in a simple master/standby replication cluster, this information is required use in a simple master/standby replication cluster, this information is required
to effectively manage cascading replication (see below). to effectively manage cascading replication (see below).
### Using Barman to clone a standby
`repmgr standby clone` also supports Barman, the Backup and
Replication manager (http://www.pgbarman.org/), as a provider of both
base backups and WAL files.
Barman support provides the following advantages:
- the primary node does not need to perform a new backup every time a
new standby is cloned;
- a standby node can be disconnected for longer periods without losing
the ability to catch up, and without causing accumulation of WAL
files on the primary node;
- therefore, `repmgr` does not need to use replication slots, and the
primary node does not need to set `wal_keep_segments`.
> *NOTE*: In view of the above, Barman support is incompatible with
> the `use_replication_slots` setting in `repmgr.conf`.
In order to enable Barman support for `repmgr standby clone`, you must
ensure that:
- the name of the server configured in Barman is equal to the
`cluster_name` setting in `repmgr.conf`;
- the `barman_server` setting in `repmgr.conf` is set to the SSH
hostname of the Barman server;
- the `restore_command` setting in `repmgr.conf` is configured to
use a copy of the `barman-wal-restore.py` script shipped with Barman
(see below);
- the Barman catalogue includes at least one valid backup for this
server.
> *NOTE*: Barman support is automatically enabled if `barman_server`
> is set. Normally it is a good practice to use Barman, for instance
> when fetching a base backup while cloning a standby; in any case,
> Barman mode can be disabled using the `--without-barman` command
> line option.
> *NOTE*: if you have a non-default SSH configuration on the Barman
> server, e.g. using a port other than 22, then you can set those
> parameters in a dedicated Host section in `~/.ssh/config`
> corresponding to the value of `barman_server` in `repmgr.conf`. See
> the "Host" section in `man 5 ssh_config` for more details.
`barman-wal-restore.py` is a Python script provided by the Barman
development team, which must be copied in a location accessible to
`repmgr`, and marked as executable; `restore_command` must then be
set in `repmgr.conf` as follows:
<script> <Barman hostname> <cluster_name> %f %p
For instance, suppose that we have installed Barman on the `barmansrv`
host, and that we have placed a copy of `barman-wal-restore.py` into
the `/usr/local/bin` directory. First, we ensure that the script is
executable:
sudo chmod +x /usr/local/bin/barman-wal-restore.py
Then we check that `repmgr.conf` includes the following lines:
barman_server=barmansrv
restore_command=/usr/local/bin/barman-wal-restore.py barmansrv test %f %p
Now we can clone a standby using the Barman server:
$ repmgr -h node1 -D 9.5/main -f /etc/repmgr.conf standby clone
[2016-06-12 20:08:35] [NOTICE] destination directory '9.5/main' provided
[2016-06-12 20:08:35] [NOTICE] getting backup from Barman...
[2016-06-12 20:08:36] [NOTICE] standby clone (from Barman) complete
[2016-06-12 20:08:36] [NOTICE] you can now start your PostgreSQL server
[2016-06-12 20:08:36] [HINT] for example : pg_ctl -D 9.5/data start
[2016-06-12 20:08:36] [HINT] After starting the server, you need to register this standby with "repmgr standby register"
Advanced options for cloning a standby Advanced options for cloning a standby
-------------------------------------- --------------------------------------
@@ -559,6 +631,10 @@ and destination server as the contents of files existing on both servers need
to be compared, meaning this method is not necessarily faster than making a to be compared, meaning this method is not necessarily faster than making a
fresh clone with `pg_basebackup`. fresh clone with `pg_basebackup`.
> *NOTE*: `barman-wal-restore.py` supports command line switches to
> control parallelism (`--parallel=N`) and compression (`--bzip2`,
> `--gzip`).
### Dealing with PostgreSQL configuration files ### Dealing with PostgreSQL configuration files
By default, `repmgr` will attempt to copy the standard configuration files By default, `repmgr` will attempt to copy the standard configuration files

View File

@@ -215,6 +215,7 @@ parse_config(t_configuration_options *options)
options->upstream_node = NO_UPSTREAM_NODE; options->upstream_node = NO_UPSTREAM_NODE;
options->use_replication_slots = 0; options->use_replication_slots = 0;
memset(options->conninfo, 0, sizeof(options->conninfo)); memset(options->conninfo, 0, sizeof(options->conninfo));
memset(options->barman_server, 0, sizeof(options->barman_server));
options->failover = MANUAL_FAILOVER; options->failover = MANUAL_FAILOVER;
options->priority = DEFAULT_PRIORITY; options->priority = DEFAULT_PRIORITY;
memset(options->node_name, 0, sizeof(options->node_name)); memset(options->node_name, 0, sizeof(options->node_name));
@@ -310,6 +311,8 @@ parse_config(t_configuration_options *options)
options->upstream_node = repmgr_atoi(value, "upstream_node", &config_errors, false); options->upstream_node = repmgr_atoi(value, "upstream_node", &config_errors, false);
else if (strcmp(name, "conninfo") == 0) else if (strcmp(name, "conninfo") == 0)
strncpy(options->conninfo, value, MAXLEN); strncpy(options->conninfo, value, MAXLEN);
else if (strcmp(name, "barman_server") == 0)
strncpy(options->barman_server, value, MAXLEN);
else if (strcmp(name, "rsync_options") == 0) else if (strcmp(name, "rsync_options") == 0)
strncpy(options->rsync_options, value, QUERY_STR_LEN); strncpy(options->rsync_options, value, QUERY_STR_LEN);
else if (strcmp(name, "ssh_options") == 0) else if (strcmp(name, "ssh_options") == 0)
@@ -635,6 +638,13 @@ reload_config(t_configuration_options *orig_options)
config_changed = true; config_changed = true;
} }
/* barman_server */
if (strcmp(orig_options->barman_server, new_options.barman_server) != 0)
{
strcpy(orig_options->barman_server, new_options.barman_server);
config_changed = true;
}
/* node */ /* node */
if (orig_options->node != new_options.node) if (orig_options->node != new_options.node)
{ {

View File

@@ -58,6 +58,7 @@ typedef struct
int node; int node;
int upstream_node; int upstream_node;
char conninfo[MAXLEN]; char conninfo[MAXLEN];
char barman_server[MAXLEN];
int failover; int failover;
int priority; int priority;
char node_name[MAXLEN]; char node_name[MAXLEN];
@@ -91,7 +92,7 @@ typedef struct
* The following will initialize the structure with a minimal set of options; * The following will initialize the structure with a minimal set of options;
* actual defaults are set in parse_config() before parsing the configuration file * actual defaults are set in parse_config() before parsing the configuration file
*/ */
#define T_CONFIGURATION_OPTIONS_INITIALIZER { "", -1, NO_UPSTREAM_NODE, "", MANUAL_FAILOVER, -1, "", "", "", "", "", "", "", "", "", "", -1, -1, -1, "", "", "", "", "", 0, 0, 0, 0, "", { NULL, NULL }, {NULL, NULL} } #define T_CONFIGURATION_OPTIONS_INITIALIZER { "", -1, NO_UPSTREAM_NODE, "", "", MANUAL_FAILOVER, -1, "", "", "", "", "", "", "", "", "", "", -1, -1, -1, "", "", "", "", "", 0, 0, 0, 0, "", { NULL, NULL }, {NULL, NULL} }
typedef struct ItemListCell typedef struct ItemListCell
{ {
@@ -105,6 +106,22 @@ typedef struct ItemList
ItemListCell *tail; ItemListCell *tail;
} ItemList; } ItemList;
typedef struct TablespaceDataListCell
{
struct TablespaceDataListCell *next;
char *name;
char *oid;
char *location;
/* optional payload */
FILE *f;
} TablespaceDataListCell;
typedef struct TablespaceDataList
{
TablespaceDataListCell *head;
TablespaceDataListCell *tail;
} TablespaceDataList;
void set_progname(const char *argv0); void set_progname(const char *argv0);
const char * progname(void); const char * progname(void);

683
repmgr.c
View File

@@ -103,6 +103,12 @@ static void check_master_standby_version_match(PGconn *conn, PGconn *master_conn
static int check_server_version(PGconn *conn, char *server_type, bool exit_on_error, char *server_version_string); static int check_server_version(PGconn *conn, char *server_type, bool exit_on_error, char *server_version_string);
static bool check_upstream_config(PGconn *conn, int server_version_num, bool exit_on_error); static bool check_upstream_config(PGconn *conn, int server_version_num, bool exit_on_error);
static bool update_node_record_set_master(PGconn *conn, int this_node_id); static bool update_node_record_set_master(PGconn *conn, int this_node_id);
static void tablespace_data_append(TablespaceDataList *list, const char *name, const char *oid, const char *location);
static int get_tablespace_data(PGconn *upstream_conn, TablespaceDataList *list);
static int get_tablespace_data_barman(char *, TablespaceDataList *);
static char *string_skip_prefix(const char *prefix, char *string);
static char *string_remove_trailing_newlines(char *string);
static char *make_pg_path(char *file); static char *make_pg_path(char *file);
@@ -130,6 +136,7 @@ static void exit_with_errors(void);
static void print_error_list(ItemList *error_list, int log_level); static void print_error_list(ItemList *error_list, int log_level);
static bool remote_command(const char *host, const char *user, const char *command, PQExpBufferData *outputbuf); static bool remote_command(const char *host, const char *user, const char *command, PQExpBufferData *outputbuf);
static bool local_command(const char *command, PQExpBufferData *outputbuf);
static void format_db_cli_params(const char *conninfo, char *output); static void format_db_cli_params(const char *conninfo, char *output);
static bool copy_file(const char *old_filename, const char *new_filename); static bool copy_file(const char *old_filename, const char *new_filename);
@@ -208,6 +215,7 @@ main(int argc, char **argv)
{"pwprompt", optional_argument, NULL, OPT_PWPROMPT}, {"pwprompt", optional_argument, NULL, OPT_PWPROMPT},
{"csv", no_argument, NULL, OPT_CSV}, {"csv", no_argument, NULL, OPT_CSV},
{"node", required_argument, NULL, OPT_NODE}, {"node", required_argument, NULL, OPT_NODE},
{"without-barman", no_argument, NULL, OPT_WITHOUT_BARMAN},
{"version", no_argument, NULL, 'V'}, {"version", no_argument, NULL, 'V'},
/* Following options deprecated */ /* Following options deprecated */
{"local-port", required_argument, NULL, 'l'}, {"local-port", required_argument, NULL, 'l'},
@@ -508,6 +516,9 @@ main(int argc, char **argv)
case OPT_NODE: case OPT_NODE:
runtime_options.node = repmgr_atoi(optarg, "--node", &cli_errors, false); runtime_options.node = repmgr_atoi(optarg, "--node", &cli_errors, false);
break; break;
case OPT_WITHOUT_BARMAN:
runtime_options.without_barman = true;
break;
/* deprecated options - output a warning */ /* deprecated options - output a warning */
case 'l': case 'l':
@@ -842,6 +853,18 @@ main(int argc, char **argv)
log_warning(_("-w/--wal-keep-segments has no effect when replication slots in use\n")); log_warning(_("-w/--wal-keep-segments has no effect when replication slots in use\n"));
} }
/*
* STANDBY CLONE in Barman mode is incompatible with
* `use_replication_slots`.
*/
if (action == STANDBY_CLONE &&
! runtime_options.without_barman
&& strcmp(options.barman_server, "") == 0)
{
log_err(_("STANDBY CLONE in Barman mode is incompatible with configuration option \"use_replication_slots\""));
}
/* Initialise the repmgr schema name */ /* Initialise the repmgr schema name */
maxlen_snprintf(repmgr_schema, "%s%s", DEFAULT_REPMGR_SCHEMA_PREFIX, maxlen_snprintf(repmgr_schema, "%s%s", DEFAULT_REPMGR_SCHEMA_PREFIX,
options.cluster_name); options.cluster_name);
@@ -1511,17 +1534,169 @@ do_standby_unregister(void)
return; return;
} }
void
tablespace_data_append(TablespaceDataList *list, const char *name, const char *oid, const char *location)
{
TablespaceDataListCell *cell;
cell = (TablespaceDataListCell *) pg_malloc0(sizeof(TablespaceDataListCell));
if (cell == NULL)
{
log_err(_("unable to allocate memory; terminating.\n"));
exit(ERR_BAD_CONFIG);
}
cell->oid = pg_malloc(1 + strlen(oid ));
cell->name = pg_malloc(1 + strlen(name ));
cell->location = pg_malloc(1 + strlen(location));
strncpy(cell->oid , oid , 1 + strlen(oid ));
strncpy(cell->name , name , 1 + strlen(name ));
strncpy(cell->location, location, 1 + strlen(location));
if (list->tail)
list->tail->next = cell;
else
list->head = cell;
list->tail = cell;
}
int
get_tablespace_data(PGconn *upstream_conn, TablespaceDataList *list)
{
int i, retval;
char sqlquery[QUERY_STR_LEN];
PGresult *res;
sqlquery_snprintf(sqlquery,
" SELECT spcname, oid, pg_tablespace_location(oid) AS spclocation "
" FROM pg_tablespace "
" WHERE spcname NOT IN ('pg_default', 'pg_global')");
res = PQexec(upstream_conn, sqlquery);
if (PQresultStatus(res) != PGRES_TUPLES_OK)
{
log_err(_("unable to execute tablespace query: %s\n"),
PQerrorMessage(upstream_conn));
PQclear(res);
return retval;
}
for (i = 0; i < PQntuples(res); i++)
tablespace_data_append(list, PQgetvalue(res, i, 0), PQgetvalue(res, i, 1),
PQgetvalue(res, i, 2));
PQclear(res);
return retval;
}
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;
}
int
get_tablespace_data_barman
( char *tablespace_data_barman,
TablespaceDataList *tablespace_list)
{
/*
* Example:
* [('main', 24674, '/var/lib/postgresql/tablespaces/9.5/main'), ('alt', 24678, '/var/lib/postgresql/tablespaces/9.5/alt')]
*/
char name[MAXLEN];
char oid[MAXLEN];
char location[MAXPGPATH];
char *p = tablespace_data_barman;
int i;
tablespace_list->head = NULL;
tablespace_list->tail = NULL;
p = string_skip_prefix("[", p);
if (p == NULL) return -1;
while (*p == '(')
{
p = string_skip_prefix("('", p);
if (p == NULL) return -1;
i = strcspn(p, "'");
strncpy(name, p, i);
name[i] = 0;
p = string_skip_prefix("', ", p + i);
if (p == NULL) return -1;
i = strcspn(p, ",");
strncpy(oid, p, i);
oid[i] = 0;
p = string_skip_prefix(", '", p + i);
if (p == NULL) return -1;
i = strcspn(p, "'");
strncpy(location, p, i);
location[i] = 0;
p = string_skip_prefix("')", p + i);
if (p == NULL) return -1;
tablespace_data_append (tablespace_list, name, oid, location);
if (*p == ']')
break;
p = string_skip_prefix(", ", p);
if (p == NULL) return -1;
}
return SUCCESS;
}
static void static void
do_standby_clone(void) do_standby_clone(void)
{ {
PGconn *primary_conn = NULL; PGconn *primary_conn = NULL;
PGconn *upstream_conn; PGconn *upstream_conn = NULL;
PGresult *res; PGresult *res;
enum {
barman,
rsync,
pg_basebackup
} mode;
char sqlquery[QUERY_STR_LEN]; char sqlquery[QUERY_STR_LEN];
int server_version_num; int server_version_num = -1;
char cluster_size[MAXLEN]; char cluster_size[MAXLEN];
@@ -1536,6 +1711,8 @@ do_standby_clone(void)
char master_data_directory[MAXPGPATH]; char master_data_directory[MAXPGPATH];
char local_data_directory[MAXPGPATH]; char local_data_directory[MAXPGPATH];
char local_repmgr_directory[MAXPGPATH];
char master_config_file[MAXPGPATH] = ""; char master_config_file[MAXPGPATH] = "";
char local_config_file[MAXPGPATH] = ""; char local_config_file[MAXPGPATH] = "";
bool config_file_outside_pgdata = false; bool config_file_outside_pgdata = false;
@@ -1556,6 +1733,15 @@ do_standby_clone(void)
PQExpBufferData event_details; PQExpBufferData event_details;
/*
* Detecting the appropriate mode
*/
if (runtime_options.rsync_only)
mode = rsync;
else if (strcmp(options.barman_server, "") != 0 && ! runtime_options.without_barman)
mode = barman;
else
mode = pg_basebackup;
/* /*
* If dest_dir (-D/--pgdata) was provided, this will become the new data * If dest_dir (-D/--pgdata) was provided, this will become the new data
@@ -1569,6 +1755,9 @@ do_standby_clone(void)
runtime_options.dest_dir); runtime_options.dest_dir);
} }
if (mode != barman)
{
param_set("application_name", options.node_name); param_set("application_name", options.node_name);
/* Connect to check configuration */ /* Connect to check configuration */
@@ -1749,6 +1938,8 @@ do_standby_clone(void)
PQclear(res); PQclear(res);
}
/* /*
* target directory (-D/--pgdata) provided - use that as new data directory * target directory (-D/--pgdata) provided - use that as new data directory
* (useful when executing backup on local machine only or creating the backup * (useful when executing backup on local machine only or creating the backup
@@ -1761,6 +1952,12 @@ do_standby_clone(void)
strncpy(local_hba_file, runtime_options.dest_dir, MAXPGPATH); strncpy(local_hba_file, runtime_options.dest_dir, MAXPGPATH);
strncpy(local_ident_file, runtime_options.dest_dir, MAXPGPATH); strncpy(local_ident_file, runtime_options.dest_dir, MAXPGPATH);
} }
else if (mode == barman)
{
log_err(_("Barman mode requires a destination directory\n"));
log_hint(_("use -D/--data-dir to explicitly specify a data directory\n"));
exit(ERR_BAD_CONFIG);
}
/* /*
* Otherwise use the same data directory as on the remote host * Otherwise use the same data directory as on the remote host
*/ */
@@ -1776,9 +1973,9 @@ do_standby_clone(void)
} }
/* /*
* When using rsync only, we need to check the SSH connection early * In rsync mode, we need to check the SSH connection early
*/ */
if (runtime_options.rsync_only) if (mode == rsync)
{ {
r = test_ssh_connection(runtime_options.host, runtime_options.remote_user); r = test_ssh_connection(runtime_options.host, runtime_options.remote_user);
if (r != 0) if (r != 0)
@@ -1806,8 +2003,11 @@ do_standby_clone(void)
* If replication slots requested, create appropriate slot on * If replication slots requested, create appropriate slot on
* the primary; this must be done before pg_start_backup() is * the primary; this must be done before pg_start_backup() is
* issued, either by us or by pg_basebackup. * issued, either by us or by pg_basebackup.
*
* Replication slots are not supported (and not very useful
* anyway) in Barman mode.
*/ */
if (options.use_replication_slots) if (mode != barman && options.use_replication_slots)
{ {
if (create_replication_slot(upstream_conn, repmgr_slot_name, server_version_num) == false) if (create_replication_slot(upstream_conn, repmgr_slot_name, server_version_num) == false)
{ {
@@ -1816,22 +2016,324 @@ do_standby_clone(void)
} }
} }
if (runtime_options.rsync_only) if (mode == rsync)
{ {
log_notice(_("starting backup (using rsync)...\n")); log_notice(_("starting backup (using rsync)...\n"));
} }
else else if (mode == barman)
{
log_notice(_("getting backup from Barman...\n"));
}
else if (mode == pg_basebackup)
{ {
log_notice(_("starting backup (using pg_basebackup)...\n")); log_notice(_("starting backup (using pg_basebackup)...\n"));
if (runtime_options.fast_checkpoint == false) if (runtime_options.fast_checkpoint == false)
log_hint(_("this may take some time; consider using the -c/--fast-checkpoint option\n")); log_hint(_("this may take some time; consider using the -c/--fast-checkpoint option\n"));
} }
if (runtime_options.rsync_only) if (mode == barman || mode == rsync)
{ {
char command[MAXLEN];
char filename[MAXLEN];
char buf[MAXLEN];
char backup_directory[MAXLEN];
char backup_id[MAXLEN] = "";
char datadir_list_filename[MAXLEN];
char *p, *q;
PQExpBufferData command_output;
TablespaceDataList tablespace_list = { NULL, NULL };
TablespaceDataListCell *cell_t;
PQExpBufferData tablespace_map; PQExpBufferData tablespace_map;
bool tablespace_map_rewrite = false; bool tablespace_map_rewrite = false;
if (mode == barman)
{
bool command_ok;
/*
* Check that there is at least one valid backup
*/
maxlen_snprintf(command, "ssh %s barman show-backup %s latest > /dev/null",
options.barman_server,
options.cluster_name);
command_ok = local_command(command, NULL);
if (command_ok == false)
{
log_err(_("No valid backup for server %s was found in the Barman catalogue\n"),
options.barman_server);
log_hint(_("Refer to the Barman documentation for more information\n"));
exit(ERR_INTERNAL);
}
/*
* Locate Barman's backup directory
*/
maxlen_snprintf(command, "ssh %s barman show-server %s | grep 'backup_directory'",
options.barman_server,
options.cluster_name);
initPQExpBuffer(&command_output);
(void)local_command(
command,
&command_output);
p = string_skip_prefix("\tbackup_directory: ", command_output.data);
if (p == NULL)
{
log_err("Unexpected output from Barman: %s\n",
command_output.data);
exit(ERR_INTERNAL);
}
strncpy(backup_directory, p, MAXLEN);
string_remove_trailing_newlines(backup_directory);
termPQExpBuffer(&command_output);
/*
* Create the local repmgr subdirectory
*/
maxlen_snprintf(local_repmgr_directory, "%s/repmgr", local_data_directory );
maxlen_snprintf(datadir_list_filename, "%s/data.txt", local_repmgr_directory);
if (!create_pg_dir(local_repmgr_directory, runtime_options.force))
{
log_err(_("unable to use directory %s ...\n"),
local_repmgr_directory);
log_hint(_("use -F/--force option to force this directory to be overwritten\n"));
r = ERR_BAD_CONFIG;
retval = ERR_BAD_CONFIG;
goto stop_backup;
}
/*
* Read the list of backup files into a local file. In the
* process:
*
* - determine the backup ID;
* - check, and remove, the prefix;
* - detect tablespaces;
* - filter files in one list per tablespace;
*/
{
FILE *fi; /* input stream */
FILE *fd; /* output for data.txt */
char prefix[MAXLEN];
char output[MAXLEN];
int n;
maxlen_snprintf(command, "ssh %s barman list-files --target=data %s latest",
options.barman_server,
options.cluster_name);
fi = popen(command, "r");
if (fi == NULL)
{
log_err("Cannot launch command: %s\n", command);
exit(ERR_INTERNAL);
}
fd = fopen(datadir_list_filename, "w");
if (fd == NULL)
{
log_err("Cannot open file: %s\n", datadir_list_filename);
exit(ERR_INTERNAL);
}
maxlen_snprintf(prefix, "%s/base/", backup_directory);
while (fgets(output, MAXLEN, fi) != NULL)
{
/*
* Remove prefix
*/
p = string_skip_prefix(prefix, output);
if (p == NULL)
{
log_err("Unexpected output from \"barman list-files\": %s\n",
output);
exit(ERR_INTERNAL);
}
/*
* Remove and note backup ID; copy backup.info
*/
if (! strcmp(backup_id, ""))
{
FILE *fi2;
n = strcspn(p, "/");
strncpy(backup_id, p, n);
strncat(prefix,backup_id,MAXLEN-1);
strncat(prefix,"/",MAXLEN-1);
p = string_skip_prefix(backup_id, p);
p = string_skip_prefix("/", p);
/*
* Copy backup.info
*/
maxlen_snprintf(command,
"rsync -a %s:%s/base/%s/backup.info %s",
options.barman_server,
backup_directory,
backup_id,
local_repmgr_directory);
(void)local_command(
command,
&command_output);
/*
* Get tablespace data
*/
maxlen_snprintf(filename, "%s/backup.info",
local_repmgr_directory);
fi2 = fopen(filename, "r");
if (fi2 == NULL)
{
log_err("Cannot open file: %s\n", filename);
exit(ERR_INTERNAL);
}
while (fgets(buf, MAXLEN, fi2) != NULL)
{
q = string_skip_prefix("tablespaces=", buf);
if (q != NULL && strncmp(q, "None\n", 5))
{
get_tablespace_data_barman
(q, &tablespace_list);
}
q = string_skip_prefix("version=", buf);
if (q != NULL)
{
server_version_num = strtol(q, NULL, 10);
}
}
fclose(fi2);
unlink(filename);
continue;
}
/*
* Skip backup.info
*/
if (string_skip_prefix("backup.info", p))
continue;
/*
* Filter data directory files
*/
if ((q = string_skip_prefix("data/", p)) != NULL)
{
fputs(q, fd);
continue;
}
/*
* Filter other files (i.e. tablespaces)
*/
for (cell_t = tablespace_list.head; cell_t; cell_t = cell_t->next)
{
if ((q = string_skip_prefix(cell_t->oid, p)) != NULL && *q == '/')
{
if (cell_t->f == NULL)
{
maxlen_snprintf(filename, "%s/%s.txt", local_repmgr_directory, cell_t->oid);
cell_t->f = fopen(filename, "w");
if (cell_t->f == NULL)
{
log_err("Cannot open file: %s\n", filename);
exit(ERR_INTERNAL);
}
}
fputs(q + 1, cell_t->f);
break;
}
}
}
fclose(fd);
pclose(fi);
}
/* For 9.5 and greater, create our own tablespace_map file */
if (server_version_num >= 90500)
{
initPQExpBuffer(&tablespace_map);
}
/*
* As of Barman version 1.6.1, the file structure of a backup
* is as follows:
*
* base/ - base backup
* wals/ - WAL files associated to the backup
*
* base/<ID> - backup files
*
* here ID has the standard timestamp form yyyymmddThhmmss
*
* base/<ID>/backup.info - backup metadata, in text format
* base/<ID>/data - data directory
* base/<ID>/<OID> - tablespace with the given oid
*/
/*
* Copy all backup files from the Barman server
*/
maxlen_snprintf(command,
"rsync --progress -a --files-from=%s %s:%s/base/%s/data %s",
datadir_list_filename,
options.barman_server,
backup_directory,
backup_id,
local_data_directory);
(void)local_command(
command,
&command_output);
unlink(datadir_list_filename);
/*
* We must create some PGDATA subdirectories because they are
* not included in the Barman backup.
*/
{
const char* const dirs[] = {
/* Only from 9.5 */
"pg_commit_ts",
/* Only from 9.4 */
"pg_dynshmem", "pg_logical", "pg_replslot",
/* Already in 9.3 */
"pg_serial", "pg_snapshots", "pg_stat", "pg_stat_tmp", "pg_tblspc",
"pg_twophase", "pg_xlog", 0
};
const int vers[] = {
90500,
90400, 90400, 90400,
0, 0, 0, 0, 0,
0, 0, 0
};
for (i = 0; dirs[i]; i++)
{
if (vers[i] > 0 && server_version_num < vers[i])
continue;
maxlen_snprintf(filename, "%s/%s", local_data_directory, dirs[i]);
if(mkdir(filename, S_IRWXU) != 0 && errno != EEXIST)
{
log_err(_("unable to create the %s directory\n"), dirs[i]);
exit(ERR_INTERNAL);
}
}
}
}
else if (mode == rsync)
{
/* For 9.5 and greater, create our own tablespace_map file */ /* For 9.5 and greater, create our own tablespace_map file */
if (server_version_num >= 90500) if (server_version_num >= 90500)
{ {
@@ -1898,47 +2400,22 @@ do_standby_clone(void)
} }
/* Copy tablespaces and, if required, remap to a new location */ /* Copy tablespaces and, if required, remap to a new location */
retval = get_tablespace_data(upstream_conn, &tablespace_list);
sqlquery_snprintf(sqlquery, if(retval != SUCCESS) goto stop_backup;
" SELECT oid, pg_tablespace_location(oid) AS spclocation "
" FROM pg_tablespace "
" WHERE spcname NOT IN ('pg_default', 'pg_global')");
res = PQexec(upstream_conn, sqlquery);
if (PQresultStatus(res) != PGRES_TUPLES_OK)
{
log_err(_("unable to execute tablespace query: %s\n"),
PQerrorMessage(upstream_conn));
PQclear(res);
r = retval = ERR_DB_QUERY;
goto stop_backup;
} }
for (i = 0; i < PQntuples(res); i++) for (cell_t = tablespace_list.head; cell_t; cell_t = cell_t->next)
{ {
bool mapping_found = false; bool mapping_found = false;
PQExpBufferData tblspc_dir_src;
PQExpBufferData tblspc_dir_dst;
PQExpBufferData tblspc_oid;
TablespaceListCell *cell; TablespaceListCell *cell;
char *tblspc_dir_dest;
initPQExpBuffer(&tblspc_dir_src);
initPQExpBuffer(&tblspc_dir_dst);
initPQExpBuffer(&tblspc_oid);
appendPQExpBuffer(&tblspc_oid, "%s", PQgetvalue(res, i, 0));
appendPQExpBuffer(&tblspc_dir_src, "%s", PQgetvalue(res, i, 1));
/* Check if tablespace path matches one of the provided tablespace mappings */ /* Check if tablespace path matches one of the provided tablespace mappings */
if (options.tablespace_mapping.head != NULL) if (options.tablespace_mapping.head != NULL)
{ {
for (cell = options.tablespace_mapping.head; cell; cell = cell->next) for (cell = options.tablespace_mapping.head; cell; cell = cell->next)
{ {
if (strcmp(tblspc_dir_src.data, cell->old_dir) == 0) if (strcmp(cell_t->location, cell->old_dir) == 0)
{ {
mapping_found = true; mapping_found = true;
break; break;
@@ -1948,19 +2425,50 @@ do_standby_clone(void)
if (mapping_found == true) if (mapping_found == true)
{ {
appendPQExpBuffer(&tblspc_dir_dst, "%s", cell->new_dir); tblspc_dir_dest = cell->new_dir;
log_debug(_("mapping source tablespace '%s' (OID %s) to '%s'\n"), log_debug(_("mapping source tablespace '%s' (OID %s) to '%s'\n"),
tblspc_dir_src.data, tblspc_oid.data, tblspc_dir_dst.data); cell_t->location, cell_t->oid, tblspc_dir_dest);
} }
else else
{ {
appendPQExpBuffer(&tblspc_dir_dst, "%s", tblspc_dir_src.data); tblspc_dir_dest = cell_t->location;
} }
/*
* Tablespace file copy
*/
if (mode == barman)
{
create_pg_dir(cell_t->location, false);
if (cell_t->f != NULL) /* cell_t->f == NULL iff the tablespace is empty */
{
maxlen_snprintf(command,
"rsync --progress -a --files-from=%s/%s.txt %s:%s/base/%s/%s %s",
local_repmgr_directory,
cell_t->oid,
options.barman_server,
backup_directory,
backup_id,
cell_t->oid,
tblspc_dir_dest);
(void)local_command(
command,
&command_output);
fclose(cell_t->f);
maxlen_snprintf(filename,
"%s/%s.txt",
local_repmgr_directory,
cell_t->oid);
unlink(filename);
}
}
else if (mode == rsync)
{
/* Copy tablespace directory */ /* Copy tablespace directory */
r = copy_remote_files(runtime_options.host, runtime_options.remote_user, r = copy_remote_files(runtime_options.host, runtime_options.remote_user,
tblspc_dir_src.data, tblspc_dir_dst.data, cell_t->location, tblspc_dir_dest,
true, server_version_num); true, server_version_num);
/* /*
@@ -1972,9 +2480,10 @@ do_standby_clone(void)
if (!WIFEXITED(r) && WEXITSTATUS(r) != 24) if (!WIFEXITED(r) && WEXITSTATUS(r) != 24)
{ {
log_warning(_("standby clone: failed copying tablespace directory '%s'\n"), log_warning(_("standby clone: failed copying tablespace directory '%s'\n"),
tblspc_dir_src.data); cell_t->location);
goto stop_backup; goto stop_backup;
} }
}
/* /*
* If a valid mapping was provide for this tablespace, arrange for it to * If a valid mapping was provide for this tablespace, arrange for it to
@@ -1982,7 +2491,7 @@ do_standby_clone(void)
* (if no tablespace mappings was provided, the link will be copied as-is * (if no tablespace mappings was provided, the link will be copied as-is
* by pg_basebackup or rsync and no action is required) * by pg_basebackup or rsync and no action is required)
*/ */
if (mapping_found == true) if (mapping_found == true || mode == barman)
{ {
/* 9.5 and later - append to the tablespace_map file */ /* 9.5 and later - append to the tablespace_map file */
if (server_version_num >= 90500) if (server_version_num >= 90500)
@@ -1990,8 +2499,8 @@ do_standby_clone(void)
tablespace_map_rewrite = true; tablespace_map_rewrite = true;
appendPQExpBuffer(&tablespace_map, appendPQExpBuffer(&tablespace_map,
"%s %s\n", "%s %s\n",
tblspc_oid.data, cell_t->oid,
tblspc_dir_dst.data); tblspc_dir_dest);
} }
/* Pre-9.5, we have to manipulate the symlinks in pg_tblspc/ ourselves */ /* Pre-9.5, we have to manipulate the symlinks in pg_tblspc/ ourselves */
else else
@@ -2001,23 +2510,19 @@ do_standby_clone(void)
initPQExpBuffer(&tblspc_symlink); initPQExpBuffer(&tblspc_symlink);
appendPQExpBuffer(&tblspc_symlink, "%s/pg_tblspc/%s", appendPQExpBuffer(&tblspc_symlink, "%s/pg_tblspc/%s",
local_data_directory, local_data_directory,
tblspc_oid.data); cell_t->oid);
if (unlink(tblspc_symlink.data) < 0 && errno != ENOENT) if (unlink(tblspc_symlink.data) < 0 && errno != ENOENT)
{ {
log_err(_("unable to remove tablespace symlink %s\n"), tblspc_symlink.data); log_err(_("unable to remove tablespace symlink %s\n"), tblspc_symlink.data);
PQclear(res);
r = retval = ERR_BAD_BASEBACKUP; r = retval = ERR_BAD_BASEBACKUP;
goto stop_backup; goto stop_backup;
} }
if (symlink(tblspc_dir_dst.data, tblspc_symlink.data) < 0) if (symlink(tblspc_dir_dest, tblspc_symlink.data) < 0)
{ {
log_err(_("unable to create tablespace symlink from %s to %s\n"), tblspc_symlink.data, tblspc_dir_dst.data); log_err(_("unable to create tablespace symlink from %s to %s\n"), tblspc_symlink.data, tblspc_dir_dest);
PQclear(res);
r = retval = ERR_BAD_BASEBACKUP; r = retval = ERR_BAD_BASEBACKUP;
goto stop_backup; goto stop_backup;
@@ -2026,8 +2531,6 @@ do_standby_clone(void)
} }
} }
PQclear(res);
/* /*
* For 9.5 and later, if tablespace remapping was requested, we'll need * For 9.5 and later, if tablespace remapping was requested, we'll need
* to rewrite the tablespace map file ourselves. * to rewrite the tablespace map file ourselves.
@@ -2035,6 +2538,7 @@ do_standby_clone(void)
* the backend; we could do this ourselves like for pre-9.5 servers, but * the backend; we could do this ourselves like for pre-9.5 servers, but
* it's better to rely on functionality the backend provides. * it's better to rely on functionality the backend provides.
*/ */
if (server_version_num >= 90500 && tablespace_map_rewrite == true) if (server_version_num >= 90500 && tablespace_map_rewrite == true)
{ {
PQExpBufferData tablespace_map_filename; PQExpBufferData tablespace_map_filename;
@@ -2095,7 +2599,9 @@ do_standby_clone(void)
* standby server as on the primary? * standby server as on the primary?
*/ */
if (external_config_file_copy_required && !runtime_options.ignore_external_config_files) if (mode != barman &&
external_config_file_copy_required &&
!runtime_options.ignore_external_config_files)
{ {
log_notice(_("copying configuration files from master\n")); log_notice(_("copying configuration files from master\n"));
r = test_ssh_connection(runtime_options.host, runtime_options.remote_user); r = test_ssh_connection(runtime_options.host, runtime_options.remote_user);
@@ -2154,7 +2660,7 @@ do_standby_clone(void)
* When using rsync, copy pg_control file last, emulating the base backup * When using rsync, copy pg_control file last, emulating the base backup
* protocol. * protocol.
*/ */
if (runtime_options.rsync_only) if (mode == rsync)
{ {
maxlen_snprintf(local_control_file, "%s/global", local_data_directory); maxlen_snprintf(local_control_file, "%s/global", local_data_directory);
@@ -2186,7 +2692,7 @@ do_standby_clone(void)
stop_backup: stop_backup:
if (runtime_options.rsync_only && pg_start_backup_executed) if (mode == rsync && pg_start_backup_executed)
{ {
log_notice(_("notifying master about backup completion...\n")); log_notice(_("notifying master about backup completion...\n"));
if (stop_backup(upstream_conn, last_wal_segment) == false) if (stop_backup(upstream_conn, last_wal_segment) == false)
@@ -2218,7 +2724,7 @@ stop_backup:
* files which won't be removed by rsync and which could * files which won't be removed by rsync and which could
* be stale or are otherwise not required * be stale or are otherwise not required
*/ */
if (runtime_options.rsync_only) if (mode == rsync)
{ {
char label_path[MAXPGPATH]; char label_path[MAXPGPATH];
char dirpath[MAXLEN] = ""; char dirpath[MAXLEN] = "";
@@ -2282,13 +2788,18 @@ stop_backup:
/* Finally, write the recovery.conf file */ /* Finally, write the recovery.conf file */
create_recovery_file(local_data_directory, upstream_conn); create_recovery_file(local_data_directory, upstream_conn);
if (runtime_options.rsync_only) /* In Barman mode, remove local_repmgr_directory */
if (mode == barman)
rmdir(local_repmgr_directory);
switch(mode)
{ {
case rsync:
log_notice(_("standby clone (using rsync) complete\n")); log_notice(_("standby clone (using rsync) complete\n"));
} case pg_basebackup:
else
{
log_notice(_("standby clone (using pg_basebackup) complete\n")); log_notice(_("standby clone (using pg_basebackup) complete\n"));
case barman:
log_notice(_("standby clone (from Barman) complete\n"));
} }
/* /*
@@ -4554,6 +5065,7 @@ do_help(void)
printf(_("Command-specific configuration options:\n")); printf(_("Command-specific configuration options:\n"));
printf(_(" -c, --fast-checkpoint (standby clone) force fast checkpoint\n")); printf(_(" -c, --fast-checkpoint (standby clone) force fast checkpoint\n"));
printf(_(" -r, --rsync-only (standby clone) use only rsync, not pg_basebackup\n")); printf(_(" -r, --rsync-only (standby clone) use only rsync, not pg_basebackup\n"));
printf(_(" --without-barman (standby clone) do not use Barman even if configured\n"));
printf(_(" --recovery-min-apply-delay=VALUE (standby clone, follow) set recovery_min_apply_delay\n" \ printf(_(" --recovery-min-apply-delay=VALUE (standby clone, follow) set recovery_min_apply_delay\n" \
" in recovery.conf (PostgreSQL 9.4 and later)\n")); " in recovery.conf (PostgreSQL 9.4 and later)\n"));
printf(_(" --ignore-external-config-files (standby clone) don't copy configuration files located\n" \ printf(_(" --ignore-external-config-files (standby clone) don't copy configuration files located\n" \
@@ -5717,10 +6229,10 @@ check_upstream_config(PGconn *conn, int server_version_num, bool exit_on_error)
} }
/* /*
* physical replication slots not available or not requested - * physical replication slots not available or not requested, and Barman mode not used -
* ensure some reasonably high value set for `wal_keep_segments` * ensure some reasonably high value set for `wal_keep_segments`
*/ */
else else if (! runtime_options.without_barman && strcmp(options.barman_server, "") == 0)
{ {
i = guc_set_typed(conn, "wal_keep_segments", ">=", i = guc_set_typed(conn, "wal_keep_segments", ">=",
runtime_options.wal_keep_segments, "integer"); runtime_options.wal_keep_segments, "integer");
@@ -6033,6 +6545,47 @@ remote_command(const char *host, const char *user, const char *command, PQExpBuf
} }
/*
* Execute a command locally. If outputbuf == NULL, discard the
* output.
*/
static 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_err(_("unable to execute local command:\n%s\n"), command);
return false;
}
/* TODO: better error handling */
while (fgets(output, MAXLEN, fp) != NULL)
{
appendPQExpBuffer(outputbuf, "%s", output);
}
pclose(fp);
log_verbose(LOG_DEBUG, "local_command(): output returned was:\n%s", outputbuf->data);
return true;
}
}
/* /*
* Extract values from provided conninfo string and return * Extract values from provided conninfo string and return
* formatted as command-line parameters suitable for passing to repmgr * formatted as command-line parameters suitable for passing to repmgr

View File

@@ -57,6 +57,7 @@
#define OPT_PWPROMPT 7 #define OPT_PWPROMPT 7
#define OPT_CSV 8 #define OPT_CSV 8
#define OPT_NODE 9 #define OPT_NODE 9
#define OPT_WITHOUT_BARMAN 10
/* deprecated command line options */ /* deprecated command line options */
#define OPT_INITDB_NO_PWPROMPT 999 #define OPT_INITDB_NO_PWPROMPT 999
@@ -83,6 +84,7 @@ typedef struct
bool fast_checkpoint; bool fast_checkpoint;
bool ignore_external_config_files; bool ignore_external_config_files;
bool csv_mode; bool csv_mode;
bool without_barman;
char masterport[MAXLEN]; char masterport[MAXLEN];
/* /*
* configuration file parameters which can be overridden on the * configuration file parameters which can be overridden on the
@@ -106,7 +108,7 @@ typedef struct
char recovery_min_apply_delay[MAXLEN]; char recovery_min_apply_delay[MAXLEN];
} t_runtime_options; } t_runtime_options;
#define T_RUNTIME_OPTIONS_INITIALIZER { "", "", "", "", "", "", "", DEFAULT_WAL_KEEP_SEGMENTS, false, false, false, false, false, false, false, false, false, false, "", "", "", "", "fast", "", 0, 0, "", ""} #define T_RUNTIME_OPTIONS_INITIALIZER { "", "", "", "", "", "", "", DEFAULT_WAL_KEEP_SEGMENTS, false, false, false, false, false, false, false, false, false, false, false, "", "", "", "", "fast", "", 0, 0, "", ""}
struct BackupLabel struct BackupLabel
{ {