Initial database functions

This commit is contained in:
Ian Barwick
2017-04-21 15:12:33 +09:00
parent 11b7dda778
commit 001d887e8d
9 changed files with 524 additions and 23 deletions

View File

@@ -26,7 +26,7 @@ include Makefile.global
$(info Building against PostgreSQL $(MAJORVERSION))
REPMGR_CLIENT_OBJS = repmgr-client.o config.o log.o
REPMGR_CLIENT_OBJS = repmgr-client.o config.o log.o strutil.o dbutils.o
REPMGRD_OBJS = repmgrd.o
repmgr4: $(REPMGR_CLIENT_OBJS)

View File

@@ -182,8 +182,7 @@ parse_config(t_configuration_options *options)
log_warning(_("the following problems were found in the configuration file:"));
for (cell = config_warnings.head; cell; cell = cell->next)
{
fprintf(stderr, " ");
log_warning("%s", cell->string);
fprintf(stderr, " %s\n", cell->string);
}
}
@@ -327,6 +326,8 @@ _parse_config(t_configuration_options *options, ItemList *error_list, ItemList *
options->node_id = repmgr_atoi(value, name, error_list, 1);
node_id_found = true;
}
else if (strcmp(name, "node_name") == 0)
strncpy(options->node_name, value, MAXLEN);
else if (strcmp(name, "upstream_node_id") == 0)
options->upstream_node_id = repmgr_atoi(value, name, error_list, 1);
else if (strcmp(name, "conninfo") == 0)

187
dbutils.c Normal file
View File

@@ -0,0 +1,187 @@
/*
* dbutils.c - Database connection/management functions
*
* Copyright (c) 2ndQuadrant, 2010-2017
*
*/
#include <unistd.h>
#include <time.h>
#include <sys/time.h>
#include "repmgr.h"
#include "catalog/pg_control.h"
static PGconn *_establish_db_connection(const char *conninfo,
const bool exit_on_error,
const bool log_notice,
const bool verbose_only);
static bool _set_config(PGconn *conn, const char *config_param, const char *sqlquery);
/* ==================== */
/* Connection functions */
/* ==================== */
/*
* _establish_db_connection()
*
* Connect to a database using a conninfo string.
*
* NOTE: *do not* use this for replication connections; instead use:
* establish_db_connection_by_params()
*/
static PGconn *
_establish_db_connection(const char *conninfo, const bool exit_on_error, const bool log_notice, const bool verbose_only)
{
PGconn *conn = NULL;
char connection_string[MAXLEN];
strncpy(connection_string, conninfo, MAXLEN);
/* TODO: only set if not already present */
strcat(connection_string, " fallback_application_name='repmgr'");
log_debug(_("connecting to: '%s'"), connection_string);
conn = PQconnectdb(connection_string);
/* Check to see that the backend connection was successfully made */
if ((PQstatus(conn) != CONNECTION_OK))
{
bool emit_log = true;
if (verbose_only == true && verbose_logging == false)
emit_log = false;
if (emit_log)
{
if (log_notice)
{
log_notice(_("connection to database failed: %s"),
PQerrorMessage(conn));
}
else
{
log_error(_("connection to database failed: %s"),
PQerrorMessage(conn));
}
}
if (exit_on_error)
{
PQfinish(conn);
exit(ERR_DB_CON);
}
}
/*
* set "synchronous_commit" to "local" in case synchronous replication is in use
*
* XXX set this explicitly before any write operations
*/
else if (set_config(conn, "synchronous_commit", "local") == false)
{
if (exit_on_error)
{
PQfinish(conn);
exit(ERR_DB_CON);
}
}
return conn;
}
/*
* Establish a database connection, optionally exit on error
*/
PGconn *
establish_db_connection(const char *conninfo, const bool exit_on_error)
{
return _establish_db_connection(conninfo, exit_on_error, false, false);
}
/* ========================== */
/* GUC manipulation functions */
/* ========================== */
static bool
_set_config(PGconn *conn, const char *config_param, const char *sqlquery)
{
PGresult *res;
res = PQexec(conn, sqlquery);
if (PQresultStatus(res) != PGRES_COMMAND_OK)
{
log_error("unable to set '%s': %s", config_param, PQerrorMessage(conn));
PQclear(res);
return false;
}
PQclear(res);
return true;
}
bool
set_config(PGconn *conn, const char *config_param, const char *config_value)
{
char sqlquery[MAX_QUERY_LEN];
sqlquery_snprintf(sqlquery,
"SET %s TO '%s'",
config_param,
config_value);
log_verbose(LOG_DEBUG, "set_config():\n%s", sqlquery);
return _set_config(conn, config_param, sqlquery);
}
bool
set_config_bool(PGconn *conn, const char *config_param, bool state)
{
char sqlquery[MAX_QUERY_LEN];
sqlquery_snprintf(sqlquery,
"SET %s TO %s",
config_param,
state ? "TRUE" : "FALSE");
log_verbose(LOG_DEBUG, "set_config_bool():\n%s\n", sqlquery);
return _set_config(conn, config_param, sqlquery);
}
/* ============================ */
/* Server information functions */
/* ============================ */
/*
* Return the server version number for the connection provided
*/
int
get_server_version(PGconn *conn, char *server_version)
{
PGresult *res;
res = PQexec(conn,
"SELECT pg_catalog.current_setting('server_version_num'), "
" pg_catalog.current_setting('server_version')");
if (PQresultStatus(res) != PGRES_TUPLES_OK)
{
log_error(_("unable to determine server version number:\n%s"),
PQerrorMessage(conn));
PQclear(res);
return -1;
}
if (server_version != NULL)
strcpy(server_version, PQgetvalue(res, 0, 0));
return atoi(PQgetvalue(res, 0, 0));
}

