From 1f3e937bbe7c8849905108812911f6a9e68b2c21 Mon Sep 17 00:00:00 2001 From: Gianni Ciolli Date: Fri, 15 Jul 2016 19:47:49 +0200 Subject: [PATCH 1/9] Add local_command function to run commands locally --- repmgr.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/repmgr.c b/repmgr.c index 701c8c1a..9a6739da 100644 --- a/repmgr.c +++ b/repmgr.c @@ -130,6 +130,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); @@ -5970,6 +5971,37 @@ remote_command(const char *host, const char *user, const char *command, PQExpBuf } +/* + * Execute a command locally. + */ +static bool +local_command(const char *command, PQExpBufferData *outputbuf) +{ + FILE *fp; + char output[MAXLEN]; + + 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 From ecdae9671fbac753fb488c76f9646de1f8335b74 Mon Sep 17 00:00:00 2001 From: Gianni Ciolli Date: Fri, 15 Jul 2016 19:48:23 +0200 Subject: [PATCH 2/9] Factor out tablespace metadata retrieval The purpose of this commit is to prepare the terrain for non-default tablespace support in the forthcoming Barman support. --- repmgr.c | 81 +++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 54 insertions(+), 27 deletions(-) diff --git a/repmgr.c b/repmgr.c index 9a6739da..f7836c96 100644 --- a/repmgr.c +++ b/repmgr.c @@ -103,6 +103,7 @@ 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 int get_tablespace_data(PGconn *upstream_conn, int*, int**, PQExpBufferData**); static char *make_pg_path(char *file); @@ -1499,6 +1500,47 @@ do_standby_unregister(void) return; } +int +get_tablespace_data(PGconn *upstream_conn, + int *tablespace_count, + int **tablespace_oids, + PQExpBufferData **tablespace_locs) +{ + int i, retval; + char sqlquery[QUERY_STR_LEN]; + PGresult *res; + + 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); + + return retval; + } + + *tablespace_count = PQntuples(res); + *tablespace_oids = pg_malloc(*tablespace_count * sizeof(int)); + *tablespace_locs = pg_malloc(*tablespace_count * sizeof(PQExpBufferData)); + for (i = 0; i < *tablespace_count; i++) + { + initPQExpBuffer(*tablespace_locs + i); + *tablespace_oids[i] = strtol(PQgetvalue(res, i, 0), NULL, 10); + appendPQExpBuffer(*tablespace_locs + i, + "%s", PQgetvalue(res, i, 1)); + } + + PQclear(res); + return retval; +} static void do_standby_clone(void) @@ -1819,6 +1861,9 @@ do_standby_clone(void) { PQExpBufferData tablespace_map; bool tablespace_map_rewrite = false; + int tablespace_count; + int *tablespace_oids; + PQExpBufferData *tablespace_locs; /* For 9.5 and greater, create our own tablespace_map file */ if (server_version_num >= 90500) @@ -1886,26 +1931,13 @@ do_standby_clone(void) } /* Copy tablespaces and, if required, remap to a new location */ + retval = get_tablespace_data(upstream_conn, + &tablespace_count, + &tablespace_oids, + &tablespace_locs); + if(retval != SUCCESS) goto stop_backup; - 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; - } - - for (i = 0; i < PQntuples(res); i++) + for (i = 0; i < tablespace_count; i++) { bool mapping_found = false; PQExpBufferData tblspc_dir_src; @@ -1918,8 +1950,8 @@ do_standby_clone(void) initPQExpBuffer(&tblspc_oid); - appendPQExpBuffer(&tblspc_oid, "%s", PQgetvalue(res, i, 0)); - appendPQExpBuffer(&tblspc_dir_src, "%s", PQgetvalue(res, i, 1)); + appendPQExpBuffer(&tblspc_oid, "%d", tablespace_oids[i]); + appendPQExpBuffer(&tblspc_dir_src, "%s", tablespace_locs[i].data); /* Check if tablespace path matches one of the provided tablespace mappings */ if (options.tablespace_mapping.head != NULL) @@ -1995,8 +2027,6 @@ do_standby_clone(void) { log_err(_("unable to remove tablespace symlink %s\n"), tblspc_symlink.data); - PQclear(res); - r = retval = ERR_BAD_BASEBACKUP; goto stop_backup; } @@ -2005,8 +2035,6 @@ do_standby_clone(void) { log_err(_("unable to create tablespace symlink from %s to %s\n"), tblspc_symlink.data, tblspc_dir_dst.data); - PQclear(res); - r = retval = ERR_BAD_BASEBACKUP; goto stop_backup; } @@ -2014,8 +2042,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. @@ -2023,6 +2049,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; From 9853581d1202ed4b5302eef31f89958c05002813 Mon Sep 17 00:00:00 2001 From: Gianni Ciolli Date: Fri, 15 Jul 2016 19:48:54 +0200 Subject: [PATCH 3/9] Introduce a Cell List to store tablespace data --- config.h | 16 +++++++++ repmgr.c | 98 ++++++++++++++++++++++++++++++-------------------------- 2 files changed, 68 insertions(+), 46 deletions(-) diff --git a/config.h b/config.h index 6f7133bd..f5087c78 100644 --- a/config.h +++ b/config.h @@ -105,6 +105,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); diff --git a/repmgr.c b/repmgr.c index f7836c96..af8bfda1 100644 --- a/repmgr.c +++ b/repmgr.c @@ -103,7 +103,8 @@ 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 int get_tablespace_data(PGconn *upstream_conn, int*, int**, PQExpBufferData**); +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 char *make_pg_path(char *file); @@ -1500,18 +1501,44 @@ 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, - int *tablespace_count, - int **tablespace_oids, - PQExpBufferData **tablespace_locs) +get_tablespace_data(PGconn *upstream_conn, TablespaceDataList *list) { int i, retval; char sqlquery[QUERY_STR_LEN]; PGresult *res; sqlquery_snprintf(sqlquery, - " SELECT oid, pg_tablespace_location(oid) AS spclocation " + " SELECT spcname, oid, pg_tablespace_location(oid) AS spclocation " " FROM pg_tablespace " " WHERE spcname NOT IN ('pg_default', 'pg_global')"); @@ -1527,16 +1554,9 @@ get_tablespace_data(PGconn *upstream_conn, return retval; } - *tablespace_count = PQntuples(res); - *tablespace_oids = pg_malloc(*tablespace_count * sizeof(int)); - *tablespace_locs = pg_malloc(*tablespace_count * sizeof(PQExpBufferData)); - for (i = 0; i < *tablespace_count; i++) - { - initPQExpBuffer(*tablespace_locs + i); - *tablespace_oids[i] = strtol(PQgetvalue(res, i, 0), NULL, 10); - appendPQExpBuffer(*tablespace_locs + i, - "%s", PQgetvalue(res, i, 1)); - } + 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; @@ -1861,9 +1881,8 @@ do_standby_clone(void) { PQExpBufferData tablespace_map; bool tablespace_map_rewrite = false; - int tablespace_count; - int *tablespace_oids; - PQExpBufferData *tablespace_locs; + TablespaceDataList tablespace_list = { NULL, NULL }; + TablespaceDataListCell *cell_t; /* For 9.5 and greater, create our own tablespace_map file */ if (server_version_num >= 90500) @@ -1931,34 +1950,21 @@ do_standby_clone(void) } /* Copy tablespaces and, if required, remap to a new location */ - retval = get_tablespace_data(upstream_conn, - &tablespace_count, - &tablespace_oids, - &tablespace_locs); + retval = get_tablespace_data(upstream_conn, &tablespace_list); if(retval != SUCCESS) goto stop_backup; - for (i = 0; i < tablespace_count; 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, "%d", tablespace_oids[i]); - appendPQExpBuffer(&tblspc_dir_src, "%s", tablespace_locs[i].data); + 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; @@ -1968,19 +1974,19 @@ 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; } /* 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); /* @@ -1992,7 +1998,7 @@ 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; } @@ -2010,8 +2016,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 @@ -2021,7 +2027,7 @@ 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) { @@ -2031,9 +2037,9 @@ do_standby_clone(void) 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); r = retval = ERR_BAD_BASEBACKUP; goto stop_backup; From 2f529e20c100862bdc074c72164a850eb5429cdc Mon Sep 17 00:00:00 2001 From: Gianni Ciolli Date: Fri, 22 Jul 2016 15:08:21 +0200 Subject: [PATCH 4/9] Barman support, draft #1 TODO: we need to check what happens with configuration files placed in non-standard locations. --- README.md | 70 +++++++ config.c | 10 + config.h | 3 +- repmgr.c | 561 ++++++++++++++++++++++++++++++++++++++++++++++++++---- repmgr.h | 4 +- 5 files changed, 609 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 11a8e0bc..9651c7cd 100644 --- a/README.md +++ b/README.md @@ -504,6 +504,76 @@ 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 `pg_restore_command` setting in `repmgr.conf` is configured to + use a copy of the `barman-wal-restore` 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 this is a good practice; however, the command line +> option `--without-barman` can be used to disable it. + +> *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` is a short shell script provided by the Barman +development team, which must be copied in a location accessible to +`repmgr`, and marked as executable; `pg_restore_command` must then be +set as follows: + +