Skip to content

Commit fce2743

Browse files
committed
BUG#35444244 rebootClusterFromCompleteOutage unable to rejoin instance after major outage
Rebooting from complete outage a Replica Cluster that can reach out to its ClusterSet results in a failure rejoining the instances back to the Cluster. The Cluster itself is rebooted and rejoined back to the ClusterSet, however, the command fails to rejoin the instances. This scenario is only reproducible when the Cluster is missing transactions from the ClusterSet, or the Primary Cluster is under load. The root cause is a missing transaction sync step after each instance's recovery account is recreated. The account must be recreated on the Primary Cluster so its replicated to the whole topology but since there's no sync with the Replica Cluster, the account is still missing when the instance is attempted to be rejoined. This patch fixes that by making sure instances are only rejoined back to their Clusters when the Cluster is rejoined back to the ClusterSet and the transactions are sync'ed. Change-Id: I58e1adcebe8907e77eccd17f81218afdfa9fddf5
1 parent e3e0a9a commit fce2743

File tree

3 files changed

+85
-9
lines changed

3 files changed

+85
-9
lines changed

modules/adminapi/cluster_set/cluster_set_impl.cc

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2122,14 +2122,44 @@ void Cluster_set_impl::set_maximum_transaction_size_limit(Cluster_impl *cluster,
21222122
cluster_transaction_size_limit = value.as_int();
21232123
}
21242124

2125+
// If there are Cluster members that are reachable but group_replication is
2126+
// either disabled or not installed, attempting to set
2127+
// group_replication_transaction_size_limit will result in an error. To avoid
2128+
// that, we check which are those instances to let the config_handler know
2129+
// that those are to be ignored
2130+
std::vector<std::string> ignore_instances_vec;
2131+
{
2132+
auto is_gr_active = [](const mysqlshdk::mysql::IInstance &instance) {
2133+
std::optional<std::string> plugin_state =
2134+
instance.get_plugin_status(mysqlshdk::gr::k_gr_plugin_name);
2135+
if (!plugin_state.has_value() ||
2136+
plugin_state.value_or("DISABLED") != "ACTIVE") {
2137+
return false;
2138+
}
2139+
return true;
2140+
};
2141+
2142+
cluster->execute_in_members(
2143+
{}, cluster->get_cluster_server()->get_connection_options(), {},
2144+
[&ignore_instances_vec, &is_gr_active](
2145+
const std::shared_ptr<Instance> &instance,
2146+
const mysqlshdk::gr::Member &) {
2147+
if (!is_gr_active(*instance)) {
2148+
ignore_instances_vec.push_back(instance->get_canonical_address());
2149+
}
2150+
2151+
return true;
2152+
});
2153+
}
2154+
21252155
// The primary must be reachable at this point so it will always be
21262156
// updated, but one of more secondaries might be unreachable and it's OK
21272157
// if they are not updated. Auto-rejoins might fail due to transactions
21282158
// being rejected, but the user will be warned about it in cluster.status()
21292159
// and can fix it with .rescan(). Also, manually rejoining instances with
21302160
// .rejoinInstance() will overcome the problem
21312161
std::unique_ptr<mysqlshdk::config::Config> config =
2132-
cluster->create_config_object({}, false, false, true);
2162+
cluster->create_config_object(ignore_instances_vec, false, false, true);
21332163

21342164
config->set(kGrTransactionSizeLimit,
21352165
std::optional<int64_t>(cluster_transaction_size_limit));

modules/adminapi/dba/reboot_cluster_from_complete_outage.cc

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -223,10 +223,20 @@ void rejoin_instances(Cluster_impl *cluster_impl,
223223
rejoin_options.gr_options.ip_allowlist = std::nullopt;
224224
}
225225

226+
// Do not handle the ClusterSet-related steps (configuration of the
227+
// managed channel and transaction sync with the primary cluster) when:
228+
// - The Cluster was a Replica Cluster that was removed from the
229+
// ClusterSet, or
230+
// - It's not a ClusterSet member, or
231+
// - It's an INVALIDATED Cluster
232+
bool ignore_cluster_set = removed_from_set ||
233+
!cluster_impl->is_cluster_set_member() ||
234+
cluster_impl->is_invalidated();
235+
226236
Cluster_topology_executor<cluster::Rejoin_instance>{
227-
cluster_impl, instance,
228-
rejoin_options, options.switch_communication_stack.has_value(),
229-
true, true}
237+
cluster_impl, instance,
238+
rejoin_options, options.switch_communication_stack.has_value(),
239+
ignore_cluster_set, true}
230240
.run();
231241

