842 lines
31 KiB
C++
842 lines
31 KiB
C++
/*
|
|
Copyright (c) 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
|
|
*/
|
|
|
|
// Implements
|
|
#include "storage/ndb/plugin/ndb_metadata_sync.h"
|
|
|
|
#include "sql/sql_class.h" // THD
|
|
#include "sql/sql_table.h" // build_table_filename
|
|
#include "storage/ndb/include/ndbapi/Ndb.hpp" // Ndb
|
|
#include "storage/ndb/plugin/ha_ndbcluster_binlog.h" // ndbcluster_binlog_setup_table
|
|
#include "storage/ndb/plugin/ndb_dd_client.h" // Ndb_dd_client
|
|
#include "storage/ndb/plugin/ndb_dd_disk_data.h" // ndb_dd_disk_data_get_object_id_and_version
|
|
#include "storage/ndb/plugin/ndb_dd_table.h" // ndb_dd_table_get_object_id_and_version
|
|
#include "storage/ndb/plugin/ndb_log.h" // ndb_log_*
|
|
#include "storage/ndb/plugin/ndb_ndbapi_util.h" // ndb_logfile_group_exists
|
|
#include "storage/ndb/plugin/ndb_schema_dist.h" // Ndb_schema_dist
|
|
#include "storage/ndb/plugin/ndb_table_guard.h" // Ndb_table_guard
|
|
#include "storage/ndb/plugin/ndb_tdc.h" // ndb_tdc_close_cached_table
|
|
#include "storage/ndb/plugin/ndb_thd.h" // get_thd_ndb
|
|
#include "storage/ndb/plugin/ndb_thd_ndb.h" // Thd_ndb
|
|
|
|
const char *Ndb_metadata_sync::object_type_str(
|
|
object_detected_type type) const {
|
|
switch (type) {
|
|
case LOGFILE_GROUP_OBJECT:
|
|
return "LOGFILE GROUP";
|
|
case TABLESPACE_OBJECT:
|
|
return "TABLESPACE";
|
|
case TABLE_OBJECT:
|
|
return "TABLE";
|
|
default:
|
|
DBUG_ASSERT(false);
|
|
return "";
|
|
}
|
|
}
|
|
|
|
bool Ndb_metadata_sync::object_sync_pending(
|
|
const Detected_object &object) const {
|
|
for (const auto &detected_object : m_objects) {
|
|
if (detected_object.m_type == object.m_type &&
|
|
detected_object.m_db_name == object.m_db_name &&
|
|
detected_object.m_name == object.m_name) {
|
|
if (object.m_type == object_detected_type::TABLE_OBJECT) {
|
|
ndb_log_info(
|
|
"Object '%s.%s' of type %s is already in the queue of "
|
|
"objects waiting to be synchronized",
|
|
object.m_db_name.c_str(), object.m_name.c_str(),
|
|
object_type_str(object.m_type));
|
|
} else {
|
|
ndb_log_info(
|
|
"Object '%s' of type %s is already in the queue of objects"
|
|
" waiting to be synchronized",
|
|
object.m_name.c_str(), object_type_str(object.m_type));
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Ndb_metadata_sync::object_blacklisted(
|
|
const Detected_object &object) const {
|
|
std::lock_guard<std::mutex> guard(m_blacklist_mutex);
|
|
for (const auto &blacklisted_object : m_blacklist) {
|
|
if (blacklisted_object.m_type == object.m_type &&
|
|
blacklisted_object.m_db_name == object.m_db_name &&
|
|
blacklisted_object.m_name == object.m_name) {
|
|
if (object.m_type == object_detected_type::TABLE_OBJECT) {
|
|
ndb_log_info(
|
|
"Object '%s.%s' of type %s is currently blacklisted and needs to "
|
|
"be synced manually",
|
|
object.m_db_name.c_str(), object.m_name.c_str(),
|
|
object_type_str(object.m_type));
|
|
} else {
|
|
ndb_log_info(
|
|
"Object '%s' of type %s is currently blacklisted and needs to be "
|
|
"synced manually",
|
|
object.m_name.c_str(), object_type_str(object.m_type));
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Ndb_metadata_sync::add_logfile_group(const std::string &lfg_name) {
|
|
std::lock_guard<std::mutex> guard(m_objects_mutex);
|
|
const Detected_object obj("", lfg_name,
|
|
object_detected_type::LOGFILE_GROUP_OBJECT);
|
|
if (object_sync_pending(obj) || object_blacklisted(obj)) {
|
|
return false;
|
|
}
|
|
m_objects.emplace_back(obj);
|
|
ndb_log_info(
|
|
"Logfile group '%s' added to queue of objects waiting to be "
|
|
"synchronized",
|
|
lfg_name.c_str());
|
|
return true;
|
|
}
|
|
|
|
bool Ndb_metadata_sync::add_tablespace(const std::string &tablespace_name) {
|
|
std::lock_guard<std::mutex> guard(m_objects_mutex);
|
|
const Detected_object obj("", tablespace_name,
|
|
object_detected_type::TABLESPACE_OBJECT);
|
|
if (object_sync_pending(obj) || object_blacklisted(obj)) {
|
|
return false;
|
|
}
|
|
m_objects.emplace_back(obj);
|
|
ndb_log_info(
|
|
"Tablespace '%s' added to queue of objects waiting to be "
|
|
"synchronized",
|
|
tablespace_name.c_str());
|
|
return true;
|
|
}
|
|
|
|
bool Ndb_metadata_sync::add_table(const std::string &schema_name,
|
|
const std::string &table_name) {
|
|
std::lock_guard<std::mutex> guard(m_objects_mutex);
|
|
const Detected_object obj(schema_name, table_name,
|
|
object_detected_type::TABLE_OBJECT);
|
|
if (object_sync_pending(obj) || object_blacklisted(obj)) {
|
|
return false;
|
|
}
|
|
m_objects.emplace_back(obj);
|
|
ndb_log_info(
|
|
"Table '%s.%s' added to queue of objects waiting to be "
|
|
"synchronized",
|
|
schema_name.c_str(), table_name.c_str());
|
|
return true;
|
|
}
|
|
|
|
bool Ndb_metadata_sync::object_queue_empty() const {
|
|
std::lock_guard<std::mutex> guard(m_objects_mutex);
|
|
return m_objects.empty();
|
|
}
|
|
|
|
void Ndb_metadata_sync::get_next_object(std::string &db_name, std::string &name,
|
|
object_detected_type &type) {
|
|
std::lock_guard<std::mutex> guard(m_objects_mutex);
|
|
const Detected_object obj = m_objects.front();
|
|
db_name = obj.m_db_name;
|
|
name = obj.m_name;
|
|
type = obj.m_type;
|
|
m_objects.pop_front();
|
|
}
|
|
|
|
static long long g_blacklist_size =
|
|
0; // protected implicitly by m_blacklist_mutex
|
|
static void increment_blacklist_size() { g_blacklist_size++; }
|
|
static void decrement_blacklist_size() { g_blacklist_size--; }
|
|
static SHOW_VAR ndb_status_vars_blacklist_size[] = {
|
|
{"metadata_blacklist_size", reinterpret_cast<char *>(&g_blacklist_size),
|
|
SHOW_LONGLONG, SHOW_SCOPE_GLOBAL},
|
|
{NullS, NullS, SHOW_LONG, SHOW_SCOPE_GLOBAL}};
|
|
|
|
int show_ndb_metadata_blacklist_size(THD *, SHOW_VAR *var, char *) {
|
|
var->type = SHOW_ARRAY;
|
|
var->value = reinterpret_cast<char *>(&ndb_status_vars_blacklist_size);
|
|
return 0;
|
|
}
|
|
|
|
void Ndb_metadata_sync::add_object_to_blacklist(const std::string &db_name,
|
|
const std::string &name,
|
|
object_detected_type type) {
|
|
std::lock_guard<std::mutex> guard(m_blacklist_mutex);
|
|
const Detected_object obj(db_name, name, type);
|
|
m_blacklist.emplace_back(obj);
|
|
ndb_log_info("Object '%s' of type %s added to blacklist", name.c_str(),
|
|
object_type_str(type));
|
|
increment_blacklist_size();
|
|
}
|
|
|
|
bool Ndb_metadata_sync::get_blacklist_object_for_validation(
|
|
std::string &db_name, std::string &name, object_detected_type &type) {
|
|
std::lock_guard<std::mutex> guard(m_blacklist_mutex);
|
|
for (Detected_object &obj : m_blacklist) {
|
|
switch (obj.m_validation_state) {
|
|
case object_validation_state::PENDING: {
|
|
// Found object pending validation. Retrieve details and mark the object
|
|
// as being validated
|
|
db_name = obj.m_db_name;
|
|
name = obj.m_name;
|
|
type = obj.m_type;
|
|
obj.m_validation_state = object_validation_state::IN_PROGRESS;
|
|
return true;
|
|
} break;
|
|
case object_validation_state::DONE: {
|
|
} break;
|
|
case object_validation_state::IN_PROGRESS: {
|
|
// Not possible since there can't be two objects being validated at once
|
|
DBUG_ASSERT(false);
|
|
return false;
|
|
} break;
|
|
default:
|
|
// Unknown state, not possible
|
|
DBUG_ASSERT(false);
|
|
return false;
|
|
}
|
|
}
|
|
// Reached the end of the blacklist having found no objects pending validation
|
|
return false;
|
|
}
|
|
|
|
bool Ndb_metadata_sync::check_blacklist_object_mismatch(
|
|
THD *thd, const std::string &db_name, const std::string &name,
|
|
object_detected_type type) const {
|
|
Thd_ndb *thd_ndb = get_thd_ndb(thd);
|
|
Ndb *ndb = thd_ndb->ndb;
|
|
NdbDictionary::Dictionary *dict = ndb->getDictionary();
|
|
Ndb_dd_client dd_client(thd);
|
|
switch (type) {
|
|
case object_detected_type::LOGFILE_GROUP_OBJECT: {
|
|
bool exists_in_NDB;
|
|
if (!ndb_logfile_group_exists(dict, name, exists_in_NDB)) {
|
|
ndb_log_info(
|
|
"Failed to determine if logfile group '%s' exists "
|
|
"in NDB, object remains blacklisted",
|
|
name.c_str());
|
|
return true;
|
|
}
|
|
|
|
if (!dd_client.mdl_lock_logfile_group(name.c_str(), true)) {
|
|
ndb_log_info(
|
|
"Failed to acquire MDL on logfile group '%s', object "
|
|
"remains blacklisted",
|
|
name.c_str());
|
|
return true;
|
|
}
|
|
bool exists_in_DD;
|
|
if (!dd_client.logfile_group_exists(name.c_str(), exists_in_DD)) {
|
|
ndb_log_info(
|
|
"Failed to determine if logfile group '%s' exists in DD, "
|
|
"object remains blacklisted",
|
|
name.c_str());
|
|
return true;
|
|
}
|
|
|
|
if (exists_in_NDB == exists_in_DD) {
|
|
ndb_log_info(
|
|
"Mismatch doesn't exist any more, logfile group '%s' "
|
|
"will be removed from blacklist",
|
|
name.c_str());
|
|
return false;
|
|
} else {
|
|
ndb_log_info(
|
|
"Mismatch still exists, logfile group '%s' remains "
|
|
"blacklisted",
|
|
name.c_str());
|
|
return true;
|
|
}
|
|
} break;
|
|
case object_detected_type::TABLESPACE_OBJECT: {
|
|
bool exists_in_NDB;
|
|
if (!ndb_tablespace_exists(dict, name, exists_in_NDB)) {
|
|
ndb_log_info(
|
|
"Failed to determine if tablespace '%s' exists in NDB, "
|
|
"object remains blacklisted",
|
|
name.c_str());
|
|
return true;
|
|
}
|
|
|
|
if (!dd_client.mdl_lock_tablespace(name.c_str(), true)) {
|
|
ndb_log_info(
|
|
"Failed to acquire MDL on tablespace '%s', object "
|
|
"remains blacklisted",
|
|
name.c_str());
|
|
return true;
|
|
}
|
|
bool exists_in_DD;
|
|
if (!dd_client.tablespace_exists(name.c_str(), exists_in_DD)) {
|
|
ndb_log_info(
|
|
"Failed to determine if tablespace '%s' exists in DD, "
|
|
"object remains blacklisted",
|
|
name.c_str());
|
|
return true;
|
|
}
|
|
|
|
if (exists_in_NDB == exists_in_DD) {
|
|
ndb_log_info(
|
|
"Mismatch doesn't exist any more, tablespace '%s' "
|
|
"will be removed from blacklist",
|
|
name.c_str());
|
|
return false;
|
|
} else {
|
|
ndb_log_info(
|
|
"Mismatch still exists, tablespace '%s' remains "
|
|
"blacklisted",
|
|
name.c_str());
|
|
return true;
|
|
}
|
|
} break;
|
|
case object_detected_type::TABLE_OBJECT: {
|
|
bool exists_in_NDB;
|
|
if (!ndb_table_exists(dict, db_name, name, exists_in_NDB)) {
|
|
ndb_log_info(
|
|
"Failed to determine if table '%s.%s' exists in NDB, "
|
|
"object remains blacklisted",
|
|
db_name.c_str(), name.c_str());
|
|
return true;
|
|
}
|
|
|
|
if (!dd_client.mdl_lock_table(db_name.c_str(), name.c_str())) {
|
|
ndb_log_info(
|
|
"Failed to acquire MDL on table '%s.%s', object "
|
|
"remains blacklisted",
|
|
db_name.c_str(), name.c_str());
|
|
return true;
|
|
}
|
|
bool exists_in_DD;
|
|
if (!dd_client.table_exists(db_name.c_str(), name.c_str(),
|
|
exists_in_DD)) {
|
|
ndb_log_info(
|
|
"Failed to determine if table '%s.%s' exists in DD, "
|
|
"object remains blacklisted",
|
|
db_name.c_str(), name.c_str());
|
|
return true;
|
|
}
|
|
|
|
if (exists_in_NDB == exists_in_DD) {
|
|
ndb_log_info(
|
|
"Mismatch doesn't exist any more, table '%s.%s' will be removed "
|
|
"from blacklist",
|
|
db_name.c_str(), name.c_str());
|
|
return false;
|
|
} else {
|
|
ndb_log_info(
|
|
"Mismatch still exists, table '%s.%s' remains "
|
|
"blacklisted",
|
|
db_name.c_str(), name.c_str());
|
|
return true;
|
|
}
|
|
} break;
|
|
default:
|
|
ndb_log_error("Unknown object type found in blacklist");
|
|
DBUG_ASSERT(false);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void Ndb_metadata_sync::validate_blacklist_object(bool check_mismatch_result) {
|
|
std::lock_guard<std::mutex> guard(m_blacklist_mutex);
|
|
for (auto it = m_blacklist.begin(); it != m_blacklist.end(); it++) {
|
|
Detected_object &obj = *it;
|
|
if (obj.m_validation_state == object_validation_state::IN_PROGRESS) {
|
|
if (!check_mismatch_result) {
|
|
// Mismatch no longer exists, remove object from blacklist
|
|
m_blacklist.erase(it);
|
|
decrement_blacklist_size();
|
|
} else {
|
|
// Mark object as already validated for this cycle
|
|
obj.m_validation_state = object_validation_state::DONE;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
DBUG_ASSERT(false);
|
|
}
|
|
|
|
void Ndb_metadata_sync::reset_blacklist_state() {
|
|
std::lock_guard<std::mutex> guard(m_blacklist_mutex);
|
|
for (Detected_object &obj : m_blacklist) {
|
|
obj.m_validation_state = object_validation_state::PENDING;
|
|
}
|
|
}
|
|
|
|
void Ndb_metadata_sync::validate_blacklist(THD *thd) {
|
|
while (true) {
|
|
std::string db_name, name;
|
|
object_detected_type type;
|
|
if (!get_blacklist_object_for_validation(db_name, name, type)) {
|
|
// No more objects pending validation
|
|
break;
|
|
}
|
|
const bool check_mismatch_result =
|
|
check_blacklist_object_mismatch(thd, db_name, name, type);
|
|
validate_blacklist_object(check_mismatch_result);
|
|
}
|
|
// Reset the states of all blacklisted objects
|
|
reset_blacklist_state();
|
|
}
|
|
|
|
bool Ndb_metadata_sync::sync_logfile_group(THD *thd,
|
|
const std::string &lfg_name,
|
|
bool &temp_error) const {
|
|
Ndb_dd_client dd_client(thd);
|
|
if (!dd_client.mdl_lock_logfile_group_exclusive(lfg_name.c_str(), true, 10)) {
|
|
ndb_log_info("Failed to acquire MDL on logfile group '%s'",
|
|
lfg_name.c_str());
|
|
temp_error = true;
|
|
// Since it's a temporary error, the THD conditions should be cleared but
|
|
// not logged
|
|
clear_thd_conditions(thd);
|
|
return false;
|
|
}
|
|
|
|
ndb_log_info("Synchronizing logfile group '%s'", lfg_name.c_str());
|
|
|
|
// Errors detected in the remainder of the function are not temporary
|
|
temp_error = false;
|
|
|
|
Thd_ndb *thd_ndb = get_thd_ndb(thd);
|
|
NdbDictionary::Dictionary *dict = thd_ndb->ndb->getDictionary();
|
|
bool exists_in_NDB;
|
|
if (!ndb_logfile_group_exists(dict, lfg_name, exists_in_NDB)) {
|
|
ndb_log_warning("Failed to determine if logfile group '%s' exists in NDB",
|
|
lfg_name.c_str());
|
|
return false;
|
|
}
|
|
|
|
bool exists_in_DD;
|
|
if (!dd_client.logfile_group_exists(lfg_name.c_str(), exists_in_DD)) {
|
|
log_and_clear_thd_conditions(thd, condition_logging_level::WARNING);
|
|
ndb_log_warning("Failed to determine if logfile group '%s' exists in DD",
|
|
lfg_name.c_str());
|
|
return false;
|
|
}
|
|
|
|
if (exists_in_NDB == exists_in_DD) {
|
|
// Mismatch doesn't exist any more, return success
|
|
return true;
|
|
}
|
|
|
|
if (exists_in_DD) {
|
|
// Logfile group exists in DD but not in NDB. Correct this by removing the
|
|
// logfile group from DD
|
|
if (!dd_client.drop_logfile_group(lfg_name.c_str())) {
|
|
log_and_clear_thd_conditions(thd, condition_logging_level::ERROR);
|
|
ndb_log_error("Failed to drop logfile group '%s' in DD",
|
|
lfg_name.c_str());
|
|
return false;
|
|
}
|
|
dd_client.commit();
|
|
ndb_log_info("Logfile group '%s' dropped from DD", lfg_name.c_str());
|
|
return true;
|
|
}
|
|
|
|
// Logfile group exists in NDB but not in DD. Correct this by installing the
|
|
// logfile group in the DD
|
|
std::vector<std::string> undofile_names;
|
|
if (!ndb_get_undofile_names(dict, lfg_name, &undofile_names)) {
|
|
ndb_log_error("Failed to get undofiles assigned to logfile group '%s'",
|
|
lfg_name.c_str());
|
|
return false;
|
|
}
|
|
|
|
int ndb_id, ndb_version;
|
|
if (!ndb_get_logfile_group_id_and_version(dict, lfg_name, ndb_id,
|
|
ndb_version)) {
|
|
ndb_log_error("Failed to get id and version of logfile group '%s'",
|
|
lfg_name.c_str());
|
|
return false;
|
|
}
|
|
if (!dd_client.install_logfile_group(lfg_name.c_str(), undofile_names, ndb_id,
|
|
ndb_version,
|
|
false /* force_overwrite */)) {
|
|
log_and_clear_thd_conditions(thd, condition_logging_level::ERROR);
|
|
ndb_log_error("Failed to install logfile group '%s' in DD",
|
|
lfg_name.c_str());
|
|
return false;
|
|
}
|
|
dd_client.commit();
|
|
ndb_log_info("Logfile group '%s' installed in DD", lfg_name.c_str());
|
|
return true;
|
|
}
|
|
|
|
bool Ndb_metadata_sync::sync_tablespace(THD *thd, const std::string &ts_name,
|
|
bool &temp_error) const {
|
|
Ndb_dd_client dd_client(thd);
|
|
if (!dd_client.mdl_lock_tablespace_exclusive(ts_name.c_str(), true, 10)) {
|
|
ndb_log_info("Failed to acquire MDL on tablespace '%s'", ts_name.c_str());
|
|
temp_error = true;
|
|
// Since it's a temporary error, the THD conditions should be cleared but
|
|
// not logged
|
|
clear_thd_conditions(thd);
|
|
return false;
|
|
}
|
|
|
|
ndb_log_info("Synchronizing tablespace '%s'", ts_name.c_str());
|
|
|
|
// Errors detected in the remainder of the function are not temporary
|
|
temp_error = false;
|
|
|
|
Thd_ndb *thd_ndb = get_thd_ndb(thd);
|
|
NdbDictionary::Dictionary *dict = thd_ndb->ndb->getDictionary();
|
|
bool exists_in_NDB;
|
|
if (!ndb_tablespace_exists(dict, ts_name, exists_in_NDB)) {
|
|
ndb_log_warning("Failed to determine if tablespace '%s' exists in NDB",
|
|
ts_name.c_str());
|
|
return false;
|
|
}
|
|
|
|
bool exists_in_DD;
|
|
if (!dd_client.tablespace_exists(ts_name.c_str(), exists_in_DD)) {
|
|
log_and_clear_thd_conditions(thd, condition_logging_level::WARNING);
|
|
ndb_log_warning("Failed to determine if tablespace '%s' exists in DD",
|
|
ts_name.c_str());
|
|
return false;
|
|
}
|
|
|
|
if (exists_in_NDB == exists_in_DD) {
|
|
// Mismatch doesn't exist any more, return success
|
|
return true;
|
|
}
|
|
|
|
if (exists_in_DD) {
|
|
// Tablespace exists in DD but not in NDB. Correct this by removing the
|
|
// tablespace from DD
|
|
if (!dd_client.drop_tablespace(ts_name.c_str())) {
|
|
log_and_clear_thd_conditions(thd, condition_logging_level::ERROR);
|
|
ndb_log_error("Failed to drop tablespace '%s' in DD", ts_name.c_str());
|
|
return false;
|
|
}
|
|
dd_client.commit();
|
|
ndb_log_info("Tablespace '%s' dropped from DD", ts_name.c_str());
|
|
return true;
|
|
}
|
|
|
|
// Tablespace exists in NDB but not in DD. Correct this by installing the
|
|
// tablespace in the DD
|
|
std::vector<std::string> datafile_names;
|
|
if (!ndb_get_datafile_names(dict, ts_name, &datafile_names)) {
|
|
ndb_log_error("Failed to get datafiles assigned to tablespace '%s'",
|
|
ts_name.c_str());
|
|
return false;
|
|
}
|
|
|
|
int ndb_id, ndb_version;
|
|
if (!ndb_get_tablespace_id_and_version(dict, ts_name, ndb_id, ndb_version)) {
|
|
ndb_log_error("Failed to get id and version of tablespace '%s'",
|
|
ts_name.c_str());
|
|
return false;
|
|
}
|
|
if (!dd_client.install_tablespace(ts_name.c_str(), datafile_names, ndb_id,
|
|
ndb_version, false /* force_overwrite */)) {
|
|
log_and_clear_thd_conditions(thd, condition_logging_level::ERROR);
|
|
ndb_log_error("Failed to install tablespace '%s' in DD", ts_name.c_str());
|
|
return false;
|
|
}
|
|
dd_client.commit();
|
|
ndb_log_info("Tablespace '%s' installed in DD", ts_name.c_str());
|
|
return true;
|
|
}
|
|
|
|
class Mutex_guard {
|
|
public:
|
|
Mutex_guard(mysql_mutex_t &mutex) : m_mutex(mutex) {
|
|
mysql_mutex_lock(&m_mutex);
|
|
}
|
|
Mutex_guard(const Mutex_guard &) = delete;
|
|
~Mutex_guard() { mysql_mutex_unlock(&m_mutex); }
|
|
|
|
private:
|
|
mysql_mutex_t &m_mutex;
|
|
};
|
|
|
|
extern mysql_mutex_t ndbcluster_mutex;
|
|
void Ndb_metadata_sync::drop_ndb_share(const char *db_name,
|
|
const char *table_name) const {
|
|
char key[FN_REFLEN + 1];
|
|
build_table_filename(key, sizeof(key) - 1, db_name, table_name, "", 0);
|
|
NDB_SHARE *share =
|
|
NDB_SHARE::acquire_reference_by_key(key,
|
|
"table_sync"); // temporary ref
|
|
if (share) {
|
|
Mutex_guard ndbcluster_mutex_guard(ndbcluster_mutex);
|
|
NDB_SHARE::mark_share_dropped(&share);
|
|
DBUG_ASSERT(share);
|
|
NDB_SHARE::release_reference_have_lock(share,
|
|
"table_sync"); // temporary ref
|
|
}
|
|
}
|
|
|
|
bool Ndb_metadata_sync::sync_table(THD *thd, const std::string &db_name,
|
|
const std::string &table_name,
|
|
bool &temp_error) {
|
|
if (DBUG_EVALUATE_IF("ndb_metadata_sync_fail", true, false)) {
|
|
temp_error = false;
|
|
return false;
|
|
}
|
|
Ndb_dd_client dd_client(thd);
|
|
if (!dd_client.mdl_locks_acquire_exclusive(db_name.c_str(),
|
|
table_name.c_str(), true, 10)) {
|
|
ndb_log_info("Failed to acquire MDL on table '%s.%s'", db_name.c_str(),
|
|
table_name.c_str());
|
|
temp_error = true;
|
|
// Since it's a temporary error, the THD conditions should be cleared but
|
|
// not logged
|
|
clear_thd_conditions(thd);
|
|
return false;
|
|
}
|
|
|
|
ndb_log_info("Synchronizing table '%s.%s'", db_name.c_str(),
|
|
table_name.c_str());
|
|
|
|
// Most of the errors detected after this are not temporary
|
|
temp_error = false;
|
|
|
|
Thd_ndb *thd_ndb = get_thd_ndb(thd);
|
|
Ndb *ndb = thd_ndb->ndb;
|
|
NdbDictionary::Dictionary *dict = ndb->getDictionary();
|
|
bool exists_in_NDB;
|
|
if (!ndb_table_exists(dict, db_name, table_name, exists_in_NDB)) {
|
|
ndb_log_warning("Failed to determine if table '%s.%s' exists in NDB",
|
|
db_name.c_str(), table_name.c_str());
|
|
return false;
|
|
}
|
|
|
|
bool exists_in_DD;
|
|
if (!dd_client.table_exists(db_name.c_str(), table_name.c_str(),
|
|
exists_in_DD)) {
|
|
log_and_clear_thd_conditions(thd, condition_logging_level::WARNING);
|
|
ndb_log_warning("Failed to determine if table '%s.%s' exists in DD",
|
|
db_name.c_str(), table_name.c_str());
|
|
return false;
|
|
}
|
|
|
|
if (exists_in_NDB == exists_in_DD) {
|
|
// Mismatch doesn't exist any more, return success
|
|
return true;
|
|
}
|
|
|
|
if (exists_in_DD) {
|
|
// Table exists in DD but not in NDB
|
|
// Check if it's a local table
|
|
bool local_table;
|
|
if (!dd_client.is_local_table(db_name.c_str(), table_name.c_str(),
|
|
local_table)) {
|
|
log_and_clear_thd_conditions(thd, condition_logging_level::ERROR);
|
|
ndb_log_error("Failed to determine if table '%s.%s' was a local table",
|
|
db_name.c_str(), table_name.c_str());
|
|
return false;
|
|
}
|
|
if (local_table) {
|
|
// Local table, the mismatch is expected
|
|
return true;
|
|
}
|
|
|
|
// Remove the table from DD
|
|
Ndb_referenced_tables_invalidator invalidator(thd, dd_client);
|
|
if (!dd_client.remove_table(db_name.c_str(), table_name.c_str(),
|
|
&invalidator)) {
|
|
log_and_clear_thd_conditions(thd, condition_logging_level::ERROR);
|
|
ndb_log_error("Failed to drop table '%s.%s' in DD", db_name.c_str(),
|
|
table_name.c_str());
|
|
return false;
|
|
}
|
|
|
|
if (!invalidator.invalidate()) {
|
|
log_and_clear_thd_conditions(thd, condition_logging_level::ERROR);
|
|
ndb_log_error(
|
|
"Failed to invalidate tables referencing table '%s.%s' in "
|
|
"DD",
|
|
db_name.c_str(), table_name.c_str());
|
|
return false;
|
|
}
|
|
|
|
// Drop share if it exists
|
|
drop_ndb_share(db_name.c_str(), table_name.c_str());
|
|
ndb_tdc_close_cached_table(thd, db_name.c_str(), table_name.c_str());
|
|
|
|
// Invalidate the table in NdbApi
|
|
if (ndb->setDatabaseName(db_name.c_str())) {
|
|
ndb_log_error("Failed to set database name of NDB object");
|
|
return false;
|
|
}
|
|
dd_client.commit();
|
|
ndb_log_info("Table '%s.%s' dropped from DD", db_name.c_str(),
|
|
table_name.c_str());
|
|
Ndb_table_guard ndbtab_guard(dict, table_name.c_str());
|
|
ndbtab_guard.invalidate();
|
|
return true;
|
|
}
|
|
|
|
// Table exists in NDB but not in DD. Correct this by installing the table in
|
|
// the DD
|
|
if (ndb->setDatabaseName(db_name.c_str())) {
|
|
ndb_log_error("Failed to set database name of NDB object");
|
|
return false;
|
|
}
|
|
|
|
Ndb_table_guard ndbtab_guard(dict, table_name.c_str());
|
|
const NdbDictionary::Table *tab = ndbtab_guard.get_table();
|
|
if (tab == nullptr) {
|
|
// Mismatch doesn't exist any more, return success
|
|
return true;
|
|
}
|
|
Uint32 extra_metadata_version, unpacked_len;
|
|
void *unpacked_data;
|
|
const int get_result = tab->getExtraMetadata(extra_metadata_version,
|
|
&unpacked_data, &unpacked_len);
|
|
if (get_result != 0) {
|
|
ndb_log_info("Failed to get extra metadata of table '%s.%s'",
|
|
db_name.c_str(), table_name.c_str());
|
|
return false;
|
|
}
|
|
|
|
if (extra_metadata_version == 1) {
|
|
// Table with "old" metadata found
|
|
if (!dd_client.migrate_table(
|
|
db_name.c_str(), table_name.c_str(),
|
|
static_cast<const unsigned char *>(unpacked_data), unpacked_len,
|
|
false)) {
|
|
log_and_clear_thd_conditions(thd, condition_logging_level::ERROR);
|
|
ndb_log_error(
|
|
"Failed to migrate table '%s.%s' with extra metadata "
|
|
"version 1",
|
|
db_name.c_str(), table_name.c_str());
|
|
free(unpacked_data);
|
|
return false;
|
|
}
|
|
free(unpacked_data);
|
|
const dd::Table *dd_table;
|
|
if (!dd_client.get_table(db_name.c_str(), table_name.c_str(), &dd_table)) {
|
|
log_and_clear_thd_conditions(thd, condition_logging_level::ERROR);
|
|
ndb_log_error(
|
|
"Failed to get table '%s.%s' from DD after it was installed",
|
|
db_name.c_str(), table_name.c_str());
|
|
return false;
|
|
}
|
|
if (ndbcluster_binlog_setup_table(thd, ndb, db_name.c_str(),
|
|
table_name.c_str(), dd_table) != 0) {
|
|
log_and_clear_thd_conditions(thd, condition_logging_level::ERROR);
|
|
ndb_log_error("Failed to setup binlogging for table '%s.%s'",
|
|
db_name.c_str(), table_name.c_str());
|
|
return false;
|
|
}
|
|
dd_client.commit();
|
|
ndb_log_info("Table '%s.%s' installed in DD", db_name.c_str(),
|
|
table_name.c_str());
|
|
return true;
|
|
}
|
|
dd::sdi_t sdi;
|
|
sdi.assign(static_cast<const char *>(unpacked_data), unpacked_len);
|
|
free(unpacked_data);
|
|
|
|
const std::string tablespace_name = ndb_table_tablespace_name(dict, tab);
|
|
if (!tablespace_name.empty()) {
|
|
// Acquire IX MDL on tablespace
|
|
if (!dd_client.mdl_lock_tablespace(tablespace_name.c_str(), true)) {
|
|
ndb_log_info("Failed to acquire MDL on tablespace '%s'",
|
|
tablespace_name.c_str());
|
|
temp_error = true;
|
|
// Since it's a temporary error, the THD conditions should be cleared but
|
|
// not logged
|
|
clear_thd_conditions(thd);
|
|
return false;
|
|
}
|
|
|
|
bool tablespace_exists;
|
|
if (!dd_client.tablespace_exists(tablespace_name.c_str(),
|
|
tablespace_exists)) {
|
|
log_and_clear_thd_conditions(thd, condition_logging_level::WARNING);
|
|
ndb_log_warning("Failed to determine if tablespace '%s' exists in DD",
|
|
tablespace_name.c_str());
|
|
return false;
|
|
}
|
|
if (!tablespace_exists) {
|
|
const Detected_object obj("", tablespace_name, TABLESPACE_OBJECT);
|
|
if (object_blacklisted(obj)) {
|
|
// The tablespace was detected but its sync failed. Such errors
|
|
// shouldn't be treated as temporary errors and the table is added to
|
|
// the blacklist
|
|
ndb_log_error("Tablespace '%s' is currently blacklisted",
|
|
tablespace_name.c_str());
|
|
ndb_log_error("Failed to install disk data table '%s.%s'",
|
|
db_name.c_str(), table_name.c_str());
|
|
return false;
|
|
} else {
|
|
// There's a possibility (especially when ndb_restore is used) that a
|
|
// disk data table is being synchronized before the tablespace has been
|
|
// synchronized which is a temporary error since the next detection
|
|
// cycle will detect and attempt to sync the tablespace before the table
|
|
ndb_log_info(
|
|
"Disk data table '%s.%s' not synced since tablespace '%s' "
|
|
"hasn't been synced yet",
|
|
db_name.c_str(), table_name.c_str(), tablespace_name.c_str());
|
|
temp_error = true;
|
|
// Since it's a temporary error, the THD conditions should be cleared
|
|
// but not logged
|
|
clear_thd_conditions(thd);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
Ndb_referenced_tables_invalidator invalidator(thd, dd_client);
|
|
if (!dd_client.install_table(db_name.c_str(), table_name.c_str(), sdi,
|
|
tab->getObjectId(), tab->getObjectVersion(),
|
|
tab->getPartitionCount(), tablespace_name, false,
|
|
&invalidator)) {
|
|
log_and_clear_thd_conditions(thd, condition_logging_level::ERROR);
|
|
ndb_log_error("Failed to install table '%s.%s' in DD", db_name.c_str(),
|
|
table_name.c_str());
|
|
return false;
|
|
}
|
|
|
|
if (!invalidator.invalidate()) {
|
|
log_and_clear_thd_conditions(thd, condition_logging_level::ERROR);
|
|
ndb_log_error("Failed to invalidate tables referencing table '%s.%s' in DD",
|
|
db_name.c_str(), table_name.c_str());
|
|
return false;
|
|
}
|
|
const dd::Table *dd_table;
|
|
if (!dd_client.get_table(db_name.c_str(), table_name.c_str(), &dd_table)) {
|
|
log_and_clear_thd_conditions(thd, condition_logging_level::ERROR);
|
|
ndb_log_error("Failed to get table '%s.%s' from DD after it was installed",
|
|
db_name.c_str(), table_name.c_str());
|
|
return false;
|
|
}
|
|
if (ndbcluster_binlog_setup_table(thd, ndb, db_name.c_str(),
|
|
table_name.c_str(), dd_table) != 0) {
|
|
log_and_clear_thd_conditions(thd, condition_logging_level::ERROR);
|
|
ndb_log_error("Failed to setup binlogging for table '%s.%s'",
|
|
db_name.c_str(), table_name.c_str());
|
|
return false;
|
|
}
|
|
dd_client.commit();
|
|
ndb_log_info("Table '%s.%s' installed in DD", db_name.c_str(),
|
|
table_name.c_str());
|
|
return true;
|
|
}
|