110
dbutils.h Normal file
View File

@@ -0,0 +1,110 @@
/*
* dbutils.h
*
* Copyright (c) 2ndQuadrant, 2010-2017
*/
#ifndef _REPMGR_DBUTILS_H_
#define _REPMGR_DBUTILS_H_
#include "access/xlogdefs.h"
#include "pqexpbuffer.h"
#include "config.h"
#include "strutil.h"
typedef enum {
UNKNOWN = 0,
MASTER,
STANDBY,
WITNESS,
BDR
} t_server_type;
/*
* Struct to store node information
*/
typedef struct s_node_info
{
int node_id;
int upstream_node_id;
t_server_type type;
char name[MAXLEN];
char conninfo[MAXLEN];
char slot_name[MAXLEN];
int priority;
bool active;
bool is_ready;
bool is_visible;
XLogRecPtr xlog_location;
} t_node_info;
#define T_NODE_INFO_INITIALIZER { \
NODE_NOT_FOUND, \
NO_UPSTREAM_NODE, \
UNKNOWN, \
"", \
"", \
"", \
DEFAULT_PRIORITY, \
true, \
false, \
false, \
InvalidXLogRecPtr \
}
typedef struct NodeInfoListCell
{
struct NodeInfoListCell *next;
t_node_info *node_info;
} NodeInfoListCell;
typedef struct NodeInfoList
{
NodeInfoListCell *head;
NodeInfoListCell *tail;
} NodeInfoList;
typedef struct s_event_info
{
char *node_name;
char *conninfo_str;
} t_event_info;
#define T_EVENT_INFO_INITIALIZER { \
NULL, \
NULL \
}
/*
* Struct to store replication slot information
*/
typedef struct s_replication_slot
{
char slot_name[MAXLEN];
char slot_type[MAXLEN];
bool active;
} t_replication_slot;
/* connection functions */
PGconn *establish_db_connection(const char *conninfo,
const bool exit_on_error);
/* GUC manipulation functions */
bool set_config(PGconn *conn, const char *config_param, const char *config_value);
bool set_config_bool(PGconn *conn, const char *config_param, bool state);
/* Server information functions */
int get_server_version(PGconn *conn, char *server_version);
#endif

View File

