diff --git a/dbutils.c b/dbutils.c index 115c37d8..87fb9390 100644 --- a/dbutils.c +++ b/dbutils.c @@ -123,6 +123,26 @@ establish_db_connection_quiet(const char *conninfo) return _establish_db_connection(conninfo, false, false, true); } + +PGconn +*establish_master_db_connection(PGconn *conn, + const bool exit_on_error) +{ + t_node_info master_node_info = T_NODE_INFO_INITIALIZER; + bool master_record_found; + + master_record_found = get_master_node_record(conn, &master_node_info); + + if (master_record_found == false) + { + return NULL; + } + + return establish_db_connection(master_node_info.conninfo, + exit_on_error); +} + + PGconn * establish_db_connection_as_user(const char *conninfo, const char *user, @@ -153,6 +173,8 @@ establish_db_connection_as_user(const char *conninfo, } + + PGconn * establish_db_connection_by_params(const char *keywords[], const char *values[], const bool exit_on_error) @@ -1241,6 +1263,19 @@ get_node_record_by_name(PGconn *conn, const char *node_name, t_node_info *node_i } +bool +get_master_node_record(PGconn *conn, t_node_info *node_info) +{ + int master_node_id = get_master_node_id(conn); + + if (master_node_id == UNKNOWN_NODE_ID) + { + return false; + } + + return get_node_record(conn, master_node_id, node_info); +} + bool create_node_record(PGconn *conn, char *repmgr_action, t_node_info *node_info) @@ -1251,6 +1286,7 @@ create_node_record(PGconn *conn, char *repmgr_action, t_node_info *node_info) return _create_update_node_record(conn, "create", node_info); } + bool update_node_record(PGconn *conn, char *repmgr_action, t_node_info *node_info) { diff --git a/dbutils.h b/dbutils.h index ad399449..77a08af9 100644 --- a/dbutils.h +++ b/dbutils.h @@ -137,6 +137,8 @@ PGconn *establish_db_connection_as_user(const char *conninfo, PGconn *establish_db_connection_by_params(const char *keywords[], const char *values[], const bool exit_on_error); +PGconn *establish_master_db_connection(PGconn *conn, + const bool exit_on_error); PGconn *get_master_connection(PGconn *standby_conn, int *master_id, char *master_conninfo_out); @@ -186,6 +188,7 @@ const char * get_node_type_string(t_server_type type); int get_node_record(PGconn *conn, int node_id, t_node_info *node_info); int get_node_record_by_name(PGconn *conn, const char *node_name, t_node_info *node_info); +bool get_master_node_record(PGconn *conn, t_node_info *node_info); bool create_node_record(PGconn *conn, char *repmgr_action, t_node_info *node_info); bool update_node_record(PGconn *conn, char *repmgr_action, t_node_info *node_info); diff --git a/repmgr-action-master.c b/repmgr-action-master.c index ed18af58..019e8b26 100644 --- a/repmgr-action-master.c +++ b/repmgr-action-master.c @@ -36,9 +36,9 @@ do_master_register(void) /* check that node is actually a master */ recovery_type = get_recovery_type(conn); - if (recovery_type != RECTYPE_STANDBY) + if (recovery_type != RECTYPE_MASTER) { - if (recovery_type == RECTYPE_MASTER) + if (recovery_type == RECTYPE_STANDBY) { log_error(_("server is in standby mode and cannot be registered as a master")); PQfinish(conn); @@ -215,60 +215,175 @@ do_master_register(void) void do_master_unregister(void) { + PGconn *master_conn = NULL; PGconn *local_conn = NULL; t_node_info local_node_info = T_NODE_INFO_INITIALIZER; - - t_node_info *node_info; bool record_found; - /* Get local node record */ - local_conn = establish_db_connection(config_file_options.conninfo, true); - record_found = get_node_record(local_conn, config_file_options.node_id, &local_node_info); + t_node_info *target_node_info_ptr; + PGconn *target_node_conn = NULL; + /* We must be able to connect to the local node */ + local_conn = establish_db_connection(config_file_options.conninfo, true); + + /* From which we obtain a connection to the master node */ + master_conn = establish_master_db_connection(local_conn, true); + + /* Local connection no longer required */ + PQfinish(local_conn); + + /* Get local node record */ + record_found = get_node_record(master_conn, config_file_options.node_id, &local_node_info); + + // XXX add function get_local_node_record() which aborts as below if (record_found == FALSE) { log_error(_("unable to retrieve record for local node")); log_detail(_("local node id is %i"), config_file_options.node_id); log_hint(_("check this node was correctly registered")); - PQfinish(local_conn); exit(ERR_BAD_CONFIG); } - PQfinish(local_conn); - - /* - * If node was explicitly specified (and it's not the local node), - * can we connect to that? - */ - if (target_node_info.node_id == config_file_options.node_id) + /* Target node is local node? */ + if (target_node_info.node_id == UNKNOWN_NODE_ID + || target_node_info.node_id == config_file_options.node_id) { - node_info = &local_node_info; + target_node_info_ptr = &local_node_info; } + /* Target node is explicitly specified, and is not local node */ else { - PGconn *target_node_conn = NULL; + target_node_info_ptr = &target_node_info; + } - target_node_conn = establish_db_connection_quiet(target_node_info.conninfo); - if (PQstatus(target_node_conn) == CONNECTION_OK) + + target_node_conn = establish_db_connection_quiet(target_node_info_ptr->conninfo); + + /* If node not reachable, check that the record is for a master node */ + if (PQstatus(target_node_conn) != CONNECTION_OK) + { + if (target_node_info_ptr->type != MASTER) { - t_recovery_type recovery_type = get_recovery_type(target_node_conn); - - // check if active master - if (recovery_type != RECTYPE_MASTER) + log_error(_("node %s (id: %i) is not a master, unable to unregister"), + target_node_info_ptr->node_name, + target_node_info_ptr->node_id); + if (target_node_info_ptr->type == STANDBY) { - log_error(_("sd")); + log_hint(_("the node can be unregistered with \"repmgr standby unregister\"")); + } + + PQfinish(master_conn); + exit(ERR_BAD_CONFIG); + } + } + /* If we can connect to the node, perform some sanity checks on it */ + else + { + t_recovery_type recovery_type = get_recovery_type(target_node_conn); + + /* Node appears to be a standby */ + if (recovery_type == RECTYPE_STANDBY) + { + /* + * If --F/--force not set, hint that it might be appropriate to + * register the node as a standby rather than unregister as master + */ + if (!runtime_options.force) + { + log_error(_("node %s (id: %i) is a standby, unable to unregister"), + target_node_info_ptr->node_name, + target_node_info_ptr->node_id); + log_hint(_("the node can be registered as a standby with \"repmgr standby register --force\"")); + log_hint(_("use \"repmgr master unregister --force\" to remove this node's metadata entirely")); + + PQfinish(target_node_conn); + PQfinish(master_conn); + exit(ERR_BAD_CONFIG); + } + } + else if (recovery_type == RECTYPE_MASTER) + { + t_node_info master_node_info = T_NODE_INFO_INITIALIZER; + bool master_record_found; + + master_record_found = get_master_node_record(local_conn, &master_node_info); + + if (master_record_found == false) + { + log_error(_("node %s (id: %i) is a master node, but no master node record found"), + target_node_info_ptr->node_name, + target_node_info_ptr->node_id); + log_hint(_("register this node as master with \"repmgr master register --force\"")); + PQfinish(target_node_conn); + PQfinish(master_conn); + exit(ERR_BAD_CONFIG); + } + /* This appears to be the cluster master - cowardly refuse + * to delete the record + */ + if (master_node_info.node_id == target_node_info_ptr->node_id) + { + log_error(_("node %s (id: %i) is the current master node, unable to unregister"), + target_node_info_ptr->node_name, + target_node_info_ptr->node_id); + + if (master_node_info.active == true) + { + log_hint(_("node is marked as inactive, activate with \"repmgr master register --force\"")); + } + PQfinish(target_node_conn); + PQfinish(master_conn); + exit(ERR_BAD_CONFIG); } } - node_info = &target_node_info; + /* We don't need the target node connection any more */ + PQfinish(target_node_conn); } - // XXX can be executed on other node - // must fail on active master + if (target_node_info_ptr->active == true) + { + if (!runtime_options.force) + { + log_error(_("node %s (id: %i) is marked as active, unable to unregister"), + target_node_info_ptr->node_name, + target_node_info_ptr->node_id); + log_hint(_("run \"repmgr master unregister --force\" to unregister this node")); + PQfinish(master_conn); + exit(ERR_BAD_CONFIG); + } + } - // can we connect to node? - // -> is master? + // check if any records point to this record, detail: each, hint: follow or unregister + if (runtime_options.dry_run == true) + { + log_notice(_("node %s (id: %i) would now be unregistered"), + target_node_info_ptr->node_name, + target_node_info_ptr->node_id); + log_hint(_("run the same command without the --dry-run option to unregister this node")); + } + else + { + bool delete_success = delete_node_record(master_conn, + target_node_info_ptr->node_id); + + if (delete_success == false) + { + log_error(_("unable to unregister node %s (id: %i)"), + target_node_info_ptr->node_name, + target_node_info_ptr->node_id); + PQfinish(master_conn); + exit(ERR_DB_QUERY); + } + + log_info(_("node %s (id: %i) was successfully unregistered"), + target_node_info_ptr->node_name, + target_node_info_ptr->node_id); + } + + PQfinish(master_conn); + return; } diff --git a/repmgr-client-global.h b/repmgr-client-global.h index 7a6f9788..01aa6016 100644 --- a/repmgr-client-global.h +++ b/repmgr-client-global.h @@ -23,6 +23,7 @@ typedef struct /* general configuration options */ char config_file[MAXPGPATH]; + bool dry_run; bool force; char pg_bindir[MAXLEN]; /* overrides setting in repmgr.conf */ @@ -75,7 +76,7 @@ typedef struct /* configuration metadata */ \ false, false, false, false, false, \ /* general configuration options */ \ - "", false, "", \ + "", false, false, "", \ /* logging options */ \ "", false, false, false, \ /* database connection options */ \ diff --git a/repmgr-client.c b/repmgr-client.c index bfc15176..6831de91 100644 --- a/repmgr-client.c +++ b/repmgr-client.c @@ -9,6 +9,7 @@ * Commands implemented are: * * [ MASTER | PRIMARY ] REGISTER + * [ MASTER | PRIMARY ] UNREGISTER * * STANDBY CLONE * STANDBY REGISTER @@ -192,6 +193,11 @@ main(int argc, char **argv) strncpy(runtime_options.config_file, optarg, MAXLEN); break; + /* --dry-run */ + case OPT_DRY_RUN: + runtime_options.dry_run = true; + break; + /* -F/--force */ case 'F': runtime_options.force = true; @@ -551,6 +557,8 @@ main(int argc, char **argv) { if (strcasecmp(repmgr_action, "REGISTER") == 0) action = MASTER_REGISTER; + else if (strcasecmp(repmgr_action, "UNREGISTER") == 0) + action = MASTER_UNREGISTER; } else if (strcasecmp(repmgr_node_type, "STANDBY") == 0) { @@ -773,6 +781,7 @@ main(int argc, char **argv) PQfinish(conn); exit(ERR_BAD_CONFIG); } + printf("xXX %s\n", target_node_info.node_name); } else if (runtime_options.node_name[0] != '\0') { @@ -819,6 +828,9 @@ main(int argc, char **argv) case MASTER_REGISTER: do_master_register(); break; + case MASTER_UNREGISTER: + do_master_unregister(); + break; case STANDBY_CLONE: do_standby_clone(); @@ -995,6 +1007,7 @@ check_cli_parameters(const int action) { switch (action) { + case MASTER_UNREGISTER: case STANDBY_UNREGISTER: case WITNESS_UNREGISTER: case CLUSTER_EVENT: @@ -1167,6 +1180,7 @@ do_help(void) printf(_("Usage:\n")); printf(_(" %s [OPTIONS] master register\n"), progname()); + printf(_(" %s [OPTIONS] master unregister\n"), progname()); printf(_(" %s [OPTIONS] cluster event\n"), progname()); puts(""); printf(_("General options:\n")); @@ -1490,7 +1504,7 @@ get_superuser_connection(PGconn **conn, PGconn **superuser_conn, PGconn **privil return; } - // XXX largely duplicatied from create_repmgr_extension() + // XXX largely duplicated from create_repmgr_extension() if (runtime_options.superuser[0] == '\0') { log_error(_("\"%s\" is not a superuser and no superuser name supplied"), userinfo.username); diff --git a/repmgr-client.h b/repmgr-client.h index 8ebc9d51..569f7a28 100644 --- a/repmgr-client.h +++ b/repmgr-client.h @@ -13,24 +13,25 @@ #define NO_ACTION 0 /* Dummy default action */ #define MASTER_REGISTER 1 -#define STANDBY_REGISTER 2 -#define STANDBY_UNREGISTER 3 -#define STANDBY_CLONE 4 -#define STANDBY_PROMOTE 5 -#define STANDBY_FOLLOW 6 -#define STANDBY_SWITCHOVER 7 -#define STANDBY_ARCHIVE_CONFIG 8 -#define STANDBY_RESTORE_CONFIG 9 -#define WITNESS_CREATE 10 -#define WITNESS_REGISTER 11 -#define WITNESS_UNREGISTER 12 -#define CLUSTER_SHOW 13 -#define CLUSTER_CLEANUP 14 -#define CLUSTER_MATRIX 15 -#define CLUSTER_CROSSCHECK 16 -#define CLUSTER_EVENT 17 -#define BDR_REGISTER 18 -#define BDR_UNREGISTER 19 +#define MASTER_UNREGISTER 2 +#define STANDBY_REGISTER 3 +#define STANDBY_UNREGISTER 4 +#define STANDBY_CLONE 5 +#define STANDBY_PROMOTE 6 +#define STANDBY_FOLLOW 7 +#define STANDBY_SWITCHOVER 8 +#define STANDBY_ARCHIVE_CONFIG 9 +#define STANDBY_RESTORE_CONFIG 10 +#define WITNESS_CREATE 11 +#define WITNESS_REGISTER 12 +#define WITNESS_UNREGISTER 13 +#define CLUSTER_SHOW 14 +#define CLUSTER_CLEANUP 15 +#define CLUSTER_MATRIX 16 +#define CLUSTER_CROSSCHECK 17 +#define CLUSTER_EVENT 18 +#define BDR_REGISTER 19 +#define BDR_UNREGISTER 20 /* command line options without short versions */ #define OPT_HELP 1 @@ -56,6 +57,7 @@ #define OPT_EVENT 20 #define OPT_LIMIT 21 #define OPT_ALL 22 +#define OPT_DRY_RUN 23 /* deprecated since 3.3 */ #define OPT_NO_CONNINFO_PASSWORD 999 @@ -68,6 +70,7 @@ static struct option long_options[] = /* general configuration options */ {"config-file", required_argument, NULL, 'f'}, + {"dry-run", no_argument, NULL, OPT_DRY_RUN}, {"force", no_argument, NULL, 'F'}, {"pg_bindir", required_argument, NULL, 'b'},