
When servers were added to monitors that were created at runtime, the server list serialization overwrote the original persisted configuration of the monitor. To solve this problem, the serialization of the server list and the monitor parameters were combined.
691 lines
19 KiB
C
691 lines
19 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: 2019-07-01
|
|
*
|
|
* 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/config_runtime.h"
|
|
|
|
#include <strings.h>
|
|
|
|
#include <maxscale/atomic.h>
|
|
#include <maxscale/paths.h>
|
|
#include <maxscale/spinlock.h>
|
|
|
|
#include "maxscale/config.h"
|
|
#include "maxscale/monitor.h"
|
|
#include "maxscale/modules.h"
|
|
#include "maxscale/service.h"
|
|
|
|
static SPINLOCK crt_lock = SPINLOCK_INIT;
|
|
|
|
bool runtime_link_server(SERVER *server, const char *target)
|
|
{
|
|
spinlock_acquire(&crt_lock);
|
|
|
|
bool rval = false;
|
|
SERVICE *service = service_find(target);
|
|
MXS_MONITOR *monitor = service ? NULL : monitor_find(target);
|
|
|
|
if (service)
|
|
{
|
|
if (serviceAddBackend(service, server))
|
|
{
|
|
service_serialize_servers(service);
|
|
rval = true;
|
|
}
|
|
}
|
|
else if (monitor)
|
|
{
|
|
if (monitorAddServer(monitor, server))
|
|
{
|
|
monitor_serialize(monitor);
|
|
rval = true;
|
|
}
|
|
}
|
|
|
|
if (rval)
|
|
{
|
|
const char *type = service ? "service" : "monitor";
|
|
MXS_NOTICE("Added server '%s' to %s '%s'", server->unique_name, type, target);
|
|
}
|
|
|
|
spinlock_release(&crt_lock);
|
|
return rval;
|
|
}
|
|
|
|
bool runtime_unlink_server(SERVER *server, const char *target)
|
|
{
|
|
spinlock_acquire(&crt_lock);
|
|
|
|
bool rval = false;
|
|
SERVICE *service = service_find(target);
|
|
MXS_MONITOR *monitor = service ? NULL : monitor_find(target);
|
|
|
|
if (service || monitor)
|
|
{
|
|
rval = true;
|
|
|
|
if (service)
|
|
{
|
|
serviceRemoveBackend(service, server);
|
|
service_serialize_servers(service);
|
|
}
|
|
else if (monitor)
|
|
{
|
|
monitorRemoveServer(monitor, server);
|
|
monitor_serialize(monitor);
|
|
}
|
|
|
|
const char *type = service ? "service" : "monitor";
|
|
MXS_NOTICE("Removed server '%s' from %s '%s'", server->unique_name, type, target);
|
|
}
|
|
|
|
spinlock_release(&crt_lock);
|
|
return rval;
|
|
}
|
|
|
|
|
|
bool runtime_create_server(const char *name, const char *address, const char *port,
|
|
const char *protocol, const char *authenticator,
|
|
const char *authenticator_options)
|
|
{
|
|
spinlock_acquire(&crt_lock);
|
|
bool rval = false;
|
|
|
|
if (server_find_by_unique_name(name) == NULL)
|
|
{
|
|
// TODO: Get default values from the protocol module
|
|
if (port == NULL)
|
|
{
|
|
port = "3306";
|
|
}
|
|
if (protocol == NULL)
|
|
{
|
|
protocol = "MySQLBackend";
|
|
}
|
|
if (authenticator == NULL && (authenticator = get_default_authenticator(protocol)) == NULL)
|
|
{
|
|
MXS_ERROR("No authenticator defined for server '%s' and no default "
|
|
"authenticator for protocol '%s'.", name, protocol);
|
|
spinlock_release(&crt_lock);
|
|
return false;
|
|
}
|
|
|
|
/** First check if this service has been created before */
|
|
SERVER *server = server_find_destroyed(name, protocol, authenticator,
|
|
authenticator_options);
|
|
|
|
if (server)
|
|
{
|
|
/** Found old server, replace network details with new ones and
|
|
* reactivate it */
|
|
snprintf(server->name, sizeof(server->name), "%s", address);
|
|
server->port = atoi(port);
|
|
server->is_active = true;
|
|
rval = true;
|
|
}
|
|
else
|
|
{
|
|
/**
|
|
* server_alloc will add the server to the global list of
|
|
* servers so we don't need to manually add it.
|
|
*/
|
|
server = server_alloc(name, address, atoi(port), protocol,
|
|
authenticator, authenticator_options);
|
|
}
|
|
|
|
if (server && server_serialize(server))
|
|
{
|
|
/** Mark that the server was created after startup */
|
|
server->created_online = true;
|
|
|
|
MXS_NOTICE("Created server '%s' at %s:%u", server->unique_name,
|
|
server->name, server->port);
|
|
rval = true;
|
|
}
|
|
}
|
|
|
|
spinlock_release(&crt_lock);
|
|
return rval;
|
|
}
|
|
|
|
bool runtime_destroy_server(SERVER *server)
|
|
{
|
|
spinlock_acquire(&crt_lock);
|
|
bool rval = false;
|
|
|
|
if (service_server_in_use(server) || monitor_server_in_use(server))
|
|
{
|
|
MXS_ERROR("Cannot destroy server '%s' as it is used by at least one "
|
|
"service or monitor", server->unique_name);
|
|
}
|
|
else
|
|
{
|
|
char filename[PATH_MAX];
|
|
snprintf(filename, sizeof(filename), "%s/%s.cnf", get_config_persistdir(),
|
|
server->unique_name);
|
|
|
|
if (unlink(filename) == -1)
|
|
{
|
|
if (errno != ENOENT)
|
|
{
|
|
char err[MXS_STRERROR_BUFLEN];
|
|
MXS_ERROR("Failed to remove persisted server configuration '%s': %d, %s",
|
|
filename, errno, strerror_r(errno, err, sizeof(err)));
|
|
}
|
|
else
|
|
{
|
|
rval = true;
|
|
MXS_WARNING("Server '%s' was not created at runtime. Remove the "
|
|
"server manually from the correct configuration file.",
|
|
server->unique_name);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rval = true;
|
|
}
|
|
|
|
if (rval)
|
|
{
|
|
MXS_NOTICE("Destroyed server '%s' at %s:%u", server->unique_name,
|
|
server->name, server->port);
|
|
server->is_active = false;
|
|
}
|
|
}
|
|
|
|
spinlock_release(&crt_lock);
|
|
return rval;
|
|
}
|
|
|
|
static SSL_LISTENER* create_ssl(const char *name, const char *key, const char *cert,
|
|
const char *ca, const char *version, const char *depth)
|
|
{
|
|
SSL_LISTENER *rval = NULL;
|
|
CONFIG_CONTEXT *obj = config_context_create(name);
|
|
|
|
if (obj)
|
|
{
|
|
if (config_add_param(obj, "ssl", "required") &&
|
|
config_add_param(obj, "ssl_key", key) &&
|
|
config_add_param(obj, "ssl_cert", cert) &&
|
|
config_add_param(obj, "ssl_ca_cert", ca) &&
|
|
(!version || config_add_param(obj, "ssl_version", version)) &&
|
|
(!depth || config_add_param(obj, "ssl_cert_verify_depth", depth)))
|
|
{
|
|
int err = 0;
|
|
SSL_LISTENER *ssl = make_ssl_structure(obj, true, &err);
|
|
|
|
if (err == 0 && ssl && listener_init_SSL(ssl) == 0)
|
|
{
|
|
rval = ssl;
|
|
}
|
|
}
|
|
|
|
config_context_free(obj);
|
|
}
|
|
|
|
return rval;
|
|
}
|
|
|
|
bool runtime_enable_server_ssl(SERVER *server, const char *key, const char *cert,
|
|
const char *ca, const char *version, const char *depth)
|
|
{
|
|
bool rval = false;
|
|
|
|
if (key && cert && ca)
|
|
{
|
|
spinlock_acquire(&crt_lock);
|
|
SSL_LISTENER *ssl = create_ssl(server->unique_name, key, cert, ca, version, depth);
|
|
|
|
if (ssl)
|
|
{
|
|
/** TODO: Properly discard old SSL configurations.This could cause the
|
|
* loss of a pointer if two update operations are done at the same time.*/
|
|
ssl->next = server->server_ssl;
|
|
|
|
/** Sync to prevent reads on partially initialized server_ssl */
|
|
atomic_synchronize();
|
|
server->server_ssl = ssl;
|
|
|
|
if (server_serialize(server))
|
|
{
|
|
MXS_NOTICE("Enabled SSL for server '%s'", server->unique_name);
|
|
rval = true;
|
|
}
|
|
}
|
|
spinlock_release(&crt_lock);
|
|
}
|
|
|
|
return rval;
|
|
}
|
|
|
|
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, char *key, char *value)
|
|
{
|
|
spinlock_acquire(&crt_lock);
|
|
bool valid = true;
|
|
|
|
if (strcmp(key, "address") == 0)
|
|
{
|
|
server_update_address(server, value);
|
|
}
|
|
else if (strcmp(key, "port") == 0)
|
|
{
|
|
server_update_port(server, atoi(value));
|
|
}
|
|
else if (strcmp(key, "monuser") == 0 || strcmp(key, "monitoruser") == 0)
|
|
{
|
|
if (strcmp(key, "monuser") == 0)
|
|
{
|
|
MXS_WARNING("Use of `monuser` is deprecated, use `monitoruser` instead.");
|
|
}
|
|
|
|
server_update_credentials(server, value, server->monpw);
|
|
}
|
|
else if (strcmp(key, "monpw") == 0 || strcmp(key, "monitorpw") == 0)
|
|
{
|
|
if (strcmp(key, "monpw") == 0)
|
|
{
|
|
MXS_WARNING("Use of `monpw` is deprecated, use `monitorpw` instead.");
|
|
}
|
|
|
|
server_update_credentials(server, server->monuser, value);
|
|
}
|
|
else if (strcmp(key, "persistpoolmax") == 0)
|
|
{
|
|
if (is_valid_integer(value))
|
|
{
|
|
server->persistpoolmax = atoi(value);
|
|
}
|
|
else
|
|
{
|
|
valid = false;
|
|
}
|
|
}
|
|
else if (strcmp(key, "persistmaxtime") == 0 && is_valid_integer(value))
|
|
{
|
|
if (is_valid_integer(value))
|
|
{
|
|
server->persistmaxtime = atoi(value);
|
|
}
|
|
else
|
|
{
|
|
valid = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!server_remove_parameter(server, key) && !value[0])
|
|
{
|
|
valid = false;
|
|
}
|
|
else if (value[0])
|
|
{
|
|
server_add_parameter(server, key, value);
|
|
|
|
/**
|
|
* It's likely that this parameter is used as a weighting parameter.
|
|
* We need to update the weights of services that use this.
|
|
*/
|
|
service_update_weights();
|
|
}
|
|
}
|
|
|
|
if (valid)
|
|
{
|
|
if (server->created_online)
|
|
{
|
|
server_serialize(server);
|
|
}
|
|
MXS_NOTICE("Updated server '%s': %s=%s", server->unique_name, key, value);
|
|
}
|
|
|
|
spinlock_release(&crt_lock);
|
|
return valid;
|
|
}
|
|
|
|
/**
|
|
* @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 long get_positive_int(const char *value)
|
|
{
|
|
char *endptr;
|
|
long ival = strtol(value, &endptr, 10);
|
|
|
|
if (*endptr == '\0' && ival > 0)
|
|
{
|
|
return ival;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Add default parameters to a monitor
|
|
*
|
|
* @param monitor Monitor to modify
|
|
*/
|
|
static void add_monitor_defaults(MXS_MONITOR *monitor)
|
|
{
|
|
/** Inject the default module parameters in case we only deleted
|
|
* a parameter */
|
|
CONFIG_CONTEXT ctx = {.object = ""};
|
|
const MXS_MODULE *mod = get_module(monitor->module_name, MODULE_MONITOR);
|
|
|
|
if (mod)
|
|
{
|
|
config_add_defaults(&ctx, mod->parameters);
|
|
monitorAddParameters(monitor, ctx.parameters);
|
|
config_parameter_free(ctx.parameters);
|
|
}
|
|
else
|
|
{
|
|
MXS_ERROR("Failed to load module '%s'. See previous error messages for more details.",
|
|
monitor->module_name);
|
|
}
|
|
}
|
|
|
|
bool runtime_alter_monitor(MXS_MONITOR *monitor, char *key, char *value)
|
|
{
|
|
spinlock_acquire(&crt_lock);
|
|
bool valid = false;
|
|
|
|
if (strcmp(key, "user") == 0)
|
|
{
|
|
valid = true;
|
|
monitorAddUser(monitor, value, monitor->password);
|
|
}
|
|
else if (strcmp(key, "password") == 0)
|
|
{
|
|
valid = true;
|
|
monitorAddUser(monitor, monitor->user, value);
|
|
}
|
|
else if (strcmp(key, "monitor_interval") == 0)
|
|
{
|
|
long ival = get_positive_int(value);
|
|
if (ival)
|
|
{
|
|
valid = true;
|
|
monitorSetInterval(monitor, ival);
|
|
}
|
|
}
|
|
else if (strcmp(key, "backend_connect_timeout") == 0)
|
|
{
|
|
long ival = get_positive_int(value);
|
|
if (ival)
|
|
{
|
|
valid = true;
|
|
monitorSetNetworkTimeout(monitor, MONITOR_CONNECT_TIMEOUT, ival);
|
|
}
|
|
}
|
|
else if (strcmp(key, "backend_write_timeout") == 0)
|
|
{
|
|
long ival = get_positive_int(value);
|
|
if (ival)
|
|
{
|
|
valid = true;
|
|
monitorSetNetworkTimeout(monitor, MONITOR_WRITE_TIMEOUT, ival);
|
|
}
|
|
}
|
|
else if (strcmp(key, "backend_read_timeout") == 0)
|
|
{
|
|
long ival = get_positive_int(value);
|
|
if (ival)
|
|
{
|
|
valid = true;
|
|
monitorSetNetworkTimeout(monitor, MONITOR_READ_TIMEOUT, ival);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/** We're modifying module specific parameters and we need to stop the monitor */
|
|
monitorStop(monitor);
|
|
|
|
if (monitorRemoveParameter(monitor, key) || value[0])
|
|
{
|
|
/** Either we're removing an existing parameter or adding a new one */
|
|
valid = true;
|
|
|
|
if (value[0])
|
|
{
|
|
MXS_CONFIG_PARAMETER p = {.name = key, .value = value};
|
|
monitorAddParameters(monitor, &p);
|
|
}
|
|
|
|
add_monitor_defaults(monitor);
|
|
}
|
|
|
|
monitorStart(monitor, monitor->parameters);
|
|
}
|
|
|
|
if (valid)
|
|
{
|
|
if (monitor->created_online)
|
|
{
|
|
monitor_serialize(monitor);
|
|
}
|
|
|
|
MXS_NOTICE("Updated monitor '%s': %s=%s", monitor->name, key, value);
|
|
}
|
|
|
|
spinlock_release(&crt_lock);
|
|
return valid;
|
|
}
|
|
|
|
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)
|
|
{
|
|
|
|
if (addr == NULL || strcasecmp(addr, "default") == 0)
|
|
{
|
|
addr = "::";
|
|
}
|
|
if (port == NULL || strcasecmp(port, "default") == 0)
|
|
{
|
|
port = "3306";
|
|
}
|
|
if (proto == NULL || strcasecmp(proto, "default") == 0)
|
|
{
|
|
proto = "MySQLClient";
|
|
}
|
|
|
|
if (auth && strcasecmp(auth, "default") == 0)
|
|
{
|
|
/** Set auth to NULL so the protocol default authenticator is used */
|
|
auth = NULL;
|
|
}
|
|
|
|
if (auth_opt && strcasecmp(auth_opt, "default") == 0)
|
|
{
|
|
/** Don't pass options to the authenticator */
|
|
auth_opt = NULL;
|
|
}
|
|
|
|
unsigned short u_port = atoi(port);
|
|
|
|
spinlock_acquire(&crt_lock);
|
|
|
|
SSL_LISTENER *ssl = NULL;
|
|
bool rval = false;
|
|
|
|
if (!serviceHasListener(service, proto, addr, u_port))
|
|
{
|
|
rval = true;
|
|
|
|
if (ssl_key && ssl_cert && ssl_ca)
|
|
{
|
|
ssl = create_ssl(name, ssl_key, ssl_cert, ssl_ca, ssl_version, ssl_depth);
|
|
|
|
if (ssl == NULL)
|
|
{
|
|
MXS_ERROR("SSL initialization for listener '%s' failed.", name);
|
|
rval = false;
|
|
}
|
|
}
|
|
|
|
if (rval)
|
|
{
|
|
const char *print_addr = addr ? addr : "::";
|
|
SERV_LISTENER *listener = serviceCreateListener(service, name, proto, addr,
|
|
u_port, auth, auth_opt, ssl);
|
|
|
|
if (listener && listener_serialize(listener) && serviceLaunchListener(service, listener))
|
|
{
|
|
MXS_NOTICE("Created %slistener '%s' at %s:%s for service '%s'",
|
|
ssl ? "TLS encrypted " : "",
|
|
name, print_addr, port, service->name);
|
|
}
|
|
else
|
|
{
|
|
MXS_ERROR("Failed to start listener '%s' at %s:%s.", name, print_addr, port);
|
|
rval = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
spinlock_release(&crt_lock);
|
|
return rval;
|
|
}
|
|
|
|
bool runtime_destroy_listener(SERVICE *service, const char *name)
|
|
{
|
|
bool rval = false;
|
|
char filename[PATH_MAX];
|
|
snprintf(filename, sizeof(filename), "%s/%s.cnf", get_config_persistdir(), name);
|
|
|
|
spinlock_acquire(&crt_lock);
|
|
|
|
if (unlink(filename) == -1)
|
|
{
|
|
if (errno != ENOENT)
|
|
{
|
|
char err[MXS_STRERROR_BUFLEN];
|
|
MXS_ERROR("Failed to remove persisted listener configuration '%s': %d, %s",
|
|
filename, errno, strerror_r(errno, err, sizeof(err)));
|
|
}
|
|
else
|
|
{
|
|
rval = false;
|
|
MXS_WARNING("Listener '%s' was not created at runtime. Remove the "
|
|
"listener manually from the correct configuration file.",
|
|
name);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rval = true;
|
|
}
|
|
|
|
if (rval)
|
|
{
|
|
rval = serviceStopListener(service, name);
|
|
|
|
if (rval)
|
|
{
|
|
MXS_NOTICE("Destroyed listener '%s' for service '%s'. The listener "
|
|
"will be removed after the next restart of MaxScale.",
|
|
name, service->name);
|
|
}
|
|
else
|
|
{
|
|
MXS_ERROR("Failed to destroy listener '%s' for service '%s'", name, service->name);
|
|
}
|
|
}
|
|
|
|
spinlock_release(&crt_lock);
|
|
return rval;
|
|
}
|
|
|
|
bool runtime_create_monitor(const char *name, const char *module)
|
|
{
|
|
spinlock_acquire(&crt_lock);
|
|
bool rval = false;
|
|
|
|
if (monitor_find(name) == NULL)
|
|
{
|
|
MXS_MONITOR *monitor = monitor_alloc((char*)name, (char*)module);
|
|
|
|
if (monitor)
|
|
{
|
|
/** Mark that this monitor was created after MaxScale was started */
|
|
monitor->created_online = true;
|
|
add_monitor_defaults(monitor);
|
|
|
|
if (monitor_serialize(monitor))
|
|
{
|
|
MXS_NOTICE("Created monitor '%s'", name);
|
|
rval = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
spinlock_release(&crt_lock);
|
|
return rval;
|
|
}
|
|
|
|
bool runtime_destroy_monitor(MXS_MONITOR *monitor)
|
|
{
|
|
bool rval = false;
|
|
char filename[PATH_MAX];
|
|
snprintf(filename, sizeof(filename), "%s/%s.cnf", get_config_persistdir(), monitor->name);
|
|
|
|
spinlock_acquire(&crt_lock);
|
|
|
|
if (unlink(filename) == -1)
|
|
{
|
|
if (errno != ENOENT)
|
|
{
|
|
char err[MXS_STRERROR_BUFLEN];
|
|
MXS_ERROR("Failed to remove persisted monitor configuration '%s': %d, %s",
|
|
filename, errno, strerror_r(errno, err, sizeof(err)));
|
|
}
|
|
else
|
|
{
|
|
rval = false;
|
|
MXS_WARNING("Monitor '%s' was not created at runtime. Remove the "
|
|
"monitor manually from the correct configuration file.",
|
|
monitor->name);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rval = true;
|
|
}
|
|
|
|
if (rval)
|
|
{
|
|
monitorStop(monitor);
|
|
|
|
while (monitor->databases)
|
|
{
|
|
monitorRemoveServer(monitor, monitor->databases->server);
|
|
}
|
|
MXS_NOTICE("Destroyed monitor '%s'. The monitor will be removed "
|
|
"after the next restart of MaxScale.", monitor->name);
|
|
}
|
|
|
|
spinlock_release(&crt_lock);
|
|
return rval;
|
|
}
|