@@ -26,6 +26,11 @@ t_configuration_options config_file_options = T_CONFIGURATION_OPTIONS_INITIALIZE
ItemList cli_errors = { NULL, NULL };
ItemList cli_warnings = { NULL, NULL };
static bool config_file_required = true;
static char repmgr_slot_name[MAXLEN] = "";
static char *repmgr_slot_name_ptr = NULL;
int
main(int argc, char **argv)
{
@@ -267,10 +272,89 @@ main(int argc, char **argv)
if (runtime_options.terse)
logger_set_terse();
/*
* Node configuration information is not needed for all actions, with
* STANDBY CLONE being the main exception.
*/
if (config_file_required)
{
/*
* if a configuration file was provided, the configuration file parser
* will already have errored out if no valid node_id found
*/
if (config_file_options.node_id == NODE_NOT_FOUND)
{
log_error(_("no node information was found - "
"please supply a configuration file"));
exit(ERR_BAD_CONFIG);
}
}
/*
* Initialise slot name, if required (9.4 and later)
*
* NOTE: the slot name will be defined for each record, including
* the master; the `slot_name` column in `repl_nodes` defines
* the name of the slot, but does not imply a slot has been created.
* The version check for 9.4 or later is done in check_upstream_config()
*/
if (config_file_options.use_replication_slots)
{
maxlen_snprintf(repmgr_slot_name, "repmgr_slot_%i", config_file_options.node_id);
repmgr_slot_name_ptr = repmgr_slot_name;
log_verbose(LOG_DEBUG, "slot name initialised as: %s", repmgr_slot_name);
}
switch (action)
{
case MASTER_REGISTER:
do_master_register();
break;
default:
/* An action will have been determined by this point */
break;
}
return SUCCESS;
}
static void
exit_with_errors(void)
{
fprintf(stderr, _("The following command line errors were encountered:\n"));
print_error_list(&cli_errors, LOG_ERR);
fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname());
exit(ERR_BAD_CONFIG);
}
static void
print_error_list(ItemList *error_list, int log_level)
{
ItemListCell *cell;
for (cell = error_list->head; cell; cell = cell->next)
{
fprintf(stderr, " ");
switch(log_level)
{
/* Currently we only need errors and warnings */
case LOG_ERROR:
log_error("%s", cell->string);
break;
case LOG_WARNING:
log_warning("%s", cell->string);
break;
}
}
}
static void
do_help(void)
{
@@ -303,41 +387,66 @@ do_help(void)
printf(_(" --log-to-file log to file (or logging facility) defined in repmgr.conf\n"));
printf(_(" -t, --terse don't display hints and other non-critical output\n"));
printf(_(" -v, --verbose display additional log output (useful for debugging)\n"));
printf(_("\n"));
puts("");
}
static void
exit_with_errors(void)
do_master_register(void)
{
fprintf(stderr, _("The following command line errors were encountered:\n"));
PGconn *conn;
print_error_list(&cli_errors, LOG_ERR);
log_info(_("connecting to master database..."));
fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname());
// XXX if con fails, have this print offending conninfo!
conn = establish_db_connection(config_file_options.conninfo, true);
check_server_version(conn, "master", true, NULL);
}
/**
* check_server_version()
*
* Verify that the server is MIN_SUPPORTED_VERSION_NUM or later
*
* PGconn *conn:
* the connection to check
*
* char *server_type:
* either "master" or "standby"; used to format error message
*
* bool exit_on_error:
* exit if reported server version is too low; optional to enable some callers
* to perform additional cleanup
*
* char *server_version_string
* passed to get_server_version(), which will place the human-readable
* server version string there (e.g. "9.4.0")
*/
static int
check_server_version(PGconn *conn, char *server_type, bool exit_on_error, char *server_version_string)
{
int server_version_num = 0;
server_version_num = get_server_version(conn, server_version_string);
if (server_version_num < MIN_SUPPORTED_VERSION_NUM)
{
if (server_version_num > 0)
log_error(_("%s requires %s to be PostgreSQL %s or later"),
progname(),
server_type,
MIN_SUPPORTED_VERSION
);
if (exit_on_error == true)
{
PQfinish(conn);
exit(ERR_BAD_CONFIG);
}
return -1;
}
static void
print_error_list(ItemList *error_list, int log_level)
{
ItemListCell *cell;
for (cell = error_list->head; cell; cell = cell->next)
{
fprintf(stderr, " ");
switch(log_level)
{
/* Currently we only need errors and warnings */
case LOG_ERROR:
log_error("%s", cell->string);
break;
case LOG_WARNING:
log_warning("%s", cell->string);
break;
}
}
return server_version_num;
}

View File

@@ -131,8 +131,10 @@ typedef struct
"", false, false, false}
static void do_help(void);
static void do_master_register(void);
static void exit_with_errors(void);
static void print_error_list(ItemList *error_list, int log_level);
static int check_server_version(PGconn *conn, char *server_type, bool exit_on_error, char *server_version_string);
#endif

View File

@@ -16,6 +16,8 @@
#include "errcode.h"
#include "strutil.h"
#include "config.h"
#include "dbutils.h"
#include "log.h"
#define MIN_SUPPORTED_VERSION "9.3"
#define MIN_SUPPORTED_VERSION_NUM 90300

78
strutil.c Normal file
View File

@@ -0,0 +1,78 @@
/*
* strutil.c
*
* Copyright (c) 2ndQuadrant, 2010-2017
*/
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include "log.h"
#include "strutil.h"
static int
xvsnprintf(char *str, size_t size, const char *format, va_list ap)
__attribute__((format(PG_PRINTF_ATTRIBUTE, 3, 0)));
static int
xvsnprintf(char *str, size_t size, const char *format, va_list ap)
{
int retval;
retval = vsnprintf(str, size, format, ap);
if (retval >= (int) size)
{
log_error(_("Buffer of size not large enough to format entire string '%s'"),
str);
exit(ERR_STR_OVERFLOW);
}
return retval;
}
int
sqlquery_snprintf(char *str, const char *format,...)
{
va_list arglist;
int retval;
va_start(arglist, format);
retval = xvsnprintf(str, MAX_QUERY_LEN, format, arglist);
va_end(arglist);
return retval;
}
int
maxlen_snprintf(char *str, const char *format,...)
{
va_list arglist;
int retval;
va_start(arglist, format);
retval = xvsnprintf(str, MAXLEN, format, arglist);
va_end(arglist);
return retval;
}
/*
* Escape a string for use as a parameter in recovery.conf
* Caller must free returned value
*/
char *
escape_recovery_conf_value(const char *src)
{
char *result = escape_single_quotes_ascii(src);
if (!result)
{
fprintf(stderr, _("%s: out of memory\n"), progname());
exit(ERR_INTERNAL);
}
return result;
}

View File

@@ -7,6 +7,18 @@
#define _STRUTIL_H_
#define MAXLEN 1024
#define MAX_QUERY_LEN 8192
/* Why? http://stackoverflow.com/a/5459929/398670 */
#define STR(x) CppAsString(x)
#define MAXLEN_STR STR(MAXLEN)
extern int
sqlquery_snprintf(char *str, const char *format,...)
__attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 3)));
extern int
maxlen_snprintf(char *str, const char *format,...)
__attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 3)));
#endif