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:
Markus Mäkelä
2018-07-06 10:50:55 +03:00
parent 4a215b9ca2
commit 2df5763b6c
3 changed files with 145 additions and 24 deletions

View File

@ -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;