617 lines
17 KiB
C++
617 lines
17 KiB
C++
/*
|
|
* Copyright (c) 2019 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: 2026-01-04
|
|
*
|
|
* 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 <maxbase/format.hh>
|
|
#include <maxscale/json_api.hh>
|
|
#include <maxscale/paths.h>
|
|
#include <maxscale/resultset.hh>
|
|
|
|
#include "internal/config.hh"
|
|
#include "internal/monitor.hh"
|
|
#include "internal/monitormanager.hh"
|
|
#include "internal/modules.hh"
|
|
#include "internal/externcmd.hh"
|
|
|
|
using maxscale::Monitor;
|
|
using maxscale::MonitorServer;
|
|
using Guard = std::lock_guard<std::mutex>;
|
|
using std::string;
|
|
using mxb::string_printf;
|
|
|
|
namespace
|
|
{
|
|
|
|
class ThisUnit
|
|
{
|
|
public:
|
|
|
|
/**
|
|
* Call a function on every monitor in the global monitor list.
|
|
*
|
|
* @param apply The function to apply. If the function returns false, iteration is discontinued.
|
|
*/
|
|
void foreach_monitor(std::function<bool(Monitor*)> apply)
|
|
{
|
|
Guard guard(m_all_monitors_lock);
|
|
for (Monitor* monitor : m_all_monitors)
|
|
{
|
|
if (!apply(monitor))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clear the internal list and return previous contents.
|
|
*
|
|
* @return Contents before clearing
|
|
*/
|
|
std::vector<Monitor*> clear()
|
|
{
|
|
Guard guard(m_all_monitors_lock);
|
|
return std::move(m_all_monitors);
|
|
}
|
|
|
|
void insert_front(Monitor* monitor)
|
|
{
|
|
Guard guard(m_all_monitors_lock);
|
|
m_all_monitors.insert(m_all_monitors.begin(), monitor);
|
|
}
|
|
|
|
void move_to_deactivated_list(Monitor* monitor)
|
|
{
|
|
Guard guard(m_all_monitors_lock);
|
|
auto iter = std::find(m_all_monitors.begin(), m_all_monitors.end(), monitor);
|
|
mxb_assert(iter != m_all_monitors.end());
|
|
m_all_monitors.erase(iter);
|
|
m_deact_monitors.push_back(monitor);
|
|
}
|
|
|
|
private:
|
|
std::mutex m_all_monitors_lock; /**< Protects access to arrays */
|
|
std::vector<Monitor*> m_all_monitors; /**< Global list of monitors, in configuration file order */
|
|
std::vector<Monitor*> m_deact_monitors; /**< Deactivated monitors. TODO: delete monitors */
|
|
};
|
|
|
|
ThisUnit this_unit;
|
|
|
|
const char RECONFIG_FAILED[] = "Monitor reconfiguration failed when %s. Check log for more details.";
|
|
}
|
|
|
|
Monitor* MonitorManager::create_monitor(const string& name, const string& module,
|
|
MXS_CONFIG_PARAMETER* params)
|
|
{
|
|
mxb_assert(Monitor::is_admin_thread());
|
|
MXS_MONITOR_API* api = (MXS_MONITOR_API*)load_module(module.c_str(), MODULE_MONITOR);
|
|
if (!api)
|
|
{
|
|
MXS_ERROR("Unable to load library file for monitor '%s'.", name.c_str());
|
|
return NULL;
|
|
}
|
|
|
|
Monitor* mon = api->createInstance(name, module);
|
|
if (!mon)
|
|
{
|
|
MXS_ERROR("Unable to create monitor instance for '%s', using module '%s'.",
|
|
name.c_str(), module.c_str());
|
|
return NULL;
|
|
}
|
|
|
|
if (mon->configure(params))
|
|
{
|
|
this_unit.insert_front(mon);
|
|
}
|
|
else
|
|
{
|
|
delete mon;
|
|
mon = NULL;
|
|
}
|
|
return mon;
|
|
}
|
|
|
|
void MonitorManager::debug_wait_one_tick()
|
|
{
|
|
mxb_assert(Monitor::is_admin_thread());
|
|
using namespace std::chrono;
|
|
std::map<Monitor*, long> ticks;
|
|
|
|
// Get tick values for all monitors
|
|
this_unit.foreach_monitor(
|
|
[&ticks](Monitor* mon) {
|
|
ticks[mon] = mon->ticks();
|
|
return true;
|
|
});
|
|
|
|
// Wait for all running monitors to advance at least one tick.
|
|
this_unit.foreach_monitor(
|
|
[&ticks](Monitor* mon) {
|
|
if (mon->is_running())
|
|
{
|
|
auto start = steady_clock::now();
|
|
// A monitor may have been added in between the two foreach-calls (not
|
|
// if config changes are
|
|
// serialized). Check if entry exists.
|
|
if (ticks.count(mon) > 0)
|
|
{
|
|
auto tick = ticks[mon];
|
|
while (mon->ticks() == tick
|
|
&& (steady_clock::now() - start < seconds(60)))
|
|
{
|
|
std::this_thread::sleep_for(milliseconds(100));
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
|
|
void MonitorManager::destroy_all_monitors()
|
|
{
|
|
mxb_assert(Monitor::is_admin_thread());
|
|
auto monitors = this_unit.clear();
|
|
for (auto monitor : monitors)
|
|
{
|
|
mxb_assert(!monitor->is_running());
|
|
delete monitor;
|
|
}
|
|
}
|
|
|
|
void MonitorManager::start_monitor(Monitor* monitor)
|
|
{
|
|
mxb_assert(Monitor::is_admin_thread());
|
|
|
|
// Only start the monitor if it's stopped.
|
|
if (!monitor->is_running())
|
|
{
|
|
if (!monitor->start())
|
|
{
|
|
MXS_ERROR("Failed to start monitor '%s'.", monitor->name());
|
|
}
|
|
}
|
|
}
|
|
|
|
void MonitorManager::populate_services()
|
|
{
|
|
mxb_assert(Monitor::is_admin_thread());
|
|
this_unit.foreach_monitor(
|
|
[](Monitor* pMonitor) -> bool {
|
|
pMonitor->populate_services();
|
|
return true;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Start all monitors
|
|
*/
|
|
void MonitorManager::start_all_monitors()
|
|
{
|
|
mxb_assert(Monitor::is_admin_thread());
|
|
this_unit.foreach_monitor(
|
|
[](Monitor* monitor) {
|
|
MonitorManager::start_monitor(monitor);
|
|
return true;
|
|
});
|
|
}
|
|
|
|
void MonitorManager::stop_monitor(Monitor* monitor)
|
|
{
|
|
mxb_assert(Monitor::is_admin_thread());
|
|
|
|
/** Only stop the monitor if it is running */
|
|
if (monitor->is_running())
|
|
{
|
|
monitor->stop();
|
|
}
|
|
}
|
|
|
|
void MonitorManager::deactivate_monitor(Monitor* monitor)
|
|
{
|
|
mxb_assert(Monitor::is_admin_thread());
|
|
// This cannot be done with configure(), since other, module-specific config settings may depend on the
|
|
// "servers"-setting of the base monitor.
|
|
monitor->deactivate();
|
|
this_unit.move_to_deactivated_list(monitor);
|
|
}
|
|
|
|
/**
|
|
* Shutdown all running monitors
|
|
*/
|
|
void MonitorManager::stop_all_monitors()
|
|
{
|
|
mxb_assert(Monitor::is_admin_thread());
|
|
this_unit.foreach_monitor(
|
|
[](Monitor* monitor) {
|
|
MonitorManager::stop_monitor(monitor);
|
|
return true;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Show all monitors
|
|
*
|
|
* @param dcb DCB for printing output
|
|
*/
|
|
void MonitorManager::show_all_monitors(DCB* dcb)
|
|
{
|
|
mxb_assert(Monitor::is_admin_thread());
|
|
this_unit.foreach_monitor(
|
|
[dcb](Monitor* monitor) {
|
|
monitor_show(dcb, monitor);
|
|
return true;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Show a single monitor
|
|
*
|
|
* @param dcb DCB for printing output
|
|
*/
|
|
void MonitorManager::monitor_show(DCB* dcb, Monitor* monitor)
|
|
{
|
|
mxb_assert(Monitor::is_admin_thread());
|
|
monitor->show(dcb);
|
|
}
|
|
|
|
/**
|
|
* List all the monitors
|
|
*
|
|
* @param dcb DCB for printing output
|
|
*/
|
|
void MonitorManager::monitor_list(DCB* dcb)
|
|
{
|
|
mxb_assert(Monitor::is_admin_thread());
|
|
dcb_printf(dcb, "---------------------+---------------------\n");
|
|
dcb_printf(dcb, "%-20s | Status\n", "Monitor");
|
|
dcb_printf(dcb, "---------------------+---------------------\n");
|
|
|
|
this_unit.foreach_monitor(
|
|
[dcb](Monitor* ptr) {
|
|
dcb_printf(dcb, "%-20s | %s\n", ptr->name(), ptr->state_string());
|
|
return true;
|
|
});
|
|
|
|
dcb_printf(dcb, "---------------------+---------------------\n");
|
|
}
|
|
|
|
/**
|
|
* Find a monitor by name
|
|
*
|
|
* @param name The name of the monitor
|
|
* @return Pointer to the monitor or NULL
|
|
*/
|
|
Monitor* MonitorManager::find_monitor(const char* name)
|
|
{
|
|
Monitor* rval = nullptr;
|
|
this_unit.foreach_monitor(
|
|
[&rval, name](Monitor* ptr) {
|
|
if (ptr->m_name == name)
|
|
{
|
|
rval = ptr;
|
|
}
|
|
return rval == nullptr;
|
|
});
|
|
return rval;
|
|
}
|
|
|
|
/**
|
|
* Return a resultset that has the current set of monitors in it
|
|
*
|
|
* @return A Result set
|
|
*/
|
|
std::unique_ptr<ResultSet> MonitorManager::monitor_get_list()
|
|
{
|
|
mxb_assert(Monitor::is_admin_thread());
|
|
std::unique_ptr<ResultSet> set = ResultSet::create({"Monitor", "Status"});
|
|
this_unit.foreach_monitor(
|
|
[&set](Monitor* ptr) {
|
|
set->add_row({ptr->m_name, ptr->state_string()});
|
|
return true;
|
|
});
|
|
return set;
|
|
}
|
|
|
|
Monitor* MonitorManager::server_is_monitored(const SERVER* server)
|
|
{
|
|
Monitor* rval = nullptr;
|
|
auto mon_name = Monitor::get_server_monitor(server);
|
|
if (!mon_name.empty())
|
|
{
|
|
rval = find_monitor(mon_name.c_str());
|
|
mxb_assert(rval);
|
|
}
|
|
return rval;
|
|
}
|
|
|
|
bool MonitorManager::create_monitor_config(const Monitor* monitor, const char* filename)
|
|
{
|
|
mxb_assert(Monitor::is_admin_thread());
|
|
int file = open(filename, O_EXCL | O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
if (file == -1)
|
|
{
|
|
MXS_ERROR("Failed to open file '%s' when serializing monitor '%s': %d, %s",
|
|
filename,
|
|
monitor->name(),
|
|
errno,
|
|
mxs_strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
const MXS_MODULE* mod = get_module(monitor->m_module.c_str(), NULL);
|
|
mxb_assert(mod);
|
|
|
|
string config = generate_config_string(monitor->m_name, monitor->parameters(),
|
|
config_monitor_params, mod->parameters);
|
|
|
|
if (dprintf(file, "%s", config.c_str()) == -1)
|
|
{
|
|
MXS_ERROR("Could not write serialized configuration to file '%s': %d, %s",
|
|
filename, errno, mxs_strerror(errno));
|
|
}
|
|
|
|
close(file);
|
|
return true;
|
|
}
|
|
|
|
bool MonitorManager::monitor_serialize(const Monitor* monitor)
|
|
{
|
|
mxb_assert(Monitor::is_admin_thread());
|
|
bool rval = false;
|
|
char filename[PATH_MAX];
|
|
snprintf(filename,
|
|
sizeof(filename),
|
|
"%s/%s.cnf.tmp",
|
|
get_config_persistdir(),
|
|
monitor->name());
|
|
|
|
if (unlink(filename) == -1 && errno != ENOENT)
|
|
{
|
|
MXS_ERROR("Failed to remove temporary monitor configuration at '%s': %d, %s",
|
|
filename,
|
|
errno,
|
|
mxs_strerror(errno));
|
|
}
|
|
else if (create_monitor_config(monitor, filename))
|
|
{
|
|
char final_filename[PATH_MAX];
|
|
strcpy(final_filename, filename);
|
|
|
|
char* dot = strrchr(final_filename, '.');
|
|
mxb_assert(dot);
|
|
*dot = '\0';
|
|
|
|
if (rename(filename, final_filename) == 0)
|
|
{
|
|
rval = true;
|
|
}
|
|
else
|
|
{
|
|
MXS_ERROR("Failed to rename temporary monitor configuration at '%s': %d, %s",
|
|
filename,
|
|
errno,
|
|
mxs_strerror(errno));
|
|
}
|
|
}
|
|
|
|
return rval;
|
|
}
|
|
|
|
bool MonitorManager::reconfigure_monitor(mxs::Monitor* monitor, const MXS_CONFIG_PARAMETER& parameters)
|
|
{
|
|
mxb_assert(Monitor::is_admin_thread());
|
|
// Backup monitor parameters in case configure fails.
|
|
auto orig = monitor->parameters();
|
|
// Stop/start monitor if it's currently running. If monitor was stopped already, this is likely
|
|
// managed by the caller.
|
|
bool stopstart = monitor->is_running();
|
|
if (stopstart)
|
|
{
|
|
monitor->stop();
|
|
}
|
|
|
|
bool success = false;
|
|
if (monitor->configure(¶meters))
|
|
{
|
|
// Serialization must also succeed.
|
|
success = MonitorManager::monitor_serialize(monitor);
|
|
}
|
|
|
|
if (!success)
|
|
{
|
|
// Try to restore old values, it should work.
|
|
MXB_AT_DEBUG(bool check = ) monitor->configure(&orig);
|
|
mxb_assert(check);
|
|
}
|
|
|
|
if (stopstart && !monitor->start())
|
|
{
|
|
MXB_ERROR("Reconfiguration of monitor '%s' failed because monitor did not start.", monitor->name());
|
|
}
|
|
return success;
|
|
}
|
|
|
|
bool MonitorManager::alter_monitor(mxs::Monitor* monitor, const std::string& key, const std::string& value,
|
|
std::string* error_out)
|
|
{
|
|
const MXS_MODULE* mod = get_module(monitor->m_module.c_str(), MODULE_MONITOR);
|
|
if (!validate_param(config_monitor_params, mod->parameters, key, value, error_out))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
MXS_CONFIG_PARAMETER modified = monitor->parameters();
|
|
modified.set(key, value);
|
|
|
|
bool success = MonitorManager::reconfigure_monitor(monitor, modified);
|
|
if (!success)
|
|
{
|
|
*error_out = string_printf(RECONFIG_FAILED, "changing a parameter");
|
|
}
|
|
return success;
|
|
}
|
|
|
|
json_t* MonitorManager::monitor_to_json(const Monitor* monitor, const char* host)
|
|
{
|
|
string self = MXS_JSON_API_MONITORS;
|
|
self += monitor->m_name;
|
|
return mxs_json_resource(host, self.c_str(), monitor->to_json(host));
|
|
}
|
|
|
|
json_t* MonitorManager::monitor_list_to_json(const char* host)
|
|
{
|
|
json_t* rval = json_array();
|
|
this_unit.foreach_monitor(
|
|
[rval, host](Monitor* mon) {
|
|
json_t* json = mon->to_json(host);
|
|
if (json)
|
|
{
|
|
json_array_append_new(rval, json);
|
|
}
|
|
return true;
|
|
});
|
|
|
|
return mxs_json_resource(host, MXS_JSON_API_MONITORS, rval);
|
|
}
|
|
|
|
json_t* MonitorManager::monitor_relations_to_server(const SERVER* server, const char* host)
|
|
{
|
|
mxb_assert(Monitor::is_admin_thread());
|
|
json_t* rel = nullptr;
|
|
|
|
string mon_name = Monitor::get_server_monitor(server);
|
|
if (!mon_name.empty())
|
|
{
|
|
rel = mxs_json_relationship(host, MXS_JSON_API_MONITORS);
|
|
mxs_json_add_relation(rel, mon_name.c_str(), CN_MONITORS);
|
|
}
|
|
|
|
return rel;
|
|
}
|
|
|
|
bool MonitorManager::set_server_status(SERVER* srv, int bit, string* errmsg_out)
|
|
{
|
|
mxb_assert(Monitor::is_admin_thread());
|
|
bool written = false;
|
|
Monitor* mon = MonitorManager::server_is_monitored(srv);
|
|
if (mon)
|
|
{
|
|
written = mon->set_server_status(srv, bit, errmsg_out);
|
|
}
|
|
else
|
|
{
|
|
/* Set the bit directly */
|
|
srv->set_status(bit);
|
|
written = true;
|
|
}
|
|
return written;
|
|
}
|
|
|
|
bool MonitorManager::clear_server_status(SERVER* srv, int bit, string* errmsg_out)
|
|
{
|
|
mxb_assert(Monitor::is_admin_thread());
|
|
bool written = false;
|
|
Monitor* mon = MonitorManager::server_is_monitored(srv);
|
|
if (mon)
|
|
{
|
|
written = mon->clear_server_status(srv, bit, errmsg_out);
|
|
}
|
|
else
|
|
{
|
|
/* Clear bit directly */
|
|
srv->clear_status(bit);
|
|
written = true;
|
|
}
|
|
return written;
|
|
}
|
|
|
|
bool MonitorManager::add_server_to_monitor(mxs::Monitor* mon, SERVER* server, std::string* error_out)
|
|
{
|
|
mxb_assert(Monitor::is_admin_thread());
|
|
bool success = false;
|
|
string server_monitor = Monitor::get_server_monitor(server);
|
|
if (!server_monitor.empty())
|
|
{
|
|
// Error, server is already monitored.
|
|
string error = string_printf("Server '%s' is already monitored by '%s', ",
|
|
server->name(), server_monitor.c_str());
|
|
error += (server_monitor == mon->name()) ? "cannot add again to the same monitor." :
|
|
"cannot add to another monitor.";
|
|
*error_out = error;
|
|
}
|
|
else
|
|
{
|
|
// To keep monitor modifications straightforward, all changes should go through the same
|
|
// reconfigure-function. As the function accepts key-value combinations (so that they are easily
|
|
// serialized), construct the value here.
|
|
MXS_CONFIG_PARAMETER modified_params = mon->parameters();
|
|
string serverlist = modified_params.get_string(CN_SERVERS);
|
|
if (serverlist.empty())
|
|
{
|
|
// Unusual.
|
|
serverlist += server->name();
|
|
}
|
|
else
|
|
{
|
|
serverlist += string(", ") + server->name();
|
|
}
|
|
modified_params.set(CN_SERVERS, serverlist);
|
|
success = reconfigure_monitor(mon, modified_params);
|
|
if (!success)
|
|
{
|
|
*error_out = string_printf(RECONFIG_FAILED, "adding a server");
|
|
}
|
|
}
|
|
return success;
|
|
}
|
|
|
|
bool MonitorManager::remove_server_from_monitor(mxs::Monitor* mon, SERVER* server, std::string* error_out)
|
|
{
|
|
mxb_assert(Monitor::is_admin_thread());
|
|
bool success = false;
|
|
string server_monitor = Monitor::get_server_monitor(server);
|
|
if (server_monitor != mon->name())
|
|
{
|
|
// Error, server is not monitored by given monitor.
|
|
string error;
|
|
if (server_monitor.empty())
|
|
{
|
|
error = string_printf("Server '%s' is not monitored by any monitor, ", server->name());
|
|
}
|
|
else
|
|
{
|
|
error = string_printf("Server '%s' is monitored by '%s', ",
|
|
server->name(), server_monitor.c_str());
|
|
}
|
|
error += string_printf("cannot remove it from '%s'.", mon->name());
|
|
*error_out = error;
|
|
}
|
|
else
|
|
{
|
|
// Construct the new server list
|
|
auto params = mon->parameters();
|
|
auto names = config_break_list_string(params.get_string(CN_SERVERS));
|
|
names.erase(std::remove(names.begin(), names.end(), server->name()));
|
|
std::string servers = mxb::join(names, ",");
|
|
params.set(CN_SERVERS, servers);
|
|
success = MonitorManager::reconfigure_monitor(mon, params);
|
|
if (!success)
|
|
{
|
|
*error_out = string_printf(RECONFIG_FAILED, "removing a server");
|
|
}
|
|
}
|
|
|
|
return success;
|
|
}
|