From 2c1f79c5d159e42c20e5aff33a26d1448a22e6a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Tue, 17 Jul 2018 09:49:34 +0300 Subject: [PATCH] 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. --- include/maxscale/filter.h | 11 ++ server/core/config_runtime.cc | 138 +++++++++++++++++++++++--- server/core/filter.cc | 72 +++++++++++++- server/core/internal/config_runtime.h | 20 ++++ server/core/resource.cc | 13 +++ 5 files changed, 239 insertions(+), 15 deletions(-) diff --git a/include/maxscale/filter.h b/include/maxscale/filter.h index 3531c443b..29a279361 100644 --- a/include/maxscale/filter.h +++ b/include/maxscale/filter.h @@ -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); +/** + * @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 dprintFilter(DCB *, const MXS_FILTER_DEF *); void dListFilters(DCB *); diff --git a/server/core/config_runtime.cc b/server/core/config_runtime.cc index a676536d8..db63c2bee 100644 --- a/server/core/config_runtime.cc +++ b/server/core/config_runtime.cc @@ -36,6 +36,7 @@ #include "internal/monitor.h" #include "internal/modules.h" #include "internal/service.h" +#include "internal/filter.h" typedef std::set StringSet; @@ -1032,6 +1033,53 @@ bool runtime_create_monitor(const char *name, const char *module) 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 rval = false; @@ -1065,6 +1113,24 @@ bool runtime_destroy_monitor(MXS_MONITOR *monitor) 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, const char* relation_type, 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())); } +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) { bool rval = true; @@ -1566,11 +1637,16 @@ static bool object_relation_is_valid(const std::string& type, const std::string& /** * @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 */ -static bool validate_monitor_json(json_t* json) +static bool validate_object_json(json_t* json, std::vector paths, + const char* relationships = nullptr, + bool (*validator)(const std::string&, const std::string&) = nullptr) { bool rval = false; json_t* value; @@ -1585,20 +1661,28 @@ static bool validate_monitor_json(json_t* json) { runtime_error("Value '%s' is not a string", MXS_JSON_PTR_ID); } - else if (!(value = mxs_json_pointer(json, MXS_JSON_PTR_MODULE))) - { - runtime_error("Invalid value for '%s'", MXS_JSON_PTR_MODULE); - } - else if (!json_is_string(value)) - { - runtime_error("Value '%s' is not a string", MXS_JSON_PTR_MODULE); - } else { - StringSet relations; - if (extract_relations(json, relations, MXS_JSON_PTR_RELATIONSHIPS_SERVERS, object_relation_is_valid)) + for (auto&& a: paths) { - rval = true; + if (!(value = mxs_json_pointer(json, a.c_str()))) + { + runtime_error("Invalid value for '%s'", a.c_str()); + } + else if (!json_is_string(value)) + { + runtime_error("Value '%s' is not a string", a.c_str()); + } + } + + if (relationships) + { + ss_dassert(validator); + StringSet relations; + if (extract_relations(json, relations, relationships, validator)) + { + rval = true; + } } } } @@ -1647,7 +1731,9 @@ MXS_MONITOR* runtime_create_monitor_from_json(json_t* json) { 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* 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; } +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) { if (mxs_json_pointer(new_json, MXS_JSON_PTR_RELATIONSHIPS) == NULL) diff --git a/server/core/filter.cc b/server/core/filter.cc index 36c58cc4b..e10d01632 100644 --- a/server/core/filter.cc +++ b/server/core/filter.cc @@ -21,13 +21,16 @@ #include #include #include +#include +#include #include #include #include #include +#include #include -#include +#include #include #include #include @@ -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 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; +} diff --git a/server/core/internal/config_runtime.h b/server/core/internal/config_runtime.h index 70cfedfa4..9e4f9d133 100644 --- a/server/core/internal/config_runtime.h +++ b/server/core/internal/config_runtime.h @@ -194,6 +194,17 @@ bool runtime_destroy_listener(SERVICE *service, const char *name); */ 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 * @@ -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); +/** + * @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 * diff --git a/server/core/resource.cc b/server/core/resource.cc index d545b12cc..ca801e51f 100644 --- a/server/core/resource.cc +++ b/server/core/resource.cc @@ -325,6 +325,18 @@ HttpResponse cb_create_monitor(const HttpRequest& request) 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) { SERVICE* service = service_find(request.uri_part(1).c_str()); @@ -859,6 +871,7 @@ public: /** Create new resources */ 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_filter, 1, "filters"))); m_post.push_back(SResource(new Resource(cb_create_service_listener, 3, "services", ":service", "listeners"))); m_post.push_back(SResource(new Resource(cb_create_user, 2, "users", "inet")));