diff --git a/Makefile.in b/Makefile.in index 93736bcd..04178428 100644 --- a/Makefile.in +++ b/Makefile.in @@ -26,7 +26,8 @@ include Makefile.global $(info Building against PostgreSQL $(MAJORVERSION)) -REPMGR_CLIENT_OBJS = repmgr-client.o config.o log.o strutil.o dbutils.o +REPMGR_CLIENT_OBJS = repmgr-client.o repmgr-action-master.o repmgr-action-cluster.o \ + config.o log.o strutil.o dbutils.o REPMGRD_OBJS = repmgrd.o $(REPMGR_CLIENT_OBJS): repmgr-client.h diff --git a/repmgr-action-cluster.c b/repmgr-action-cluster.c new file mode 100644 index 00000000..c0b35926 --- /dev/null +++ b/repmgr-action-cluster.c @@ -0,0 +1,113 @@ +/* + * repmgr-action-cluster.c + * + * Implements cluster information actions for the repmgr command line utility + * + * Copyright (c) 2ndQuadrant, 2010-2017 + */ + +#include "repmgr.h" + +#include "repmgr-client-global.h" +#include "repmgr-action-cluster.h" + +/* + * CLUSTER EVENT + * + * Parameters: + * --limit[=20] + * --all + * --node_[id|name] + * --event + */ +void +do_cluster_event(void) +{ + PGconn *conn; + PQExpBufferData query; + PQExpBufferData where_clause; + PGresult *res; + int i; + + conn = establish_db_connection(config_file_options.conninfo, true); + + initPQExpBuffer(&query); + initPQExpBuffer(&where_clause); + + appendPQExpBuffer(&query, + " SELECT node_id, event, successful, \n" + " TO_CHAR(event_timestamp, 'YYYY-MM-DD HH24:MI:SS') AS timestamp, \n" + " details \n" + " FROM repmgr.events"); + + if (runtime_options.node_id != UNKNOWN_NODE_ID) + { + append_where_clause(&where_clause, + "node_id=%i", runtime_options.node_id); + } + + if (runtime_options.event[0] != '\0') + { + char *escaped = escape_string(conn, runtime_options.event); + + if (escaped == NULL) + { + log_error(_("unable to escape value provided for event")); + } + else + { + append_where_clause(&where_clause, + "event='%s'", + escaped); + pfree(escaped); + } + } + + appendPQExpBuffer(&query, "\n%s\n", + where_clause.data); + + appendPQExpBuffer(&query, + " ORDER BY timestamp DESC"); + + if (runtime_options.all == false && runtime_options.limit > 0) + { + appendPQExpBuffer(&query, " LIMIT %i", + runtime_options.limit); + } + + log_debug("do_cluster_event():\n%s", query.data); + res = PQexec(conn, query.data); + + if (PQresultStatus(res) != PGRES_TUPLES_OK) + { + log_error(_("unable to execute event query:\n %s"), + PQerrorMessage(conn)); + PQclear(res); + PQfinish(conn); + exit(ERR_DB_QUERY); + } + + if (PQntuples(res) == 0) { + printf(_("no matching events found\n")); + PQclear(res); + PQfinish(conn); + return; + } + + /* XXX improve formatting */ + puts("node_id,event,ok,timestamp,details"); + puts("----------------------------------"); + for(i = 0; i < PQntuples(res); i++) + { + printf("%s,%s,%s,%s,%s\n", + PQgetvalue(res, i, 0), + PQgetvalue(res, i, 1), + PQgetvalue(res, i, 2), + PQgetvalue(res, i, 3), + PQgetvalue(res, i, 4)); + } + + PQclear(res); + + PQfinish(conn); +} diff --git a/repmgr-action-cluster.h b/repmgr-action-cluster.h new file mode 100644 index 00000000..d1774a19 --- /dev/null +++ b/repmgr-action-cluster.h @@ -0,0 +1,12 @@ +/* + * repmgr-action-cluster.h + * Copyright (c) 2ndQuadrant, 2010-2017 + */ + +#ifndef _REPMGR_ACTION_CLUSTER_H_ +#define _REPMGR_ACTION_CLUSTER_H_ + +extern void do_cluster_event(void); + + +#endif diff --git a/repmgr-action-master.c b/repmgr-action-master.c new file mode 100644 index 00000000..a9374038 --- /dev/null +++ b/repmgr-action-master.c @@ -0,0 +1,196 @@ +/* + * repmgr-action-cluster.c + * + * Implements master actions for the repmgr command line utility + * + * Copyright (c) 2ndQuadrant, 2010-2017 + */ + +#include "repmgr.h" + +#include "repmgr-client-global.h" +#include "repmgr-action-master.h" + + +void +do_master_register(void) +{ + PGconn *conn = NULL; + PGconn *master_conn = NULL; + int current_master_id = UNKNOWN_NODE_ID; + int ret; + + t_node_info node_info = T_NODE_INFO_INITIALIZER; + int record_found; + bool record_created; + + PQExpBufferData event_description; + + log_info(_("connecting to master database...")); + + conn = establish_db_connection(config_file_options.conninfo, true); + log_verbose(LOG_INFO, _("connected to server, checking its state")); + + /* verify that node is running a supported server version */ + check_server_version(conn, "master", true, NULL); + + /* check that node is actually a master */ + ret = is_standby(conn); + if (ret) + { + log_error(_(ret == 1 ? "server is in standby mode and cannot be registered as a master" : + "connection to node lost!")); + + PQfinish(conn); + exit(ERR_BAD_CONFIG); + } + + log_verbose(LOG_INFO, _("server is not in recovery")); + + /* create the repmgr extension if it doesn't already exist */ + if (!create_repmgr_extension(conn)) + { + PQfinish(conn); + exit(ERR_BAD_CONFIG); + } + + /* Ensure there isn't another active master already registered */ + master_conn = get_master_connection(conn, ¤t_master_id, NULL); + + if (master_conn != NULL) + { + if (current_master_id != config_file_options.node_id) + { + /* it's impossible to add a second master to a streaming replication cluster */ + log_error(_("there is already an active registered master (node ID: %i) in this cluster"), current_master_id); + PQfinish(master_conn); + PQfinish(conn); + exit(ERR_BAD_CONFIG); + } + + /* we've probably connected to ourselves */ + PQfinish(master_conn); + } + + + begin_transaction(conn); + + /* + * Check if a node with a different ID is registered as master. This shouldn't + * happen but could do if an existing master was shut down without being + * unregistered. + */ + + current_master_id = get_master_node_id(conn); + if (current_master_id != NODE_NOT_FOUND && current_master_id != config_file_options.node_id) + { + log_error(_("another node with id %i is already registered as master"), current_master_id); + // attempt to connect, add info/hint depending if active... + log_info(_("a streaming replication cluster can have only one master node")); + rollback_transaction(conn); + PQfinish(conn); + exit(ERR_BAD_CONFIG); + } + + /* + * Check whether there's an existing record for this node, and + * update it if --force set + */ + + record_found = get_node_record(conn, config_file_options.node_id, &node_info); + + if (record_found) + { + if (!runtime_options.force) + { + log_error(_("this node is already registered")); + log_hint(_("use -F/--force to overwrite the existing node record")); + rollback_transaction(conn); + PQfinish(conn); + exit(ERR_BAD_CONFIG); + } + } + else + { + node_info.node_id = config_file_options.node_id; + } + + /* if upstream_node_id set, warn that it will be ignored */ + if (config_file_options.upstream_node_id != NO_UPSTREAM_NODE) + { + log_warning(_("master node %i is configured with \"upstream_node_id\" set to %i"), + node_info.node_id, + config_file_options.upstream_node_id); + log_detail(_("the value set for \"upstream_node_id\" will be ignored")); + } + /* set type to "master", active to "true" and unset upstream_node_id*/ + node_info.type = MASTER; + node_info.upstream_node_id = NO_UPSTREAM_NODE; + node_info.active = true; + + /* update node record structure with settings from config file */ + strncpy(node_info.node_name, config_file_options.node_name, MAXLEN); + strncpy(node_info.conninfo, config_file_options.conninfo, MAXLEN); + strncpy(node_info.slot_name, repmgr_slot_name_ptr, MAXLEN); + node_info.priority = config_file_options.priority; + + initPQExpBuffer(&event_description); + + if (record_found) + { + record_created = update_node_record(conn, + "master register", + &node_info); + if (record_created == true) + { + appendPQExpBuffer(&event_description, + "existing master record updated"); + } + else + { + appendPQExpBuffer(&event_description, + "error encountered while updating master record:\n%s", + PQerrorMessage(conn)); + } + + } + else + { + record_created = create_node_record(conn, + "master register", + &node_info); + if (record_created == false) + { + appendPQExpBuffer(&event_description, + "error encountered while creating master record:\n%s", + PQerrorMessage(conn)); + } + + } + + + /* Log the event */ + create_event_record(conn, + &config_file_options, + config_file_options.node_id, + "master_register", + record_created, + event_description.data); + + if (record_created == false) + { + rollback_transaction(conn); + PQfinish(conn); + + log_notice(_("unable to register master node - see preceding messages")); + exit(ERR_DB_QUERY); + } + + commit_transaction(conn); + PQfinish(conn); + + log_notice(_("master node record (id: %i) %s"), + config_file_options.node_id, + record_found ? "updated" : "registered"); + return; +} diff --git a/repmgr-action-master.h b/repmgr-action-master.h new file mode 100644 index 00000000..195f20b2 --- /dev/null +++ b/repmgr-action-master.h @@ -0,0 +1,12 @@ +/* + * repmgr-action-master.h + * Copyright (c) 2ndQuadrant, 2010-2017 + */ + +#ifndef _REPMGR_ACTION_MASTER_H_ +#define _REPMGR_ACTION_MASTER_H_ + +extern void do_master_register(void); + + +#endif diff --git a/repmgr-client-global.h b/repmgr-client-global.h new file mode 100644 index 00000000..b0dd0d48 --- /dev/null +++ b/repmgr-client-global.h @@ -0,0 +1,62 @@ +/* + * repmgr-client-global.h + * Copyright (c) 2ndQuadrant, 2010-2017 + */ + +#ifndef _REPMGR_CLIENT_GLOBAL_H_ +#define _REPMGR_CLIENT_GLOBAL_H_ + +#include "config.h" + +typedef struct +{ + /* configuration metadata */ + bool conninfo_provided; + bool connection_param_provided; + bool host_param_provided; + bool limit_provided; + + /* general configuration options */ + char config_file[MAXPGPATH]; + bool force; + char pg_bindir[MAXLEN]; /* overrides setting in repmgr.conf */ + + /* logging options */ + char loglevel[MAXLEN]; /* overrides setting in repmgr.conf */ + bool log_to_file; + bool terse; + bool verbose; + + /* connection options */ + char superuser[MAXLEN]; + + /* node options */ + int node_id; + char node_name[MAXLEN]; + char data_dir[MAXPGPATH]; + + /* event options */ + char event[MAXLEN]; + int limit; + bool all; + +} t_runtime_options; + +/* global configuration structures */ +extern t_runtime_options runtime_options; +extern t_configuration_options config_file_options; + + +extern bool config_file_required; +extern char pg_bindir[MAXLEN]; + +extern char repmgr_slot_name[MAXLEN]; +extern char *repmgr_slot_name_ptr; + +extern t_node_info target_node_info; + + +extern int check_server_version(PGconn *conn, char *server_type, bool exit_on_error, char *server_version_string); +extern bool create_repmgr_extension(PGconn *conn); + +#endif diff --git a/repmgr-client.c b/repmgr-client.c index a7443b76..a7b865a1 100644 --- a/repmgr-client.c +++ b/repmgr-client.c @@ -19,25 +19,28 @@ #include "repmgr.h" #include "repmgr-client.h" +#include "repmgr-client-global.h" +#include "repmgr-action-cluster.h" +#include "repmgr-action-master.h" - -/* global configuration structures */ t_runtime_options runtime_options = T_RUNTIME_OPTIONS_INITIALIZER; t_configuration_options config_file_options = T_CONFIGURATION_OPTIONS_INITIALIZER; + + +bool config_file_required = true; +char pg_bindir[MAXLEN] = ""; + +char repmgr_slot_name[MAXLEN] = ""; +char *repmgr_slot_name_ptr = NULL; + +t_node_info target_node_info = T_NODE_INFO_INITIALIZER; + + /* Collate command line errors and warnings here for friendlier reporting */ ItemList cli_errors = { NULL, NULL }; ItemList cli_warnings = { NULL, NULL }; -static bool config_file_required = true; -static char pg_bindir[MAXLEN] = ""; - -static char repmgr_slot_name[MAXLEN] = ""; -static char *repmgr_slot_name_ptr = NULL; - -static t_node_info target_node_info = T_NODE_INFO_INITIALIZER; - - int main(int argc, char **argv) { @@ -743,188 +746,6 @@ do_help(void) } -static void -do_master_register(void) -{ - PGconn *conn = NULL; - PGconn *master_conn = NULL; - int current_master_id = UNKNOWN_NODE_ID; - int ret; - - t_node_info node_info = T_NODE_INFO_INITIALIZER; - int record_found; - bool record_created; - - PQExpBufferData event_description; - - log_info(_("connecting to master database...")); - - conn = establish_db_connection(config_file_options.conninfo, true); - log_verbose(LOG_INFO, _("connected to server, checking its state")); - - /* verify that node is running a supported server version */ - check_server_version(conn, "master", true, NULL); - - /* check that node is actually a master */ - ret = is_standby(conn); - if (ret) - { - log_error(_(ret == 1 ? "server is in standby mode and cannot be registered as a master" : - "connection to node lost!")); - - PQfinish(conn); - exit(ERR_BAD_CONFIG); - } - - log_verbose(LOG_INFO, _("server is not in recovery")); - - /* create the repmgr extension if it doesn't already exist */ - if (!create_repmgr_extension(conn)) - { - PQfinish(conn); - exit(ERR_BAD_CONFIG); - } - - /* Ensure there isn't another active master already registered */ - master_conn = get_master_connection(conn, ¤t_master_id, NULL); - - if (master_conn != NULL) - { - if (current_master_id != config_file_options.node_id) - { - /* it's impossible to add a second master to a streaming replication cluster */ - log_error(_("there is already an active registered master (node ID: %i) in this cluster"), current_master_id); - PQfinish(master_conn); - PQfinish(conn); - exit(ERR_BAD_CONFIG); - } - - /* we've probably connected to ourselves */ - PQfinish(master_conn); - } - - - begin_transaction(conn); - - /* - * Check if a node with a different ID is registered as master. This shouldn't - * happen but could do if an existing master was shut down without being - * unregistered. - */ - - current_master_id = get_master_node_id(conn); - if (current_master_id != NODE_NOT_FOUND && current_master_id != config_file_options.node_id) - { - log_error(_("another node with id %i is already registered as master"), current_master_id); - // attempt to connect, add info/hint depending if active... - log_info(_("a streaming replication cluster can have only one master node")); - rollback_transaction(conn); - PQfinish(conn); - exit(ERR_BAD_CONFIG); - } - - /* - * Check whether there's an existing record for this node, and - * update it if --force set - */ - - record_found = get_node_record(conn, config_file_options.node_id, &node_info); - - if (record_found) - { - if (!runtime_options.force) - { - log_error(_("this node is already registered")); - log_hint(_("use -F/--force to overwrite the existing node record")); - rollback_transaction(conn); - PQfinish(conn); - exit(ERR_BAD_CONFIG); - } - } - else - { - node_info.node_id = config_file_options.node_id; - } - - /* if upstream_node_id set, warn that it will be ignored */ - if (config_file_options.upstream_node_id != NO_UPSTREAM_NODE) - { - log_warning(_("master node %i is configured with \"upstream_node_id\" set to %i"), - node_info.node_id, - config_file_options.upstream_node_id); - log_detail(_("the value set for \"upstream_node_id\" will be ignored")); - } - /* set type to "master", active to "true" and unset upstream_node_id*/ - node_info.type = MASTER; - node_info.upstream_node_id = NO_UPSTREAM_NODE; - node_info.active = true; - - /* update node record structure with settings from config file */ - strncpy(node_info.node_name, config_file_options.node_name, MAXLEN); - strncpy(node_info.conninfo, config_file_options.conninfo, MAXLEN); - strncpy(node_info.slot_name, repmgr_slot_name_ptr, MAXLEN); - node_info.priority = config_file_options.priority; - - initPQExpBuffer(&event_description); - - if (record_found) - { - record_created = update_node_record(conn, - "master register", - &node_info); - if (record_created == true) - { - appendPQExpBuffer(&event_description, - "existing master record updated"); - } - else - { - appendPQExpBuffer(&event_description, - "error encountered while updating master record:\n%s", - PQerrorMessage(conn)); - } - - } - else - { - record_created = create_node_record(conn, - "master register", - &node_info); - if (record_created == false) - { - appendPQExpBuffer(&event_description, - "error encountered while creating master record:\n%s", - PQerrorMessage(conn)); - } - - } - - - /* Log the event */ - create_event_record(conn, - &config_file_options, - config_file_options.node_id, - "master_register", - record_created, - event_description.data); - - if (record_created == false) - { - rollback_transaction(conn); - PQfinish(conn); - - log_notice(_("unable to register master node - see preceding messages")); - exit(ERR_DB_QUERY); - } - - commit_transaction(conn); - PQfinish(conn); - - log_notice(_("master node record (id: %i) %s"), - config_file_options.node_id, - record_found ? "updated" : "registered"); - return; -} static void @@ -934,107 +755,6 @@ do_standby_clone(void) } -/* - * CLUSTER EVENT - * - * Parameters: - * --limit[=20] - * --all - * --node_[id|name] - * --event - * --event-matching - */ -static void -do_cluster_event(void) -{ - PGconn *conn; - PQExpBufferData query; - PQExpBufferData where_clause; - PGresult *res; - int i; - - conn = establish_db_connection(config_file_options.conninfo, true); - - initPQExpBuffer(&query); - initPQExpBuffer(&where_clause); - - appendPQExpBuffer(&query, - " SELECT node_id, event, successful, \n" - " TO_CHAR( event_timestamp, 'YYYY-MM-DD HH24:MI:SS') AS timestamp, \n" - " details \n" - " FROM repmgr.events"); - - if (runtime_options.node_id != UNKNOWN_NODE_ID) - { - append_where_clause(&where_clause, - "node_id=%i", runtime_options.node_id); - } - - if (runtime_options.event[0] != '\0') - { - char *escaped = escape_string(conn, runtime_options.event); - - if (escaped == NULL) - { - log_error(_("unable to escape value provided for event")); - } - else - { - append_where_clause(&where_clause, - "event='%s'", - escaped); - pfree(escaped); - } - } - - appendPQExpBuffer(&query, "\n%s\n", - where_clause.data); - - appendPQExpBuffer(&query, - " ORDER BY timestamp DESC"); - - if (runtime_options.all == false && runtime_options.limit > 0) - { - appendPQExpBuffer(&query, " LIMIT %i", - runtime_options.limit); - } - - log_debug("do_cluster_event():\n%s", query.data); - res = PQexec(conn, query.data); - - if (PQresultStatus(res) != PGRES_TUPLES_OK) - { - log_error(_("unable to execute event query:\n %s"), - PQerrorMessage(conn)); - PQclear(res); - PQfinish(conn); - exit(ERR_DB_QUERY); - } - - if (PQntuples(res) == 0) { - printf(_("no matching events found\n")); - PQclear(res); - PQfinish(conn); - return; - } - - /* XXX improve formatting */ - puts("node_id,event,ok,timestamp,details"); - puts("----------------------------------"); - for(i = 0; i < PQntuples(res); i++) - { - printf("%s,%s,%s,%s,%s\n", - PQgetvalue(res, i, 0), - PQgetvalue(res, i, 1), - PQgetvalue(res, i, 2), - PQgetvalue(res, i, 3), - PQgetvalue(res, i, 4)); - } - - PQclear(res); - - PQfinish(conn); -} /* @@ -1046,8 +766,9 @@ do_cluster_event(void) * We should also consider possible scenarious where a non-superuser * has sufficient privileges to install the extension. */ -static -bool create_repmgr_extension(PGconn *conn) + +bool +create_repmgr_extension(PGconn *conn) { PQExpBufferData query; PGresult *res; @@ -1245,7 +966,7 @@ bool create_repmgr_extension(PGconn *conn) * passed to get_server_version(), which will place the human-readable * server version string there (e.g. "9.4.0") */ -static int +int check_server_version(PGconn *conn, char *server_type, bool exit_on_error, char *server_version_string) { int server_version_num = 0; diff --git a/repmgr-client.h b/repmgr-client.h index 3b5ebffd..da0293f2 100644 --- a/repmgr-client.h +++ b/repmgr-client.h @@ -9,16 +9,7 @@ #include #include "log.h" -#ifndef RECOVERY_COMMAND_FILE -#define RECOVERY_COMMAND_FILE "recovery.conf" -#endif -#ifndef TABLESPACE_MAP -#define TABLESPACE_MAP "tablespace_map" -#endif - -#define WITNESS_DEFAULT_PORT "5499" /* If this value is ever changed, remember - * to update comments and documentation */ #define NO_ACTION 0 /* Dummy default action */ #define MASTER_REGISTER 1 @@ -133,39 +124,6 @@ static struct option long_options[] = }; -typedef struct -{ - /* configuration metadata */ - bool conninfo_provided; - bool connection_param_provided; - bool host_param_provided; - bool limit_provided; - - /* general configuration options */ - char config_file[MAXPGPATH]; - bool force; - char pg_bindir[MAXLEN]; /* overrides setting in repmgr.conf */ - - /* logging options */ - char loglevel[MAXLEN]; /* overrides setting in repmgr.conf */ - bool log_to_file; - bool terse; - bool verbose; - - /* connection options */ - char superuser[MAXLEN]; - - /* node options */ - int node_id; - char node_name[MAXLEN]; - char data_dir[MAXPGPATH]; - - /* event options */ - char event[MAXLEN]; - int limit; - bool all; - -} t_runtime_options; #define T_RUNTIME_OPTIONS_INITIALIZER { \ /* configuration metadata */ \ @@ -183,18 +141,13 @@ typedef struct static void do_help(void); -static void do_master_register(void); static void do_standby_clone(void); -static void do_cluster_event(void); - static const char *action_name(const int action); static void exit_with_errors(void); static void print_item_list(ItemList *item_list); static void check_cli_parameters(const int action); -static int check_server_version(PGconn *conn, char *server_type, bool exit_on_error, char *server_version_string); -static bool create_repmgr_extension(PGconn *conn); #endif diff --git a/repmgr.h b/repmgr.h index 6cd697fc..ca23d7e8 100644 --- a/repmgr.h +++ b/repmgr.h @@ -38,4 +38,15 @@ #define FAILOVER_NODES_MAX_CHECK 50 +#ifndef RECOVERY_COMMAND_FILE +#define RECOVERY_COMMAND_FILE "recovery.conf" +#endif + +#ifndef TABLESPACE_MAP +#define TABLESPACE_MAP "tablespace_map" +#endif + +#define WITNESS_DEFAULT_PORT "5499" /* If this value is ever changed, remember + * to update comments and documentation */ + #endif