232242
} catch (const shcore::Error &e) {
@@ -1258,6 +1268,8 @@ std::shared_ptr<Cluster> Reboot_cluster_from_complete_outage::do_run() {
12581268
reboot_seed();
12591269
}
12601270

1271+
bool rejoin_remaning_instances = true;
1272+
12611273
// don't rejoin the instances *if* cluster is in a cluster set and is
12621274
// invalidated (former primary) or is a replica and the primary doesn't have
12631275
// global status OK
@@ -1276,6 +1288,7 @@ std::shared_ptr<Cluster> Reboot_cluster_from_complete_outage::do_run() {
12761288
"Cluster is rejoined to the ClusterSet.";
12771289
console->print_info(msg);
12781290

1291+
rejoin_remaning_instances = false;
12791292
} else if (!m_options.get_dry_run()) {
12801293
// it's either a non ClusterSet instance or it is but it's not the
12811294
// primary, so we just need to acquire the primary before rejoining the
@@ -1304,9 +1317,6 @@ std::shared_ptr<Cluster> Reboot_cluster_from_complete_outage::do_run() {
13041317
}
13051318
}
13061319
}
1307-
1308-
rejoin_instances(cluster_impl.get(), *m_target_instance, instances,
1309-
m_options, !cluster_is_multi_primary);
13101320
}
13111321

13121322
// if the cluster is part of a set
@@ -1339,7 +1349,6 @@ std::shared_ptr<Cluster> Reboot_cluster_from_complete_outage::do_run() {
13391349

13401350
// also ensure SRO is enabled on all members
13411351
cluster_set_impl->ensure_replica_settings(cluster_impl.get(), false);
1342-
13431352
} catch (const shcore::Exception &e) {
13441353
switch (e.code()) {
13451354
case SHERR_DBA_DATA_ERRANT_TRANSACTIONS:
@@ -1377,6 +1386,12 @@ std::shared_ptr<Cluster> Reboot_cluster_from_complete_outage::do_run() {
13771386
}
13781387
}
13791388

1389+
if (rejoin_remaning_instances && !m_options.get_dry_run()) {
1390+
// and finally, rejoin all instances
1391+
rejoin_instances(cluster_impl.get(), *m_target_instance, instances,
1392+
m_options, !cluster_is_multi_primary);
1393+
}
1394+
13801395
if (m_options.get_dry_run()) {
13811396
console->print_info("dryRun finished.");
13821397
console->print_info();

unittest/scripts/auto/js_adminapi_clusterset/scripts/reboot_cluster_more.js

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,13 @@ shell.connect(__sandbox_uri1);
120120
cs = dba.getClusterSet();
121121
EXPECT_NO_THROWS(function(){ cs.rejoinCluster("replica"); });
122122

123+
// Add some data to the primary Cluster
124+
session.runSql("create schema test");
125+
session.runSql("create table test.data (a int primary key auto_increment, data longtext)");
126+
for (i = 0; i < 20; i++) {
127+
session.runSql("insert into test.data values (default, repeat('x', 4*1024*1024))");
128+
}
129+
123130
EXPECT_NO_THROWS(function(){ old_primary.rejoinInstance(__sandbox_uri5); });
124131
EXPECT_NO_THROWS(function(){ old_primary.rejoinInstance(__sandbox_uri6); });
125132

@@ -182,9 +189,13 @@ EXPECT_NO_THROWS(function(){ replica = dba.rebootClusterFromCompleteOutage("repl
182189

183190
cs.rejoinCluster("replica");
184191

185-
replica = dba.getCluster();
192+
wait_channel_ready(session, __mysql_sandbox_port6, "clusterset_replication");
193+
186194
replica.rejoinInstance(__sandbox_uri5);
187195

196+
// Rejoin creates a VCLE, so let's reconcile the GTID-set already
197+
cs.rejoinCluster("replica");
198+
188199
shell.connect(__sandbox_uri6);
189200
replica2 = dba.getCluster();
190201

@@ -354,6 +365,26 @@ CHECK_PRIMARY_CLUSTER([__sandbox_uri1, __sandbox_uri2, __sandbox_uri3], cluster)
354365
CHECK_REPLICA_CLUSTER([__sandbox_uri4, __sandbox_uri5, __sandbox_uri6], cluster, replica);
355366
CHECK_CLUSTER_SET(session);
356367

368+
//@<> Rebooting a Replica Cluster should succeed to rejoin back its members even though there are missing transactions from the Primary Cluster
369+
testutil.stopGroup([__mysql_sandbox_port4, __mysql_sandbox_port5, __mysql_sandbox_port6]);
370+
371+
shell.connect(__sandbox_uri1);
372+
373+
// Add some data to the Primary Cluster
374+
session.runSql("create schema test2");
375+
session.runSql("create table test2.data (a int primary key auto_increment, data longtext)");
376+
for (i = 0; i < 20; i++) {
377+
session.runSql("insert into test2.data values (default, repeat('x', 4*1024*1024))");
378+
}
379+
380+
shell.connect(__sandbox_uri4);
381+
382+
EXPECT_NO_THROWS(function(){ replica = dba.rebootClusterFromCompleteOutage(); });
383+
384+
CHECK_PRIMARY_CLUSTER([__sandbox_uri1, __sandbox_uri2, __sandbox_uri3], cluster);
385+
CHECK_REPLICA_CLUSTER([__sandbox_uri4, __sandbox_uri5, __sandbox_uri6], cluster, replica);
386+
CHECK_CLUSTER_SET(session);
387+
357388
//@<> Cleanup
358389
scene.destroy();
359390
testutil.destroySandbox(__mysql_sandbox_port4);

0 commit comments

Comments
 (0)