3037 lines
		
	
	
		
			89 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			3037 lines
		
	
	
		
			89 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|  * Copyright (c) 2016 MariaDB Corporation Ab
 | |
|  *
 | |
|  * Use of this software is governed by the Business Source License included
 | |
|  * in the LICENSE.TXT file and at www.mariadb.com/bsl11.
 | |
|  *
 | |
|  * Change Date: 2024-06-02
 | |
|  *
 | |
|  * On the date above, in accordance with the Business Source License, use
 | |
|  * of this software will be governed by version 2 or later of the General
 | |
|  * Public License.
 | |
|  */
 | |
| 
 | |
| #include <maxscale/ccdefs.hh>
 | |
| 
 | |
| #include "internal/config_runtime.hh"
 | |
| 
 | |
| #include <algorithm>
 | |
| #include <cinttypes>
 | |
| #include <functional>
 | |
| #include <iterator>
 | |
| #include <set>
 | |
| #include <sstream>
 | |
| #include <string>
 | |
| #include <strings.h>
 | |
| #include <tuple>
 | |
| #include <vector>
 | |
| 
 | |
| #include <maxbase/atomic.h>
 | |
| #include <maxscale/clock.h>
 | |
| #include <maxscale/jansson.hh>
 | |
| #include <maxscale/json_api.hh>
 | |
| #include <maxscale/paths.h>
 | |
| #include <maxscale/router.hh>
 | |
| #include <maxscale/users.h>
 | |
| 
 | |
| #include "internal/config.hh"
 | |
| #include "internal/filter.hh"
 | |
| #include "internal/modules.hh"
 | |
| #include "internal/monitor.hh"
 | |
| #include "internal/monitormanager.hh"
 | |
| #include "internal/query_classifier.hh"
 | |
| #include "internal/server.hh"
 | |
| 
 | |
| typedef std::set<std::string>    StringSet;
 | |
| typedef std::vector<std::string> StringVector;
 | |
| 
 | |
| using std::tie;
 | |
| using maxscale::Monitor;
 | |
| 
 | |
| static std::mutex crt_lock;
 | |
| 
 | |
| #define RUNTIME_ERRMSG_BUFSIZE 512
 | |
| thread_local std::vector<std::string> runtime_errmsg;
 | |
| 
 | |
| typedef std::function<bool (const std::string&, const std::string&)> JsonValidator;
 | |
| typedef std::pair<const char*, JsonValidator>                        Relationship;
 | |
| 
 | |
| void config_runtime_error(const char* fmt, ...)
 | |
| {
 | |
|     char errmsg[RUNTIME_ERRMSG_BUFSIZE];
 | |
|     va_list list;
 | |
|     va_start(list, fmt);
 | |
|     vsnprintf(errmsg, sizeof(errmsg), fmt, list);
 | |
|     va_end(list);
 | |
|     runtime_errmsg.push_back(errmsg);
 | |
| }
 | |
| 
 | |
| static std::string runtime_get_error()
 | |
