Add configuration exporting
The runtime configuration of a MaxScale can now be exported to a single file. This allows modifications made via runtime configuration commands to be "committed" for later use.
This commit is contained in:
parent
4a215b9ca2
commit
2df5763b6c
@ -29,6 +29,8 @@
|
||||
#include <ini.h>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
|
||||
#include <maxscale/adminusers.h>
|
||||
#include <maxscale/alloc.h>
|
||||
@ -197,6 +199,7 @@ static const char *config_file = NULL;
|
||||
static MXS_CONFIG gateway;
|
||||
char *version_string = NULL;
|
||||
static bool is_persisted_config = false; /**< True if a persisted configuration file is being parsed */
|
||||
static CONFIG_CONTEXT config_context;
|
||||
|
||||
const char *config_service_params[] =
|
||||
{
|
||||
@ -295,6 +298,17 @@ const char *server_params[] =
|
||||
NULL
|
||||
};
|
||||
|
||||
void config_init()
|
||||
{
|
||||
config_context.object = (char*)"";
|
||||
config_context.next = NULL;
|
||||
}
|
||||
|
||||
void config_finish()
|
||||
{
|
||||
config_context_free(config_context.next);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the context object used for tracking duplicate sections.
|
||||
*
|
||||
@ -475,6 +489,11 @@ static bool is_empty_string(const char* str)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool is_maxscale_section(const char* section)
|
||||
{
|
||||
return strcasecmp(section, CN_GATEWAY) == 0 || strcasecmp(section, CN_MAXSCALE) == 0;
|
||||
}
|
||||
|
||||
static bool is_root_config_file = true;
|
||||
|
||||
/**
|
||||
@ -525,19 +544,7 @@ static int ini_handler(void *userdata, const char *section, const char *name, co
|
||||
}
|
||||
}
|
||||
|
||||
if (strcmp(section, CN_GATEWAY) == 0 || strcasecmp(section, CN_MAXSCALE) == 0)
|
||||
{
|
||||
if (is_root_config_file || is_persisted_config)
|
||||
{
|
||||
return handle_global_item(name, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_ERROR("The [maxscale] section must only be defined in the root configuration file.");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else if (strlen(section) == 0)
|
||||
if (strlen(section) == 0)
|
||||
{
|
||||
MXS_ERROR("Parameter '%s=%s' declared outside a section.", name, value);
|
||||
return 0;
|
||||
@ -590,6 +597,19 @@ static int ini_handler(void *userdata, const char *section, const char *name, co
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (is_maxscale_section(section))
|
||||
{
|
||||
if (is_root_config_file || is_persisted_config)
|
||||
{
|
||||
return handle_global_item(name, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_ERROR("The [maxscale] section must only be defined in the root configuration file.");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -827,6 +847,59 @@ static bool contains_cnf_files(const char *path)
|
||||
return rval;
|
||||
}
|
||||
|
||||
bool export_config_file(const char* filename)
|
||||
{
|
||||
bool rval = true;
|
||||
std::vector<CONFIG_CONTEXT*> contexts;
|
||||
|
||||
// The config objects are stored in reverse order so first convert it back
|
||||
// to the correct order
|
||||
for (CONFIG_CONTEXT* ctx = config_context.next; ctx; ctx = ctx->next)
|
||||
{
|
||||
contexts.push_back(ctx);
|
||||
}
|
||||
|
||||
std::ofstream file(filename);
|
||||
|
||||
if (file)
|
||||
{
|
||||
time_t now = time(NULL);
|
||||
file << "# Generated by MaxScale " << MAXSCALE_VERSION << '\n';
|
||||
file << "# Documentation: https://mariadb.com/kb/en/mariadb-enterprise/maxscale/ \n\n";
|
||||
|
||||
for (auto it = contexts.rbegin(); it != contexts.rend(); it++)
|
||||
{
|
||||
CONFIG_CONTEXT* ctx = *it;
|
||||
|
||||
file << '[' << ctx->object << "]\n";
|
||||
|
||||
// Parameters are also stored in reverse order
|
||||
std::vector<MXS_CONFIG_PARAMETER*> params;
|
||||
|
||||
for (MXS_CONFIG_PARAMETER* p = ctx->parameters; p; p = p->next)
|
||||
{
|
||||
params.push_back(p);
|
||||
}
|
||||
|
||||
for (auto pit = params.rbegin(); pit != params.rend(); pit++)
|
||||
{
|
||||
MXS_CONFIG_PARAMETER* p = *pit;
|
||||
file << p->name << '=' << p->value << '\n';
|
||||
}
|
||||
|
||||
file << '\n';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MXS_ERROR("Failed to open configuration export file '%s': %d, %s",
|
||||
filename, errno, mxs_strerror(errno));
|
||||
rval = false;
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Load the specified configuration file for MaxScale
|
||||
*
|
||||
@ -843,15 +916,11 @@ static bool
|
||||
config_load_and_process(const char* filename, bool (*process_config)(CONFIG_CONTEXT*))
|
||||
{
|
||||
bool rval = false;
|
||||
|
||||
DUPLICATE_CONTEXT dcontext;
|
||||
|
||||
if (duplicate_context_init(&dcontext))
|
||||
{
|
||||
CONFIG_CONTEXT ccontext = {};
|
||||
ccontext.object = (char*)"";
|
||||
|
||||
if (config_load_single_file(filename, &dcontext, &ccontext))
|
||||
if (config_load_single_file(filename, &dcontext, &config_context))
|
||||
{
|
||||
is_root_config_file = false;
|
||||
const char DIR_SUFFIX[] = ".d";
|
||||
@ -864,7 +933,7 @@ config_load_and_process(const char* filename, bool (*process_config)(CONFIG_CONT
|
||||
|
||||
if (is_directory(dir))
|
||||
{
|
||||
rval = config_load_dir(dir, &dcontext, &ccontext);
|
||||
rval = config_load_dir(dir, &dcontext, &config_context);
|
||||
}
|
||||
|
||||
/** Create the persisted configuration directory if it doesn't exist */
|
||||
@ -893,7 +962,7 @@ config_load_and_process(const char* filename, bool (*process_config)(CONFIG_CONT
|
||||
*/
|
||||
if (duplicate_context_init(&p_dcontext))
|
||||
{
|
||||
rval = config_load_dir(persist_cnf, &p_dcontext, &ccontext);
|
||||
rval = config_load_dir(persist_cnf, &p_dcontext, &config_context);
|
||||
duplicate_context_finish(&p_dcontext);
|
||||
}
|
||||
else
|
||||
@ -905,7 +974,7 @@ config_load_and_process(const char* filename, bool (*process_config)(CONFIG_CONT
|
||||
|
||||
if (rval)
|
||||
{
|
||||
if (!check_config_objects(ccontext.next) || !process_config(ccontext.next))
|
||||
if (!check_config_objects(config_context.next) || !process_config(config_context.next))
|
||||
{
|
||||
rval = false;
|
||||
if (contains_cnf_files(persist_cnf))
|
||||
@ -919,8 +988,6 @@ config_load_and_process(const char* filename, bool (*process_config)(CONFIG_CONT
|
||||
}
|
||||
}
|
||||
|
||||
config_context_free(ccontext.next);
|
||||
|
||||
duplicate_context_finish(&dcontext);
|
||||
}
|
||||
return rval;
|
||||
@ -997,6 +1064,12 @@ process_config_context(CONFIG_CONTEXT *context)
|
||||
obj = context;
|
||||
while (obj)
|
||||
{
|
||||
if (is_maxscale_section(obj->object))
|
||||
{
|
||||
obj = obj->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
char *type = config_get_value(obj->parameters, CN_TYPE);
|
||||
if (type)
|
||||
{
|
||||
@ -1031,6 +1104,12 @@ process_config_context(CONFIG_CONTEXT *context)
|
||||
obj = context;
|
||||
while (obj)
|
||||
{
|
||||
if (is_maxscale_section(obj->object))
|
||||
{
|
||||
obj = obj->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
char *type = config_get_value(obj->parameters, CN_TYPE);
|
||||
if (type)
|
||||
{
|
||||
@ -2258,6 +2337,12 @@ check_config_objects(CONFIG_CONTEXT *context)
|
||||
|
||||
while (obj)
|
||||
{
|
||||
if (is_maxscale_section(obj->object))
|
||||
{
|
||||
obj = obj->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
const char **param_set = NULL;
|
||||
const char *module = NULL;
|
||||
const char *type;
|
||||
|
@ -109,6 +109,7 @@ const char *progname = NULL;
|
||||
static struct option long_options[] =
|
||||
{
|
||||
{"config-check", no_argument, 0, 'c'},
|
||||
{"export-config", required_argument, 0, 'e'},
|
||||
{"daemon", no_argument, 0, 'n'},
|
||||
{"nodaemon", no_argument, 0, 'd'},
|
||||
{"config", required_argument, 0, 'f'},
|
||||
@ -1060,6 +1061,7 @@ static void usage(void)
|
||||
fprintf(stderr,
|
||||
"\nUsage : %s [OPTION]...\n\n"
|
||||
" -c, --config-check validate configuration file and exit\n"
|
||||
" -e, --export-config=FILE export configuration to a single file\n"
|
||||
" -d, --nodaemon enable running in terminal process\n"
|
||||
" -f, --config=FILE relative or absolute pathname of config file\n"
|
||||
" -l, --log=[file|shm|stdout] log to file, shared memory or stdout\n"
|
||||
@ -1439,7 +1441,9 @@ int main(int argc, char **argv)
|
||||
bool pid_file_created = false;
|
||||
Worker* worker;
|
||||
const char* specified_user = NULL;
|
||||
char export_cnf[PATH_MAX + 1] = "";
|
||||
|
||||
config_init();
|
||||
config_set_global_defaults();
|
||||
ss_dassert(cnf);
|
||||
|
||||
@ -1453,7 +1457,7 @@ int main(int argc, char **argv)
|
||||
file_write_header(stderr);
|
||||
|
||||
// Option string for getopt
|
||||
const char accepted_opts[] = "dncf:g:l:vVs:S:?L:D:C:B:U:A:P:G:N:E:F:M:H:p";
|
||||
const char accepted_opts[] = "dnce:f:g:l:vVs:S:?L:D:C:B:U:A:P:G:N:E:F:M:H:p";
|
||||
|
||||
/*<
|
||||
* Register functions which are called at exit.
|
||||
@ -1747,6 +1751,11 @@ int main(int argc, char **argv)
|
||||
cnf->config_check = true;
|
||||
break;
|
||||
|
||||
case 'e':
|
||||
cnf->config_check = true;
|
||||
strcpy(export_cnf, optarg);
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
cnf->passive = true;
|
||||
break;
|
||||
@ -2164,6 +2173,12 @@ int main(int argc, char **argv)
|
||||
if (cnf->config_check)
|
||||
{
|
||||
MXS_NOTICE("Configuration was successfully verified.");
|
||||
|
||||
if (*export_cnf && export_config_file(export_cnf))
|
||||
{
|
||||
MXS_NOTICE("Configuration exported to '%s'", export_cnf);
|
||||
}
|
||||
|
||||
rc = MAXSCALE_SHUTDOWN;
|
||||
goto return_main;
|
||||
}
|
||||
@ -2345,6 +2360,8 @@ return_main:
|
||||
MXS_FREE(cnf_file_path);
|
||||
}
|
||||
|
||||
config_finish();
|
||||
|
||||
return rc;
|
||||
} /*< End of main */
|
||||
|
||||
|
@ -44,6 +44,16 @@ extern const char *config_monitor_params[];
|
||||
extern const char *config_filter_params[];
|
||||
extern const char *config_server_params[];
|
||||
|
||||
/**
|
||||
* Initialize the configuration subsystem
|
||||
*/
|
||||
void config_init();
|
||||
|
||||
/**
|
||||
* Finalize the configuration subsystem
|
||||
*/
|
||||
void config_finish();
|
||||
|
||||
/**
|
||||
* Set the defaults for the global configuration options
|
||||
*/
|
||||
@ -163,4 +173,13 @@ void fix_section_name(char *section);
|
||||
*/
|
||||
bool config_global_serialize();
|
||||
|
||||
/**
|
||||
* Export the configuration to a file
|
||||
*
|
||||
* @param filename Filename where the configuration will be written
|
||||
*
|
||||
* @return True if configuration was successfully exported
|
||||
*/
|
||||
bool export_config_file(const char* filename);
|
||||
|
||||
MXS_END_DECLS
|
||||
|
Loading…
x
Reference in New Issue
Block a user