Add "repmgr bdr register"

This commit is contained in:
Ian Barwick
2017-07-11 15:38:04 +09:00
parent 2962ffe605
commit 71a0871232
8 changed files with 458 additions and 17 deletions

View File

@@ -26,7 +26,8 @@ include Makefile.global
$(info Building against PostgreSQL $(MAJORVERSION))
REPMGR_CLIENT_OBJS = repmgr-client.o repmgr-action-primary.o repmgr-action-standby.o repmgr-action-cluster.o \
REPMGR_CLIENT_OBJS = repmgr-client.o \
repmgr-action-primary.o repmgr-action-standby.o repmgr-action-bdr.o repmgr-action-cluster.o \
config.o log.o strutil.o dbutils.o dirutil.o compat.o controldata.o
REPMGRD_OBJS = repmgrd.o config.o log.o dbutils.o strutil.o
@@ -52,9 +53,10 @@ maintainer-clean: additional-maintainer-clean
additional-clean:
rm -f repmgr-client.o
rm -f repmgr-action-cluster.o
rm -f repmgr-action-primary.o
rm -f repmgr-action-standby.o
rm -f repmgr-action-bdr.o
rm -f repmgr-action-cluster.o
rm -f repmgrd.o
rm -f compat.o
rm -f config.o

View File

@@ -37,6 +37,8 @@ Currently available:
repmgr standby promote
repmgr standby follow
repmgr bdr register
repmgr cluster event [--all] [--node-id] [--node-name] [--event] [--event-matching]
@@ -63,3 +65,67 @@ and vice-versa, e.g. to avoid hard-coding things like a node's
upstream ID which might change.
TODO: possibly add a config file conversion script/function.
Generating event notifications with repmgr/repmgrd
--------------------------------------------------
Each time `repmgr` or `repmgrd` perform a significant event, a record
of that event is written into the `repl_events` table together with
a timestamp, an indication of failure or success, and further details
if appropriate. This is useful for gaining an overview of events
affecting the replication cluster. However note that this table has
advisory character and should be used in combination with the `repmgr`
and PostgreSQL logs to obtain details of any events.
Example output after a master was registered and a standby cloned
and registered:
repmgr=# SELECT * from repmgr_test.repl_events ;
node_id | event | successful | event_timestamp | details
---------+------------------+------------+-------------------------------+-------------------------------------------------------------------------------------
1 | master_register | t | 2016-01-08 15:04:39.781733+09 |
2 | standby_clone | t | 2016-01-08 15:04:49.530001+09 | Cloned from host 'repmgr_node1', port 5432; backup method: pg_basebackup; --force: N
2 | standby_register | t | 2016-01-08 15:04:50.621292+09 |
(3 rows)
Additionally, event notifications can be passed to a user-defined program
or script which can take further action, e.g. send email notifications.
This is done by setting the `event_notification_command` parameter in
`repmgr.conf`.
This parameter accepts the following format placeholders:
%n - node ID
%e - event type
%s - success (1 or 0)
%t - timestamp
%d - details
The values provided for "%t" and "%d" will probably contain spaces,
so should be quoted in the provided command configuration, e.g.:
event_notification_command='/path/to/some/script %n %e %s "%t" "%d"'
Additionally the following format placeholders are available for the even
type `bdr_failover`:
%c - conninfo string of the next available node
%a - name of the next available node
These should always be quoted.
By default, all notification type will be passed to the designated script;
the notification types can be filtered to explicitly named ones:
event_notifications=master_register,standby_register
The following event types are available:
...
* `bdr_failover`
* `bdr_register`
* `bdr_unregister`
Note that under some circumstances (e.g. no replication cluster master could
be located), it will not be possible to write an entry into the `repl_events`
table, in which case `event_notification_command` can serve as a fallback.

111
dbutils.c
View File

