Add /etc/repmgr.conf as a default configuration file location

Also refactor configuration file handling while we're at it.

Previously a configuration file would be ignored if it couldn't
be opened, however that is now treated as an error.
This commit is contained in:
Ian Barwick
2015-11-19 15:16:18 +09:00
parent 64d038c823
commit d1b4280182
7 changed files with 141 additions and 86 deletions

3
TODO
View File

@@ -30,8 +30,7 @@ Planned feature improvements
* If no configuration file supplied, search in sensible default locations * If no configuration file supplied, search in sensible default locations
(currently: current directory and `pg_config --sysconfdir`); if (currently: current directory and `pg_config --sysconfdir`); if
possible this should include the location provided by the package, possible this should include the location provided by the package,
if installed. Also check /etc/repmgr.conf as a likely standard if installed.
location.
* repmgrd: if connection to the upstream node fails on startup, optionally * repmgrd: if connection to the upstream node fails on startup, optionally
retry for a certain period before giving up; this will cover cases when retry for a certain period before giving up; this will cover cases when

193
config.c
View File

@@ -31,6 +31,7 @@ static void exit_with_errors(ErrorList *config_errors);
const static char *_progname = '\0'; const static char *_progname = '\0';
static char config_file_path[MAXPGPATH]; static char config_file_path[MAXPGPATH];
static bool config_file_provided = false; static bool config_file_provided = false;
static bool config_file_found = false;
void void
@@ -55,68 +56,130 @@ progname(void)
* *
* Any configuration options changed in this function must also be changed in * Any configuration options changed in this function must also be changed in
* reload_config() * reload_config()
*
* NOTE: this function is called before the logger is set up, so we need
* to handle the verbose option ourselves; also the default log level is NOTICE,
* so we can't use DEBUG.
*/ */
bool bool
load_config(const char *config_file, t_configuration_options *options, char *argv0) load_config(const char *config_file, bool verbose, t_configuration_options *options, char *argv0)
{ {
struct stat config; struct stat stat_config;
/* Sanity checks */
/* /*
* If a configuration file was provided, check it exists, otherwise * If a configuration file was provided, check it exists, otherwise
* emit an error and terminate * emit an error and terminate. We assume that if a user explicitly
* provides a configuration file, they'll want to make sure it's
* used and not fall back to any of the defaults.
*/ */
if (config_file[0]) if (config_file[0])
{ {
strncpy(config_file_path, config_file, MAXPGPATH); strncpy(config_file_path, config_file, MAXPGPATH);
canonicalize_path(config_file_path); canonicalize_path(config_file_path);
if (stat(config_file_path, &config) != 0) if (stat(config_file_path, &stat_config) != 0)
{ {
log_err(_("provided configuration file '%s' not found: %s\n"), log_err(_("provided configuration file \"%s\" not found: %s\n"),
config_file, config_file,
strerror(errno) strerror(errno)
); );
exit(ERR_BAD_CONFIG); exit(ERR_BAD_CONFIG);
} }
if (verbose == true)
{
log_notice(_("using configuration file \"%s\"\n"), config_file);
}
config_file_provided = true; config_file_provided = true;
config_file_found = true;
} }
/* /*
* If no configuration file was provided, attempt to find a default file * If no configuration file was provided, attempt to find a default file
* in this order:
* - current directory
* - /etc/repmgr.conf
* - default sysconfdir
*
* here we just check for the existence of the file; parse_config()
* will handle read errors etc.
*/ */
if (config_file_provided == false) if (config_file_provided == false)
{ {
char my_exec_path[MAXPGPATH]; char my_exec_path[MAXPGPATH];
char etc_path[MAXPGPATH]; char sysconf_etc_path[MAXPGPATH];
/* First check if one is in the default sysconfdir */ /* 1. "./repmgr.conf" */
if (verbose == true)
{
log_notice(_("looking for configuration file in current directory\n"));
}
snprintf(config_file_path, MAXPGPATH, "./%s", CONFIG_FILE_NAME);
canonicalize_path(config_file_path);
if (stat(config_file_path, &stat_config) == 0)
{
config_file_found = true;
goto end_search;
}
/* 2. "/etc/repmgr.conf" */
if (verbose == true)
{
log_notice(_("looking for configuration file in /etc\n"));
}
snprintf(config_file_path, MAXPGPATH, "/etc/%s", CONFIG_FILE_NAME);
if (stat(config_file_path, &stat_config) == 0)
{
config_file_found = true;
goto end_search;
}
/* 3. default sysconfdir */
if (find_my_exec(argv0, my_exec_path) < 0) if (find_my_exec(argv0, my_exec_path) < 0)
{ {
fprintf(stderr, _("%s: could not find own program executable\n"), argv0); fprintf(stderr, _("%s: could not find own program executable\n"), argv0);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
get_etc_path(my_exec_path, etc_path); get_etc_path(my_exec_path, sysconf_etc_path);
snprintf(config_file_path, MAXPGPATH, "%s/repmgr.conf", etc_path); if (verbose == true)
log_debug(_("Looking for configuration file in %s\n"), etc_path);
if (stat(config_file_path, &config) != 0)
{ {
/* Not found - default to ./repmgr.conf */ log_notice(_("looking for configuration file in %s"), sysconf_etc_path);
strncpy(config_file_path, DEFAULT_CONFIG_FILE, MAXPGPATH); }
canonicalize_path(config_file_path);
log_debug(_("Looking for configuration file in %s\n"), config_file_path); snprintf(config_file_path, MAXPGPATH, "%s/%s", sysconf_etc_path, CONFIG_FILE_NAME);
if (stat(config_file_path, &stat_config) == 0)
{
config_file_found = true;
goto end_search;
}
end_search:
if (config_file_found == true)
{
if (verbose == true)
{
log_notice(_("configuration file found at: %s\n"), config_file_path);
}
}
else
{
if (verbose == true)
{
log_notice(_("no configuration file provided or found\n"));
}
} }
} }
return parse_config(options); return parse_config(options);
} }
/* /*
* Parse configuration file; if any errors are encountered, * Parse configuration file; if any errors are encountered,
* list them and exit. * list them and exit.
@@ -129,7 +192,7 @@ parse_config(t_configuration_options *options)
{ {
FILE *fp; FILE *fp;
char *s, char *s,
buff[MAXLINELENGTH]; buf[MAXLINELENGTH];
char name[MAXLEN]; char name[MAXLEN];
char value[MAXLEN]; char value[MAXLEN];
@@ -140,32 +203,6 @@ parse_config(t_configuration_options *options)
/* Collate configuration file errors here for friendlier reporting */ /* Collate configuration file errors here for friendlier reporting */
static ErrorList config_errors = { NULL, NULL }; static ErrorList config_errors = { NULL, NULL };
fp = fopen(config_file_path, "r");
/*
* Since some commands don't require a config file at all, not having one
* isn't necessarily a problem.
*
* If the user explictly provided a configuration file and we can't
* read it we'll raise an error.
*
* If no configuration file was provided, we'll try and read the default\
* file if it exists and is readable, but won't worry if it's not.
*/
if (fp == NULL)
{
if (config_file_provided)
{
log_err(_("unable to open provided configuration file '%s'; terminating\n"), config_file_path);
exit(ERR_BAD_CONFIG);
}
log_notice(_("no configuration file provided and default file '%s' not found - "
"continuing with default values\n"),
DEFAULT_CONFIG_FILE);
return false;
}
/* Initialize configuration options with sensible defaults /* Initialize configuration options with sensible defaults
* note: the default log level is set in log.c and does not need * note: the default log level is set in log.c and does not need
* to be initialised here * to be initialised here
@@ -201,14 +238,45 @@ parse_config(t_configuration_options *options)
options->tablespace_mapping.head = NULL; options->tablespace_mapping.head = NULL;
options->tablespace_mapping.tail = NULL; options->tablespace_mapping.tail = NULL;
/*
* If no configuration file available (user didn't specify and none found
* in the default locations), return with default values
*/
if (config_file_found == false)
{
log_notice(_("no configuration file provided and no default file found - "
"continuing with default values\n"));
return true;
}
/* Read next line */ fp = fopen(config_file_path, "r");
while ((s = fgets(buff, sizeof buff, fp)) != NULL)
/*
* A configuration file has been found, either provided by the user
* or found in one of the default locations. If we can't open it,
* fail with an error.
*/
if (fp == NULL)
{
if (config_file_provided)
{
log_err(_("unable to open provided configuration file \"%s\"; terminating\n"), config_file_path);
}
else
{
log_err(_("unable to open default configuration file \"%s\"; terminating\n"), config_file_path);
}
exit(ERR_BAD_CONFIG);
}
/* Read file */
while ((s = fgets(buf, sizeof buf, fp)) != NULL)
{ {
bool known_parameter = true; bool known_parameter = true;
/* Parse name/value pair from line */ /* Parse name/value pair from line */
parse_line(buff, name, value); parse_line(buf, name, value);
/* Skip blank lines */ /* Skip blank lines */
if (!strlen(name)) if (!strlen(name))
@@ -251,8 +319,7 @@ parse_config(t_configuration_options *options)
} }
else else
{ {
log_err(_("value for 'failover' must be 'automatic' or 'manual'\n")); error_list_append(&config_errors,_("value for 'failover' must be 'automatic' or 'manual'\n"));
exit(ERR_BAD_CONFIG);
} }
} }
else if (strcmp(name, "priority") == 0) else if (strcmp(name, "priority") == 0)
@@ -347,7 +414,7 @@ parse_config(t_configuration_options *options)
/* Sanity check the provided conninfo string /* Sanity check the provided conninfo string
* *
* NOTE: this verifies the string format and checks for valid options * NOTE: PQconninfoParse() verifies the string format and checks for valid options
* but does not sanity check values * but does not sanity check values
*/ */
conninfo_options = PQconninfoParse(options->conninfo, &conninfo_errmsg); conninfo_options = PQconninfoParse(options->conninfo, &conninfo_errmsg);
@@ -365,11 +432,11 @@ parse_config(t_configuration_options *options)
PQconninfoFree(conninfo_options); PQconninfoFree(conninfo_options);
} }
// exit_with_errors here
if (config_errors.head != NULL) if (config_errors.head != NULL)
{ {
exit_with_errors(&config_errors); exit_with_errors(&config_errors);
} }
return true; return true;
} }
@@ -402,7 +469,7 @@ trim(char *s)
} }
void void
parse_line(char *buff, char *name, char *value) parse_line(char *buf, char *name, char *value)
{ {
int i = 0; int i = 0;
int j = 0; int j = 0;
@@ -413,10 +480,10 @@ parse_line(char *buff, char *name, char *value)
for (; i < MAXLEN; ++i) for (; i < MAXLEN; ++i)
{ {
if (buff[i] == '=') if (buf[i] == '=')
break; break;
switch(buff[i]) switch(buf[i])
{ {
/* Ignore whitespace */ /* Ignore whitespace */
case ' ': case ' ':
@@ -425,7 +492,7 @@ parse_line(char *buff, char *name, char *value)
case '\t': case '\t':
continue; continue;
default: default:
name[j++] = buff[i]; name[j++] = buf[i];
} }
} }
name[j] = '\0'; name[j] = '\0';
@@ -435,9 +502,9 @@ parse_line(char *buff, char *name, char *value)
*/ */
for (; i < MAXLEN; ++i) for (; i < MAXLEN; ++i)
{ {
if (buff[i+1] == ' ') if (buf[i+1] == ' ')
continue; continue;
if (buff[i+1] == '\t') if (buf[i+1] == '\t')
continue; continue;
break; break;
@@ -448,12 +515,12 @@ parse_line(char *buff, char *name, char *value)
*/ */
j = 0; j = 0;
for (++i; i < MAXLEN; ++i) for (++i; i < MAXLEN; ++i)
if (buff[i] == '\'') if (buf[i] == '\'')
continue; continue;
else if (buff[i] == '#') else if (buf[i] == '#')
break; break;
else if (buff[i] != '\n') else if (buf[i] != '\n')
value[j++] = buff[i]; value[j++] = buf[i];
else else
break; break;
value[j] = '\0'; value[j] = '\0';

View File

@@ -24,6 +24,7 @@
#include "strutil.h" #include "strutil.h"
#define CONFIG_FILE_NAME "repmgr.conf"
typedef struct EventNotificationListCell typedef struct EventNotificationListCell
{ {
@@ -97,7 +98,7 @@ typedef struct ErrorList
void set_progname(const char *argv0); void set_progname(const char *argv0);
const char * progname(void); const char * progname(void);
bool load_config(const char *config_file, t_configuration_options *options, char *argv0); bool load_config(const char *config_file, bool verbose, t_configuration_options *options, char *argv0);
bool reload_config(t_configuration_options *orig_options); bool reload_config(t_configuration_options *orig_options);
bool parse_config(t_configuration_options *options); bool parse_config(t_configuration_options *options);
void parse_line(char *buff, char *name, char *value); void parse_line(char *buff, char *name, char *value);

2
log.c
View File

@@ -248,9 +248,9 @@ logger_init(t_configuration_options * opts, const char *ident)
} }
return true; return true;
} }
bool bool
logger_shutdown(void) logger_shutdown(void)
{ {

View File

@@ -469,26 +469,15 @@ main(int argc, char **argv)
strncpy(runtime_options.masterport, DEFAULT_MASTER_PORT, MAXLEN); strncpy(runtime_options.masterport, DEFAULT_MASTER_PORT, MAXLEN);
} }
/*
* The logger is not yet set up as some configuration can be included
* in the configuration file; however if verbosity requested, we'll
* display the name of the configuration file we're attempting to open
* for the user's convenience as we might be opening the default
* ./repmgr.conf.
*/
if (runtime_options.verbose && runtime_options.config_file[0])
{
log_notice(_("opening configuration file: %s\n"),
runtime_options.config_file);
}
/* /*
* The configuration file is not required for some actions (e.g. 'standby clone'), * The configuration file is not required for some actions (e.g. 'standby clone'),
* however if available we'll parse it anyway for options like 'log_level', * however if available we'll parse it anyway for options like 'log_level',
* 'use_replication_slots' etc. * 'use_replication_slots' etc.
*/ */
config_file_parsed = load_config(runtime_options.config_file, &options, argv[0]); config_file_parsed = load_config(runtime_options.config_file,
runtime_options.verbose,
&options,
argv[0]);
/* /*
* Initialise pg_bindir - command line parameter will override * Initialise pg_bindir - command line parameter will override
@@ -1363,7 +1352,7 @@ do_standby_clone(void)
strncpy(local_ident_file, master_ident_file, MAXFILENAME); strncpy(local_ident_file, master_ident_file, MAXFILENAME);
log_notice(_("setting data directory to: %s\n"), local_data_directory); log_notice(_("setting data directory to: %s\n"), local_data_directory);
log_hint(_("use -D/--data-dir to explicitly specify a data directory")); log_hint(_("use -D/--data-dir to explicitly specify a data directory\n"));
} }
/* /*

View File

@@ -36,7 +36,6 @@
#define MAXFILENAME 1024 #define MAXFILENAME 1024
#define ERRBUFF_SIZE 512 #define ERRBUFF_SIZE 512
#define DEFAULT_CONFIG_FILE "./repmgr.conf"
#define DEFAULT_WAL_KEEP_SEGMENTS "5000" #define DEFAULT_WAL_KEEP_SEGMENTS "5000"
#define DEFAULT_DEST_DIR "." #define DEFAULT_DEST_DIR "."
#define DEFAULT_MASTER_PORT "5432" #define DEFAULT_MASTER_PORT "5432"

View File

@@ -68,7 +68,7 @@ t_configuration_options master_options;
PGconn *master_conn = NULL; PGconn *master_conn = NULL;
char *config_file = DEFAULT_CONFIG_FILE; char *config_file = "";
bool verbose = false; bool verbose = false;
bool monitoring_history = false; bool monitoring_history = false;
t_node_info node_info; t_node_info node_info;
@@ -199,7 +199,7 @@ main(int argc, char **argv)
* which case we'll need to refactor parse_config() not to abort, * which case we'll need to refactor parse_config() not to abort,
* and return the error message. * and return the error message.
*/ */
load_config(config_file, &local_options, argv[0]); load_config(config_file, verbose, &local_options, argv[0]);
if (daemonize) if (daemonize)
{ {