Merge branch '2.3' into develop

This commit is contained in:
Esa Korhonen
2019-05-28 10:57:18 +03:00
4 changed files with 151 additions and 50 deletions

View File

@ -134,10 +134,12 @@ namespace maxscale
* @param conn Server connection * @param conn Server connection
* @param query The query * @param query The query
* @param errmsg_out Where to store an error message if query fails. Can be null. * @param errmsg_out Where to store an error message if query fails. Can be null.
* @param errno_out Error code output. Can be null.
* @return Pointer to query results, or an empty pointer on failure * @return Pointer to query results, or an empty pointer on failure
*/ */
std::unique_ptr<mxq::QueryResult> execute_query(MYSQL* conn, const std::string& query, std::unique_ptr<mxq::QueryResult> execute_query(MYSQL* conn, const std::string& query,
std::string* errmsg_out = NULL); std::string* errmsg_out = nullptr,
unsigned int* errno_out = nullptr);
} }
MXS_END_DECLS MXS_END_DECLS

View File

@ -374,7 +374,7 @@ namespace maxscale
{ {
std::unique_ptr<mxq::QueryResult> execute_query(MYSQL* conn, const std::string& query, std::unique_ptr<mxq::QueryResult> execute_query(MYSQL* conn, const std::string& query,
std::string* errmsg_out) std::string* errmsg_out, unsigned int* errno_out)
{ {
using mxq::QueryResult; using mxq::QueryResult;
std::unique_ptr<QueryResult> rval; std::unique_ptr<QueryResult> rval;
@ -383,10 +383,19 @@ std::unique_ptr<mxq::QueryResult> execute_query(MYSQL* conn, const std::string&
{ {
rval = std::unique_ptr<QueryResult>(new QueryResult(result)); rval = std::unique_ptr<QueryResult>(new QueryResult(result));
} }
else if (errmsg_out) else
{
if (errmsg_out)
{ {
*errmsg_out = mxb::string_printf("Query '%s' failed: '%s'.", query.c_str(), mysql_error(conn)); *errmsg_out = mxb::string_printf("Query '%s' failed: '%s'.", query.c_str(), mysql_error(conn));
} }
if (errno_out)
{
*errno_out = mysql_errno(conn);
}
}
return rval; return rval;
} }
} }

View File

@ -73,9 +73,10 @@ uint64_t MariaDBServer::relay_log_events(const SlaveStatus& slave_conn)
return slave_conn.gtid_io_pos.events_ahead(m_gtid_current_pos, GtidList::MISSING_DOMAIN_IGNORE); return slave_conn.gtid_io_pos.events_ahead(m_gtid_current_pos, GtidList::MISSING_DOMAIN_IGNORE);
} }
std::unique_ptr<QueryResult> MariaDBServer::execute_query(const string& query, std::string* errmsg_out) std::unique_ptr<QueryResult> MariaDBServer::execute_query(const string& query, string* errmsg_out,
unsigned int* errno_out)
{ {
return maxscale::execute_query(m_server_base->con, query, errmsg_out); return maxscale::execute_query(m_server_base->con, query, errmsg_out, errno_out);
} }
/** /**
@ -1618,18 +1619,33 @@ bool MariaDBServer::demote(GeneralOpData& general, ServerOperation& demotion)
{ {
// The server should either be the master or be a standalone being rejoined. // The server should either be the master or be a standalone being rejoined.
mxb_assert(is_master() || m_slave_status.empty()); mxb_assert(is_master() || m_slave_status.empty());
// Step 2a: Remove [Master] from this server. This prevents compatible routers (RWS)
// from routing writes to this server. Writes in flight will go through, at least until
// read_only is set.
clear_status(SERVER_MASTER);
// Step 2b: If other users with SUPER privileges are on, kick them out now since
// read_only doesn't stop them from doing writes. This does not stop them from immediately
// logging back in but it's better than nothing. This also stops super-user writes going
// through MaxScale.
if (!kick_out_super_users(general))
{
demotion_error = true;
}
// Step 2c: Enabling read-only can take time if writes are on or table locks taken.
StopWatch timer; StopWatch timer;
// Step 2a: Enabling read-only can take time if writes are on or table locks taken. if (!demotion_error)
// TODO: use max_statement_time to be safe! {
bool ro_enabled = set_read_only(ReadOnlySetting::ENABLE, general.time_remaining, error_out); bool ro_enabled = set_read_only(ReadOnlySetting::ENABLE, general.time_remaining, error_out);
general.time_remaining -= timer.lap(); general.time_remaining -= timer.lap();
if (!ro_enabled) if (!ro_enabled)
{ {
demotion_error = true; demotion_error = true;
} }
else }
{
if (m_settings.handle_event_scheduler) if (!demotion_error && m_settings.handle_event_scheduler)
{ {
// TODO: Add query replying to enable_events // TODO: Add query replying to enable_events
// Step 2b: Using BINLOG_OFF to avoid adding any gtid events, // Step 2b: Using BINLOG_OFF to avoid adding any gtid events,
@ -1643,7 +1659,7 @@ bool MariaDBServer::demote(GeneralOpData& general, ServerOperation& demotion)
} }
} }
// Step 2c: Run demotion_sql_file if no errors so far. // Step 2e: Run demotion_sql_file if no errors so far.
const string& sql_file = m_settings.demotion_sql_file; const string& sql_file = m_settings.demotion_sql_file;
if (!demotion_error && !sql_file.empty()) if (!demotion_error && !sql_file.empty())
{ {
@ -1660,7 +1676,7 @@ bool MariaDBServer::demote(GeneralOpData& general, ServerOperation& demotion)
if (!demotion_error) if (!demotion_error)
{ {
// Step 2d: FLUSH LOGS to ensure that all events have been written to binlog. // Step 2f: FLUSH LOGS to ensure that all events have been written to binlog.
string error_msg; string error_msg;
bool logs_flushed = execute_cmd_time_limit("FLUSH LOGS;", general.time_remaining, bool logs_flushed = execute_cmd_time_limit("FLUSH LOGS;", general.time_remaining,
&error_msg); &error_msg);
@ -1674,7 +1690,6 @@ bool MariaDBServer::demote(GeneralOpData& general, ServerOperation& demotion)
} }
} }
} }
}
if (!demotion_error) if (!demotion_error)
{ {
@ -2249,3 +2264,67 @@ void MariaDBServer::update_server(bool time_to_update_disk_space,
bool in_maintenance = server->is_in_maintenance(); bool in_maintenance = server->is_in_maintenance();
mon_srv->mon_err_count = (is_running || in_maintenance) ? 0 : mon_srv->mon_err_count + 1; mon_srv->mon_err_count = (is_running || in_maintenance) ? 0 : mon_srv->mon_err_count + 1;
} }
bool MariaDBServer::kick_out_super_users(GeneralOpData& op)
{
bool error = false;
Duration time_remaining = op.time_remaining;
auto error_out = op.error_out;
// Only select unique rows...
string get_ids_query = "SELECT DISTINCT * FROM ("
// select conn id and username from live connections ...
"SELECT P.id,P.user FROM information_schema.PROCESSLIST as P "
// match with user information ...
"INNER JOIN mysql.user as U ON (U.user = P.user) WHERE "
// where the user has super-privileges, is not replicating ...
"(U.Super_priv = 'Y' AND P.COMMAND != 'Binlog Dump' "
// and is not the current user.
"AND P.id != (SELECT CONNECTION_ID()))) as I;";
string error_msg;
unsigned int error_num = 0;
auto res = execute_query(get_ids_query, &error_msg, &error_num);
if (res)
{
int id_col = 0;
int user_col = 1;
while (res->next_row())
{
auto conn_id = res->get_int(id_col);
auto user = res->get_string(user_col);
string kill_query = mxb::string_printf("KILL SOFT CONNECTION %li;", conn_id);
StopWatch timer;
if (execute_cmd_time_limit(kill_query, time_remaining, &error_msg))
{
MXB_WARNING("Killed connection id %lu to '%s' from super-user '%s' to prevent writes.",
conn_id, name(), user.c_str());
}
else
{
error = true;
PRINT_MXS_JSON_ERROR(error_out, "Could not kill connection %lu from super-user '%s': %s",
conn_id, user.c_str(), error_msg.c_str());
}
time_remaining -= timer.split();
}
}
else
{
// If query failed because of insufficient rights, don't consider this an error, just print a warning.
// Perhaps the user doesn't want the monitor doing this.
if (error_num == ER_DBACCESS_DENIED_ERROR || error_num == ER_TABLEACCESS_DENIED_ERROR
|| error_num == ER_COLUMNACCESS_DENIED_ERROR)
{
MXB_WARNING("Insufficient rights to query logged in super-users for server '%s': %s Super-users "
"may perform writes during the cluster manipulation operation.",
name(), error_msg.c_str());
}
else
{
error = true;
PRINT_MXS_JSON_ERROR(error_out, "Could not query connected super-users: %s", error_msg.c_str());
}
}
return !error;
}

View File

@ -200,9 +200,11 @@ public:
* *
* @param query The query * @param query The query
* @param errmsg_out Where to store an error message if query fails. Can be null. * @param errmsg_out Where to store an error message if query fails. Can be null.
* @param errno_out Error code output. Can be null.
* @return Pointer to query results, or an empty pointer on failure * @return Pointer to query results, or an empty pointer on failure
*/ */
std::unique_ptr<mxq::QueryResult> execute_query(const std::string& query, std::string* errmsg_out = NULL); std::unique_ptr<mxq::QueryResult> execute_query(const std::string& query, std::string* errmsg_out = NULL,
unsigned int* errno_out = NULL);
/** /**
* execute_cmd_ex with query retry ON. * execute_cmd_ex with query retry ON.
@ -430,6 +432,15 @@ public:
*/ */
bool create_start_slave(GeneralOpData& op, const SlaveStatus& slave_conn); bool create_start_slave(GeneralOpData& op, const SlaveStatus& slave_conn);
/**
* Kill the connections of any super-users except for the monitor itself.
*
* @param op Operation descriptor
* @return True on success. If super-users cannot be queried because of insufficient privileges,
* return true as it means the user does not want this feature.
*/
bool kick_out_super_users(GeneralOpData& op);
/** /**
* Is binary log on? 'update_replication_settings' should be ran before this function to query the data. * Is binary log on? 'update_replication_settings' should be ran before this function to query the data.
* *