Skip to content

Commit e3e0a9a

Browse files
committed
BUG#35358531 ClusterSet.createReplicaCluster proposing non usable clone provisioning
By default, all commands that allow clone-based recovery will pick a clone donor if not explicitly set using the 'cloneDonor' option. However, the validations regarding donor and recipient are only performed when the cloneDonor option is used. This patch fixes that by ensuring the usage of clone is valid for recovery and fail soon avoiding any execution of steps and possible revert of operations. Other than that, this patch also fixes a bug in rejoinInstance() for Read-Replicas on which the cloneDonor option was being ignored so a donor would be picked automatically. Change-Id: I9f8983c0a2f2a78744de9c3a04cde0873875a811
1 parent 3b094c9 commit e3e0a9a

File tree

15 files changed

+271
-139
lines changed

15 files changed

+271
-139
lines changed

modules/adminapi/cluster/add_replica_instance.cc

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -377,13 +377,14 @@ void Add_replica_instance::do_run() {
377377
m_options.clone_options.clone_donor.has_value()) {
378378
std::string donor = *m_options.clone_options.clone_donor;
379379

380-
// Check if the donor is valid
381-
m_cluster_impl->ensure_compatible_clone_donor(donor, m_target_instance);
382-
383380
m_donor_instance =
384381
Scoped_instance(m_cluster_impl->connect_target_instance(donor));
385382
}
386383

384+
// Check if the donor is valid
385+
m_cluster_impl->ensure_compatible_clone_donor(*m_donor_instance,
386+
*m_target_instance);
387+
387388
console->print_info("* Checking transaction state of the instance...");
388389
m_options.clone_options.recovery_method = validate_instance_recovery();
389390
}

modules/adminapi/cluster/cluster_impl.cc

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2432,6 +2432,43 @@ void Cluster_impl::refresh_connections() {
24322432
}
24332433
}
24342434

