From f023b9c90c77af9797b1b7f912c29c033cdeaee3 Mon Sep 17 00:00:00 2001 From: Ian Barwick Date: Tue, 1 Aug 2017 17:38:54 +0900 Subject: [PATCH] Add "repmgr node archive-config" --- dbutils.c | 66 ++++++++++++++- dbutils.h | 4 + repmgr-action-node.c | 176 ++++++++++++++++++++++++++++++++++++++++ repmgr-action-node.h | 2 + repmgr-action-standby.c | 13 --- repmgr-action-standby.h | 2 - repmgr-client-global.h | 6 +- repmgr-client.c | 33 +++++--- repmgr-client.h | 18 ++-- 9 files changed, 281 insertions(+), 39 deletions(-) diff --git a/dbutils.c b/dbutils.c index 1689cb0a..611d609b 100644 --- a/dbutils.c +++ b/dbutils.c @@ -2078,6 +2078,7 @@ delete_node_record(PGconn *conn, int node) return true; } + void get_node_replication_stats(PGconn *conn, t_node_info *node_info) { @@ -2099,8 +2100,8 @@ get_node_replication_stats(PGconn *conn, t_node_info *node_info) if (PQresultStatus(res) != PGRES_TUPLES_OK) { - log_warning(_("unable to retrieve node replication statistics:\n %s"), - PQerrorMessage(conn)); + log_warning(_("unable to retrieve node replication statistics")); + log_detail("%s", PQerrorMessage(conn)); PQclear(res); return; } @@ -2153,6 +2154,65 @@ clear_node_info_list(NodeInfoList *nodes) } +/* ================================================ */ +/* PostgreSQL configuration file location functions */ +/* ================================================ */ + +bool +get_datadir_configuration_files(PGconn *conn, KeyValueList *list) +{ + PQExpBufferData query; + PGresult *res; + int i; + + initPQExpBuffer(&query); + + appendPQExpBuffer( + &query, + "WITH files AS ( " + " WITH dd AS ( " + " SELECT setting " + " FROM pg_catalog.pg_settings " + " WHERE name = 'data_directory') " + " SELECT distinct(sourcefile) AS config_file" + " FROM dd, pg_catalog.pg_settings ps " + " WHERE ps.sourcefile IS NOT NULL " + " AND ps.sourcefile ~ ('^' || dd.setting) " + " UNION " + " SELECT ps.setting AS config_file" + " FROM dd, pg_catalog.pg_settings ps " + " WHERE ps.name IN ( 'config_file', 'hba_file', 'ident_file') " + " AND ps.setting ~ ('^' || dd.setting) " + ") " + " SELECT config_file, " + " regexp_replace(config_file, '^.*\\/','') AS filename " + " FROM files " + "ORDER BY config_file"); + + res = PQexec(conn, query.data); + termPQExpBuffer(&query); + + if (PQresultStatus(res) != PGRES_TUPLES_OK) + { + log_error(_("unable to retrieve configuration file information")); + log_detail("%s", PQerrorMessage(conn)); + PQclear(res); + return false; + } + + for (i = 0; i < PQntuples(res); i++) + { + key_value_list_set( + list, + PQgetvalue(res, i, 1), + PQgetvalue(res, i, 0)); + } + + PQclear(res); + return true; +} + + /* ====================== */ /* event record functions */ /* ====================== */ @@ -2174,6 +2234,8 @@ create_event_record(PGconn *conn, t_configuration_options *options, int node_id, return _create_event(conn, options, node_id, event, successful, details, &event_info, false); } + + /* * create_event_notification() * diff --git a/dbutils.h b/dbutils.h index ee83f64c..4a41ea0b 100644 --- a/dbutils.h +++ b/dbutils.h @@ -338,6 +338,10 @@ void clear_node_info_list(NodeInfoList *nodes); void get_node_replication_stats(PGconn *conn, t_node_info *node_info); +/* PostgreSQL configuration file location functions */ +bool get_datadir_configuration_files(PGconn *conn, KeyValueList *list); + + /* event functions */ bool create_event_record(PGconn *conn, t_configuration_options *options, int node_id, char *event, bool successful, char *details); bool create_event_notification(PGconn *conn, t_configuration_options *options, int node_id, char *event, bool successful, char *details); diff --git a/repmgr-action-node.c b/repmgr-action-node.c index 4251da3b..908980cf 100644 --- a/repmgr-action-node.c +++ b/repmgr-action-node.c @@ -6,12 +6,16 @@ * Copyright (c) 2ndQuadrant, 2010-2017 */ +#include +#include #include "repmgr.h" #include "repmgr-client-global.h" #include "repmgr-action-node.h" +static bool copy_file(const char *src_file, const char *dest_file); + void do_node_status(void) { @@ -275,3 +279,175 @@ void do_node_check(void) { } + + +/* + * Intended mainly for "internal" use by `standby switchover`, which + * calls this on the target server to archive any configuration files + * in the data directory, which may be overwritten by an operation + * like pg_rewind + * + * Requires configuration file. + */ +void +do_node_archive_config(void) +{ + char archive_dir[MAXPGPATH]; + struct stat statbuf; + struct dirent *arcdir_ent; + DIR *arcdir; + + PGconn *local_conn = NULL; + KeyValueList config_files = { NULL, NULL }; + KeyValueListCell *cell; + int copied_count = 0; + + snprintf(archive_dir, + MAXPGPATH, + "%s/repmgr-config-archive-%s", + runtime_options.config_archive_dir, + config_file_options.node_name); + + log_verbose(LOG_DEBUG, "using archive directory \"%s\"", archive_dir); + + /* sanity-check directory path */ + if (stat(archive_dir, &statbuf) == -1) + { + if (errno != ENOENT) + { + log_error(_("error encountered when checking archive directory \"%s\""), + archive_dir); + log_detail("%s",strerror(errno)); + exit(ERR_BAD_CONFIG); + } + + /* attempt to create and open the directory */ + if (mkdir(archive_dir, S_IRWXU) != 0 && errno != EEXIST) + { + log_error(_("unable to create temporary archive directory \"%s\""), + archive_dir); + log_detail("%s", strerror(errno)); + exit(ERR_BAD_CONFIG); + } + + + } + else if(!S_ISDIR(statbuf.st_mode)) + { + log_error(_("\"%s\" exists but is not a directory"), + archive_dir); + exit(ERR_BAD_CONFIG); + } + + + arcdir = opendir(archive_dir); + + if (arcdir == NULL) + { + log_error(_("unable to open archive directory \"%s\""), + archive_dir); + log_detail("%s", strerror(errno)); + exit(ERR_BAD_CONFIG); + } + + /* + * attempt to remove any existing files in the directory + * TODO: collate problem files into list + */ + while ((arcdir_ent = readdir(arcdir)) != NULL) + { + char arcdir_ent_path[MAXPGPATH]; + + snprintf(arcdir_ent_path, MAXPGPATH, + "%s/%s", + archive_dir, + arcdir_ent->d_name); + + + if (stat(arcdir_ent_path, &statbuf) == 0 && !S_ISREG(statbuf.st_mode)) + { + continue; + } + + + if (unlink(arcdir_ent_path) == -1) + { + log_error(_("unable to create temporary archive directory \"%s\""), + archive_dir); + log_detail("%s", strerror(errno)); + closedir(arcdir); + exit(ERR_BAD_CONFIG); + } + } + + closedir(arcdir); + + local_conn = establish_db_connection(config_file_options.conninfo, true); + + get_datadir_configuration_files(local_conn, &config_files); + + for (cell = config_files.head; cell; cell = cell->next) + { + char dest_file[MAXPGPATH] = ""; + + snprintf(dest_file, MAXPGPATH, + "%s/%s", + archive_dir, + cell->key); + + copy_file(cell->value, dest_file); + copied_count++; + } + + PQfinish(local_conn); + + log_verbose(LOG_INFO, _("%i files copied to %s"), + copied_count, archive_dir); +} + + +void +do_node_restore_config(void) +{ + return; +} + +static bool +copy_file(const char *src_file, const char *dest_file) +{ + FILE *ptr_old, *ptr_new; + int a; + + ptr_old = fopen(src_file, "r"); + ptr_new = fopen(dest_file, "w"); + + if (ptr_old == NULL) + return false; + + if (ptr_new == NULL) + { + fclose(ptr_old); + return false; + } + + chmod(dest_file, S_IRUSR | S_IWUSR); + + while(1) + { + a = fgetc(ptr_old); + + if (!feof(ptr_old)) + { + fputc(a, ptr_new); + } + else + { + break; + } + } + + fclose(ptr_new); + fclose(ptr_old); + + return true; +} diff --git a/repmgr-action-node.h b/repmgr-action-node.h index da4ccc6b..06e75285 100644 --- a/repmgr-action-node.h +++ b/repmgr-action-node.h @@ -8,5 +8,7 @@ extern void do_node_status(void); extern void do_node_check(void); +extern void do_node_archive_config(void); +extern void do_node_restore_config(void); #endif /* _REPMGR_ACTION_NODE_H_ */ diff --git a/repmgr-action-standby.c b/repmgr-action-standby.c index 68a01c31..3068c9d9 100644 --- a/repmgr-action-standby.c +++ b/repmgr-action-standby.c @@ -1569,19 +1569,6 @@ do_standby_switchover(void) } -void -do_standby_archive_config(void) -{ - return; -} - -void -do_standby_restore_config(void) -{ - return; -} - - static void check_source_server() { diff --git a/repmgr-action-standby.h b/repmgr-action-standby.h index 83551f90..43d07893 100644 --- a/repmgr-action-standby.h +++ b/repmgr-action-standby.h @@ -12,8 +12,6 @@ extern void do_standby_unregister(void); extern void do_standby_promote(void); extern void do_standby_follow(void); extern void do_standby_switchover(void); -extern void do_standby_archive_config(void); -extern void do_standby_restore_config(void); typedef struct { diff --git a/repmgr-client-global.h b/repmgr-client-global.h index 78092ecd..aa3bd8cd 100644 --- a/repmgr-client-global.h +++ b/repmgr-client-global.h @@ -78,6 +78,8 @@ typedef struct char event[MAXLEN]; int limit; + /* following options for internal use */ + char config_archive_dir[MAXPGPATH]; } t_runtime_options; #define T_RUNTIME_OPTIONS_INITIALIZER { \ @@ -100,7 +102,9 @@ typedef struct /* standby register options */ \ false, 0, \ /* event options */ \ - false, "", CLUSTER_EVENT_LIMIT } + false, "", CLUSTER_EVENT_LIMIT, \ + "/tmp" \ +} typedef enum { diff --git a/repmgr-client.c b/repmgr-client.c index 00b73760..c9f63ede 100644 --- a/repmgr-client.c +++ b/repmgr-client.c @@ -23,6 +23,12 @@ * CLUSTER EVENT * CLUSTER CROSSCHECK * CLUSTER MATRIX + * + * NODE STATUS + * + * For internal use: + * NODE ARCHIVE-CONFIG + * NODE RESTORE-CONFIG */ #include @@ -450,6 +456,11 @@ main(int argc, char **argv) runtime_options.csv = true; break; + /* internal options */ + case OPT_CONFIG_ARCHIVE_DIR: + /* TODO: check this is an absolute path */ + strncpy(runtime_options.config_archive_dir, optarg, MAXPGPATH); + break; /* options deprecated since 3.3 * * ---------------------------- */ @@ -615,10 +626,6 @@ main(int argc, char **argv) action = STANDBY_FOLLOW; else if (strcasecmp(repmgr_action, "SWITCHOVER") == 0) action = STANDBY_SWITCHOVER; - else if (strcasecmp(repmgr_action, "ARCHIVE-CONFIG") == 0) - action = STANDBY_ARCHIVE_CONFIG; - else if (strcasecmp(repmgr_action, "RESTORE-CONFIG") == 0) - action = STANDBY_RESTORE_CONFIG; else if (strcasecmp(repmgr_action, "CHECK") == 0) action = NODE_CHECK; else if (strcasecmp(repmgr_action, "STATUS") == 0) @@ -645,6 +652,10 @@ main(int argc, char **argv) action = NODE_CHECK; else if (strcasecmp(repmgr_action, "STATUS") == 0) action = NODE_STATUS; + else if (strcasecmp(repmgr_action, "ARCHIVE-CONFIG") == 0) + action = NODE_ARCHIVE_CONFIG; + else if (strcasecmp(repmgr_action, "RESTORE-CONFIG") == 0) + action = NODE_RESTORE_CONFIG; } else if (strcasecmp(repmgr_node_type, "CLUSTER") == 0) @@ -934,11 +945,7 @@ main(int argc, char **argv) case STANDBY_SWITCHOVER: do_standby_switchover(); break; - case STANDBY_ARCHIVE_CONFIG: - do_standby_archive_config(); - break; - case STANDBY_RESTORE_CONFIG: - do_standby_restore_config(); + break; #else /* we won't ever reach here, but stop the compiler complaining */ @@ -950,8 +957,6 @@ main(int argc, char **argv) case STANDBY_PROMOTE: case STANDBY_FOLLOW: case STANDBY_SWITCHOVER: - case STANDBY_ARCHIVE_CONFIG: - case STANDBY_RESTORE_CONFIG: break; #endif @@ -967,6 +972,11 @@ main(int argc, char **argv) case NODE_STATUS: do_node_status(); break; + case NODE_ARCHIVE_CONFIG: + do_node_archive_config(); + break; + case NODE_RESTORE_CONFIG: + do_node_restore_config(); /* CLUSTER */ case CLUSTER_SHOW: @@ -1135,7 +1145,6 @@ check_cli_parameters(const int action) { case STANDBY_CLONE: case STANDBY_FOLLOW: - case STANDBY_RESTORE_CONFIG: break; default: item_list_append_format(&cli_warnings, diff --git a/repmgr-client.h b/repmgr-client.h index efcda6dc..6c4b0744 100644 --- a/repmgr-client.h +++ b/repmgr-client.h @@ -20,12 +20,12 @@ #define STANDBY_PROMOTE 6 #define STANDBY_FOLLOW 7 #define STANDBY_SWITCHOVER 8 -#define STANDBY_ARCHIVE_CONFIG 9 -#define STANDBY_RESTORE_CONFIG 10 -#define BDR_REGISTER 11 -#define BDR_UNREGISTER 12 -#define NODE_STATUS 13 -#define NODE_CHECK 14 +#define BDR_REGISTER 9 +#define BDR_UNREGISTER 10 +#define NODE_STATUS 11 +#define NODE_CHECK 12 +#define NODE_ARCHIVE_CONFIG 13 +#define NODE_RESTORE_CONFIG 14 #define CLUSTER_SHOW 15 #define CLUSTER_CLEANUP 16 #define CLUSTER_MATRIX 17 @@ -118,6 +118,9 @@ static struct option long_options[] = {"event", required_argument, NULL, OPT_EVENT }, {"limit", required_argument, NULL, OPT_LIMIT }, +/* Following options for internal use */ + {"config-archive-dir", required_argument, NULL, OPT_CONFIG_ARCHIVE_DIR}, + /* deprecated */ {"no-conninfo-password", no_argument, NULL, OPT_NO_CONNINFO_PASSWORD}, /* legacy alias for -D/--pgdata*/ @@ -135,9 +138,6 @@ static struct option long_options[] = {"without-barman", no_argument, NULL, OPT_WITHOUT_BARMAN}, {"copy-external-config-files", optional_argument, NULL, OPT_COPY_EXTERNAL_CONFIG_FILES}, {"wait-sync", optional_argument, NULL, OPT_REGISTER_WAIT}, - /* Following options for internal use */ - {"cluster", required_argument, NULL, OPT_CLUSTER}, - {"config-archive-dir", required_argument, NULL, OPT_CONFIG_ARCHIVE_DIR}, {NULL, 0, NULL, 0} };