mirror of
https://github.com/EnterpriseDB/repmgr.git
synced 2026-03-28 09:26:29 +00:00
Merge pull request #191 from gciolli/feature-barman-support
Add Barman support to repmgr standby clone
This commit is contained in:
76
README.md
76
README.md
@@ -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
|
||||
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
|
||||
--------------------------------------
|
||||
@@ -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
|
||||
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
|
||||
|
||||
By default, `repmgr` will attempt to copy the standard configuration files
|
||||
|
||||
10
config.c
10
config.c
@@ -215,6 +215,7 @@ parse_config(t_configuration_options *options)
|
||||
options->upstream_node = NO_UPSTREAM_NODE;
|
||||
options->use_replication_slots = 0;
|
||||
memset(options->conninfo, 0, sizeof(options->conninfo));
|
||||
memset(options->barman_server, 0, sizeof(options->barman_server));
|
||||
options->failover = MANUAL_FAILOVER;
|
||||
options->priority = DEFAULT_PRIORITY;
|
||||
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);
|
||||
else if (strcmp(name, "conninfo") == 0)
|
||||
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)
|
||||
strncpy(options->rsync_options, value, QUERY_STR_LEN);
|
||||
else if (strcmp(name, "ssh_options") == 0)
|
||||
@@ -635,6 +638,13 @@ reload_config(t_configuration_options *orig_options)
|
||||
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 */
|
||||
if (orig_options->node != new_options.node)
|
||||
{
|
||||
|
||||
19
config.h
19
config.h
@@ -58,6 +58,7 @@ typedef struct
|
||||
int node;
|
||||
int upstream_node;
|
||||
char conninfo[MAXLEN];
|
||||
char barman_server[MAXLEN];
|
||||
int failover;
|
||||
int priority;
|
||||
char node_name[MAXLEN];
|
||||
@@ -91,7 +92,7 @@ typedef struct
|
||||
* The following will initialize the structure with a minimal set of options;
|
||||
* actual defaults are set in parse_config() before parsing the configuration file
|
||||
*/
|
||||
#define T_CONFIGURATION_OPTIONS_INITIALIZER { "", -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
|
||||
{
|
||||
@@ -105,6 +106,22 @@ typedef struct ItemList
|
||||
ItemListCell *tail;
|
||||
} 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);
|
||||
const char * progname(void);
|
||||
|
||||
|
||||
683
repmgr.c
683
repmgr.c
@@ -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 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 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);
|
||||
|
||||
@@ -130,6 +136,7 @@ static void exit_with_errors(void);
|
||||
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 local_command(const char *command, PQExpBufferData *outputbuf);
|
||||
static void format_db_cli_params(const char *conninfo, char *output);
|
||||
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},
|
||||
{"csv", no_argument, NULL, OPT_CSV},
|
||||
{"node", required_argument, NULL, OPT_NODE},
|
||||
{"without-barman", no_argument, NULL, OPT_WITHOUT_BARMAN},
|
||||
{"version", no_argument, NULL, 'V'},
|
||||
/* Following options deprecated */
|
||||
{"local-port", required_argument, NULL, 'l'},
|
||||
@@ -508,6 +516,9 @@ main(int argc, char **argv)
|
||||
case OPT_NODE:
|
||||
runtime_options.node = repmgr_atoi(optarg, "--node", &cli_errors, false);
|
||||
break;
|
||||
case OPT_WITHOUT_BARMAN:
|
||||
runtime_options.without_barman = true;
|
||||
break;
|
||||
|
||||
/* deprecated options - output a warning */
|
||||
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"));
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 */
|
||||
maxlen_snprintf(repmgr_schema, "%s%s", DEFAULT_REPMGR_SCHEMA_PREFIX,
|
||||
options.cluster_name);
|
||||
@@ -1511,17 +1534,169 @@ do_standby_unregister(void)
|
||||
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
|
||||
do_standby_clone(void)
|
||||
{
|
||||
PGconn *primary_conn = NULL;
|
||||
PGconn *upstream_conn;
|
||||
PGconn *upstream_conn = NULL;
|
||||
PGresult *res;
|
||||
|
||||
enum {
|
||||
barman,
|
||||
rsync,
|
||||
pg_basebackup
|
||||
} mode;
|
||||
|
||||
char sqlquery[QUERY_STR_LEN];
|
||||
|
||||
int server_version_num;
|
||||
int server_version_num = -1;
|
||||
|
||||
char cluster_size[MAXLEN];
|
||||
|
||||
@@ -1536,6 +1711,8 @@ do_standby_clone(void)
|
||||
char master_data_directory[MAXPGPATH];
|
||||
char local_data_directory[MAXPGPATH];
|
||||
|
||||
char local_repmgr_directory[MAXPGPATH];
|
||||
|
||||
char master_config_file[MAXPGPATH] = "";
|
||||
char local_config_file[MAXPGPATH] = "";
|
||||
bool config_file_outside_pgdata = false;
|
||||
@@ -1556,6 +1733,15 @@ do_standby_clone(void)
|
||||
|
||||
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
|
||||
@@ -1569,6 +1755,9 @@ do_standby_clone(void)
|
||||
runtime_options.dest_dir);
|
||||
}
|
||||
|
||||
if (mode != barman)
|
||||
{
|
||||
|
||||
param_set("application_name", options.node_name);
|
||||
|
||||
/* Connect to check configuration */
|
||||
@@ -1749,6 +1938,8 @@ do_standby_clone(void)
|
||||
|
||||
PQclear(res);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* target directory (-D/--pgdata) provided - use that as new data directory
|
||||
* (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_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
|
||||
*/
|
||||
@@ -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);
|
||||
if (r != 0)
|
||||
@@ -1806,8 +2003,11 @@ do_standby_clone(void)
|
||||
* If replication slots requested, create appropriate slot on
|
||||
* the primary; this must be done before pg_start_backup() is
|
||||
* 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)
|
||||
{
|
||||
@@ -1816,22 +2016,324 @@ do_standby_clone(void)
|
||||
}
|
||||
}
|
||||
|
||||
if (runtime_options.rsync_only)
|
||||
if (mode == rsync)
|
||||
{
|
||||
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"));
|
||||
if (runtime_options.fast_checkpoint == false)
|
||||
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;
|
||||
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 */
|
||||
if (server_version_num >= 90500)
|
||||
{
|
||||
@@ -1898,47 +2400,22 @@ do_standby_clone(void)
|
||||
}
|
||||
|
||||
/* Copy tablespaces and, if required, remap to a new location */
|
||||
|
||||
sqlquery_snprintf(sqlquery,
|
||||
" 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;
|
||||
retval = get_tablespace_data(upstream_conn, &tablespace_list);
|
||||
if(retval != SUCCESS) 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;
|
||||
PQExpBufferData tblspc_dir_src;
|
||||
PQExpBufferData tblspc_dir_dst;
|
||||
PQExpBufferData tblspc_oid;
|
||||
TablespaceListCell *cell;
|
||||
|
||||
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));
|
||||
char *tblspc_dir_dest;
|
||||
|
||||
/* Check if tablespace path matches one of the provided tablespace mappings */
|
||||
if (options.tablespace_mapping.head != NULL)
|
||||
{
|
||||
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;
|
||||
break;
|
||||
@@ -1948,19 +2425,50 @@ do_standby_clone(void)
|
||||
|
||||
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"),
|
||||
tblspc_dir_src.data, tblspc_oid.data, tblspc_dir_dst.data);
|
||||
cell_t->location, cell_t->oid, tblspc_dir_dest);
|
||||
}
|
||||
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 */
|
||||
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);
|
||||
|
||||
/*
|
||||
@@ -1972,9 +2480,10 @@ do_standby_clone(void)
|
||||
if (!WIFEXITED(r) && WEXITSTATUS(r) != 24)
|
||||
{
|
||||
log_warning(_("standby clone: failed copying tablespace directory '%s'\n"),
|
||||
tblspc_dir_src.data);
|
||||
cell_t->location);
|
||||
goto stop_backup;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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
|
||||
* 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 */
|
||||
if (server_version_num >= 90500)
|
||||
@@ -1990,8 +2499,8 @@ do_standby_clone(void)
|
||||
tablespace_map_rewrite = true;
|
||||
appendPQExpBuffer(&tablespace_map,
|
||||
"%s %s\n",
|
||||
tblspc_oid.data,
|
||||
tblspc_dir_dst.data);
|
||||
cell_t->oid,
|
||||
tblspc_dir_dest);
|
||||
}
|
||||
/* Pre-9.5, we have to manipulate the symlinks in pg_tblspc/ ourselves */
|
||||
else
|
||||
@@ -2001,23 +2510,19 @@ do_standby_clone(void)
|
||||
initPQExpBuffer(&tblspc_symlink);
|
||||
appendPQExpBuffer(&tblspc_symlink, "%s/pg_tblspc/%s",
|
||||
local_data_directory,
|
||||
tblspc_oid.data);
|
||||
cell_t->oid);
|
||||
|
||||
if (unlink(tblspc_symlink.data) < 0 && errno != ENOENT)
|
||||
{
|
||||
log_err(_("unable to remove tablespace symlink %s\n"), tblspc_symlink.data);
|
||||
|
||||
PQclear(res);
|
||||
|
||||
r = retval = ERR_BAD_BASEBACKUP;
|
||||
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);
|
||||
|
||||
PQclear(res);
|
||||
log_err(_("unable to create tablespace symlink from %s to %s\n"), tblspc_symlink.data, tblspc_dir_dest);
|
||||
|
||||
r = retval = ERR_BAD_BASEBACKUP;
|
||||
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
|
||||
* 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
|
||||
* it's better to rely on functionality the backend provides.
|
||||
*/
|
||||
|
||||
if (server_version_num >= 90500 && tablespace_map_rewrite == true)
|
||||
{
|
||||
PQExpBufferData tablespace_map_filename;
|
||||
@@ -2095,7 +2599,9 @@ do_standby_clone(void)
|
||||
* 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"));
|
||||
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
|
||||
* protocol.
|
||||
*/
|
||||
if (runtime_options.rsync_only)
|
||||
if (mode == rsync)
|
||||
{
|
||||
maxlen_snprintf(local_control_file, "%s/global", local_data_directory);
|
||||
|
||||
@@ -2186,7 +2692,7 @@ do_standby_clone(void)
|
||||
|
||||
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"));
|
||||
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
|
||||
* be stale or are otherwise not required
|
||||
*/
|
||||
if (runtime_options.rsync_only)
|
||||
if (mode == rsync)
|
||||
{
|
||||
char label_path[MAXPGPATH];
|
||||
char dirpath[MAXLEN] = "";
|
||||
@@ -2282,13 +2788,18 @@ stop_backup:
|
||||
/* Finally, write the recovery.conf file */
|
||||
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"));
|
||||
}
|
||||
else
|
||||
{
|
||||
case pg_basebackup:
|
||||
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(_(" -c, --fast-checkpoint (standby clone) force fast checkpoint\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" \
|
||||
" in recovery.conf (PostgreSQL 9.4 and later)\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`
|
||||
*/
|
||||
else
|
||||
else if (! runtime_options.without_barman && strcmp(options.barman_server, "") == 0)
|
||||
{
|
||||
i = guc_set_typed(conn, "wal_keep_segments", ">=",
|
||||
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
|
||||
* formatted as command-line parameters suitable for passing to repmgr
|
||||
|
||||
4
repmgr.h
4
repmgr.h
@@ -57,6 +57,7 @@
|
||||
#define OPT_PWPROMPT 7
|
||||
#define OPT_CSV 8
|
||||
#define OPT_NODE 9
|
||||
#define OPT_WITHOUT_BARMAN 10
|
||||
|
||||
/* deprecated command line options */
|
||||
#define OPT_INITDB_NO_PWPROMPT 999
|
||||
@@ -83,6 +84,7 @@ typedef struct
|
||||
bool fast_checkpoint;
|
||||
bool ignore_external_config_files;
|
||||
bool csv_mode;
|
||||
bool without_barman;
|
||||
char masterport[MAXLEN];
|
||||
/*
|
||||
* configuration file parameters which can be overridden on the
|
||||
@@ -106,7 +108,7 @@ typedef struct
|
||||
char recovery_min_apply_delay[MAXLEN];
|
||||
} 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
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user