2435+
void Cluster_impl::ensure_compatible_clone_donor(
2436+
const mysqlshdk::mysql::IInstance &donor,
2437+
const mysqlshdk::mysql::IInstance &recipient) {
2438+
/*
2439+
* A donor is compatible if:
2440+
*
2441+
* - It's an ONLINE member of the Cluster
2442+
* - The target (recipient) and donor instances support clone (version
2443+
* >= 8.0.17)
2444+
* - It has the same version of the recipient
2445+
* - It has the same operating system as the recipient
2446+
*/
2447+
2448+
// Check if the donor belongs to the PRIMARY Cluster (MD)
2449+
std::string donor_address = donor.get_canonical_address();
2450+
try {
2451+
get_metadata_storage()->get_instance_by_address(donor_address);
2452+
} catch (const shcore::Exception &e) {
2453+
if (e.code() == SHERR_DBA_MEMBER_METADATA_MISSING) {
2454+
throw shcore::Exception("Instance " + donor_address +
2455+
" does not belong to the Primary Cluster",
2456+
SHERR_DBA_BADARG_INSTANCE_NOT_IN_CLUSTER);
2457+
}
2458+
throw;
2459+
}
2460+
2461+
auto target_status = mysqlshdk::gr::get_member_state(donor);
2462+
2463+
if (target_status != mysqlshdk::gr::Member_state::ONLINE) {
2464+
throw shcore::Exception("Instance " + donor_address +
2465+
" is not an ONLINE member of the Cluster.",
2466+
SHERR_DBA_BADARG_INSTANCE_NOT_ONLINE);
2467+
}
2468+
2469+
Base_cluster_impl::ensure_compatible_clone_donor(donor, recipient);
2470+
}
2471+
24352472
void Cluster_impl::setup_admin_account(const std::string &username,
24362473
const std::string &host,
24372474
const Setup_account_options &options) {

modules/adminapi/cluster/cluster_impl.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,10 @@ class Cluster_impl final : public Base_cluster_impl,
584584

585585
void refresh_connections();
586586

587+
void ensure_compatible_clone_donor(
588+
const mysqlshdk::mysql::IInstance &donor,
589+
const mysqlshdk::mysql::IInstance &recipient) override;
590+
587591
// Lock methods
588592

589593
[[nodiscard]] mysqlshdk::mysql::Lock_scoped get_lock_shared(

modules/adminapi/cluster/rejoin_replica_instance.cc

Lines changed: 50 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -127,45 +127,19 @@ bool Rejoin_replica_instance::check_rejoinable() {
127127
}
128128

129129
Member_recovery_method Rejoin_replica_instance::validate_instance_recovery() {
130-
// When replicationSources is set to 'primary' or 'secondary' the main source
131-
// is the primary member of the cluster
132-
if (m_replication_sources.source_type == Source_type::PRIMARY ||
133-
m_replication_sources.source_type == Source_type::SECONDARY) {
134-
m_clone_donor_instance = m_cluster_impl->get_cluster_server();
135-
} else {
136-
// When replicationSources is set to a list of instances the main source
137-
// must be the first member of the list (the one with highest weight)
138-
auto first_element = *(m_replication_sources.replication_sources.begin());
139-
140-
std::string source_str = first_element.to_string();
141-
142-
std::shared_ptr<Instance> source_instance;
143-
144-
try {
145-
source_instance =
146-
m_cluster_impl->get_session_to_cluster_instance(source_str);
147-
} catch (const shcore::Exception &e) {
148-
throw shcore::Exception(
149-
"Source is not reachable",
150-
SHERR_DBA_READ_REPLICA_INVALID_SOURCE_LIST_UNREACHABLE);
151-
}
152-
153-
m_clone_donor_instance = source_instance;
154-
}
155-
156130
auto check_recoverable =
157131
[=](const mysqlshdk::mysql::IInstance &tgt_instance) {
158132
// Get the gtid state in regards to the donor
159133
mysqlshdk::mysql::Replica_gtid_state state =
160-
check_replica_group_gtid_state(*m_clone_donor_instance,
161-
tgt_instance, nullptr, nullptr);
134+
check_replica_group_gtid_state(*m_donor_instance, tgt_instance,
135+
nullptr, nullptr);
162136

163137
return (state != mysqlshdk::mysql::Replica_gtid_state::IRRECOVERABLE);
164138
};
165139

166140
return mysqlsh::dba::validate_instance_recovery(
167141
Cluster_type::GROUP_REPLICATION, Member_op_action::ADD_INSTANCE,
168-
*m_clone_donor_instance, *m_target_instance, check_recoverable,
142+
*m_donor_instance, *m_target_instance, check_recoverable,
169143
m_options.clone_options.recovery_method.get_safe(),
170144
m_cluster_impl->get_gtid_set_is_complete(), m_options.interactive());
171145
}
@@ -188,23 +162,57 @@ void Rejoin_replica_instance::validate_replication_channels() {
188162
}
189163
}
190164

165+
std::shared_ptr<Instance>
166+
Rejoin_replica_instance::get_default_donor_instance() {
167+
// When replicationSources is set to 'primary' or 'secondary' the main source
168+
// is the primary member of the cluster
169+
if (m_replication_sources.source_type == Source_type::PRIMARY ||
170+
m_replication_sources.source_type == Source_type::SECONDARY) {
171+
return m_cluster_impl->get_cluster_server();
172+
} else {
173+
// When replicationSources is set to a list of instances the main source
174+
// must be the first member of the list (the one with highest weight)
175+
assert(!m_replication_sources.replication_sources.empty());
176+
auto first_element = *(m_replication_sources.replication_sources.begin());
177+
178+
std::string source_str = first_element.to_string();
179+
180+
std::shared_ptr<Instance> source_instance;
181+
182+
try {
183+
source_instance =
184+
m_cluster_impl->get_session_to_cluster_instance(source_str);
185+
} catch (const shcore::Exception &e) {
186+
throw shcore::Exception(
187+
"Source is not reachable",
188+
SHERR_DBA_READ_REPLICA_INVALID_SOURCE_LIST_UNREACHABLE);
189+
}
190+
191+
return source_instance;
192+
}
193+
}
194+
191195
void Rejoin_replica_instance::prepare() {
192196
auto console = current_console();
193197

194198
// Validate the Clone options.
195199
m_options.clone_options.check_option_values(m_target_instance->get_version());
196200

197-
// If the cloneDonor option us used, validate if the selected donor is
198-
// valid and set it as the donor instance
201+
// Get the main donor and source instance
202+
// By default, the instance is:
203+
//
204+
// - The primary member when 'replicationSources' is set to "primary" or
205+
// "secondary"
206+
// - The first member of the 'replicationSources' list
207+
m_donor_instance = get_default_donor_instance();
208+
209+
// If the cloneDonor option us used set it as the donor instance
199210
if (*m_options.clone_options.recovery_method ==
200211
Member_recovery_method::CLONE) {
201212
if (m_options.clone_options.clone_donor.has_value()) {
202213
std::string donor = *m_options.clone_options.clone_donor;
203214

204-
// Check if the donor is valid
205-
m_cluster_impl->ensure_compatible_clone_donor(donor, m_target_instance);
206-
207-
m_clone_donor_instance =
215+
m_donor_instance =
208216
Scoped_instance(m_cluster_impl->connect_target_instance(donor));
209217
}
210218
}
@@ -220,6 +228,10 @@ void Rejoin_replica_instance::prepare() {
220228

221229
console->print_info("* Checking transaction state of the instance...");
222230
m_options.clone_options.recovery_method = validate_instance_recovery();
231+
232+
// Check if the donor is valid
233+
m_cluster_impl->ensure_compatible_clone_donor(*m_donor_instance,
234+
*m_target_instance);
223235
}
224236

225237
void Rejoin_replica_instance::do_run() {
@@ -257,9 +269,9 @@ void Rejoin_replica_instance::do_run() {
257269
if (*m_options.clone_options.recovery_method ==
258270
Member_recovery_method::CLONE) {
259271
m_cluster_impl->handle_clone_provisioning(
260-
m_target_instance, m_clone_donor_instance, ar_options,
261-
repl_account_host, "", "", m_options.get_recovery_progress(),
262-
m_options.timeout, m_options.dry_run);
272+
m_target_instance, m_donor_instance, ar_options, repl_account_host, "",
273+
"", m_options.get_recovery_progress(), m_options.timeout,
274+
m_options.dry_run);
263275

264276
// Clone will copy all tables, including the replication settings stored
265277
// in mysql.slave_master_info. MySQL will start replication by default

modules/adminapi/cluster/rejoin_replica_instance.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ class Rejoin_replica_instance {
5858
void prepare();
5959
Member_recovery_method validate_instance_recovery();
6060
void validate_replication_channels();
61+
std::shared_ptr<Instance> get_default_donor_instance();
6162

6263
protected:
6364
shcore::Scoped_callback_list m_undo_list;
@@ -66,7 +67,7 @@ class Rejoin_replica_instance {
6667
private:
6768
Cluster_impl *m_cluster_impl = nullptr;
6869
std::shared_ptr<mysqlsh::dba::Instance> m_target_instance;
69-
std::shared_ptr<mysqlsh::dba::Instance> m_clone_donor_instance;
70+
std::shared_ptr<mysqlsh::dba::Instance> m_donor_instance;
7071
Rejoin_instance_options m_options;
7172
std::string m_account_host;
7273
std::string m_target_read_replica_address;

modules/adminapi/cluster_set/create_replica_cluster.cc

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -430,13 +430,11 @@ shcore::Value Create_replica_cluster::execute() {
430430
// Handle clone provisioning
431431
if (*m_options.clone_options.recovery_method ==
432432
Member_recovery_method::CLONE) {
433-
// Ensure cloneDonor is valid and pick a valid donor if the option is not
434-
// used. By default, the donor must be the PRIMARY member of the PRIMARY
435-
// Cluster.
433+
// Pick a donor if the option is not used. By default, the donor must be
434+
// the PRIMARY member of the PRIMARY Cluster.
436435
std::string donor;
437436
if (m_options.clone_options.clone_donor.has_value()) {
438437
donor = *m_options.clone_options.clone_donor;
439-
m_cluster_set->ensure_compatible_clone_donor(donor, m_target_instance);
440438
} else {
441439
// Pick the primary instance of the primary cluster as donor
442440
donor = m_primary_instance->get_canonical_address();
@@ -445,6 +443,13 @@ shcore::Value Create_replica_cluster::execute() {
445443
const auto donor_instance =
446444
Scoped_instance(m_cluster_set->connect_target_instance(donor));
447445

446+
// Ensure the donor is valid:
447+
// - It's an ONLINE member of the Primary Cluster
448+
// - It has the same version of the recipient
449+
// - It has the same operating system as the recipient
450+
m_cluster_set->get_primary_cluster()->ensure_compatible_clone_donor(
451+
donor_instance, *m_target_instance);
452+
448453
m_cluster_set->handle_clone_provisioning(
449454
m_target_instance, donor_instance, ar_options, repl_account_host,
450455
m_cluster_set->query_clusterset_auth_cert_issuer(),

modules/adminapi/common/base_cluster_impl.cc

Lines changed: 40 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@
3030
#include <string>
3131
#include <vector>
3232

33-
#include "adminapi/cluster/cluster_impl.h"
34-
#include "adminapi/common/cluster_types.h"
35-
#include "adminapi/common/common.h"
33+
#include "modules/adminapi/cluster/cluster_impl.h"
3634
#include "modules/adminapi/common/accounts.h"
3735
#include "modules/adminapi/common/async_topology.h"
36+
#include "modules/adminapi/common/cluster_types.h"
37+
#include "modules/adminapi/common/common.h"
3838
#include "modules/adminapi/common/dba_errors.h"
3939
#include "modules/adminapi/common/errors.h"
4040
#include "modules/adminapi/common/instance_monitoring.h"
@@ -43,11 +43,13 @@
4343
#include "modules/adminapi/common/metadata_storage.h"
4444
#include "modules/adminapi/common/preconditions.h"
4545
#include "modules/adminapi/common/router.h"
46+
#include "modules/adminapi/common/server_features.h"
4647
#include "modules/adminapi/common/setup_account.h"
4748
#include "modules/adminapi/common/sql.h"
4849
#include "modules/mod_shell.h"
4950
#include "modules/mysqlxtest_utils.h"
5051
#include "mysqlshdk/include/shellcore/console.h"
52+
#include "mysqlshdk/include/shellcore/utils_help.h"
5153
#include "mysqlshdk/libs/mysql/async_replication.h"
5254
#include "mysqlshdk/libs/mysql/group_replication.h"
5355
#include "mysqlshdk/libs/mysql/replication.h"
@@ -56,7 +58,7 @@
5658
#include "mysqlshdk/libs/utils/utils_general.h"
5759
#include "mysqlshdk/libs/utils/utils_mysql_parsing.h"
5860
#include "mysqlshdk/libs/utils/utils_net.h"
59-
#include "shellcore/utils_help.h"
61+
#include "mysqlshdk/libs/utils/version.h"
6062

6163
namespace mysqlsh {
6264
namespace dba {
@@ -577,62 +579,67 @@ void Base_cluster_impl::handle_clone_provisioning(
577579
}
578580

579581
void Base_cluster_impl::ensure_compatible_clone_donor(
580-
const std::string &donor_def,
581-
const std::shared_ptr<mysqlsh::dba::Instance> &recipient) {
582+
const mysqlshdk::mysql::IInstance &donor,
583+
const mysqlshdk::mysql::IInstance &recipient) {
582584
/*
583585
* A donor is compatible if:
584586
*
585-
* - It's an ONLINE member of the Cluster
587+
* - Supports clone
586588
* - It has the same version of the recipient
587589
* - It has the same operating system as the recipient
588590
*/
591+
std::string target_address = donor.get_canonical_address();
589592

590-
const auto target = Scoped_instance(connect_target_instance(donor_def));
591-
592-
// Check if the target belongs to the PRIMARY Cluster (MD)
593-
std::string target_address = target->get_canonical_address();
594-
try {
595-
get_metadata_storage()->get_instance_by_address(target_address);
596-
} catch (const shcore::Exception &e) {
597-
if (e.code() == SHERR_DBA_MEMBER_METADATA_MISSING) {
598-
throw shcore::Exception("Instance " + target_address +
599-
" does not belong to the Primary Cluster",
600-
SHERR_DBA_BADARG_INSTANCE_NOT_IN_CLUSTER);
601-
}
602-
throw;
593+
// Check if the instance support clone
594+
if (!supports_mysql_clone(donor.get_version())) {
595+
throw shcore::Exception(
596+
"Instance " + target_address + " does not support MySQL Clone.",
597+
SHERR_DBA_CLONE_NO_SUPPORT);
603598
}
604599

605-
auto target_status = mysqlshdk::gr::get_member_state(*target);
600+
auto donor_version = donor.get_version();
601+
auto recipient_version = recipient.get_version();
606602

607-
if (target_status != mysqlshdk::gr::Member_state::ONLINE) {
608-
throw shcore::Exception("Instance " + target_address +
609-
" is not an ONLINE member of the Cluster.",
610-
SHERR_DBA_BADARG_INSTANCE_NOT_ONLINE);
611-
}
603+
DBUG_EXECUTE_IF("dba_clone_version_check_fail",
604+
{ donor_version = mysqlshdk::utils::Version(8, 0, 17); });
612605

613606
// Check if the instance has the same version of the recipient
614-
if (target->get_version() != recipient->get_version()) {
607+
if (donor_version != recipient_version) {
615608
throw shcore::Exception("Instance " + target_address +
616609
" cannot be a donor because it has a different "
617-
"version than the recipient.",
610+
"version (" +
611+
donor_version.get_full() +
612+
") than the recipient (" +
613+
recipient_version.get_full() + ").",
618614
SHERR_DBA_CLONE_DIFF_VERSION);
619615
}
620616

621617
// Check if the instance has the same OS the recipient
622-
if (target->get_version_compile_os() != recipient->get_version_compile_os()) {
618+
auto donor_version_compile_os = donor.get_version_compile_os();
619+
auto recipient_version_compile_os = recipient.get_version_compile_os();
620+
621+
if (donor_version_compile_os != recipient_version_compile_os) {
623622
throw shcore::Exception("Instance " + target_address +
624623
" cannot be a donor because it has a different "
625-
"Operating System than the recipient.",
624+
"Operating System (" +
625+
donor_version_compile_os +
626+
") than the recipient (" +
627+
recipient_version_compile_os + ").",
626628
SHERR_DBA_CLONE_DIFF_OS);
627629
}
628630

629631
// Check if the instance is running on the same platform of the recipient
630-
if (target->get_version_compile_machine() !=
631-
recipient->get_version_compile_machine()) {
632+
auto donor_version_compile_machine = donor.get_version_compile_machine();
633+
auto recipient_version_compile_machine =
634+
recipient.get_version_compile_machine();
635+
636+
if (donor_version_compile_machine != recipient_version_compile_machine) {
632637
throw shcore::Exception(
633638
"Instance " + target_address +
634639
" cannot be a donor because it is running on a different "
635-
"platform than the recipient.",
640+
"platform (" +
641+
donor_version_compile_machine + ") than the recipient (" +
642+
recipient_version_compile_machine + ").",
636643
SHERR_DBA_CLONE_DIFF_PLATFORM);
637644
}
638645
}

0 commit comments

Comments
 (0)