polardbxengine/storage/ndb/plugin/ha_ndbcluster_connection.cc

556 lines
18 KiB
C++

/*
Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License, version 2.0,
as published by the Free Software Foundation.
This program is also distributed with certain software (including
but not limited to OpenSSL) that is licensed under separate terms,
as designated in a particular file or component or in included license
documentation. The authors of MySQL hereby grant you an additional
permission to link the program and your derivative works with the
separately licensed software that they have included with MySQL.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License, version 2.0, for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "storage/ndb/plugin/ha_ndbcluster_connection.h"
#include <mysql/psi/mysql_thread.h>
#include "my_dbug.h"
#include "mysql/plugin.h"
#include "mysqld_error.h"
#include "sql/auth/auth_acls.h"
#include "sql/mysqld.h" // server_id, connection_events_loop_aborted
#include "sql/sql_class.h"
#include "sql/sql_lex.h"
#include "storage/ndb/include/kernel/ndb_limits.h"
#include "storage/ndb/include/ndbapi/NdbApi.hpp"
#include "storage/ndb/include/portlib/NdbTick.h"
#include "storage/ndb/include/util/BaseString.hpp"
#include "storage/ndb/include/util/Vector.hpp"
#ifndef _WIN32
#include <netdb.h> // getservbyname
#endif
#include "sql/table.h"
#include "storage/ndb/plugin/ndb_log.h"
#include "storage/ndb/plugin/ndb_sleep.h"
Ndb *g_ndb = NULL;
Ndb_cluster_connection *g_ndb_cluster_connection = NULL;
static Ndb_cluster_connection **g_pool = NULL;
static uint g_pool_alloc = 0;
static uint g_pool_pos = 0;
static mysql_mutex_t g_pool_mutex;
/**
@brief Parse the --ndb-cluster-connection-pool-nodeids=nodeid[,nodeidN]
comma separated list of nodeids to use for the pool
@param opt_str string containing list of nodeids to parse.
@param pool_size size used for the connection pool
@param force_nodeid nodeid requested with --ndb-nodeid
@param nodeids the parsed list of nodeids
@return true or false when option parsing failed. Error message
describing the problem has been printed to error log.
*/
static bool parse_pool_nodeids(const char *opt_str, uint pool_size,
uint force_nodeid, Vector<uint> &nodeids) {
if (!opt_str) {
// The option was not specified.
return true;
}
BaseString tmp(opt_str);
Vector<BaseString> list(pool_size);
tmp.split(list, ",");
for (unsigned i = 0; i < list.size(); i++) {
list[i].trim();
// Don't allow empty string
if (list[i].empty()) {
ndb_log_error(
"Found empty nodeid specified in "
"--ndb-cluster-connection-pool-nodeids='%s'.",
opt_str);
return false;
}
// Convert string to number
uint nodeid = 0;
if (sscanf(list[i].c_str(), "%u", &nodeid) != 1) {
ndb_log_error(
"Could not parse '%s' in "
"--ndb-cluster-connection-pool-nodeids='%s'.",
list[i].c_str(), opt_str);
return false;
}
// Check that number is a valid nodeid
if (nodeid <= 0 || nodeid > MAX_NODES_ID) {
ndb_log_error(
"Invalid nodeid %d in "
"--ndb-cluster-connection-pool-nodeids='%s'.",
nodeid, opt_str);
return false;
}
// Check that nodeid is unique(not already in the list)
for (unsigned j = 0; j < nodeids.size(); j++) {
if (nodeid == nodeids[j]) {
ndb_log_error(
"Found duplicate nodeid %d in "
"--ndb-cluster-connection-pool-nodeids='%s'.",
nodeid, opt_str);
return false;
}
}
nodeids.push_back(nodeid);
}
// Check that size of nodeids match the pool size
if (nodeids.size() != pool_size) {
ndb_log_error(
"The size of the cluster connection pool must be "
"equal to the number of nodeids in "
"--ndb-cluster-connection-pool-nodeids='%s'.",
opt_str);
return false;
}
// Check that --ndb-nodeid(if given) is first in the list
if (force_nodeid != 0 && force_nodeid != nodeids[0]) {
ndb_log_error(
"The nodeid specified by --ndb-nodeid must be equal "
"to the first nodeid in "
"--ndb-cluster-connection-pool-nodeids='%s'.",
opt_str);
return false;
}
return true;
}
/* Get the port number, hostname, and socket path for processinfo.
opt_disable_networking, mysqld_port, my_bind_addr_str, report_port,
report_host, and mysqld_unix_port are all server global variables.
*/
extern uint report_port;
extern char *report_host;
extern char *my_bind_addr_str;
static int get_processinfo_port() {
int port = 0;
if (!opt_disable_networking) {
port = report_port ? report_port : mysqld_port;
DBUG_ASSERT(port);
}
return port;
}
static const char *get_processinfo_host() {
const char *host = report_host;
if (!host) {
host = my_bind_addr_str;
if (!(strcmp(host, "*") && // If bind_address matches any of
strcmp(host, "0.0.0.0") && // these strings, let ProcessInfo
strcmp(host, "::"))) // use the NDB transporter address.
{
host = nullptr;
}
}
return host;
}
static const char *get_processinfo_path() { return mysqld_unix_port; }
/*
Global flag in ndbapi to specify if api should wait to connect
until dict cache is clean.
Set to 1 below to not wait, as ndb handler makes sure that no
old ndb objects are used.
*/
extern int global_flag_skip_waiting_for_clean_cache;
int ndbcluster_connect(int (*connect_callback)(void),
ulong wait_connected, // Timeout in seconds
uint connection_pool_size,
const char *connection_pool_nodeids_str,
bool optimized_node_select, const char *connect_string,
uint force_nodeid, uint recv_thread_activation_threshold,
uint data_node_neighbour) {
const char mysqld_name[] = "mysqld";
int res;
DBUG_TRACE;
DBUG_PRINT("enter", ("connect_string: %s, force_nodeid: %d", connect_string,
force_nodeid));
/* For Service URI in ndbinfo */
const int processinfo_port = get_processinfo_port();
const char *processinfo_host = get_processinfo_host();
const char *processinfo_path = processinfo_port ? "" : get_processinfo_path();
char server_id_string[64];
if (server_id > 0)
snprintf(server_id_string, sizeof(server_id_string), "?server-id=%lu",
server_id);
else
server_id_string[0] = '\0';
// Parse the --ndb-cluster-connection-pool-nodeids=nodeid[,nodeidN]
// comma separated list of nodeids to use for the pool
Vector<uint> nodeids;
if (!parse_pool_nodeids(connection_pool_nodeids_str, connection_pool_size,
force_nodeid, nodeids)) {
// Error message already printed
return -1;
}
// Find specified nodeid for first connection and let it override
// force_nodeid(if both has been specified they are equal).
if (nodeids.size()) {
assert(force_nodeid == 0 || force_nodeid == nodeids[0]);
force_nodeid = nodeids[0];
ndb_log_info("using nodeid %u", force_nodeid);
}
global_flag_skip_waiting_for_clean_cache = 1;
g_ndb_cluster_connection =
new (std::nothrow) Ndb_cluster_connection(connect_string, force_nodeid);
if (g_ndb_cluster_connection == nullptr) {
ndb_log_error("failed to allocate global ndb cluster connection");
DBUG_PRINT("error", ("Ndb_cluster_connection(%s)", connect_string));
return -1;
}
{
char buf[128];
snprintf(buf, sizeof(buf), "%s --server-id=%lu", mysqld_name, server_id);
g_ndb_cluster_connection->set_name(buf);
snprintf(buf, sizeof(buf), "%s%s", processinfo_path, server_id_string);
g_ndb_cluster_connection->set_service_uri("mysql", processinfo_host,
processinfo_port, buf);
}
g_ndb_cluster_connection->set_optimized_node_selection(optimized_node_select);
g_ndb_cluster_connection->set_recv_thread_activation_threshold(
recv_thread_activation_threshold);
g_ndb_cluster_connection->set_data_node_neighbour(data_node_neighbour);
// Create a Ndb object to open the connection to NDB
g_ndb = new (std::nothrow) Ndb(g_ndb_cluster_connection, "sys");
if (g_ndb == nullptr) {
ndb_log_error("failed to allocate global ndb object");
DBUG_PRINT("error", ("failed to create global ndb object"));
return -1;
}
if (g_ndb->init() != 0) {
DBUG_PRINT("error", ("%d message: %s", g_ndb->getNdbError().code,
g_ndb->getNdbError().message));
return -1;
}
/* Connect to management server */
const NDB_TICKS start = NdbTick_getCurrentTicks();
while ((res = g_ndb_cluster_connection->connect(0, 0, 0)) == 1) {
const NDB_TICKS now = NdbTick_getCurrentTicks();
if (NdbTick_Elapsed(start, now).seconds() > wait_connected) break;
ndb_retry_sleep(100);
if (connection_events_loop_aborted()) return -1;
}
{
g_pool_alloc = connection_pool_size;
g_pool = (Ndb_cluster_connection **)my_malloc(
PSI_INSTRUMENT_ME, g_pool_alloc * sizeof(Ndb_cluster_connection *),
MYF(MY_WME | MY_ZEROFILL));
mysql_mutex_init(PSI_INSTRUMENT_ME, &g_pool_mutex, MY_MUTEX_INIT_FAST);
g_pool[0] = g_ndb_cluster_connection;
for (uint i = 1; i < g_pool_alloc; i++) {
// Find specified nodeid for this connection or use default zero
uint nodeid = 0;
if (i < nodeids.size()) {
nodeid = nodeids[i];
ndb_log_info("connection[%u], using nodeid %u", i, nodeid);
}
g_pool[i] = new (std::nothrow) Ndb_cluster_connection(
connect_string, g_ndb_cluster_connection, nodeid);
if (g_pool[i] == nullptr) {
ndb_log_error("connection[%u], failed to allocate connect object", i);
DBUG_PRINT("error",
("Ndb_cluster_connection[%u](%s)", i, connect_string));
return -1;
}
{
char buf[128];
snprintf(buf, sizeof(buf), "%s --server-id=%lu (connection %u)",
mysqld_name, server_id, i + 1);
g_pool[i]->set_name(buf);
const char *uri_sep = server_id ? ";" : "?";
snprintf(buf, sizeof(buf), "%s%s%sconnection=%u", processinfo_path,
server_id_string, uri_sep, i + 1);
g_pool[i]->set_service_uri("mysql", processinfo_host, processinfo_port,
buf);
}
g_pool[i]->set_optimized_node_selection(optimized_node_select);
g_pool[i]->set_recv_thread_activation_threshold(
recv_thread_activation_threshold);
g_pool[i]->set_data_node_neighbour(data_node_neighbour);
}
}
if (res == 0) {
connect_callback();
for (uint i = 0; i < g_pool_alloc; i++) {
int node_id = g_pool[i]->node_id();
if (node_id == 0) {
// not connected to mgmd yet, try again
g_pool[i]->connect(0, 0, 0);
if (g_pool[i]->node_id() == 0) {
ndb_log_info("connection[%u], starting connect thread", i);
g_pool[i]->start_connect_thread();
continue;
}
node_id = g_pool[i]->node_id();
}
DBUG_PRINT("info", ("NDBCLUSTER storage engine (%u) at %s on port %d", i,
g_pool[i]->get_connected_host(),
g_pool[i]->get_connected_port()));
Uint64 waited;
do {
res = g_pool[i]->wait_until_ready(1, 1);
const NDB_TICKS now = NdbTick_getCurrentTicks();
waited = NdbTick_Elapsed(start, now).seconds();
} while (res != 0 && waited < wait_connected);
const char *msg = 0;
if (res == 0) {
msg = "all storage nodes connected";
} else if (res > 0) {
msg = "some storage nodes connected";
} else if (res < 0) {
msg = "no storage nodes connected (timed out)";
}
ndb_log_info("connection[%u], NodeID: %d, %s", i, node_id, msg);
}
} else if (res == 1) {
for (uint i = 0; i < g_pool_alloc; i++) {
if (g_pool[i]->start_connect_thread(i == 0 ? connect_callback : NULL)) {
ndb_log_error("connection[%u], failed to start connect thread", i);
DBUG_PRINT("error",
("g_ndb_cluster_connection->start_connect_thread()"));
return -1;
}
}
#ifndef DBUG_OFF
{
char buf[1024];
DBUG_PRINT("info", ("NDBCLUSTER storage engine not started, "
"will connect using %s",
g_ndb_cluster_connection->get_connectstring(
buf, sizeof(buf))));
}
#endif
} else {
DBUG_ASSERT(res == -1);
DBUG_PRINT("error", ("permanent error"));
ndb_log_error("error (%u) %s", g_ndb_cluster_connection->get_latest_error(),
g_ndb_cluster_connection->get_latest_error_msg());
return -1;
}
return 0;
}
void ndbcluster_disconnect(void) {
DBUG_TRACE;
if (g_ndb) delete g_ndb;
g_ndb = NULL;
{
if (g_pool) {
/* first in pool is the main one, wait with release */
for (uint i = 1; i < g_pool_alloc; i++) {
if (g_pool[i]) delete g_pool[i];
}
my_free(g_pool);
mysql_mutex_destroy(&g_pool_mutex);
g_pool = 0;
}
g_pool_alloc = 0;
g_pool_pos = 0;
}
if (g_ndb_cluster_connection) delete g_ndb_cluster_connection;
g_ndb_cluster_connection = NULL;
}
Ndb_cluster_connection *ndb_get_cluster_connection() {
mysql_mutex_lock(&g_pool_mutex);
Ndb_cluster_connection *connection = g_pool[g_pool_pos];
g_pool_pos++;
if (g_pool_pos == g_pool_alloc) g_pool_pos = 0;
mysql_mutex_unlock(&g_pool_mutex);
return connection;
}
ulonglong ndb_get_latest_trans_gci() {
ulonglong val = *g_ndb_cluster_connection->get_latest_trans_gci();
for (uint i = 1; i < g_pool_alloc; i++) {
ulonglong tmp = *g_pool[i]->get_latest_trans_gci();
if (tmp > val) val = tmp;
}
return val;
}
void ndb_set_latest_trans_gci(ulonglong val) {
for (uint i = 0; i < g_pool_alloc; i++) {
*g_pool[i]->get_latest_trans_gci() = val;
}
}
int ndb_has_node_id(uint id) {
for (uint i = 0; i < g_pool_alloc; i++) {
if (id == g_pool[i]->node_id()) return 1;
}
return 0;
}
int ndb_set_recv_thread_activation_threshold(Uint32 threshold) {
for (uint i = 0; i < g_pool_alloc; i++) {
g_pool[i]->set_recv_thread_activation_threshold(threshold);
}
return 0;
}
int ndb_set_recv_thread_cpu(Uint16 *cpuid_array, Uint32 cpuid_array_size) {
int ret_code = 0;
Uint32 num_cpu_needed = g_pool_alloc;
if (cpuid_array_size == 0) {
for (Uint32 i = 0; i < g_pool_alloc; i++) {
ret_code = g_pool[i]->unset_recv_thread_cpu(0);
}
return ret_code;
}
if (cpuid_array_size < num_cpu_needed) {
/* Ignore cpu masks that is too short */
ndb_log_info(
"Ignored receive thread CPU mask, mask too short,"
" %u CPUs needed in mask, only %u CPUs provided",
num_cpu_needed, cpuid_array_size);
return 1;
}
for (Uint32 i = 0; i < g_pool_alloc; i++) {
ret_code = g_pool[i]->set_recv_thread_cpu(&cpuid_array[i], (Uint32)1, 0);
}
return ret_code;
}
void ndb_set_data_node_neighbour(ulong data_node_neighbour) {
for (uint i = 0; i < g_pool_alloc; i++)
g_pool[i]->set_data_node_neighbour(data_node_neighbour);
}
void ndb_get_connection_stats(Uint64 *statsArr) {
Uint64 connectionStats[Ndb::NumClientStatistics];
memset(statsArr, 0, sizeof(connectionStats));
for (uint i = 0; i < g_pool_alloc; i++) {
g_pool[i]->collect_client_stats(connectionStats, Ndb::NumClientStatistics);
for (Uint32 s = 0; s < Ndb::NumClientStatistics; s++)
statsArr[s] += connectionStats[s];
}
}
static ST_FIELD_INFO ndb_transid_mysql_connection_map_fields_info[] = {
{"mysql_connection_id", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0,
MY_I_S_UNSIGNED, "", 0},
{"node_id", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONG, 0,
MY_I_S_UNSIGNED, "", 0},
{"ndb_transid", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0,
MY_I_S_UNSIGNED, "", 0},
{0, 0, MYSQL_TYPE_NULL, 0, 0, "", 0}};
static int ndb_transid_mysql_connection_map_fill_table(THD *thd,
TABLE_LIST *tables,
Item *) {
DBUG_TRACE;
const bool all = (check_global_access(thd, PROCESS_ACL) == 0);
const ulonglong self = thd_get_thread_id(thd);
TABLE *table = tables->table;
for (uint i = 0; i < g_pool_alloc; i++) {
if (g_pool[i]) {
g_pool[i]->lock_ndb_objects();
const Ndb *p = g_pool[i]->get_next_ndb_object(0);
while (p) {
Uint64 connection_id = p->getCustomData64();
if ((connection_id == self) || all) {
table->field[0]->set_notnull();
table->field[0]->store(p->getCustomData64(), true);
table->field[1]->set_notnull();
table->field[1]->store(g_pool[i]->node_id());
table->field[2]->set_notnull();
table->field[2]->store(p->getNextTransactionId(), true);
schema_table_store_record(thd, table);
}
p = g_pool[i]->get_next_ndb_object(p);
}
g_pool[i]->unlock_ndb_objects();
}
}
return 0;
}
static int ndb_transid_mysql_connection_map_init(void *p) {
DBUG_TRACE;
ST_SCHEMA_TABLE *schema = reinterpret_cast<ST_SCHEMA_TABLE *>(p);
schema->fields_info = ndb_transid_mysql_connection_map_fields_info;
schema->fill_table = ndb_transid_mysql_connection_map_fill_table;
return 0;
}
static struct st_mysql_information_schema i_s_info = {
MYSQL_INFORMATION_SCHEMA_INTERFACE_VERSION};
/*
information_schema table plugin providing a list of MySQL
connection ID's and their corresponding NDB transaction ID
*/
struct st_mysql_plugin ndb_transid_mysql_connection_map_table = {
MYSQL_INFORMATION_SCHEMA_PLUGIN,
&i_s_info,
"ndb_transid_mysql_connection_map",
"Oracle Corporation",
"Map between MySQL connection ID and NDB transaction ID",
PLUGIN_LICENSE_GPL,
ndb_transid_mysql_connection_map_init,
NULL,
NULL,
0x0001,
NULL,
NULL,
NULL,
0};