MXS-1929: Add runtime creation of filters
Filters can now be created at runtime. This is not yet a usable feature since the filters can't be added to services at runtime.
This commit is contained in:
@ -277,6 +277,17 @@ json_t* filter_to_json(const MXS_FILTER_DEF* filter, const char* host);
|
|||||||
*/
|
*/
|
||||||
json_t* filter_list_to_json(const char* host);
|
json_t* filter_list_to_json(const char* host);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Serialize a filter to a file
|
||||||
|
*
|
||||||
|
* This converts the static configuration of the filter into an INI format file.
|
||||||
|
*
|
||||||
|
* @param filter Monitor to serialize
|
||||||
|
*
|
||||||
|
* @return True if serialization was successful
|
||||||
|
*/
|
||||||
|
bool filter_serialize(const MXS_FILTER_DEF *filter);
|
||||||
|
|
||||||
void dprintAllFilters(DCB *);
|
void dprintAllFilters(DCB *);
|
||||||
void dprintFilter(DCB *, const MXS_FILTER_DEF *);
|
void dprintFilter(DCB *, const MXS_FILTER_DEF *);
|
||||||
void dListFilters(DCB *);
|
void dListFilters(DCB *);
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
#include "internal/monitor.h"
|
#include "internal/monitor.h"
|
||||||
#include "internal/modules.h"
|
#include "internal/modules.h"
|
||||||
#include "internal/service.h"
|
#include "internal/service.h"
|
||||||
|
#include "internal/filter.h"
|
||||||
|
|
||||||
typedef std::set<std::string> StringSet;
|
typedef std::set<std::string> StringSet;
|
||||||
|
|
||||||
@ -1032,6 +1033,53 @@ bool runtime_create_monitor(const char *name, const char *module)
|
|||||||
return rval;
|
return rval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool runtime_create_filter(const char *name, const char *module, MXS_CONFIG_PARAMETER* params)
|
||||||
|
{
|
||||||
|
mxs::SpinLockGuard guard(crt_lock);
|
||||||
|
bool rval = false;
|
||||||
|
|
||||||
|
if (filter_def_find(name) == NULL)
|
||||||
|
{
|
||||||
|
MXS_FILTER_DEF* filter = NULL;
|
||||||
|
CONFIG_CONTEXT ctx{(char*)""};
|
||||||
|
ctx.parameters = load_defaults(module, MODULE_FILTER, CN_FILTER);
|
||||||
|
|
||||||
|
if (ctx.parameters)
|
||||||
|
{
|
||||||
|
for (MXS_CONFIG_PARAMETER* p = params; p; p = p->next)
|
||||||
|
{
|
||||||
|
config_replace_param(&ctx, p->name, p->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((filter = filter_alloc(name, module, ctx.parameters)) == NULL)
|
||||||
|
{
|
||||||
|
runtime_error("Could not create filter '%s' with module '%s'", name, module);
|
||||||
|
}
|
||||||
|
|
||||||
|
config_parameter_free(ctx.parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter)
|
||||||
|
{
|
||||||
|
if (filter_serialize(filter))
|
||||||
|
{
|
||||||
|
MXS_NOTICE("Created filter '%s'", name);
|
||||||
|
rval = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
runtime_error("Failed to serialize filter '%s'", name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
runtime_error("Can't create filter '%s', it already exists", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
|
||||||
bool runtime_destroy_monitor(MXS_MONITOR *monitor)
|
bool runtime_destroy_monitor(MXS_MONITOR *monitor)
|
||||||
{
|
{
|
||||||
bool rval = false;
|
bool rval = false;
|
||||||
@ -1065,6 +1113,24 @@ bool runtime_destroy_monitor(MXS_MONITOR *monitor)
|
|||||||
return rval;
|
return rval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static MXS_CONFIG_PARAMETER* extract_parameters_from_json(json_t* json)
|
||||||
|
{
|
||||||
|
CONFIG_CONTEXT ctx{(char*)""};
|
||||||
|
|
||||||
|
if (json_t* parameters = mxs_json_pointer(json, MXS_JSON_PTR_PARAMETERS))
|
||||||
|
{
|
||||||
|
const char* key;
|
||||||
|
json_t* value;
|
||||||
|
|
||||||
|
json_object_foreach(parameters, key, value)
|
||||||
|
{
|
||||||
|
config_add_param(&ctx, key, json_string_value(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.parameters;
|
||||||
|
}
|
||||||
|
|
||||||
static bool extract_relations(json_t* json, StringSet& relations,
|
static bool extract_relations(json_t* json, StringSet& relations,
|
||||||
const char* relation_type,
|
const char* relation_type,
|
||||||
bool (*relation_check)(const std::string&, const std::string&))
|
bool (*relation_check)(const std::string&, const std::string&))
|
||||||
@ -1242,6 +1308,11 @@ static bool server_relation_is_valid(const std::string& type, const std::string&
|
|||||||
(type == CN_MONITORS && monitor_find(value.c_str()));
|
(type == CN_MONITORS && monitor_find(value.c_str()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool filter_relation_is_valid(const std::string& type, const std::string& value)
|
||||||
|
{
|
||||||
|
return type == CN_SERVICES && service_find(value.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
static bool unlink_server_from_objects(SERVER* server, StringSet& relations)
|
static bool unlink_server_from_objects(SERVER* server, StringSet& relations)
|
||||||
{
|
{
|
||||||
bool rval = true;
|
bool rval = true;
|
||||||
@ -1567,10 +1638,15 @@ static bool object_relation_is_valid(const std::string& type, const std::string&
|
|||||||
* @brief Do a coarse validation of the monitor JSON
|
* @brief Do a coarse validation of the monitor JSON
|
||||||
*
|
*
|
||||||
* @param json JSON to validate
|
* @param json JSON to validate
|
||||||
|
* @param paths List of paths that must be string values
|
||||||
|
* @param relationships Path to relationships to validate
|
||||||
|
* @param validator Validation function
|
||||||
*
|
*
|
||||||
* @return True of the JSON is valid
|
* @return True of the JSON is valid
|
||||||
*/
|
*/
|
||||||
static bool validate_monitor_json(json_t* json)
|
static bool validate_object_json(json_t* json, std::vector<std::string> paths,
|
||||||
|
const char* relationships = nullptr,
|
||||||
|
bool (*validator)(const std::string&, const std::string&) = nullptr)
|
||||||
{
|
{
|
||||||
bool rval = false;
|
bool rval = false;
|
||||||
json_t* value;
|
json_t* value;
|
||||||
@ -1585,23 +1661,31 @@ static bool validate_monitor_json(json_t* json)
|
|||||||
{
|
{
|
||||||
runtime_error("Value '%s' is not a string", MXS_JSON_PTR_ID);
|
runtime_error("Value '%s' is not a string", MXS_JSON_PTR_ID);
|
||||||
}
|
}
|
||||||
else if (!(value = mxs_json_pointer(json, MXS_JSON_PTR_MODULE)))
|
else
|
||||||
{
|
{
|
||||||
runtime_error("Invalid value for '%s'", MXS_JSON_PTR_MODULE);
|
for (auto&& a: paths)
|
||||||
|
{
|
||||||
|
if (!(value = mxs_json_pointer(json, a.c_str())))
|
||||||
|
{
|
||||||
|
runtime_error("Invalid value for '%s'", a.c_str());
|
||||||
}
|
}
|
||||||
else if (!json_is_string(value))
|
else if (!json_is_string(value))
|
||||||
{
|
{
|
||||||
runtime_error("Value '%s' is not a string", MXS_JSON_PTR_MODULE);
|
runtime_error("Value '%s' is not a string", a.c_str());
|
||||||
}
|
}
|
||||||
else
|
}
|
||||||
|
|
||||||
|
if (relationships)
|
||||||
{
|
{
|
||||||
|
ss_dassert(validator);
|
||||||
StringSet relations;
|
StringSet relations;
|
||||||
if (extract_relations(json, relations, MXS_JSON_PTR_RELATIONSHIPS_SERVERS, object_relation_is_valid))
|
if (extract_relations(json, relations, relationships, validator))
|
||||||
{
|
{
|
||||||
rval = true;
|
rval = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return rval;
|
return rval;
|
||||||
}
|
}
|
||||||
@ -1647,7 +1731,9 @@ MXS_MONITOR* runtime_create_monitor_from_json(json_t* json)
|
|||||||
{
|
{
|
||||||
MXS_MONITOR* rval = NULL;
|
MXS_MONITOR* rval = NULL;
|
||||||
|
|
||||||
if (validate_monitor_json(json))
|
if (validate_object_json(json, {MXS_JSON_PTR_MODULE},
|
||||||
|
MXS_JSON_PTR_RELATIONSHIPS_SERVERS,
|
||||||
|
object_relation_is_valid))
|
||||||
{
|
{
|
||||||
const char* name = json_string_value(mxs_json_pointer(json, MXS_JSON_PTR_ID));
|
const char* name = json_string_value(mxs_json_pointer(json, MXS_JSON_PTR_ID));
|
||||||
const char* module = json_string_value(mxs_json_pointer(json, MXS_JSON_PTR_MODULE));
|
const char* module = json_string_value(mxs_json_pointer(json, MXS_JSON_PTR_MODULE));
|
||||||
@ -1668,6 +1754,30 @@ MXS_MONITOR* runtime_create_monitor_from_json(json_t* json)
|
|||||||
return rval;
|
return rval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MXS_FILTER_DEF* runtime_create_filter_from_json(json_t* json)
|
||||||
|
{
|
||||||
|
MXS_FILTER_DEF* rval = NULL;
|
||||||
|
|
||||||
|
if (validate_object_json(json, {MXS_JSON_PTR_MODULE},
|
||||||
|
MXS_JSON_PTR_RELATIONSHIPS_SERVICES,
|
||||||
|
filter_relation_is_valid))
|
||||||
|
{
|
||||||
|
const char* name = json_string_value(mxs_json_pointer(json, MXS_JSON_PTR_ID));
|
||||||
|
const char* module = json_string_value(mxs_json_pointer(json, MXS_JSON_PTR_MODULE));
|
||||||
|
MXS_CONFIG_PARAMETER* params = extract_parameters_from_json(json);
|
||||||
|
|
||||||
|
if (runtime_create_filter(name, module, params))
|
||||||
|
{
|
||||||
|
rval = filter_def_find(name);
|
||||||
|
ss_dassert(rval);
|
||||||
|
}
|
||||||
|
|
||||||
|
config_parameter_free(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
|
||||||
bool object_to_server_relations(const char* target, json_t* old_json, json_t* new_json)
|
bool object_to_server_relations(const char* target, json_t* old_json, json_t* new_json)
|
||||||
{
|
{
|
||||||
if (mxs_json_pointer(new_json, MXS_JSON_PTR_RELATIONSHIPS) == NULL)
|
if (mxs_json_pointer(new_json, MXS_JSON_PTR_RELATIONSHIPS) == NULL)
|
||||||
|
@ -21,13 +21,16 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <fcntl.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
|
||||||
#include <maxscale/alloc.h>
|
#include <maxscale/alloc.h>
|
||||||
#include <maxscale/log_manager.h>
|
#include <maxscale/log_manager.h>
|
||||||
|
#include <maxscale/paths.h>
|
||||||
#include <maxscale/session.h>
|
#include <maxscale/session.h>
|
||||||
#include <maxscale/spinlock.h>
|
#include <maxscale/spinlock.hh>
|
||||||
#include <maxscale/service.h>
|
#include <maxscale/service.h>
|
||||||
#include <maxscale/filter.hh>
|
#include <maxscale/filter.hh>
|
||||||
#include <maxscale/json_api.h>
|
#include <maxscale/json_api.h>
|
||||||
@ -532,3 +535,70 @@ json_t* FilterSession::diagnostics_json() const
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool create_filter_config(const MXS_FILTER_DEF *filter, const char *filename)
|
||||||
|
{
|
||||||
|
int file = open(filename, O_EXCL | O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
||||||
|
|
||||||
|
if (file == -1)
|
||||||
|
{
|
||||||
|
MXS_ERROR("Failed to open file '%s' when serializing filter '%s': %d, %s",
|
||||||
|
filename, filter->name, errno, mxs_strerror(errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mxs::SpinLockGuard guard(filter->spin);
|
||||||
|
|
||||||
|
dprintf(file, "[%s]\n", filter->name);
|
||||||
|
dprintf(file, "%s=%s\n", CN_TYPE, CN_FILTER);
|
||||||
|
dprintf(file, "%s=%s\n", CN_MODULE, filter->module);
|
||||||
|
|
||||||
|
std::set<std::string> param_set{CN_TYPE, CN_MODULE};
|
||||||
|
|
||||||
|
for (MXS_CONFIG_PARAMETER* p = filter->parameters; p; p = p->next)
|
||||||
|
{
|
||||||
|
if (param_set.count(p->name) == 0)
|
||||||
|
{
|
||||||
|
dprintf(file, "%s=%s\n", p->name, p->value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close(file);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool filter_serialize(const MXS_FILTER_DEF *filter)
|
||||||
|
{
|
||||||
|
bool rval = false;
|
||||||
|
char filename[PATH_MAX];
|
||||||
|
snprintf(filename, sizeof(filename), "%s/%s.cnf.tmp", get_config_persistdir(),
|
||||||
|
filter->name);
|
||||||
|
|
||||||
|
if (unlink(filename) == -1 && errno != ENOENT)
|
||||||
|
{
|
||||||
|
MXS_ERROR("Failed to remove temporary filter configuration at '%s': %d, %s",
|
||||||
|
filename, errno, mxs_strerror(errno));
|
||||||
|
}
|
||||||
|
else if (create_filter_config(filter, filename))
|
||||||
|
{
|
||||||
|
char final_filename[PATH_MAX];
|
||||||
|
strcpy(final_filename, filename);
|
||||||
|
|
||||||
|
char *dot = strrchr(final_filename, '.');
|
||||||
|
ss_dassert(dot);
|
||||||
|
*dot = '\0';
|
||||||
|
|
||||||
|
if (rename(filename, final_filename) == 0)
|
||||||
|
{
|
||||||
|
rval = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MXS_ERROR("Failed to rename temporary filter configuration at '%s': %d, %s",
|
||||||
|
filename, errno, mxs_strerror(errno));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
@ -194,6 +194,17 @@ bool runtime_destroy_listener(SERVICE *service, const char *name);
|
|||||||
*/
|
*/
|
||||||
bool runtime_create_monitor(const char *name, const char *module);
|
bool runtime_create_monitor(const char *name, const char *module);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create a new filter
|
||||||
|
*
|
||||||
|
* @param name Name of the filter
|
||||||
|
* @param module Filter module
|
||||||
|
* @param params Filter parameters
|
||||||
|
*
|
||||||
|
* @return True if a new filter was created and persisted
|
||||||
|
*/
|
||||||
|
bool runtime_create_filter(const char *name, const char *module, MXS_CONFIG_PARAMETER* params);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Destroy a monitor
|
* @brief Destroy a monitor
|
||||||
*
|
*
|
||||||
@ -244,6 +255,15 @@ bool runtime_alter_server_relationships_from_json(SERVER* server, const char* ty
|
|||||||
*/
|
*/
|
||||||
MXS_MONITOR* runtime_create_monitor_from_json(json_t* json);
|
MXS_MONITOR* runtime_create_monitor_from_json(json_t* json);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create a new filter from JSON
|
||||||
|
*
|
||||||
|
* @param json JSON defining the filter
|
||||||
|
*
|
||||||
|
* @return Created filter or NULL on error
|
||||||
|
*/
|
||||||
|
MXS_FILTER_DEF* runtime_create_filter_from_json(json_t* json);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Alter a monitor using JSON
|
* @brief Alter a monitor using JSON
|
||||||
*
|
*
|
||||||
|
@ -325,6 +325,18 @@ HttpResponse cb_create_monitor(const HttpRequest& request)
|
|||||||
return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error());
|
return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HttpResponse cb_create_filter(const HttpRequest& request)
|
||||||
|
{
|
||||||
|
ss_dassert(request.get_json());
|
||||||
|
|
||||||
|
if (runtime_create_filter_from_json(request.get_json()))
|
||||||
|
{
|
||||||
|
return HttpResponse(MHD_HTTP_NO_CONTENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
return HttpResponse(MHD_HTTP_FORBIDDEN, runtime_get_json_error());
|
||||||
|
}
|
||||||
|
|
||||||
HttpResponse cb_create_service_listener(const HttpRequest& request)
|
HttpResponse cb_create_service_listener(const HttpRequest& request)
|
||||||
{
|
{
|
||||||
SERVICE* service = service_find(request.uri_part(1).c_str());
|
SERVICE* service = service_find(request.uri_part(1).c_str());
|
||||||
@ -859,6 +871,7 @@ public:
|
|||||||
/** Create new resources */
|
/** Create new resources */
|
||||||
m_post.push_back(SResource(new Resource(cb_create_server, 1, "servers")));
|
m_post.push_back(SResource(new Resource(cb_create_server, 1, "servers")));
|
||||||
m_post.push_back(SResource(new Resource(cb_create_monitor, 1, "monitors")));
|
m_post.push_back(SResource(new Resource(cb_create_monitor, 1, "monitors")));
|
||||||
|
m_post.push_back(SResource(new Resource(cb_create_filter, 1, "filters")));
|
||||||
m_post.push_back(SResource(new Resource(cb_create_service_listener, 3,
|
m_post.push_back(SResource(new Resource(cb_create_service_listener, 3,
|
||||||
"services", ":service", "listeners")));
|
"services", ":service", "listeners")));
|
||||||
m_post.push_back(SResource(new Resource(cb_create_user, 2, "users", "inet")));
|
m_post.push_back(SResource(new Resource(cb_create_user, 2, "users", "inet")));
|
||||||
|
Reference in New Issue
Block a user