| {
 | |
|     std::string rval;
 | |
| 
 | |
|     if (!runtime_errmsg.empty())
 | |
|     {
 | |
|         rval = runtime_errmsg.back();
 | |
|         runtime_errmsg.clear();
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| static const MXS_MODULE_PARAM* get_type_parameters(const char* type)
 | |
| {
 | |
|     if (strcmp(type, CN_SERVICE) == 0)
 | |
|     {
 | |
|         return config_service_params;
 | |
|     }
 | |
|     else if (strcmp(type, CN_LISTENER) == 0)
 | |
|     {
 | |
|         return config_listener_params;
 | |
|     }
 | |
|     else if (strcmp(type, CN_MONITOR) == 0)
 | |
|     {
 | |
|         return config_monitor_params;
 | |
|     }
 | |
|     else if (strcmp(type, CN_FILTER) == 0)
 | |
|     {
 | |
|         return config_filter_params;
 | |
|     }
 | |
|     else if (strcmp(type, CN_SERVER) == 0)
 | |
|     {
 | |
|         return config_server_params;
 | |
|     }
 | |
| 
 | |
|     MXS_NOTICE("Module type with no default parameters used: %s", type);
 | |
|     mxb_assert_message(!true, "Module type with no default parameters used");
 | |
|     return NULL;
 | |
| }
 | |
| 
 | |
| std::string get_module_param_name(const std::string& type)
 | |
| {
 | |
|     if (type == CN_SERVICE)
 | |
|     {
 | |
|         return CN_ROUTER;
 | |
|     }
 | |
|     else if (type == CN_LISTENER || type == CN_SERVER)
 | |
|     {
 | |
|         return CN_PROTOCOL;
 | |
|     }
 | |
|     else if (type == CN_MONITOR || type == CN_FILTER)
 | |
|     {
 | |
|         return CN_MODULE;
 | |
|     }
 | |
| 
 | |
|     mxb_assert(!true);
 | |
|     return "";
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Load module default parameters
 | |
|  *
 | |
|  * @param name        Name of the module to load
 | |
|  * @param module_type Type of the module (MODULE_ROUTER, MODULE_PROTOCOL etc.)
 | |
|  * @param object_type Type of the object (server, service, listener etc.)
 | |
|  *
 | |
|  * @return Whether loading succeeded and the list of default parameters
 | |
|  */
 | |
| static std::pair<bool, MXS_CONFIG_PARAMETER> load_defaults(const char* name,
 | |
|                                                            const char* module_type,
 | |
|                                                            const char* object_type)
 | |
| {
 | |
|     bool rval;
 | |
|     MXS_CONFIG_PARAMETER params;
 | |
|     CONFIG_CONTEXT ctx = {(char*)""};
 | |
| 
 | |
|     if (const MXS_MODULE* mod = get_module(name, module_type))
 | |
|     {
 | |
|         config_add_defaults(&ctx, get_type_parameters(object_type));
 | |
|         config_add_defaults(&ctx, mod->parameters);
 | |
|         params = ctx.m_parameters;
 | |
|         params.set(get_module_param_name(object_type), name);
 | |
|         rval = true;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         config_runtime_error("Failed to load module '%s': %s",
 | |
|                              name,
 | |
|                              errno ? mxs_strerror(errno) : "See MaxScale logs for details");
 | |
|     }
 | |
| 
 | |
|     return {rval, params};
 | |
| }
 | |
| 
 | |
| bool runtime_link_server(Server* server, const char* target)
 | |
| {
 | |
|     std::lock_guard<std::mutex> guard(crt_lock);
 | |
| 
 | |
|     bool rval = false;
 | |
|     Service* service = service_internal_find(target);
 | |
|     Monitor* monitor = service ? NULL : MonitorManager::find_monitor(target);
 | |
| 
 | |
|     if (service)
 | |
|     {
 | |
|         if (!service->uses_cluster())
 | |
|         {
 | |
|             if (serviceAddBackend(service, server))
 | |
|             {
 | |
|                 service_serialize(service);
 | |
|                 rval = true;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 config_runtime_error("Service '%s' already uses server '%s'",
 | |
|                                      service->name(),
 | |
|                                      server->name());
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             config_runtime_error("The servers of the service '%s' are defined by the monitor '%s'. "
 | |
|                                  "Servers cannot explicitly be added to the service.",
 | |
|                                  service->name(),
 | |
|                                  service->m_monitor->name());
 | |
|         }
 | |
|     }
 | |
|     else if (monitor)
 | |
|     {
 | |
|         std::string error_msg;
 | |
|         if (MonitorManager::add_server_to_monitor(monitor, server, &error_msg))
 | |
|         {
 | |
|             rval = true;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             config_runtime_error("%s", error_msg.c_str());
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (rval)
 | |
|     {
 | |
|         const char* type = service ? "service" : "monitor";
 | |
|         MXS_NOTICE("Added server '%s' to %s '%s'", server->name(), type, target);
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool runtime_unlink_server(Server* server, const char* target)
 | |
| {
 | |
|     std::lock_guard<std::mutex> guard(crt_lock);
 | |
| 
 | |
|     bool rval = false;
 | |
|     Service* service = service_internal_find(target);
 | |
|     Monitor* monitor = service ? NULL : MonitorManager::find_monitor(target);
 | |
| 
 | |
|     if (service || monitor)
 | |
|     {
 | |
|         if (service)
 | |
|         {
 | |
|             if (!service->uses_cluster())
 | |
|             {
 | |
|                 serviceRemoveBackend(service, server);
 | |
|                 service_serialize(service);
 | |
|                 rval = true;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 config_runtime_error("The servers of the service '%s' are defined by the monitor '%s'. "
 | |
|                                      "Servers cannot explicitly be removed from the service.",
 | |
|                                      service->name(),
 | |
|                                      service->m_monitor->name());
 | |
|             }
 | |
|         }
 | |
|         else if (monitor)
 | |
|         {
 | |
|             std::string error_msg;
 | |
|             if (MonitorManager::remove_server_from_monitor(monitor, server, &error_msg))
 | |
|             {
 | |
|                 rval = true;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 config_runtime_error("%s", error_msg.c_str());
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (rval)
 | |
|         {
 | |
|             const char* type = service ? "service" : "monitor";
 | |
|             MXS_NOTICE("Removed server '%s' from %s '%s'", server->name(), type, target);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool runtime_create_server(const char* name,
 | |
|                            const char* address,
 | |
|                            const char* port,
 | |
|                            const char* protocol,
 | |
|                            const char* authenticator,
 | |
|                            bool external)
 | |
| {
 | |
|     std::lock_guard<std::mutex> guard(crt_lock);
 | |
|     bool rval = false;
 | |
| 
 | |
|     if (Server::find_by_unique_name(name) == NULL)
 | |
|     {
 | |
|         std::string reason;
 | |
|         if (!external || config_is_valid_name(name, &reason))
 | |
|         {
 | |
|             if (protocol == NULL)
 | |
|             {
 | |
|                 protocol = "mariadbbackend";
 | |
|             }
 | |
| 
 | |
|             MXS_CONFIG_PARAMETER parameters;
 | |
|             bool ok;
 | |
|             tie(ok, parameters) = load_defaults(protocol, MODULE_PROTOCOL, CN_SERVER);
 | |
| 
 | |
|             if (ok)
 | |
|             {
 | |
|                 if (address)
 | |
|                 {
 | |
|                     auto param_name = *address == '/' ? CN_SOCKET : CN_ADDRESS;
 | |
|                     parameters.set(param_name, address);
 | |
|                 }
 | |
|                 if (port)
 | |
|                 {
 | |
|                     parameters.set(CN_PORT, port);
 | |
|                 }
 | |
|                 if (authenticator)
 | |
|                 {
 | |
|                     parameters.set(CN_AUTHENTICATOR, authenticator);
 | |
|                 }
 | |
| 
 | |
|                 Server* server = Server::server_alloc(name, parameters);
 | |
| 
 | |
|                 if (server && (!external || server->serialize()))
 | |
|                 {
 | |
|                     rval = true;
 | |
|                     MXS_NOTICE("Created server '%s' at %s:%u",
 | |
|                                server->name(),
 | |
|                                server->address,
 | |
|                                server->port);
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     config_runtime_error("Failed to create server '%s', see error log for more details",
 | |
|                                          name);
 | |
|                 }
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 config_runtime_error("Server creation failed when loading protocol module '%s'", protocol);
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             config_runtime_error("%s", reason.c_str());
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         config_runtime_error("Server '%s' already exists", name);
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool runtime_destroy_server(Server* server)
 | |
| {
 | |
|     std::lock_guard<std::mutex> guard(crt_lock);
 | |
|     bool rval = false;
 | |
| 
 | |
|     if (service_server_in_use(server) || MonitorManager::server_is_monitored(server))
 | |
|     {
 | |
|         const char* err = "Cannot destroy server '%s' as it is used by at least "
 | |
|                           "one service or monitor";
 | |
|         config_runtime_error(err, server->name());
 | |
|         MXS_ERROR(err, server->name());
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         char filename[PATH_MAX];
 | |
|         snprintf(filename,
 | |
|                  sizeof(filename),
 | |
|                  "%s/%s.cnf",
 | |
|                  get_config_persistdir(),
 | |
|                  server->name());
 | |
| 
 | |
|         if (unlink(filename) == -1)
 | |
|         {
 | |
|             if (errno != ENOENT)
 | |
|             {
 | |
|                 MXS_ERROR("Failed to remove persisted server configuration '%s': %d, %s",
 | |
|                           filename,
 | |
|                           errno,
 | |
|                           mxs_strerror(errno));
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 rval = true;
 | |
|                 MXS_WARNING("Server '%s' was not created at runtime. Remove the "
 | |
|                             "server manually from the correct configuration file.",
 | |
|                             server->name());
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             rval = true;
 | |
|         }
 | |
| 
 | |
|         if (rval)
 | |
|         {
 | |
|             MXS_NOTICE("Destroyed server '%s' at %s:%u",
 | |
|                        server->name(),
 | |
|                        server->address,
 | |
|                        server->port);
 | |
|             server->is_active = false;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Convert a string value to a positive integer
 | |
|  *
 | |
|  * If the value is not a positive integer, an error is printed to @c dcb.
 | |
|  *
 | |
|  * @param value String value
 | |
|  * @return 0 on error, otherwise a positive integer
 | |
|  */
 | |
| static int get_positive_int(const char* value)
 | |
| {
 | |
|     char* endptr;
 | |
|     long ival = strtol(value, &endptr, 10);
 | |
| 
 | |
|     if (*endptr == '\0' && ival > 0 && ival < std::numeric_limits<int>::max())
 | |
|     {
 | |
|         return ival;
 | |
|     }
 | |
| 
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| static inline bool is_valid_integer(const char* value)
 | |
| {
 | |
|     char* endptr;
 | |
|     return strtol(value, &endptr, 10) >= 0 && *value && *endptr == '\0';
 | |
| }
 | |
| 
 | |
| bool runtime_alter_server(Server* server, const char* key, const char* value)
 | |
| {
 | |
|     if (!value[0])
 | |
|     {
 | |
|         config_runtime_error("Empty value for parameter: %s", key);
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     bool is_normal_parameter = !server->is_custom_parameter(key);
 | |
| 
 | |
|     // Only validate known parameters as custom parameters cannot be validated.
 | |
|     if (is_normal_parameter)
 | |
|     {
 | |
|         const MXS_MODULE* mod = get_module(server->protocol().c_str(), MODULE_PROTOCOL);
 | |
|         if (!param_is_valid(config_server_params, mod->parameters, key, value))
 | |
|         {
 | |
|             config_runtime_error("Invalid value for parameter '%s': %s", key, value);
 | |
|             return false;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     std::lock_guard<std::mutex> guard(crt_lock);
 | |
| 
 | |
|     bool setting_changed = false;
 | |
|     if (is_normal_parameter)
 | |
|     {
 | |
|         // Only some normal parameters can be changed runtime. The key/value-combination has already
 | |
|         // been checked to be valid.
 | |
|         if (strcmp(key, CN_ADDRESS) == 0 || strcmp(key, CN_SOCKET) == 0)
 | |
|         {
 | |
|             server->server_update_address(value);
 | |
|             setting_changed = true;
 | |
|         }
 | |
|         else if (strcmp(key, CN_PORT) == 0)
 | |
|         {
 | |
|             if (int ival = get_positive_int(value))
 | |
|             {
 | |
|                 server->update_port(ival);
 | |
|                 setting_changed = true;
 | |
|             }
 | |
|         }
 | |
|         else if (strcmp(key, CN_EXTRA_PORT) == 0)
 | |
|         {
 | |
|             server->update_extra_port(atoi(value));
 | |
|             setting_changed = true;
 | |
|         }
 | |
|         else if (strcmp(key, CN_MONITORUSER) == 0)
 | |
|         {
 | |
|             server->set_monitor_user(value);
 | |
|             setting_changed = true;
 | |
|         }
 | |
|         else if (strcmp(key, CN_MONITORPW) == 0)
 | |
|         {
 | |
|             server->set_monitor_password(value);
 | |
|             setting_changed = true;
 | |
|         }
 | |
|         else if (strcmp(key, CN_PERSISTPOOLMAX) == 0)
 | |
|         {
 | |
|             if (is_valid_integer(value))
 | |
|             {
 | |
|                 server->set_persistpoolmax(atoi(value));
 | |
|                 setting_changed = true;
 | |
|             }
 | |
|         }
 | |
|         else if (strcmp(key, CN_PERSISTMAXTIME) == 0)
 | |
|         {
 | |
|             if (is_valid_integer(value))
 | |
|             {
 | |
|                 server->set_persistmaxtime(atoi(value));
 | |
|                 setting_changed = true;
 | |
|             }
 | |
|         }
 | |
|         else if (strcmp(key, CN_RANK) == 0)
 | |
|         {
 | |
|             auto v = config_enum_to_value(value, rank_values);
 | |
|             if (v != MXS_UNKNOWN_ENUM_VALUE)
 | |
|             {
 | |
|                 server->set_rank(v);
 | |
|                 setting_changed = true;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 config_runtime_error("Invalid value for '%s': %s", CN_RANK, value);
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             // Was a recognized parameter but runtime modification is not supported.
 | |
|             config_runtime_error("Server parameter '%s' cannot be modified during runtime. A similar "
 | |
|                                  "effect can be produced by destroying the server and recreating it "
 | |
|                                  "with the new settings.", key);
 | |
|         }
 | |
| 
 | |
|         if (setting_changed)
 | |
|         {
 | |
|             // Successful modification of a normal parameter, write to text storage.
 | |
|             server->set_normal_parameter(key, value);
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         // This is a custom parameter and may be used for weighting. Update the weights of services.
 | |
|         server->set_custom_parameter(key, value);
 | |
|         service_update_weights();
 | |
|         setting_changed = true;
 | |
|     }
 | |
| 
 | |
|     if (setting_changed)
 | |
|     {
 | |
|         server->serialize();
 | |
|         MXS_NOTICE("Updated server '%s': %s=%s", server->name(), key, value);
 | |
|     }
 | |
|     return setting_changed;
 | |
| }
 | |
| 
 | |
| static bool undefined_mandatory_parameter(const MXS_MODULE_PARAM* mod_params,
 | |
|                                           const MXS_CONFIG_PARAMETER* params)
 | |
| {
 | |
|     bool rval = false;
 | |
|     mxb_assert(mod_params);
 | |
| 
 | |
|     for (int i = 0; mod_params[i].name; i++)
 | |
|     {
 | |
|         if ((mod_params[i].options & MXS_MODULE_OPT_REQUIRED) && !params->contains(mod_params[i].name))
 | |
|         {
 | |
|             config_runtime_error("Mandatory parameter '%s' is not defined.", mod_params[i].name);
 | |
|             rval = true;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool validate_param(const MXS_MODULE_PARAM* basic,
 | |
|                     const MXS_MODULE_PARAM* module,
 | |
|                     const char* key,
 | |
|                     const char* value)
 | |
| {
 | |
|     std::string error;
 | |
|     bool rval = validate_param(basic, module, key, value, &error);
 | |
|     if (!rval)
 | |
|     {
 | |
|         config_runtime_error("%s", error.c_str());
 | |
|     }
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool validate_param(const MXS_MODULE_PARAM* basic,
 | |
|                     const MXS_MODULE_PARAM* module,
 | |
|                     MXS_CONFIG_PARAMETER* params)
 | |
| {
 | |
|     bool rval = std::all_of(params->begin(), params->end(),
 | |
|                             [basic, module](const std::pair<std::string, std::string>& p) {
 | |
|                                 return validate_param(basic, module, p.first.c_str(), p.second.c_str());
 | |
|                             });
 | |
| 
 | |
|     if (undefined_mandatory_parameter(basic, params) || undefined_mandatory_parameter(module, params))
 | |
|     {
 | |
|         rval = false;
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool runtime_alter_monitor(Monitor* monitor, const char* key, const char* value)
 | |
| {
 | |
|     std::string error_msg;
 | |
|     bool success = MonitorManager::alter_monitor(monitor, key, value, &error_msg);
 | |
|     if (success)
 | |
|     {
 | |
|         MXS_NOTICE("Updated monitor '%s': %s=%s", monitor->name(), key, value);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         config_runtime_error("%s", error_msg.c_str());
 | |
|     }
 | |
|     return success;
 | |
| }
 | |
| 
 | |
| bool runtime_alter_service(Service* service, const char* zKey, const char* zValue)
 | |
| {
 | |
|     const MXS_MODULE* mod = get_module(service->router_name(), MODULE_ROUTER);
 | |
|     std::string key(zKey);
 | |
|     std::string value(zValue);
 | |
| 
 | |
|     if (!validate_param(config_service_params, mod->parameters, zKey, zValue))
 | |
|     {
 | |
|         return false;
 | |
|     }
 | |
|     else if (key == "filters" || key == "servers")
 | |
|     {
 | |
|         config_runtime_error("Parameter '%s' cannot be altered via this method", zKey);
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     std::lock_guard<std::mutex> guard(crt_lock);
 | |
|     bool rval = true;
 | |
| 
 | |
|     if (service->is_basic_parameter(key))
 | |
|     {
 | |
|         service_replace_parameter(service, zKey, zValue);
 | |
|         service->update_basic_parameter(key, value);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         if (service->router->configureInstance && service->capabilities & RCAP_TYPE_RUNTIME_CONFIG)
 | |
|         {
 | |
|             // Stash the old value in case the reconfiguration fails.
 | |
|             std::string old_value = service->svc_config_param.get_string(key);
 | |
|             service_replace_parameter(service, key.c_str(), value.c_str());
 | |
| 
 | |
|             if (!service->router->configureInstance(service->router_instance, &service->svc_config_param))
 | |
|             {
 | |
|                 // Reconfiguration failed, restore the old value of the parameter
 | |
|                 if (old_value.empty())
 | |
|                 {
 | |
|                     service_remove_parameter(service, key.c_str());
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     service_replace_parameter(service, key.c_str(), old_value.c_str());
 | |
|                 }
 | |
| 
 | |
|                 rval = false;
 | |
|                 config_runtime_error("Reconfiguration of service '%s' failed. See log "
 | |
|                                      "file for more details.",
 | |
|                                      service->name());
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             rval = false;
 | |
|             config_runtime_error("Router '%s' does not support reconfiguration.",
 | |
|                                  service->router_name());
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (rval)
 | |
|     {
 | |
|         service_serialize(service);
 | |
|         MXS_NOTICE("Updated service '%s': %s=%s", service->name(), key.c_str(), value.c_str());
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool runtime_alter_maxscale(const char* name, const char* value)
 | |
| {
 | |
|     MXS_CONFIG& cnf = *config_get_global_options();
 | |
|     std::string key = name;
 | |
|     bool rval = false;
 | |
| 
 | |
|     std::lock_guard<std::mutex> guard(crt_lock);
 | |
| 
 | |
|     if (key == CN_AUTH_CONNECT_TIMEOUT)
 | |
|     {
 | |
|         time_t timeout = get_positive_int(value);
 | |
|         if (timeout)
 | |
|         {
 | |
|             MXS_NOTICE("Updated '%s' from %ld to %ld",
 | |
|                        CN_AUTH_CONNECT_TIMEOUT,
 | |
|                        cnf.auth_conn_timeout,
 | |
|                        timeout);
 | |
|             cnf.auth_conn_timeout = timeout;
 | |
|             rval = true;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             config_runtime_error("Invalid timeout value for '%s': %s", CN_AUTH_CONNECT_TIMEOUT, value);
 | |
|         }
 | |
|     }
 | |
|     else if (key == CN_AUTH_READ_TIMEOUT)
 | |
|     {
 | |
|         time_t timeout = get_positive_int(value);
 | |
|         if (timeout)
 | |
|         {
 | |
|             MXS_NOTICE("Updated '%s' from %ld to %ld",
 | |
|                        CN_AUTH_READ_TIMEOUT,
 | |
|                        cnf.auth_read_timeout,
 | |
|                        timeout);
 | |
|             cnf.auth_read_timeout = timeout;
 | |
|             rval = true;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             config_runtime_error("Invalid timeout value for '%s': %s", CN_AUTH_READ_TIMEOUT, value);
 | |
|         }
 | |
|     }
 | |
|     else if (key == CN_AUTH_WRITE_TIMEOUT)
 | |
|     {
 | |
|         time_t timeout = get_positive_int(value);
 | |
|         if (timeout)
 | |
|         {
 | |
|             MXS_NOTICE("Updated '%s' from %ld to %ld",
 | |
|                        CN_AUTH_WRITE_TIMEOUT,
 | |
|                        cnf.auth_write_timeout,
 | |
|                        timeout);
 | |
|             cnf.auth_write_timeout = timeout;
 | |
|             rval = true;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             config_runtime_error("Invalid timeout value for '%s': %s", CN_AUTH_WRITE_TIMEOUT, value);
 | |
|         }
 | |
|     }
 | |
|     else if (key == CN_ADMIN_AUTH)
 | |
|     {
 | |
|         int boolval = config_truth_value(value);
 | |
| 
 | |
|         if (boolval != -1)
 | |
|         {
 | |
|             MXS_NOTICE("Updated '%s' from '%s' to '%s'",
 | |
|                        CN_ADMIN_AUTH,
 | |
|                        cnf.admin_auth ? "true" : "false",
 | |
|                        boolval ? "true" : "false");
 | |
|             cnf.admin_auth = boolval;
 | |
|             rval = true;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             config_runtime_error("Invalid boolean value for '%s': %s", CN_ADMIN_AUTH, value);
 | |
|         }
 | |
|     }
 | |
|     else if (key == CN_ADMIN_LOG_AUTH_FAILURES)
 | |
|     {
 | |
|         int boolval = config_truth_value(value);
 | |
| 
 | |
|         if (boolval != -1)
 | |
|         {
 | |
|             MXS_NOTICE("Updated '%s' from '%s' to '%s'",
 | |
|                        CN_ADMIN_LOG_AUTH_FAILURES,
 | |
|                        cnf.admin_log_auth_failures ? "true" : "false",
 | |
|                        boolval ? "true" : "false");
 | |
|             cnf.admin_log_auth_failures = boolval;
 | |
|             rval = true;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             config_runtime_error("Invalid boolean value for '%s': %s", CN_ADMIN_LOG_AUTH_FAILURES, value);
 | |
|         }
 | |
|     }
 | |
|     else if (key == CN_PASSIVE)
 | |
|     {
 | |
|         int boolval = config_truth_value(value);
 | |
| 
 | |
|         if (boolval != -1)
 | |
|         {
 | |
|             MXS_NOTICE("Updated '%s' from '%s' to '%s'",
 | |
|                        CN_PASSIVE,
 | |
|                        cnf.passive ? "true" : "false",
 | |
|                        boolval ? "true" : "false");
 | |
| 
 | |
|             if (cnf.passive && !boolval)
 | |
|             {
 | |
|                 // This MaxScale is being promoted to the active instance
 | |
|                 cnf.promoted_at = mxs_clock();
 | |
|             }
 | |
| 
 | |
|             cnf.passive = boolval;
 | |
|             rval = true;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             config_runtime_error("Invalid boolean value for '%s': %s", CN_PASSIVE, value);
 | |
|         }
 | |
|     }
 | |
|     else if (key == CN_QUERY_CLASSIFIER_CACHE_SIZE)
 | |
|     {
 | |
|         uint64_t max_size;
 | |
| 
 | |
|         if (get_suffixed_size(value, &max_size))
 | |
|         {
 | |
|             decltype(QC_CACHE_PROPERTIES::max_size) new_size = max_size;
 | |
| 
 | |
|             if (new_size >= 0)
 | |
|             {
 | |
|                 MXS_NOTICE("Updated '%s' from %" PRIi64 " to %lu",
 | |
|                            CN_QUERY_CLASSIFIER_CACHE_SIZE,
 | |
|                            cnf.qc_cache_properties.max_size,
 | |
|                            max_size);
 | |
| 
 | |
|                 cnf.qc_cache_properties.max_size = new_size;
 | |
|                 qc_set_cache_properties(&cnf.qc_cache_properties);
 | |
|                 rval = true;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 config_runtime_error("Value too large for '%s': %s", CN_QUERY_CLASSIFIER_CACHE_SIZE, value);
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             config_runtime_error("Invalid size value for '%s': %s", CN_QUERY_CLASSIFIER_CACHE_SIZE, value);
 | |
|         }
 | |
|     }
 | |
|     else if (key == CN_WRITEQ_HIGH_WATER)
 | |
|     {
 | |
|         uint64_t size = 0;
 | |
| 
 | |
|         if (!get_suffixed_size(value, &size))
 | |
|         {
 | |
|             config_runtime_error("Invalid value for %s: %s", CN_WRITEQ_HIGH_WATER, value);
 | |
|         }
 | |
|         else if (size < MIN_WRITEQ_HIGH_WATER)
 | |
|         {
 | |
|             config_runtime_error("The specified '%s' is smaller than the minimum allowed size %lu.",
 | |
|                                  CN_WRITEQ_HIGH_WATER, MIN_WRITEQ_HIGH_WATER);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             rval = true;
 | |
|             config_set_writeq_high_water(size);
 | |
|             MXS_NOTICE("'%s' set to: %lu", CN_WRITEQ_HIGH_WATER, size);
 | |
|         }
 | |
|     }
 | |
|     else if (key == CN_WRITEQ_LOW_WATER)
 | |
|     {
 | |
|         uint64_t size = 0;
 | |
| 
 | |
|         if (!get_suffixed_size(value, &size))
 | |
|         {
 | |
|             config_runtime_error("Invalid value for '%s': %s", CN_WRITEQ_LOW_WATER, value);
 | |
|         }
 | |
|         else if (size < MIN_WRITEQ_LOW_WATER)
 | |
|         {
 | |
|             config_runtime_error("The specified '%s' is smaller than the minimum allowed size %lu.",
 | |
|                                  CN_WRITEQ_LOW_WATER, MIN_WRITEQ_LOW_WATER);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             rval = true;
 | |
|             config_set_writeq_low_water(size);
 | |
|             MXS_NOTICE("'%s' set to: %lu", CN_WRITEQ_LOW_WATER, size);
 | |
|         }
 | |
|     }
 | |
|     else if (key == CN_MS_TIMESTAMP)
 | |
|     {
 | |
|         mxs_log_set_highprecision_enabled(config_truth_value(value));
 | |
|         rval = true;
 | |
|     }
 | |
|     else if (key == CN_SKIP_PERMISSION_CHECKS)
 | |
|     {
 | |
|         cnf.skip_permission_checks = config_truth_value(value);
 | |
|         rval = true;
 | |
|     }
 | |
|     else if (key == CN_QUERY_RETRIES)
 | |
|     {
 | |
|         int intval = get_positive_int(value);
 | |
|         if (intval)
 | |
|         {
 | |
|             cnf.query_retries = intval;
 | |
|             rval = true;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             config_runtime_error("Invalid timeout value for '%s': %s", CN_QUERY_RETRIES, value);
 | |
|         }
 | |
|     }
 | |
|     else if (key == CN_QUERY_RETRY_TIMEOUT)
 | |
|     {
 | |
|         int intval = get_positive_int(value);
 | |
|         if (intval)
 | |
|         {
 | |
|             cnf.query_retry_timeout = intval;
 | |
|             rval = true;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             config_runtime_error("Invalid timeout value for '%s': %s", CN_QUERY_RETRY_TIMEOUT, value);
 | |
|         }
 | |
|     }
 | |
|     else if (key == CN_RETAIN_LAST_STATEMENTS)
 | |
|     {
 | |
|         if (is_valid_integer(value))
 | |
|         {
 | |
|             session_set_retain_last_statements(atoi(value));
 | |
|             rval = true;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             config_runtime_error("Invalid value for '%s': %s", CN_RETAIN_LAST_STATEMENTS, value);
 | |
|         }
 | |
|     }
 | |
|     else if (key == CN_DUMP_LAST_STATEMENTS)
 | |
|     {
 | |
|         rval = true;
 | |
|         if (strcmp(value, "on_close") == 0)
 | |
|         {
 | |
|             session_set_dump_statements(SESSION_DUMP_STATEMENTS_ON_CLOSE);
 | |
|         }
 | |
|         else if (strcmp(value, "on_error") == 0)
 | |
|         {
 | |
|             session_set_dump_statements(SESSION_DUMP_STATEMENTS_ON_ERROR);
 | |
|         }
 | |
|         else if (strcmp(value, "never") == 0)
 | |
|         {
 | |
|             session_set_dump_statements(SESSION_DUMP_STATEMENTS_NEVER);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             rval = false;
 | |
|             config_runtime_error("%s can have the values 'never', 'on_close' or 'on_error'.",
 | |
|                                  CN_DUMP_LAST_STATEMENTS);
 | |
|         }
 | |
|     }
 | |
|     else if (key == CN_MAX_AUTH_ERRORS_UNTIL_BLOCK)
 | |
|     {
 | |
|         if (is_valid_integer(value))
 | |
|         {
 | |
|             int intval = atoi(value);
 | |
|             MXS_NOTICE("Updated '%s' from %d to %d",
 | |
|                        CN_MAX_AUTH_ERRORS_UNTIL_BLOCK,
 | |
|                        cnf.max_auth_errors_until_block,
 | |
|                        intval);
 | |
|             cnf.max_auth_errors_until_block = intval;
 | |
|             rval = true;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             config_runtime_error("Invalid value for '%s': %s", CN_MAX_AUTH_ERRORS_UNTIL_BLOCK, value);
 | |
|         }
 | |
|     }
 | |
|     else if (key == CN_SESSION_TRACE)
 | |
|     {
 | |
|         char* endptr;
 | |
|         long intval = strtol(value, &endptr, 10);
 | |
| 
 | |
|         if (*endptr == '\0' && intval >= 0)
 | |
|         {
 | |
|             session_set_session_trace(intval);
 | |
|             mxb_log_set_session_trace(true);
 | |
|             rval = true;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             rval = false;
 | |
|             config_runtime_error("Invalid value for '%s': %s", CN_SESSION_TRACE, value);
 | |
|         }
 | |
|     }
 | |
|     else if (config_can_modify_at_runtime(key.c_str()))
 | |
|     {
 | |
|         config_runtime_error("Global parameter '%s' cannot be modified at runtime", name);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         config_runtime_error("Unknown global parameter: %s=%s", name, value);
 | |
|     }
 | |
| 
 | |
|     if (rval)
 | |
|     {
 | |
|         config_global_serialize();
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| // Helper for runtime_create_listener
 | |
| inline void set_if_not_null(MXS_CONFIG_PARAMETER& params, const char* name,
 | |
|                             const char* value, const char* dflt = nullptr)
 | |
| {
 | |
|     if ((!value || strcasecmp(value, CN_DEFAULT) == 0) && dflt)
 | |
|     {
 | |
|         params.set(name, dflt);
 | |
|     }
 | |
|     else if (value)
 | |
|     {
 | |
|         params.set(name, value);
 | |
|     }
 | |
| }
 | |
| 
 | |
| bool runtime_create_listener(Service* service,
 | |
|                              const char* name,
 | |
|                              const char* addr,
 | |
|                              const char* port,
 | |
|                              const char* proto,
 | |
|                              const char* auth,
 | |
|                              const char* auth_opt,
 | |
|                              const char* ssl_key,
 | |
|                              const char* ssl_cert,
 | |
|                              const char* ssl_ca,
 | |
|                              const char* ssl_version,
 | |
|                              const char* ssl_depth,
 | |
|                              const char* verify_ssl)
 | |
| {
 | |
|     if (proto == NULL || strcasecmp(proto, CN_DEFAULT) == 0)
 | |
|     {
 | |
|         proto = "mariadbclient";
 | |
|     }
 | |
| 
 | |
|     if (auth && strcasecmp(auth, CN_DEFAULT) == 0)
 | |
|     {
 | |
|         // The protocol default authenticator is used
 | |
|         auth = nullptr;
 | |
|     }
 | |
|     if (auth_opt && strcasecmp(auth_opt, CN_DEFAULT) == 0)
 | |
|     {
 | |
|         auth_opt = nullptr;
 | |
|     }
 | |
| 
 | |
|     MXS_CONFIG_PARAMETER params;
 | |
|     bool ok;
 | |
|     tie(ok, params) = load_defaults(proto, MODULE_PROTOCOL, CN_LISTENER);
 | |
|     params.set(CN_SERVICE, service->name());
 | |
|     set_if_not_null(params, CN_AUTHENTICATOR, auth);
 | |
|     set_if_not_null(params, CN_AUTHENTICATOR_OPTIONS, auth_opt);
 | |
|     bool use_socket = (addr && *addr == '/');
 | |
| 
 | |
|     if (use_socket)
 | |
|     {
 | |
|         params.set(CN_SOCKET, addr);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         set_if_not_null(params, CN_PORT, port, "3306");
 | |
|         set_if_not_null(params, CN_ADDRESS, addr, "::");
 | |
|     }
 | |
| 
 | |
|     if (ssl_key || ssl_cert || ssl_ca)
 | |
|     {
 | |
|         params.set(CN_SSL, CN_REQUIRED);
 | |
|         set_if_not_null(params, CN_SSL_KEY, ssl_key);
 | |
|         set_if_not_null(params, CN_SSL_CERT, ssl_cert);
 | |
|         set_if_not_null(params, CN_SSL_CA_CERT, ssl_ca);
 | |
|         set_if_not_null(params, CN_SSL_VERSION, ssl_version);
 | |
|         set_if_not_null(params, CN_SSL_CERT_VERIFY_DEPTH, ssl_depth);
 | |
|         set_if_not_null(params, CN_SSL_VERIFY_PEER_CERTIFICATE, verify_ssl);
 | |
|     }
 | |
| 
 | |
|     unsigned short u_port = atoi(port);
 | |
|     bool rval = false;
 | |
| 
 | |
|     std::lock_guard<std::mutex> guard(crt_lock);
 | |
|     std::string reason;
 | |
| 
 | |
|     SListener old_listener = use_socket ?
 | |
|         listener_find_by_address(params.get_string(CN_ADDRESS), params.get_integer(CN_PORT)) :
 | |
|         listener_find_by_socket(params.get_string(CN_SOCKET));
 | |
| 
 | |
|     if (!ok)
 | |
|     {
 | |
|         config_runtime_error("Failed to load module '%s'", proto);
 | |
|     }
 | |
|     else if (listener_find(name))
 | |
|     {
 | |
|         config_runtime_error("Listener '%s' already exists", name);
 | |
|     }
 | |
|     else if (old_listener)
 | |
|     {
 | |
|         config_runtime_error("Listener '%s' already listens on [%s]:%u", old_listener->name(),
 | |
|                              old_listener->address(), old_listener->port());
 | |
|     }
 | |
|     else if (config_is_valid_name(name, &reason))
 | |
|     {
 | |
|         auto listener = Listener::create(name, proto, params);
 | |
| 
 | |
|         if (listener && listener_serialize(listener))
 | |
|         {
 | |
|             if (listener->listen())
 | |
|             {
 | |
|                 MXS_NOTICE("Created listener '%s' at %s:%u for service '%s'",
 | |
|                            name, listener->address(), listener->port(), service->name());
 | |
| 
 | |
|                 rval = true;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 config_runtime_error("Listener '%s' was created but failed to start it.", name);
 | |
|                 Listener::destroy(listener);
 | |
|                 mxb_assert(!listener_find(name));
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             config_runtime_error("Failed to create listener '%s'. Read the MaxScale error log "
 | |
|                                  "for more details.", name);
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         config_runtime_error("%s", reason.c_str());
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool runtime_destroy_listener(Service* service, const char* name)
 | |
| {
 | |
|     char filename[PATH_MAX];
 | |
|     snprintf(filename, sizeof(filename), "%s/%s.cnf", get_config_persistdir(), name);
 | |
| 
 | |
|     std::lock_guard<std::mutex> guard(crt_lock);
 | |
| 
 | |
|     if (unlink(filename) == -1)
 | |
|     {
 | |
|         if (errno != ENOENT)
 | |
|         {
 | |
|             MXS_ERROR("Failed to remove persisted listener configuration '%s': %d, %s",
 | |
|                       filename, errno, mxs_strerror(errno));
 | |
|             return false;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             MXS_WARNING("Persisted configuration file for listener '%s' was not found. This means that the "
 | |
|                         "listener was not created at runtime. Remove the listener manually from the correct "
 | |
|                         "configuration file to permanently destroy the listener.", name);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     bool rval = false;
 | |
| 
 | |
|     if (!service_remove_listener(service, name))
 | |
|     {
 | |
|         MXS_ERROR("Failed to destroy listener '%s' for service '%s'", name, service->name());
 | |
|         config_runtime_error("Failed to destroy listener '%s' for service '%s'", name, service->name());
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         rval = true;
 | |
|         MXS_NOTICE("Destroyed listener '%s' for service '%s'. The listener "
 | |
|                    "will be removed after the next restart of MaxScale or "
 | |
|                    "when the associated service is destroyed.",
 | |
|                    name,
 | |
|                    service->name());
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool runtime_create_monitor(const char* name, const char* module, MXS_CONFIG_PARAMETER* params)
 | |
| {
 | |
|     std::lock_guard<std::mutex> guard(crt_lock);
 | |
|     bool rval = false;
 | |
| 
 | |
|     if (MonitorManager::find_monitor(name) == NULL)
 | |
|     {
 | |
|         std::string reason;
 | |
| 
 | |
|         if (config_is_valid_name(name, &reason))
 | |
|         {
 | |
|             MXS_CONFIG_PARAMETER final_params;
 | |
|             bool ok;
 | |
|             tie(ok, final_params) = load_defaults(module, MODULE_MONITOR, CN_MONITOR);
 | |
| 
 | |
|             if (ok)
 | |
|             {
 | |
|                 if (params)
 | |
|                 {
 | |
|                     final_params.set_multiple(*params);
 | |
|                 }
 | |
| 
 | |
|                 Monitor* monitor = MonitorManager::create_monitor(name, module, &final_params);
 | |
| 
 | |
|                 if (!monitor)
 | |
|                 {
 | |
|                     config_runtime_error("Could not create monitor '%s' with module '%s'", name, module);
 | |
|                 }
 | |
|                 else if (!MonitorManager::monitor_serialize(monitor))
 | |
|                 {
 | |
|                     config_runtime_error("Failed to serialize monitor '%s'", name);
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     MXS_NOTICE("Created monitor '%s'", name);
 | |
|                     rval = true;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             config_runtime_error("%s", reason.c_str());
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         config_runtime_error("Can't create monitor '%s', it already exists", name);
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool runtime_create_filter(const char* name, const char* module, MXS_CONFIG_PARAMETER* params)
 | |
| {
 | |
|     std::lock_guard<std::mutex> guard(crt_lock);
 | |
|     bool rval = false;
 | |
| 
 | |
|     if (!filter_find(name))
 | |
|     {
 | |
|         SFilterDef filter;
 | |
|         MXS_CONFIG_PARAMETER parameters;
 | |
|         bool ok;
 | |
|         tie(ok, parameters) = load_defaults(module, MODULE_FILTER, CN_FILTER);
 | |
| 
 | |
|         if (ok)
 | |
|         {
 | |
|             std::string reason;
 | |
| 
 | |
|             if (config_is_valid_name(name, &reason))
 | |
|             {
 | |
|                 if (params)
 | |
|                 {
 | |
|                     parameters.set_multiple(*params);
 | |
|                 }
 | |
| 
 | |
|                 if (!(filter = filter_alloc(name, module, ¶meters)))
 | |
|                 {
 | |
|                     config_runtime_error("Could not create filter '%s' with module '%s'", name, module);
 | |
|                 }
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 config_runtime_error("%s", reason.c_str());
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (filter)
 | |
|         {
 | |
|             if (filter_serialize(filter))
 | |
|             {
 | |
|                 MXS_NOTICE("Created filter '%s'", name);
 | |
|                 rval = true;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 config_runtime_error("Failed to serialize filter '%s'", name);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         config_runtime_error("Can't create filter '%s', it already exists", name);
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool runtime_destroy_filter(const SFilterDef& filter)
 | |
| {
 | |
|     mxb_assert(filter);
 | |
|     bool rval = false;
 | |
|     std::lock_guard<std::mutex> guard(crt_lock);
 | |
| 
 | |
|     if (filter_can_be_destroyed(filter))
 | |
|     {
 | |
|         filter_destroy(filter);
 | |
|         rval = true;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         config_runtime_error("Filter '%s' cannot be destroyed: Remove it from all services "
 | |
|                              "first",
 | |
|                              filter->name.c_str());
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| static bool runtime_create_service(const char* name, const char* router, MXS_CONFIG_PARAMETER* params)
 | |
| {
 | |
|     std::lock_guard<std::mutex> guard(crt_lock);
 | |
|     bool rval = false;
 | |
| 
 | |
|     if (service_internal_find(name) == NULL)
 | |
|     {
 | |
|         Service* service = NULL;
 | |
|         MXS_CONFIG_PARAMETER parameters;
 | |
|         bool ok;
 | |
|         tie(ok, parameters) = load_defaults(router, MODULE_ROUTER, CN_SERVICE);
 | |
| 
 | |
|         if (ok)
 | |
|         {
 | |
|             std::string reason;
 | |
|             if (config_is_valid_name(name, &reason))
 | |
|             {
 | |
|                 if (params)
 | |
|                 {
 | |
|                     parameters.set_multiple(*params);
 | |
|                 }
 | |
| 
 | |
|                 if ((service = service_alloc(name, router, ¶meters)) == nullptr)
 | |
|                 {
 | |
|                     config_runtime_error("Could not create service '%s' with module '%s'", name, router);
 | |
|                 }
 | |
|                 else if (!service_serialize(service))
 | |
|                 {
 | |
|                     config_runtime_error("Failed to serialize service '%s'", name);
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     MXS_NOTICE("Created service '%s'", name);
 | |
|                     rval = true;
 | |
|                 }
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 config_runtime_error("%s", reason.c_str());
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         config_runtime_error("Can't create service '%s', it already exists", name);
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool runtime_destroy_service(Service* service)
 | |
| {
 | |
|     bool rval = false;
 | |
|     std::lock_guard<std::mutex> guard(crt_lock);
 | |
|     mxb_assert(service && service->active);
 | |
| 
 | |
|     if (service_can_be_destroyed(service))
 | |
|     {
 | |
|         service_destroy(service);
 | |
|         rval = true;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         config_runtime_error("Service '%s' cannot be destroyed: Remove all servers and "
 | |
|                              "destroy all listeners first",
 | |
|                              service->name());
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool runtime_destroy_monitor(Monitor* monitor)
 | |
| {
 | |
|     bool rval = false;
 | |
| 
 | |
|     if (!monitor->servers().empty())
 | |
|     {
 | |
|         config_runtime_error("Cannot destroy monitor '%s', it is monitoring servers.", monitor->name());
 | |
|     }
 | |
|     else if (Service* s = service_uses_monitor(monitor))
 | |
|     {
 | |
|         config_runtime_error("Monitor '%s' cannot be destroyed as it is used by service '%s'",
 | |
|                              monitor->name(), s->name());
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         char filename[PATH_MAX];
 | |
|         snprintf(filename, sizeof(filename), "%s/%s.cnf", get_config_persistdir(), monitor->name());
 | |
| 
 | |
|         std::lock_guard<std::mutex> guard(crt_lock);
 | |
| 
 | |
|         if (unlink(filename) == -1 && errno != ENOENT)
 | |
|         {
 | |
|             MXS_ERROR("Failed to remove persisted monitor configuration '%s': %d, %s",
 | |
|                       filename,
 | |
|                       errno,
 | |
|                       mxs_strerror(errno));
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             rval = true;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (rval)
 | |
|     {
 | |
|         MonitorManager::deactivate_monitor(monitor);
 | |
|         MXS_NOTICE("Destroyed monitor '%s'", monitor->name());
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| static MXS_CONFIG_PARAMETER extract_parameters_from_json(json_t* json)
 | |
| {
 | |
|     MXS_CONFIG_PARAMETER rval;
 | |
|     if (json_t* parameters = mxs_json_pointer(json, MXS_JSON_PTR_PARAMETERS))
 | |
|     {
 | |
|         const char* key;
 | |
|         json_t* value;
 | |
| 
 | |
|         json_object_foreach(parameters, key, value)
 | |
|         {
 | |
|             if (!json_is_null(value) && !json_is_array(value) && !json_is_object(value))
 | |
|             {
 | |
|                 mxb_assert(!mxs::json_to_string(value).empty());
 | |
|                 rval.set(key, mxs::json_to_string(value));
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| static bool extract_ordered_relations(json_t* json,
 | |
|                                       StringVector& relations,
 | |
|                                       const char* relation_type,
 | |
|                                       JsonValidator relation_check)
 | |
| {
 | |
|     bool rval = true;
 | |
|     json_t* arr = mxs_json_pointer(json, relation_type);
 | |
| 
 | |
|     if (arr && json_is_array(arr))
 | |
|     {
 | |
|         size_t size = json_array_size(arr);
 | |
| 
 | |
|         for (size_t j = 0; j < size; j++)
 | |
|         {
 | |
|             json_t* obj = json_array_get(arr, j);
 | |
|             json_t* id = json_object_get(obj, CN_ID);
 | |
|             json_t* type = mxs_json_pointer(obj, CN_TYPE);
 | |
| 
 | |
|             if (id && json_is_string(id)
 | |
|                 && type && json_is_string(type))
 | |
|             {
 | |
|                 std::string id_value = json_string_value(id);
 | |
|                 std::string type_value = json_string_value(type);
 | |
| 
 | |
|                 if (relation_check(type_value, id_value))
 | |
|                 {
 | |
|                     relations.push_back(id_value);
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     rval = false;
 | |
|                 }
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 rval = false;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| static bool extract_relations(json_t* json,
 | |
|                               StringSet& relations,
 | |
|                               const char* relation_type,
 | |
|                               JsonValidator relation_check)
 | |
| {
 | |
|     StringVector values;
 | |
|     bool rval = extract_ordered_relations(json, values, relation_type, relation_check);
 | |
|     relations.insert(values.begin(), values.end());
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| static inline bool is_null_relation(json_t* json, const char* relation)
 | |
| {
 | |
|     std::string str(relation);
 | |
|     size_t pos = str.rfind("/data");
 | |
| 
 | |
|     mxb_assert(pos != std::string::npos);
 | |
|     str = str.substr(0, pos);
 | |
| 
 | |
|     json_t* data = mxs_json_pointer(json, relation);
 | |
|     json_t* base = mxs_json_pointer(json, str.c_str());
 | |
| 
 | |
|     return (data && json_is_null(data)) || (base && json_is_null(base));
 | |
| }
 | |
| 
 | |
| static const char* json_type_to_string(const json_t* json)
 | |
| {
 | |
|     mxb_assert(json);
 | |
| 
 | |
|     if (json_is_object(json))
 | |
|     {
 | |
|         return "an object";
 | |
|     }
 | |
|     else if (json_is_array(json))
 | |
|     {
 | |
|         return "an array";
 | |
|     }
 | |
|     else if (json_is_string(json))
 | |
|     {
 | |
|         return "a string";
 | |
|     }
 | |
|     else if (json_is_integer(json))
 | |
|     {
 | |
|         return "an integer";
 | |
|     }
 | |
|     else if (json_is_real(json))
 | |
|     {
 | |
|         return "a real number";
 | |
|     }
 | |
|     else if (json_is_boolean(json))
 | |
|     {
 | |
|         return "a boolean";
 | |
|     }
 | |
|     else if (json_is_null(json))
 | |
|     {
 | |
|         return "a null value";
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         mxb_assert(!true);
 | |
|         return "an unknown type";
 | |
|     }
 | |
| }
 | |
| 
 | |
| static inline const char* get_string_or_null(json_t* json, const char* path)
 | |
| {
 | |
|     const char* rval = NULL;
 | |
|     json_t* value = mxs_json_pointer(json, path);
 | |
| 
 | |
|     if (value && json_is_string(value))
 | |
|     {
 | |
|         rval = json_string_value(value);
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool runtime_is_string_or_null(json_t* json, const char* path)
 | |
| {
 | |
|     bool rval = true;
 | |
|     json_t* value = mxs_json_pointer(json, path);
 | |
| 
 | |
|     if (value && !json_is_string(value))
 | |
|     {
 | |
|         config_runtime_error("Parameter '%s' is not a string but %s", path, json_type_to_string(value));
 | |
|         rval = false;
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool runtime_is_bool_or_null(json_t* json, const char* path)
 | |
| {
 | |
|     bool rval = true;
 | |
|     json_t* value = mxs_json_pointer(json, path);
 | |
| 
 | |
|     if (value && !json_is_boolean(value))
 | |
|     {
 | |
|         config_runtime_error("Parameter '%s' is not a boolean but %s", path, json_type_to_string(value));
 | |
|         rval = false;
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool runtime_is_size_or_null(json_t* json, const char* path)
 | |
| {
 | |
|     bool rval = true;
 | |
|     json_t* value = mxs_json_pointer(json, path);
 | |
| 
 | |
|     if (value)
 | |
|     {
 | |
|         if (!json_is_integer(value) && !json_is_string(value))
 | |
|         {
 | |
|             config_runtime_error("Parameter '%s' is not an integer or a string but %s",
 | |
|                                  path,
 | |
|                                  json_type_to_string(value));
 | |
|             rval = false;
 | |
|         }
 | |
|         else if ((json_is_integer(value) && json_integer_value(value) < 0)
 | |
|                  || (json_is_string(value) && !get_suffixed_size(json_string_value(value), nullptr)))
 | |
|         {
 | |
|             config_runtime_error("Parameter '%s' is not a valid size", path);
 | |
|             rval = false;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool runtime_is_count_or_null(json_t* json, const char* path)
 | |
| {
 | |
|     bool rval = true;
 | |
|     json_t* value = mxs_json_pointer(json, path);
 | |
| 
 | |
|     if (value)
 | |
|     {
 | |
|         if (!json_is_integer(value))
 | |
|         {
 | |
|             config_runtime_error("Parameter '%s' is not an integer but %s", path, json_type_to_string(value));
 | |
|             rval = false;
 | |
|         }
 | |
|         else if (json_integer_value(value) < 0)
 | |
|         {
 | |
|             config_runtime_error("Parameter '%s' is a negative integer", path);
 | |
|             rval = false;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| /** Check that the body at least defies a data member */
 | |
| static bool is_valid_resource_body(json_t* json)
 | |
| {
 | |
|     bool rval = true;
 | |
| 
 | |
|     if (mxs_json_pointer(json, MXS_JSON_PTR_DATA) == NULL)
 | |
|     {
 | |
|         config_runtime_error("No '%s' field defined", MXS_JSON_PTR_DATA);
 | |
|         rval = false;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         // Check that the relationship JSON is well-formed
 | |
|         const std::vector<const char*> relations =
 | |
|         {
 | |
|             MXS_JSON_PTR_RELATIONSHIPS "/servers",
 | |
|             MXS_JSON_PTR_RELATIONSHIPS "/services",
 | |
|             MXS_JSON_PTR_RELATIONSHIPS "/monitors",
 | |
|             MXS_JSON_PTR_RELATIONSHIPS "/filters",
 | |
|         };
 | |
| 
 | |
|         for (auto it = relations.begin(); it != relations.end(); it++)
 | |
|         {
 | |
|             json_t* j = mxs_json_pointer(json, *it);
 | |
| 
 | |
|             if (j && !json_is_object(j))
 | |
|             {
 | |
|                 config_runtime_error("Relationship '%s' is not an object", *it);
 | |
|                 rval = false;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| static bool server_alter_fields_are_valid(json_t* json)
 | |
| {
 | |
|     bool rval = false;
 | |
|     json_t* address = mxs_json_pointer(json, MXS_JSON_PTR_PARAM_ADDRESS);
 | |
|     json_t* socket = mxs_json_pointer(json, MXS_JSON_PTR_PARAM_SOCKET);
 | |
|     json_t* port = mxs_json_pointer(json, MXS_JSON_PTR_PARAM_PORT);
 | |
| 
 | |
|     if (address && socket)
 | |
|     {
 | |
|         config_runtime_error("Request body defines both of the '%s' and '%s' fields",
 | |
|                              MXS_JSON_PTR_PARAM_ADDRESS, MXS_JSON_PTR_PARAM_SOCKET);
 | |
|     }
 | |
|     else if (address && !json_is_string(address))
 | |
|     {
 | |
|         config_runtime_error("The '%s' field is not a string", MXS_JSON_PTR_PARAM_ADDRESS);
 | |
|     }
 | |
|     else if (address && json_is_string(address) && json_string_value(address)[0] == '/')
 | |
|     {
 | |
|         config_runtime_error("The '%s' field is not a valid address", MXS_JSON_PTR_PARAM_ADDRESS);
 | |
|     }
 | |
|     else if (socket && !json_is_string(socket))
 | |
|     {
 | |
|         config_runtime_error("The '%s' field is not a string", MXS_JSON_PTR_PARAM_SOCKET);
 | |
|     }
 | |
|     else if (port && !json_is_integer(port))
 | |
|     {
 | |
|         config_runtime_error("The '%s' field is not an integer", MXS_JSON_PTR_PARAM_PORT);
 | |
|     }
 | |
|     else if (socket && !json_is_string(socket))
 | |
|     {
 | |
|         config_runtime_error("The '%s' field is not a string", MXS_JSON_PTR_PARAM_SOCKET);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         rval = true;
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| static bool server_contains_required_fields(json_t* json)
 | |
| {
 | |
|     json_t* id = mxs_json_pointer(json, MXS_JSON_PTR_ID);
 | |
|     json_t* port = mxs_json_pointer(json, MXS_JSON_PTR_PARAM_PORT);
 | |
|     json_t* address = mxs_json_pointer(json, MXS_JSON_PTR_PARAM_ADDRESS);
 | |
|     json_t* socket = mxs_json_pointer(json, MXS_JSON_PTR_PARAM_SOCKET);
 | |
|     json_t* proto = mxs_json_pointer(json, MXS_JSON_PTR_PARAM_PROTOCOL);
 | |
|     bool rval = false;
 | |
| 
 | |
|     if (!id)
 | |
|     {
 | |
|         config_runtime_error("Request body does not define the '%s' field", MXS_JSON_PTR_ID);
 | |
|     }
 | |
|     else if (!json_is_string(id))
 | |
|     {
 | |
|         config_runtime_error("The '%s' field is not a string", MXS_JSON_PTR_ID);
 | |
|     }
 | |
|     else if (!address && !socket)
 | |
|     {
 | |
|         config_runtime_error("Request body does not define '%s' or '%s'",
 | |
|                              MXS_JSON_PTR_PARAM_ADDRESS, MXS_JSON_PTR_PARAM_SOCKET);
 | |
|     }
 | |
|     else if (address && socket)
 | |
|     {
 | |
|         config_runtime_error("Request body defines both of the '%s' and '%s' fields",
 | |
|                              MXS_JSON_PTR_PARAM_ADDRESS, MXS_JSON_PTR_PARAM_SOCKET);
 | |
|     }
 | |
|     else if (address && !json_is_string(address))
 | |
|     {
 | |
|         config_runtime_error("The '%s' field is not a string", MXS_JSON_PTR_PARAM_ADDRESS);
 | |
|     }
 | |
|     else if (address && json_is_string(address) && json_string_value(address)[0] == '/')
 | |
|     {
 | |
|         config_runtime_error("The '%s' field is not a valid address", MXS_JSON_PTR_PARAM_ADDRESS);
 | |
|     }
 | |
|     else if (socket && !json_is_string(socket))
 | |
|     {
 | |
|         config_runtime_error("The '%s' field is not a string", MXS_JSON_PTR_PARAM_SOCKET);
 | |
|     }
 | |
|     else if (!address && port)
 | |
|     {
 | |
|         config_runtime_error("Request body does not define the '%s' field", MXS_JSON_PTR_PARAM_PORT);
 | |
|     }
 | |
|     else if (port && !json_is_integer(port))
 | |
|     {
 | |
|         config_runtime_error("The '%s' field is not an integer", MXS_JSON_PTR_PARAM_PORT);
 | |
|     }
 | |
|     else if (!proto)
 | |
|     {
 | |
|         config_runtime_error("No protocol defined at JSON path '%s'", MXS_JSON_PTR_PARAM_PROTOCOL);
 | |
|     }
 | |
|     else if (!json_is_string(proto))
 | |
|     {
 | |
|         config_runtime_error("The '%s' field is not a string", MXS_JSON_PTR_PARAM_PROTOCOL);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         rval = true;
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| static bool server_relation_is_valid(const std::string& type, const std::string& value)
 | |
| {
 | |
|     return (type == CN_SERVICES && service_internal_find(value.c_str()))
 | |
|            || (type == CN_MONITORS && MonitorManager::find_monitor(value.c_str()));
 | |
| }
 | |
| 
 | |
| static bool filter_to_service_relation_is_valid(const std::string& type, const std::string& value)
 | |
| {
 | |
|     return type == CN_SERVICES && service_internal_find(value.c_str());
 | |
| }
 | |
| 
 | |
| static bool service_to_filter_relation_is_valid(const std::string& type, const std::string& value)
 | |
| {
 | |
|     return type == CN_FILTERS && filter_find(value.c_str());
 | |
| }
 | |
| 
 | |
| static bool unlink_server_from_objects(Server* server, StringSet& relations)
 | |
| {
 | |
|     bool rval = true;
 | |
| 
 | |
|     for (StringSet::iterator it = relations.begin(); it != relations.end(); it++)
 | |
|     {
 | |
|         if (!runtime_unlink_server(server, it->c_str()))
 | |
|         {
 | |
|             rval = false;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| static bool link_server_to_objects(Server* server, StringSet& relations)
 | |
| {
 | |
|     bool rval = true;
 | |
| 
 | |
|     for (StringSet::iterator it = relations.begin(); it != relations.end(); it++)
 | |
|     {
 | |
|         if (!runtime_link_server(server, it->c_str()))
 | |
|         {
 | |
|             unlink_server_from_objects(server, relations);
 | |
|             rval = false;
 | |
|             break;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| static std::string json_int_to_string(json_t* json)
 | |
| {
 | |
|     char str[25];   // Enough to store any 64-bit integer value
 | |
|     int64_t i = json_integer_value(json);
 | |
|     snprintf(str, sizeof(str), "%ld", i);
 | |
|     return std::string(str);
 | |
| }
 | |
| 
 | |
| static inline bool have_ssl_json(json_t* params)
 | |
| {
 | |
|     return mxs_json_pointer(params, CN_SSL_KEY)
 | |
|            || mxs_json_pointer(params, CN_SSL_CERT)
 | |
|            || mxs_json_pointer(params, CN_SSL_CA_CERT);
 | |
| }
 | |
| 
 | |
| namespace
 | |
| {
 | |
| 
 | |
| enum object_type
 | |
| {
 | |
|     OT_SERVER,
 | |
|     OT_LISTENER
 | |
| };
 | |
| }
 | |
| 
 | |
| static bool validate_ssl_json(json_t* params, object_type type)
 | |
| {
 | |
|     bool rval = true;
 | |
| 
 | |
|     if (runtime_is_string_or_null(params, CN_SSL_KEY)
 | |
|         && runtime_is_string_or_null(params, CN_SSL_CERT)
 | |
|         && runtime_is_string_or_null(params, CN_SSL_CA_CERT)
 | |
|         && runtime_is_string_or_null(params, CN_SSL_VERSION)
 | |
|         && runtime_is_count_or_null(params, CN_SSL_CERT_VERIFY_DEPTH))
 | |
|     {
 | |
|         json_t* key = mxs_json_pointer(params, CN_SSL_KEY);
 | |
|         json_t* cert = mxs_json_pointer(params, CN_SSL_CERT);
 | |
|         json_t* ca_cert = mxs_json_pointer(params, CN_SSL_CA_CERT);
 | |
| 
 | |
|         if (type == OT_LISTENER && !(key && cert && ca_cert))
 | |
|         {
 | |
|             config_runtime_error("SSL configuration for listeners requires "
 | |
|                                  "'%s', '%s' and '%s' parameters",
 | |
|                                  CN_SSL_KEY,
 | |
|                                  CN_SSL_CERT,
 | |
|                                  CN_SSL_CA_CERT);
 | |
|             rval = false;
 | |
|         }
 | |
|         else if (type == OT_SERVER)
 | |
|         {
 | |
|             if (!ca_cert)
 | |
|             {
 | |
|                 config_runtime_error("SSL configuration for servers requires "
 | |
|                                      "at least the '%s' parameter",
 | |
|                                      CN_SSL_CA_CERT);
 | |
|                 rval = false;
 | |
|             }
 | |
|             else if ((key == nullptr) != (cert == nullptr))
 | |
|             {
 | |
|                 config_runtime_error("Both '%s' and '%s' must be defined", CN_SSL_KEY, CN_SSL_CERT);
 | |
|                 rval = false;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         json_t* ssl_version = mxs_json_pointer(params, CN_SSL_VERSION);
 | |
|         const char* ssl_version_str = ssl_version ? json_string_value(ssl_version) : NULL;
 | |
| 
 | |
|         if (ssl_version_str && string_to_ssl_method_type(ssl_version_str) == SERVICE_SSL_UNKNOWN)
 | |
|         {
 | |
|             config_runtime_error("Invalid value for '%s': %s", CN_SSL_VERSION, ssl_version_str);
 | |
|             rval = false;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool runtime_create_server_from_json(json_t* json)
 | |
| {
 | |
|     bool rval = false;
 | |
|     StringSet relations;
 | |
| 
 | |
|     if (is_valid_resource_body(json) && server_contains_required_fields(json)
 | |
|         && extract_relations(json, relations, MXS_JSON_PTR_RELATIONSHIPS_SERVICES, server_relation_is_valid)
 | |
|         && extract_relations(json, relations, MXS_JSON_PTR_RELATIONSHIPS_MONITORS, server_relation_is_valid))
 | |
|     {
 | |
|         const char* name = json_string_value(mxs_json_pointer(json, MXS_JSON_PTR_ID));
 | |
|         const char* protocol = json_string_value(mxs_json_pointer(json, MXS_JSON_PTR_PARAM_PROTOCOL));
 | |
|         mxb_assert(name && protocol);
 | |
| 
 | |
|         if (Server::find_by_unique_name(name))
 | |
|         {
 | |
|             config_runtime_error("Server '%s' already exists", name);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             MXS_CONFIG_PARAMETER params;
 | |
|             bool ok;
 | |
|             tie(ok, params) = load_defaults(protocol, MODULE_PROTOCOL, CN_SERVER);
 | |
| 
 | |
|             if (ok)
 | |
|             {
 | |
|                 params.set_multiple(extract_parameters_from_json(json));
 | |
| 
 | |
|                 if (params.contains_any({CN_SSL_KEY, CN_SSL_CERT, CN_SSL_CA_CERT}))
 | |
|                 {
 | |
|                     params.set(CN_SSL, "true");
 | |
|                 }
 | |
| 
 | |
|                 if (Server* server = Server::server_alloc(name, params))
 | |
|                 {
 | |
|                     if (link_server_to_objects(server, relations) && server->serialize())
 | |
|                     {
 | |
|                         rval = true;
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         runtime_destroy_server(server);
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 if (!rval)
 | |
|                 {
 | |
|                     config_runtime_error("Failed to create server '%s', see error log for more details",
 | |
|                                          name);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool server_to_object_relations(Server* server, json_t* old_json, json_t* new_json)
 | |
| {
 | |
|     if (mxs_json_pointer(new_json, MXS_JSON_PTR_RELATIONSHIPS_SERVICES) == NULL
 | |
|         && mxs_json_pointer(new_json, MXS_JSON_PTR_RELATIONSHIPS_MONITORS) == NULL)
 | |
|     {
 | |
|         /** No change to relationships */
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     const char* server_relation_types[] =
 | |
|     {
 | |
|         MXS_JSON_PTR_RELATIONSHIPS_SERVICES,
 | |
|         MXS_JSON_PTR_RELATIONSHIPS_MONITORS,
 | |
|         NULL
 | |
|     };
 | |
| 
 | |
|     bool rval = true;
 | |
|     StringSet old_relations;
 | |
|     StringSet new_relations;
 | |
| 
 | |
|     for (int i = 0; server_relation_types[i]; i++)
 | |
|     {
 | |
|         // Extract only changed or deleted relationships
 | |
|         if (is_null_relation(new_json, server_relation_types[i])
 | |
|             || mxs_json_pointer(new_json, server_relation_types[i]))
 | |
|         {
 | |
|             if (!extract_relations(new_json,
 | |
|                                    new_relations,
 | |
|                                    server_relation_types[i],
 | |
|                                    server_relation_is_valid)
 | |
|                 || !extract_relations(old_json,
 | |
|                                       old_relations,
 | |
|                                       server_relation_types[i],
 | |
|                                       server_relation_is_valid))
 | |
|             {
 | |
|                 rval = false;
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (rval)
 | |
|     {
 | |
|         StringSet removed_relations;
 | |
|         StringSet added_relations;
 | |
| 
 | |
|         std::set_difference(old_relations.begin(),
 | |
|                             old_relations.end(),
 | |
|                             new_relations.begin(),
 | |
|                             new_relations.end(),
 | |
|                             std::inserter(removed_relations, removed_relations.begin()));
 | |
| 
 | |
|         std::set_difference(new_relations.begin(),
 | |
|                             new_relations.end(),
 | |
|                             old_relations.begin(),
 | |
|                             old_relations.end(),
 | |
|                             std::inserter(added_relations, added_relations.begin()));
 | |
| 
 | |
|         if (!unlink_server_from_objects(server, removed_relations)
 | |
|             || !link_server_to_objects(server, added_relations))
 | |
|         {
 | |
|             rval = false;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool runtime_alter_server_from_json(Server* server, json_t* new_json)
 | |
| {
 | |
|     bool rval = false;
 | |
|     std::unique_ptr<json_t> old_json(server->to_json(""));
 | |
|     mxb_assert(old_json.get());
 | |
| 
 | |
|     if (is_valid_resource_body(new_json)
 | |
|         && server_to_object_relations(server, old_json.get(), new_json)
 | |
|         && server_alter_fields_are_valid(new_json))
 | |
|     {
 | |
|         rval = true;
 | |
|         json_t* parameters = mxs_json_pointer(new_json, MXS_JSON_PTR_PARAMETERS);
 | |
|         json_t* old_parameters = mxs_json_pointer(old_json.get(), MXS_JSON_PTR_PARAMETERS);
 | |
| 
 | |
|         mxb_assert(old_parameters);
 | |
| 
 | |
|         if (parameters)
 | |
|         {
 | |
|             const char* key;
 | |
|             json_t* value;
 | |
| 
 | |
|             json_object_foreach(parameters, key, value)
 | |
|             {
 | |
|                 json_t* new_val = json_object_get(parameters, key);
 | |
|                 json_t* old_val = json_object_get(old_parameters, key);
 | |
| 
 | |
|                 if (old_val && new_val && mxs::json_to_string(new_val) == mxs::json_to_string(old_val))
 | |
|                 {
 | |
|                     /** No change in values */
 | |
|                 }
 | |
|                 else if (!runtime_alter_server(server, key, mxs::json_to_string(value).c_str()))
 | |
|                 {
 | |
|                     rval = false;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| static bool is_valid_relationship_body(json_t* json)
 | |
| {
 | |
|     bool rval = true;
 | |
| 
 | |
|     json_t* obj = mxs_json_pointer(json, MXS_JSON_PTR_DATA);
 | |
| 
 | |
|     if (!obj)
 | |
|     {
 | |
|         config_runtime_error("Field '%s' is not defined", MXS_JSON_PTR_DATA);
 | |
|         rval = false;
 | |
|     }
 | |
|     else if (!json_is_array(obj) && !json_is_null(obj))
 | |
|     {
 | |
|         config_runtime_error("Field '%s' is not an array", MXS_JSON_PTR_DATA);
 | |
|         rval = false;
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool runtime_alter_server_relationships_from_json(Server* server, const char* type, json_t* json)
 | |
| {
 | |
|     bool rval = false;
 | |
|     std::unique_ptr<json_t> old_json(server->to_json(""));
 | |
|     mxb_assert(old_json.get());
 | |
| 
 | |
|     if (is_valid_relationship_body(json))
 | |
|     {
 | |
|         std::unique_ptr<json_t> j(json_pack("{s: {s: {s: {s: O}}}}",
 | |
|                                             "data",
 | |
|                                             "relationships",
 | |
|                                             type,
 | |
|                                             "data",
 | |
|                                             json_object_get(json, "data")));
 | |
| 
 | |
|         if (server_to_object_relations(server, old_json.get(), j.get()))
 | |
|         {
 | |
|             rval = true;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| static bool object_relation_is_valid(const std::string& type, const std::string& value)
 | |
| {
 | |
|     return type == CN_SERVERS && Server::find_by_unique_name(value);
 | |
| }
 | |
| 
 | |
| static bool filter_relation_is_valid(const std::string& type, const std::string& value)
 | |
| {
 | |
|     return type == CN_FILTERS && filter_find(value.c_str());
 | |
| }
 | |
| 
 | |
| //
 | |
| // Constants for validate_object_json
 | |
| //
 | |
| const Relationship object_to_server
 | |
| {
 | |
|     MXS_JSON_PTR_RELATIONSHIPS_SERVERS,
 | |
|     object_relation_is_valid
 | |
| };
 | |
| 
 | |
| const Relationship filter_to_service
 | |
| {
 | |
|     MXS_JSON_PTR_RELATIONSHIPS_SERVICES,
 | |
|     filter_to_service_relation_is_valid
 | |
| };
 | |
| 
 | |
| const Relationship service_to_filter
 | |
| {
 | |
|     MXS_JSON_PTR_RELATIONSHIPS_FILTERS,
 | |
|     service_to_filter_relation_is_valid
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * @brief Do a coarse validation of the monitor JSON
 | |
|  *
 | |
|  * @param json          JSON to validate
 | |
|  * @param paths         List of paths that must be string values
 | |
|  * @param relationships List of JSON paths and validation functions to check
 | |
|  *
 | |
|  * @return True of the JSON is valid
 | |
|  */
 | |
| static bool validate_object_json(json_t* json,
 | |
|                                  std::vector<std::string> paths,
 | |
|                                  std::vector<Relationship> relationships)
 | |
| {
 | |
|     bool rval = false;
 | |
|     json_t* value;
 | |
| 
 | |
|     if (is_valid_resource_body(json))
 | |
|     {
 | |
|         if (json_t* parameters = mxs_json_pointer(json, MXS_JSON_PTR_PARAMETERS))
 | |
|         {
 | |
|             const char* key;
 | |
|             json_t* value;
 | |
| 
 | |
|             json_object_foreach(parameters, key, value)
 | |
|             {
 | |
|                 if (json_is_string(value) && strchr(json_string_value(value), '\n'))
 | |
|                 {
 | |
|                     config_runtime_error("Parameter '%s' contains unescaped newlines", key);
 | |
|                     return false;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (!(value = mxs_json_pointer(json, MXS_JSON_PTR_ID)))
 | |
|         {
 | |
|             config_runtime_error("Value not found: '%s'", MXS_JSON_PTR_ID);
 | |
|         }
 | |
|         else if (!json_is_string(value))
 | |
|         {
 | |
|             config_runtime_error("Value '%s' is not a string", MXS_JSON_PTR_ID);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             for (const auto& a : paths)
 | |
|             {
 | |
|                 if (!(value = mxs_json_pointer(json, a.c_str())))
 | |
|                 {
 | |
|                     config_runtime_error("Invalid value for '%s'", a.c_str());
 | |
|                 }
 | |
|                 else if (!json_is_string(value))
 | |
|                 {
 | |
|                     config_runtime_error("Value '%s' is not a string", a.c_str());
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             for (const auto& a : relationships)
 | |
|             {
 | |
|                 StringSet relations;
 | |
|                 if (extract_relations(json, relations, a.first, a.second))
 | |
|                 {
 | |
|                     rval = true;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool server_relationship_to_parameter(json_t* json, MXS_CONFIG_PARAMETER* params)
 | |
| {
 | |
|     StringSet relations;
 | |
|     bool rval = false;
 | |
| 
 | |
|     if (extract_relations(json, relations, MXS_JSON_PTR_RELATIONSHIPS_SERVERS, object_relation_is_valid))
 | |
|     {
 | |
|         rval = true;
 | |
| 
 | |
|         if (!relations.empty())
 | |
|         {
 | |
|             auto servers = std::accumulate(std::next(relations.begin()), relations.end(), *relations.begin(),
 | |
|                                            [](std::string sum, std::string s) {
 | |
|                                                return sum + ',' + s;
 | |
|                                            });
 | |
|             params->set(CN_SERVERS, servers);
 | |
|         }
 | |
|         else if (json_t* rel = mxs_json_pointer(json, MXS_JSON_PTR_RELATIONSHIPS_SERVERS))
 | |
|         {
 | |
|             if (json_is_array(rel) || json_is_null(rel))
 | |
|             {
 | |
|                 mxb_assert(json_is_null(rel) || json_array_size(rel) == 0);
 | |
|                 // Empty relationship, remove the parameter
 | |
|                 params->remove(CN_SERVERS);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| static bool unlink_object_from_servers(const char* target, StringSet& relations)
 | |
| {
 | |
|     bool rval = true;
 | |
| 
 | |
|     for (StringSet::iterator it = relations.begin(); it != relations.end(); it++)
 | |
|     {
 | |
|         auto server = Server::find_by_unique_name(*it);
 | |
| 
 | |
|         if (!server || !runtime_unlink_server(server, target))
 | |
|         {
 | |
|             rval = false;
 | |
|             break;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| static bool link_object_to_servers(const char* target, StringSet& relations)
 | |
| {
 | |
|     bool rval = true;
 | |
| 
 | |
|     for (StringSet::iterator it = relations.begin(); it != relations.end(); it++)
 | |
|     {
 | |
|         auto server = Server::find_by_unique_name(*it);
 | |
| 
 | |
|         if (!server || !runtime_link_server(server, target))
 | |
|         {
 | |
|             unlink_server_from_objects(server, relations);
 | |
|             rval = false;
 | |
|             break;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| static bool validate_monitor_json(json_t* json)
 | |
| {
 | |
|     bool rval = true;
 | |
|     json_t* params = mxs_json_pointer(json, MXS_JSON_PTR_PARAMETERS);
 | |
| 
 | |
|     for (auto a : {CN_USER, CN_PASSWORD})
 | |
|     {
 | |
|         if (!mxs_json_pointer(params, a))
 | |
|         {
 | |
|             config_runtime_error("Mandatory parameter '%s' is not defined", a);
 | |
|             rval = false;
 | |
|             break;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| MXS_CONFIG_PARAMETER extract_parameters(json_t* json)
 | |
| {
 | |
|     MXS_CONFIG_PARAMETER params;
 | |
|     json_t* parameters = mxs_json_pointer(json, MXS_JSON_PTR_PARAMETERS);
 | |
| 
 | |
|     if (parameters && json_is_object(parameters))
 | |
|     {
 | |
|         const char* key;
 | |
|         json_t* value;
 | |
| 
 | |
|         json_object_foreach(parameters, key, value)
 | |
|         {
 | |
|             params.set(key, mxs::json_to_string(value));
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return params;
 | |
| }
 | |
| 
 | |
| Monitor* runtime_create_monitor_from_json(json_t* json)
 | |
| {
 | |
|     Monitor* rval = nullptr;
 | |
| 
 | |
|     if (validate_object_json(json, {MXS_JSON_PTR_MODULE}, {object_to_server})
 | |
|         && validate_monitor_json(json))
 | |
|     {
 | |
|         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));
 | |
| 
 | |
|         if (const MXS_MODULE* mod = get_module(module, MODULE_MONITOR))
 | |
|         {
 | |
|             MXS_CONFIG_PARAMETER params;
 | |
|             bool ok;
 | |
|             tie(ok, params) = load_defaults(module, MODULE_MONITOR, CN_MONITOR);
 | |
|             mxb_assert(ok);
 | |
| 
 | |
|             params.set_multiple(extract_parameters(json));
 | |
| 
 | |
|             if (validate_param(config_monitor_params, mod->parameters, ¶ms)
 | |
|                 && server_relationship_to_parameter(json, ¶ms))
 | |
|             {
 | |
|                 if (runtime_create_monitor(name, module, ¶ms))
 | |
|                 {
 | |
|                     rval = MonitorManager::find_monitor(name);
 | |
|                     mxb_assert(rval);
 | |
|                     MonitorManager::start_monitor(rval);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             config_runtime_error("Unknown module: %s", module);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool runtime_create_filter_from_json(json_t* json)
 | |
| {
 | |
|     bool rval = false;
 | |
| 
 | |
|     if (validate_object_json(json, {MXS_JSON_PTR_MODULE}, {filter_to_service}))
 | |
|     {
 | |
|         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));
 | |
|         auto params = extract_parameters_from_json(json);
 | |
| 
 | |
|         rval = runtime_create_filter(name, module, ¶ms);
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| Service* runtime_create_service_from_json(json_t* json)
 | |
| {
 | |
|     Service* rval = NULL;
 | |
| 
 | |
|     if (validate_object_json(json, {MXS_JSON_PTR_ROUTER}, {service_to_filter, object_to_server}))
 | |
|     {
 | |
|         const char* name = json_string_value(mxs_json_pointer(json, MXS_JSON_PTR_ID));
 | |
|         const char* router = json_string_value(mxs_json_pointer(json, MXS_JSON_PTR_ROUTER));
 | |
|         auto params = extract_parameters_from_json(json);
 | |
| 
 | |
|         if (runtime_create_service(name, router, ¶ms))
 | |
|         {
 | |
|             rval = service_internal_find(name);
 | |
|             mxb_assert(rval);
 | |
| 
 | |
|             // Performing an alter right after creation takes care of server relationships
 | |
|             if (!runtime_alter_service_from_json(rval, json))
 | |
|             {
 | |
|                 runtime_destroy_service(rval);
 | |
|                 rval = NULL;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 // This function sets the service in the correct state
 | |
|                 serviceStart(rval);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     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_SERVERS) == NULL)
 | |
|     {
 | |
|         /** No change to relationships */
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     bool rval = false;
 | |
|     StringSet old_relations;
 | |
|     StringSet new_relations;
 | |
|     const char* object_relation = MXS_JSON_PTR_RELATIONSHIPS_SERVERS;
 | |
| 
 | |
|     if (extract_relations(old_json, old_relations, object_relation, object_relation_is_valid)
 | |
|         && extract_relations(new_json, new_relations, object_relation, object_relation_is_valid))
 | |
|     {
 | |
|         StringSet removed_relations;
 | |
|         StringSet added_relations;
 | |
| 
 | |
|         std::set_difference(old_relations.begin(),
 | |
|                             old_relations.end(),
 | |
|                             new_relations.begin(),
 | |
|                             new_relations.end(),
 | |
|                             std::inserter(removed_relations, removed_relations.begin()));
 | |
| 
 | |
|         std::set_difference(new_relations.begin(),
 | |
|                             new_relations.end(),
 | |
|                             old_relations.begin(),
 | |
|                             old_relations.end(),
 | |
|                             std::inserter(added_relations, added_relations.begin()));
 | |
| 
 | |
|         if (unlink_object_from_servers(target, removed_relations)
 | |
|             && link_object_to_servers(target, added_relations))
 | |
|         {
 | |
|             rval = true;
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         config_runtime_error("Could not find all servers that '%s' relates to", target);
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool service_to_filter_relations(Service* service, json_t* old_json, json_t* new_json)
 | |
| {
 | |
|     if (mxs_json_pointer(new_json, MXS_JSON_PTR_RELATIONSHIPS) == NULL)
 | |
|     {
 | |
|         // No relationships defined, nothing to change
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     bool rval = false;
 | |
|     StringVector old_relations;
 | |
|     StringVector new_relations;
 | |
|     const char* filter_relation = MXS_JSON_PTR_RELATIONSHIPS_FILTERS;
 | |
| 
 | |
|     if (extract_ordered_relations(old_json, old_relations, filter_relation, filter_relation_is_valid)
 | |
|         && extract_ordered_relations(new_json, new_relations, filter_relation, filter_relation_is_valid))
 | |
|     {
 | |
|         if (old_relations == new_relations || service->set_filters(new_relations))
 | |
|         {
 | |
|             // Either no change in relationships took place or we successfully
 | |
|             // updated the filter relationships
 | |
|             rval = true;
 | |
|         }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         config_runtime_error("Could not find all filters that '%s' relates to", service->name());
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool runtime_alter_monitor_from_json(Monitor* monitor, json_t* new_json)
 | |
| {
 | |
|     bool success = false;
 | |
|     std::unique_ptr<json_t> old_json(MonitorManager::monitor_to_json(monitor, ""));
 | |
|     mxb_assert(old_json.get());
 | |
|     const MXS_MODULE* mod = get_module(monitor->m_module.c_str(), MODULE_MONITOR);
 | |
| 
 | |
|     auto params = monitor->parameters();
 | |
|     params.set_multiple(extract_parameters(new_json));
 | |
| 
 | |
|     if (is_valid_resource_body(new_json)
 | |
|         && validate_param(config_monitor_params, mod->parameters, ¶ms)
 | |
|         && server_relationship_to_parameter(new_json, ¶ms))
 | |
|     {
 | |
|         success = MonitorManager::reconfigure_monitor(monitor, params);
 | |
|     }
 | |
| 
 | |
|     return success;
 | |
| }
 | |
| 
 | |
| bool runtime_alter_monitor_relationships_from_json(Monitor* monitor, json_t* json)
 | |
| {
 | |
|     bool rval = false;
 | |
|     std::unique_ptr<json_t> old_json(MonitorManager::monitor_to_json(monitor, ""));
 | |
|     mxb_assert(old_json.get());
 | |
| 
 | |
|     if (is_valid_relationship_body(json))
 | |
|     {
 | |
|         std::unique_ptr<json_t> j(json_pack("{s: {s: {s: {s: O}}}}",
 | |
|                                             "data",
 | |
|                                             "relationships",
 | |
|                                             "servers",
 | |
|                                             "data",
 | |
|                                             json_object_get(json, "data")));
 | |
| 
 | |
|         if (runtime_alter_monitor_from_json(monitor, j.get()))
 | |
|         {
 | |
|             rval = true;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool runtime_alter_service_relationships_from_json(Service* service, const char* type, json_t* json)
 | |
| {
 | |
|     bool rval = false;
 | |
|     std::unique_ptr<json_t> old_json(service_to_json(service, ""));
 | |
|     mxb_assert(old_json.get());
 | |
| 
 | |
|     if (is_valid_relationship_body(json))
 | |
|     {
 | |
|         std::unique_ptr<json_t> j(json_pack("{s: {s: {s: {s: O}}}}",
 | |
|                                             "data",
 | |
|                                             "relationships",
 | |
|                                             type,
 | |
|                                             "data",
 | |
|                                             json_object_get(json, "data")));
 | |
| 
 | |
|         if (strcmp(type, CN_SERVERS) == 0)
 | |
|         {
 | |
|             rval = object_to_server_relations(service->name(), old_json.get(), j.get());
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             mxb_assert(strcmp(type, CN_FILTERS) == 0);
 | |
|             rval = service_to_filter_relations(service, old_json.get(), j.get());
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Check if the service parameter can be altered at runtime
 | |
|  *
 | |
|  * @param key Parameter name
 | |
|  * @return True if the parameter can be altered
 | |
|  */
 | |
| static bool is_dynamic_param(const std::string& key)
 | |
| {
 | |
|     return key != CN_TYPE
 | |
|            && key != CN_ROUTER
 | |
|            && key != CN_SERVERS;
 | |
| }
 | |
| 
 | |
| bool runtime_alter_service_from_json(Service* service, json_t* new_json)
 | |
| {
 | |
|     bool rval = false;
 | |
|     std::unique_ptr<json_t> old_json(service_to_json(service, ""));
 | |
|     mxb_assert(old_json.get());
 | |
| 
 | |
|     if (is_valid_resource_body(new_json)
 | |
|         && object_to_server_relations(service->name(), old_json.get(), new_json)
 | |
|         && service_to_filter_relations(service, old_json.get(), new_json))
 | |
|     {
 | |
|         rval = true;
 | |
|         json_t* parameters = mxs_json_pointer(new_json, MXS_JSON_PTR_PARAMETERS);
 | |
|         json_t* old_parameters = mxs_json_pointer(old_json.get(), MXS_JSON_PTR_PARAMETERS);
 | |
| 
 | |
|         mxb_assert(old_parameters);
 | |
| 
 | |
|         if (parameters)
 | |
|         {
 | |
|             /** Create a set of accepted service parameters */
 | |
|             StringSet paramset;
 | |
|             for (int i = 0; config_service_params[i].name; i++)
 | |
|             {
 | |
|                 if (is_dynamic_param(config_service_params[i].name))
 | |
|                 {
 | |
|                     paramset.insert(config_service_params[i].name);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             const MXS_MODULE* mod = get_module(service->router_name(), MODULE_ROUTER);
 | |
| 
 | |
|             for (int i = 0; mod->parameters[i].name; i++)
 | |
|             {
 | |
|                 paramset.insert(mod->parameters[i].name);
 | |
|             }
 | |
| 
 | |
|             const char* key;
 | |
|             json_t* value;
 | |
| 
 | |
|             json_object_foreach(parameters, key, value)
 | |
|             {
 | |
|                 json_t* new_val = json_object_get(parameters, key);
 | |
|                 json_t* old_val = json_object_get(old_parameters, key);
 | |
| 
 | |
|                 if (old_val && new_val && mxs::json_to_string(new_val) == mxs::json_to_string(old_val))
 | |
|                 {
 | |
|                     /** No change in values */
 | |
|                 }
 | |
|                 else if (paramset.find(key) != paramset.end())
 | |
|                 {
 | |
|                     /** Parameter can be altered */
 | |
|                     if (!runtime_alter_service(service, key, mxs::json_to_string(value).c_str()))
 | |
|                     {
 | |
|                         rval = false;
 | |
|                     }
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     std::string v = mxs::json_to_string(value);
 | |
| 
 | |
|                     if (!is_dynamic_param(key))
 | |
|                     {
 | |
|                         config_runtime_error("Runtime modifications to static service "
 | |
|                                              "parameters is not supported: %s=%s",
 | |
|                                              key,
 | |
|                                              v.c_str());
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         config_runtime_error("Parameter '%s' cannot be modified at runtime", key);
 | |
|                     }
 | |
| 
 | |
|                     rval = false;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool validate_logs_json(json_t* json)
 | |
| {
 | |
|     json_t* param = mxs_json_pointer(json, MXS_JSON_PTR_PARAMETERS);
 | |
|     bool rval = false;
 | |
| 
 | |
|     if (param && json_is_object(param))
 | |
|     {
 | |
|         rval = runtime_is_bool_or_null(param, "highprecision")
 | |
|             && runtime_is_bool_or_null(param, "maxlog")
 | |
|             && runtime_is_bool_or_null(param, "syslog")
 | |
|             && runtime_is_bool_or_null(param, "log_info")
 | |
|             && runtime_is_bool_or_null(param, "log_warning")
 | |
|             && runtime_is_bool_or_null(param, "log_notice")
 | |
|             && runtime_is_bool_or_null(param, "log_debug")
 | |
|             && runtime_is_count_or_null(param, "throttling/count")
 | |
|             && runtime_is_count_or_null(param, "throttling/suppress_ms")
 | |
|             && runtime_is_count_or_null(param, "throttling/window_ms");
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool runtime_alter_logs_from_json(json_t* json)
 | |
| {
 | |
|     bool rval = false;
 | |
| 
 | |
|     if (validate_logs_json(json))
 | |
|     {
 | |
|         json_t* param = mxs_json_pointer(json, MXS_JSON_PTR_PARAMETERS);
 | |
|         json_t* value;
 | |
|         rval = true;
 | |
| 
 | |
|         if ((value = mxs_json_pointer(param, "highprecision")))
 | |
|         {
 | |
|             mxs_log_set_highprecision_enabled(json_boolean_value(value));
 | |
|         }
 | |
| 
 | |
|         if ((value = mxs_json_pointer(param, "maxlog")))
 | |
|         {
 | |
|             mxs_log_set_maxlog_enabled(json_boolean_value(value));
 | |
|         }
 | |
| 
 | |
|         if ((value = mxs_json_pointer(param, "syslog")))
 | |
|         {
 | |
|             mxs_log_set_syslog_enabled(json_boolean_value(value));
 | |
|         }
 | |
| 
 | |
|         if ((value = mxs_json_pointer(param, "log_info")))
 | |
|         {
 | |
|             mxs_log_set_priority_enabled(LOG_INFO, json_boolean_value(value));
 | |
|         }
 | |
| 
 | |
|         if ((value = mxs_json_pointer(param, "log_warning")))
 | |
|         {
 | |
|             mxs_log_set_priority_enabled(LOG_WARNING, json_boolean_value(value));
 | |
|         }
 | |
| 
 | |
|         if ((value = mxs_json_pointer(param, "log_notice")))
 | |
|         {
 | |
|             mxs_log_set_priority_enabled(LOG_NOTICE, json_boolean_value(value));
 | |
|         }
 | |
| 
 | |
|         if ((value = mxs_json_pointer(param, "log_debug")))
 | |
|         {
 | |
|             mxs_log_set_priority_enabled(LOG_DEBUG, json_boolean_value(value));
 | |
|         }
 | |
| 
 | |
|         if ((param = mxs_json_pointer(param, "throttling")) && json_is_object(param))
 | |
|         {
 | |
|             MXS_LOG_THROTTLING throttle;
 | |
|             mxs_log_get_throttling(&throttle);
 | |
| 
 | |
|             if ((value = mxs_json_pointer(param, "count")))
 | |
|             {
 | |
|                 throttle.count = json_integer_value(value);
 | |
|             }
 | |
| 
 | |
|             if ((value = mxs_json_pointer(param, "suppress_ms")))
 | |
|             {
 | |
|                 throttle.suppress_ms = json_integer_value(value);
 | |
|             }
 | |
| 
 | |
|             if ((value = mxs_json_pointer(param, "window_ms")))
 | |
|             {
 | |
|                 throttle.window_ms = json_integer_value(value);
 | |
|             }
 | |
| 
 | |
|             mxs_log_set_throttling(&throttle);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| static bool validate_listener_json(json_t* json)
 | |
| {
 | |
|     bool rval = false;
 | |
|     json_t* param;
 | |
| 
 | |
|     if (!(param = mxs_json_pointer(json, MXS_JSON_PTR_ID)))
 | |
|     {
 | |
|         config_runtime_error("Value not found: '%s'", MXS_JSON_PTR_ID);
 | |
|     }
 | |
|     else if (!json_is_string(param))
 | |
|     {
 | |
|         config_runtime_error("Value '%s' is not a string", MXS_JSON_PTR_ID);
 | |
|     }
 | |
|     else if (!(param = mxs_json_pointer(json, MXS_JSON_PTR_PARAMETERS)))
 | |
|     {
 | |
|         config_runtime_error("Value not found: '%s'", MXS_JSON_PTR_PARAMETERS);
 | |
|     }
 | |
|     else if (!json_is_object(param))
 | |
|     {
 | |
|         config_runtime_error("Value '%s' is not an object", MXS_JSON_PTR_PARAMETERS);
 | |
|     }
 | |
|     else if (runtime_is_count_or_null(param, CN_PORT)
 | |
|              && runtime_is_string_or_null(param, CN_ADDRESS)
 | |
|              && runtime_is_string_or_null(param, CN_AUTHENTICATOR)
 | |
|              && runtime_is_string_or_null(param, CN_AUTHENTICATOR_OPTIONS)
 | |
|              && (!have_ssl_json(param) || validate_ssl_json(param, OT_LISTENER)))
 | |
|     {
 | |
|         rval = true;
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool runtime_create_listener_from_json(Service* service, json_t* json)
 | |
| {
 | |
|     bool rval = false;
 | |
| 
 | |
|     if (validate_listener_json(json))
 | |
|     {
 | |
|         std::string port = json_int_to_string(mxs_json_pointer(json, MXS_JSON_PTR_PARAM_PORT));
 | |
| 
 | |
|         const char* id = get_string_or_null(json, MXS_JSON_PTR_ID);
 | |
|         const char* address = get_string_or_null(json, MXS_JSON_PTR_PARAM_ADDRESS);
 | |
|         const char* protocol = get_string_or_null(json, MXS_JSON_PTR_PARAM_PROTOCOL);
 | |
|         const char* authenticator = get_string_or_null(json, MXS_JSON_PTR_PARAM_AUTHENTICATOR);
 | |
|         const char* authenticator_options =
 | |
|             get_string_or_null(json, MXS_JSON_PTR_PARAM_AUTHENTICATOR_OPTIONS);
 | |
|         const char* ssl_key = get_string_or_null(json, MXS_JSON_PTR_PARAM_SSL_KEY);
 | |
|         const char* ssl_cert = get_string_or_null(json, MXS_JSON_PTR_PARAM_SSL_CERT);
 | |
|         const char* ssl_ca_cert = get_string_or_null(json, MXS_JSON_PTR_PARAM_SSL_CA_CERT);
 | |
|         const char* ssl_version = get_string_or_null(json, MXS_JSON_PTR_PARAM_SSL_VERSION);
 | |
|         const char* ssl_cert_verify_depth =
 | |
|             get_string_or_null(json, MXS_JSON_PTR_PARAM_SSL_CERT_VERIFY_DEPTH);
 | |
|         const char* ssl_verify_peer_certificate = get_string_or_null(json,
 | |
|                                                                      MXS_JSON_PTR_PARAM_SSL_VERIFY_PEER_CERT);
 | |
| 
 | |
|         rval = runtime_create_listener(service,
 | |
|                                        id,
 | |
|                                        address,
 | |
|                                        port.c_str(),
 | |
|                                        protocol,
 | |
|                                        authenticator,
 | |
|                                        authenticator_options,
 | |
|                                        ssl_key,
 | |
|                                        ssl_cert,
 | |
|                                        ssl_ca_cert,
 | |
|                                        ssl_version,
 | |
|                                        ssl_cert_verify_depth,
 | |
|                                        ssl_verify_peer_certificate);
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| json_t* runtime_get_json_error()
 | |
| {
 | |
|     json_t* obj = NULL;
 | |
| 
 | |
|     if (!runtime_errmsg.empty())
 | |
|     {
 | |
|         obj = mxs_json_error(runtime_errmsg);
 | |
|         runtime_errmsg.clear();
 | |
|     }
 | |
| 
 | |
|     return obj;
 | |
| }
 | |
| 
 | |
| bool validate_user_json(json_t* json)
 | |
| {
 | |
|     bool rval = false;
 | |
|     json_t* id = mxs_json_pointer(json, MXS_JSON_PTR_ID);
 | |
|     json_t* type = mxs_json_pointer(json, MXS_JSON_PTR_TYPE);
 | |
|     json_t* password = mxs_json_pointer(json, MXS_JSON_PTR_PASSWORD);
 | |
|     json_t* account = mxs_json_pointer(json, MXS_JSON_PTR_ACCOUNT);
 | |
| 
 | |
|     if (!id)
 | |
|     {
 | |
|         config_runtime_error("Request body does not define the '%s' field", MXS_JSON_PTR_ID);
 | |
|     }
 | |
|     else if (!json_is_string(id))
 | |
|     {
 | |
|         config_runtime_error("The '%s' field is not a string", MXS_JSON_PTR_ID);
 | |
|     }
 | |
|     else if (!type)
 | |
|     {
 | |
|         config_runtime_error("Request body does not define the '%s' field", MXS_JSON_PTR_TYPE);
 | |
|     }
 | |
|     else if (!json_is_string(type))
 | |
|     {
 | |
|         config_runtime_error("The '%s' field is not a string", MXS_JSON_PTR_TYPE);
 | |
|     }
 | |
|     else if (!account)
 | |
|     {
 | |
|         config_runtime_error("Request body does not define the '%s' field", MXS_JSON_PTR_ACCOUNT);
 | |
|     }
 | |
|     else if (!json_is_string(account))
 | |
|     {
 | |
|         config_runtime_error("The '%s' field is not a string", MXS_JSON_PTR_ACCOUNT);
 | |
|     }
 | |
|     else if (json_to_account_type(account) == USER_ACCOUNT_UNKNOWN)
 | |
|     {
 | |
|         config_runtime_error("The '%s' field is not a valid account value", MXS_JSON_PTR_ACCOUNT);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         if (strcmp(json_string_value(type), CN_INET) == 0)
 | |
|         {
 | |
|             if (!password)
 | |
|             {
 | |
|                 config_runtime_error("Request body does not define the '%s' field", MXS_JSON_PTR_PASSWORD);
 | |
|             }
 | |
|             else if (!json_is_string(password))
 | |
|             {
 | |
|                 config_runtime_error("The '%s' field is not a string", MXS_JSON_PTR_PASSWORD);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 rval = true;
 | |
|             }
 | |
|         }
 | |
|         else if (strcmp(json_string_value(type), CN_UNIX) == 0)
 | |
|         {
 | |
|             rval = true;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             config_runtime_error("Invalid value for field '%s': %s",
 | |
|                                  MXS_JSON_PTR_TYPE,
 | |
|                                  json_string_value(type));
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool runtime_create_user_from_json(json_t* json)
 | |
| {
 | |
|     bool rval = false;
 | |
| 
 | |
|     if (validate_user_json(json))
 | |
|     {
 | |
|         const char* user = json_string_value(mxs_json_pointer(json, MXS_JSON_PTR_ID));
 | |
|         const char* password = json_string_value(mxs_json_pointer(json, MXS_JSON_PTR_PASSWORD));
 | |
|         std::string strtype = json_string_value(mxs_json_pointer(json, MXS_JSON_PTR_TYPE));
 | |
|         user_account_type type = json_to_account_type(mxs_json_pointer(json, MXS_JSON_PTR_ACCOUNT));
 | |
|         const char* err = NULL;
 | |
| 
 | |
|         if (strtype == CN_INET && (err = admin_add_inet_user(user, password, type)) == ADMIN_SUCCESS)
 | |
|         {
 | |
|             MXS_NOTICE("Create network user '%s'", user);
 | |
|             rval = true;
 | |
|         }
 | |
|         else if (strtype == CN_UNIX && (err = admin_enable_linux_account(user, type)) == ADMIN_SUCCESS)
 | |
|         {
 | |
|             MXS_NOTICE("Enabled account '%s'", user);
 | |
|             rval = true;
 | |
|         }
 | |
|         else if (err)
 | |
|         {
 | |
|             config_runtime_error("Failed to add user '%s': %s", user, err);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool runtime_remove_user(const char* id, enum user_type type)
 | |
| {
 | |
|     bool rval = false;
 | |
|     const char* err = type == USER_TYPE_INET ?
 | |
|         admin_remove_inet_user(id) :
 | |
|         admin_disable_linux_account(id);
 | |
| 
 | |
|     if (err == ADMIN_SUCCESS)
 | |
|     {
 | |
|         MXS_NOTICE("%s '%s'",
 | |
|                    type == USER_TYPE_INET ?
 | |
|                    "Deleted network user" : "Disabled account",
 | |
|                    id);
 | |
|         rval = true;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         config_runtime_error("Failed to remove user '%s': %s", id, err);
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool runtime_alter_user(const std::string& user, const std::string& type, json_t* json)
 | |
| {
 | |
|     bool rval = false;
 | |
|     const char* password = json_string_value(mxs_json_pointer(json, MXS_JSON_PTR_PASSWORD));
 | |
| 
 | |
|     if (!password)
 | |
|     {
 | |
|         config_runtime_error("No password provided");
 | |
|     }
 | |
|     else if (type != CN_INET)
 | |
|     {
 | |
|         config_runtime_error("Users of type '%s' cannot be altered", type.c_str());
 | |
|     }
 | |
|     else if (const char* err = admin_alter_inet_user(user.c_str(), password))
 | |
|     {
 | |
|         config_runtime_error("%s", err);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         rval = true;
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool validate_maxscale_json(json_t* json)
 | |
| {
 | |
|     bool rval = false;
 | |
|     json_t* param = mxs_json_pointer(json, MXS_JSON_PTR_PARAMETERS);
 | |
| 
 | |
|     if (param)
 | |
|     {
 | |
|         rval = runtime_is_count_or_null(param, CN_AUTH_CONNECT_TIMEOUT)
 | |
|             && runtime_is_count_or_null(param, CN_AUTH_READ_TIMEOUT)
 | |
|             && runtime_is_count_or_null(param, CN_AUTH_WRITE_TIMEOUT)
 | |
|             && runtime_is_bool_or_null(param, CN_ADMIN_AUTH)
 | |
|             && runtime_is_bool_or_null(param, CN_ADMIN_LOG_AUTH_FAILURES)
 | |
|             && runtime_is_size_or_null(param, CN_QUERY_CLASSIFIER_CACHE_SIZE);
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool ignored_core_parameters(const char* key)
 | |
| {
 | |
|     static const char* params[] =
 | |
|     {
 | |
|         "libdir",
 | |
|         "datadir",
 | |
|         "process_datadir",
 | |
|         "cachedir",
 | |
|         "configdir",
 | |
|         "config_persistdir",
 | |
|         "module_configdir",
 | |
|         "piddir",
 | |
|         "logdir",
 | |
|         "langdir",
 | |
|         "execdir",
 | |
|         "connector_plugindir",
 | |
|         NULL
 | |
|     };
 | |
| 
 | |
|     for (int i = 0; params[i]; i++)
 | |
|     {
 | |
|         if (strcmp(key, params[i]) == 0)
 | |
|         {
 | |
|             return true;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| bool runtime_alter_maxscale_from_json(json_t* new_json)
 | |
| {
 | |
|     bool rval = false;
 | |
| 
 | |
|     if (validate_maxscale_json(new_json))
 | |
|     {
 | |
|         rval = true;
 | |
|         json_t* old_json = config_maxscale_to_json("");
 | |
|         mxb_assert(old_json);
 | |
| 
 | |
|         json_t* new_param = mxs_json_pointer(new_json, MXS_JSON_PTR_PARAMETERS);
 | |
|         json_t* old_param = mxs_json_pointer(old_json, MXS_JSON_PTR_PARAMETERS);
 | |
| 
 | |
|         const char* key;
 | |
|         json_t* value;
 | |
| 
 | |
|         json_object_foreach(new_param, key, value)
 | |
|         {
 | |
|             json_t* new_val = json_object_get(new_param, key);
 | |
|             json_t* old_val = json_object_get(old_param, key);
 | |
| 
 | |
|             if (old_val && new_val && mxs::json_to_string(new_val) == mxs::json_to_string(old_val))
 | |
|             {
 | |
|                 /** No change in values */
 | |
|             }
 | |
|             else if (ignored_core_parameters(key))
 | |
|             {
 | |
|                 /** We can't change these at runtime */
 | |
|                 MXS_INFO("Ignoring runtime change to '%s': Cannot be altered at runtime", key);
 | |
|             }
 | |
|             else if (!runtime_alter_maxscale(key, mxs::json_to_string(value).c_str()))
 | |
|             {
 | |
|                 rval = false;
 | |
|             }
 | |
|         }
 | |
|         json_decref(old_json);
 | |
|     }
 | |
| 
 | |
|     return rval;
 | |
| }
 | |
| 
 | |
| bool runtime_alter_qc_from_json(json_t* json)
 | |
| {
 | |
|     return qc_alter_from_json(json);
 | |
| }
 | 