@@ -47,6 +47,33 @@ parse_lsn(const char *str)
}
/*
* Wrap query with appropriate DDL function, if required.
*/
void
wrap_ddl_query(PQExpBufferData *query_buf, int replication_type, const char *fmt, ...)
{
va_list arglist;
char buf[MAXLEN];
if (replication_type == REPLICATION_TYPE_BDR)
{
appendPQExpBuffer(query_buf, "SELECT bdr.bdr_replicate_ddl_command($repmgr$");
}
va_start(arglist, fmt);
vsnprintf(buf, MAXLEN, fmt, arglist);
va_end(arglist);
appendPQExpBuffer(query_buf, "%s", buf);
if (replication_type == REPLICATION_TYPE_BDR)
{
appendPQExpBuffer(query_buf, "$repmgr$)");
}
}
/* ==================== */
/* Connection functions */
/* ==================== */
@@ -1517,6 +1544,7 @@ update_node_record(PGconn *conn, char *repmgr_action, t_node_info *node_info)
return _create_update_node_record(conn, "update", node_info);
}
static bool
_create_update_node_record(PGconn *conn, char *action, t_node_info *node_info)
{
@@ -2793,7 +2821,7 @@ is_bdr_repmgr(PGconn *conn)
bool
is_table_in_bdr_replication_set(PGconn *conn, char *tablename, char *set)
is_table_in_bdr_replication_set(PGconn *conn, const char *tablename, const char *set)
{
PQExpBufferData query;
PGresult *res;
@@ -2829,10 +2857,10 @@ is_table_in_bdr_replication_set(PGconn *conn, char *tablename, char *set)
bool
add_table_to_bdr_replication_set(PGconn *conn, char *tablename, char *set)
add_table_to_bdr_replication_set(PGconn *conn, const char *tablename, const char *set)
{
PQExpBufferData query;
PGresult *res;
PGresult *res;
initPQExpBuffer(&query);
@@ -2861,3 +2889,80 @@ add_table_to_bdr_replication_set(PGconn *conn, char *tablename, char *set)
return true;
}
bool
bdr_node_exists(PGconn *conn, const char *node_name)
{
PQExpBufferData query;
PGresult *res;
bool node_exists;
initPQExpBuffer(&query);
appendPQExpBuffer(
&query,
"SELECT COUNT(*)"
" FROM bdr.bdr_nodes"
" WHERE node_name = '%s'",
node_name);
res = PQexec(conn, query.data);
termPQExpBuffer(&query);
if (!res || PQresultStatus(res) != PGRES_TUPLES_OK)
{
node_exists = false;
}
else
{
node_exists = atoi(PQgetvalue(res, 0, 0)) == 1 ? true : false;
}
PQclear(res);
return node_exists;
}
void
add_extension_tables_to_bdr_replication_set(PGconn *conn)
{
PQExpBufferData query;
PGresult *res;
initPQExpBuffer(&query);
appendPQExpBuffer(
&query,
" SELECT c.relname "
" FROM pg_class c "
"INNER JOIN pg_namespace n "
" ON c.relnamespace = n.oid "
" WHERE n.nspname = 'repmgr' "
" AND c.relkind = 'r' ");
res = PQexec(conn, query.data);
termPQExpBuffer(&query);
if (!res || PQresultStatus(res) != PGRES_TUPLES_OK)
{
//
}
else
{
int i;
for (i = 0; i < PQntuples(res); i++)
{
add_table_to_bdr_replication_set(
conn,
PQgetvalue(res, i, 0),
"repmgr");
}
}
PQclear(res);
return;
}

View File

@@ -148,6 +148,8 @@ typedef struct s_connection_user
XLogRecPtr parse_lsn(const char *str);
extern void wrap_ddl_query(PQExpBufferData *query_buf, int replication_type, const char *fmt, ...)
__attribute__((format(PG_PRINTF_ATTRIBUTE, 3, 4)));
/* connection functions */
PGconn *establish_db_connection(const char *conninfo,
@@ -268,8 +270,10 @@ XLogRecPtr get_last_wal_receive_location(PGconn *conn);
bool is_bdr_db(PGconn *conn);
bool is_bdr_repmgr(PGconn *conn);
bool is_table_in_bdr_replication_set(PGconn *conn, char *tablename, char *set);
bool add_table_to_bdr_replication_set(PGconn *conn, char *tablename, char *set);
bool is_table_in_bdr_replication_set(PGconn *conn, const char *tablename, const char *set);
bool add_table_to_bdr_replication_set(PGconn *conn, const char *tablename, const char *set);
bool bdr_node_exists(PGconn *conn, const char *node_name);
void add_extension_tables_to_bdr_replication_set(PGconn *conn);
#endif /* dbutils.h */

221
repmgr-action-bdr.c Normal file
View File

@@ -0,0 +1,221 @@
/*
* repmgr-action-standby.c
*
* Implements BDR-related actions for the repmgr command line utility
*
* Copyright (c) 2ndQuadrant, 2010-2017
*/
#include "repmgr.h"
#include "repmgr-client-global.h"
#include "repmgr-action-bdr.h"
/*
* do_bdr_register()
*
* As each BDR node is its own master, registering a BDR node
* will create the repmgr metadata schema if necessary.
*/
void
do_bdr_register(void)
{
PGconn *conn = NULL;
ExtensionStatus extension_status;
t_node_info node_info = T_NODE_INFO_INITIALIZER;
RecordStatus record_status;
PQExpBufferData event_details;
bool success = true;
/* sanity-check configuration for BDR-compatability */
if (config_file_options.replication_type != REPLICATION_TYPE_BDR)
{
log_error(_("cannot run BDR REGISTER on a non-BDR node"));
exit(ERR_BAD_CONFIG);
}
conn = establish_db_connection(config_file_options.conninfo, true);
if (!is_bdr_db(conn))
{
/* TODO: name database */
log_error(_("database is not BDR-enabled"));
log_hint(_("when using repmgr with BDR, the repmgr schema must be stored in the BDR database"));
exit(ERR_BAD_CONFIG);
}
/* check whether repmgr extension exists, and that any other nodes are BDR */
extension_status = get_repmgr_extension_status(conn);
if (extension_status == REPMGR_UNKNOWN)
{
log_error(_("unable to determine status of \"repmgr\" extension"));
PQfinish(conn);
}
if (extension_status == REPMGR_UNAVAILABLE)
{
log_error(_("\"repmgr\" extension is not available"));
PQfinish(conn);
}
if (extension_status == REPMGR_INSTALLED)
{
if (!is_bdr_repmgr(conn))
{
log_error(_("repmgr metadatabase contains records for non-BDR nodes"));
exit(ERR_BAD_CONFIG);
}
}
else
{
//log_info(_("bdr register: creating database objects inside the %s schema"),
// get_repmgr_schema());
begin_transaction(conn);
if (!create_repmgr_extension(conn))
{
log_error(_("unable to create repmgr extension - see preceding error message(s); aborting"));
rollback_transaction(conn);
PQfinish(conn);
exit(ERR_BAD_CONFIG);
}
commit_transaction(conn);
}
/* check for a matching BDR node */
{
bool node_exists = bdr_node_exists(conn, config_file_options.node_name);
if (node_exists == false)
{
log_error(_("no BDR node with node_name '%s' found"), config_file_options.node_name);
log_hint(_("'node_name' in repmgr.conf must match 'node_name' in bdr.bdr_nodes\n"));
PQfinish(conn);
exit(ERR_BAD_CONFIG);
}
}
initPQExpBuffer(&event_details);
begin_transaction(conn);
/*
* we'll check if a record exists (even if the schema was just created),
* as there's a faint chance of a race condition
*/
record_status = get_node_record(conn, config_file_options.node_id, &node_info);
/* Update internal node record */
node_info.type = BDR;
node_info.node_id = config_file_options.node_id;
node_info.upstream_node_id = NO_UPSTREAM_NODE;
node_info.active = true;
node_info.priority = config_file_options.priority;
strncpy(node_info.node_name, config_file_options.node_name, MAXLEN);
strncpy(node_info.location, config_file_options.location, MAXLEN);
strncpy(node_info.conninfo, config_file_options.conninfo, MAXLEN);
if (record_status == RECORD_FOUND)
{
bool node_updated;
/*
* At this point we will have established there are no non-BDR records,
* so no need to verify the node type
*/
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);
}
/*
* don't permit changing the node name - this must match the
* BDR node name set when the node was registered.
*/
if (strncmp(node_info.node_name, config_file_options.node_name, MAXLEN) != 0)
{
log_error(_("a record for node %i is already registered with node_name '%s'"),
config_file_options.node_id, node_info.node_name);
log_hint(_("node_name configured in repmgr.conf is '%s'"), config_file_options.node_name);
rollback_transaction(conn);
PQfinish(conn);
exit(ERR_BAD_CONFIG);
}
node_updated = update_node_record(conn, "bdr register", &node_info);
if (node_updated == true)
{
appendPQExpBuffer(&event_details, _("node record updated for node '%s' (%i)"),
config_file_options.node_name, config_file_options.node_id);
log_verbose(LOG_NOTICE, "%s\n", event_details.data);
}
{
success = false;
}
}
else
{
/* create new node record */
bool node_created = create_node_record(conn, "bdr register", &node_info);
if (node_created == true)
{
appendPQExpBuffer(&event_details,
_("node record created for node '%s' (ID: %i)"),
config_file_options.node_name, config_file_options.node_id);
log_notice("%s", event_details.data);
}
else
{
success = false;
}
}
if (success == false)
{
rollback_transaction(conn);
PQfinish(conn);
exit(ERR_DB_QUERY);
}
commit_transaction(conn);
/* Log the event */
create_event_notification(
conn,
&config_file_options,
config_file_options.node_id,
"bdr_register",
true,
event_details.data);
termPQExpBuffer(&event_details);
PQfinish(conn);
log_notice(_("BDR node %i registered (conninfo: %s)"),
config_file_options.node_id, config_file_options.conninfo);
return;
}
void
do_bdr_unregister(void)
{
return;
}

13
repmgr-action-bdr.h Normal file
View File

@@ -0,0 +1,13 @@
/*
* repmgr-action-bdr.h
* Copyright (c) 2ndQuadrant, 2010-2017
*/
#ifndef _REPMGR_ACTION_BDR_H_
#define _REPMGR_ACTION_BDR_H_
extern void do_bdr_register(void);
extern void do_bdr_unregister(void);
#endif /* _REPMGR_ACTION_BDR_H_ */

View File

@@ -33,4 +33,4 @@ typedef struct
#define T_CONFIGFILE_LIST_INITIALIZER { 0, 0, NULL }
#endif
#endif /* _REPMGR_ACTION_STANDBY_H_ */

View File

@@ -28,6 +28,8 @@
#include "repmgr-client-global.h"
#include "repmgr-action-primary.h"
#include "repmgr-action-standby.h"
#include "repmgr-action-bdr.h"
#include "repmgr-action-cluster.h"
#include <storage/fd.h> /* for PG_TEMP_FILE_PREFIX */
@@ -579,6 +581,7 @@ main(int argc, char **argv)
else if (strcasecmp(repmgr_action, "UNREGISTER") == 0)
action = PRIMARY_UNREGISTER;
}
else if (strcasecmp(repmgr_node_type, "STANDBY") == 0)
{
if (strcasecmp(repmgr_action, "CLONE") == 0)
@@ -599,6 +602,14 @@ main(int argc, char **argv)
action = STANDBY_RESTORE_CONFIG;
}
else if (strcasecmp(repmgr_node_type, "BDR") == 0)
{
if (strcasecmp(repmgr_action, "REGISTER") == 0)
action = BDR_REGISTER;
else if (strcasecmp(repmgr_action, "UNREGISTER") == 0)
action = BDR_UNREGISTER;
}
else if (strcasecmp(repmgr_node_type, "CLUSTER") == 0)
{
if (strcasecmp(repmgr_action, "EVENT") == 0)
@@ -845,6 +856,7 @@ main(int argc, char **argv)
switch (action)
{
/* PRIMARY */
case PRIMARY_REGISTER:
do_primary_register();
break;
@@ -852,6 +864,7 @@ main(int argc, char **argv)
do_primary_unregister();
break;
/* STANDBY */
case STANDBY_CLONE:
do_standby_clone();
break;
@@ -877,6 +890,15 @@ main(int argc, char **argv)
do_standby_restore_config();
break;
/* BDR */
case BDR_REGISTER:
do_bdr_register();
break;
case BDR_UNREGISTER:
do_bdr_unregister();
break;
/* CLUSTER */
case CLUSTER_EVENT:
do_cluster_event();
break;
@@ -1336,14 +1358,14 @@ create_repmgr_extension(PGconn *conn)
/* 4. Create extension */
initPQExpBuffer(&query);
appendPQExpBuffer(&query,
"CREATE EXTENSION repmgr");
wrap_ddl_query(&query, config_file_options.replication_type,
"CREATE EXTENSION repmgr");
res = PQexec(schema_create_conn, query.data);
termPQExpBuffer(&query);
if (PQresultStatus(res) != PGRES_COMMAND_OK)
if ((PQresultStatus(res) != PGRES_COMMAND_OK && PQresultStatus(res) != PGRES_TUPLES_OK))
{
log_error(_("unable to create \"repmgr\" extension:\n %s"),
PQerrorMessage(schema_create_conn));
@@ -1357,14 +1379,21 @@ create_repmgr_extension(PGconn *conn)
PQclear(res);
/* For BDR, we'll need to add the repmgr extension tables to a replication set */
if (config_file_options.replication_type == REPLICATION_TYPE_BDR)
{
add_extension_tables_to_bdr_replication_set(conn);
}
/* 5. If not superuser, grant usage */
if (is_superuser == false)
{
initPQExpBuffer(&query);
appendPQExpBuffer(&query,
"GRANT USAGE ON SCHEMA repmgr TO %s",
userinfo.username);
wrap_ddl_query(&query, config_file_options.replication_type,
"GRANT USAGE ON SCHEMA repmgr TO %s",
userinfo.username);
res = PQexec(schema_create_conn, query.data);
termPQExpBuffer(&query);
@@ -1382,9 +1411,10 @@ create_repmgr_extension(PGconn *conn)
}
initPQExpBuffer(&query);
appendPQExpBuffer(&query,
"GRANT ALL ON ALL TABLES IN SCHEMA repmgr TO %s",
userinfo.username);
wrap_ddl_query(&query, config_file_options.replication_type,
"GRANT ALL ON ALL TABLES IN SCHEMA repmgr TO %s",
userinfo.username);
res = PQexec(schema_create_conn, query.data);
termPQExpBuffer(&query);