YugabyteDB (2.13.0.0-b42, bfc6a6643e7399ac8a0e81d06a3ee6d6571b33ab)

Coverage Report

Created: 2022-03-09 17:30

/Users/deen/code/yugabyte-db/src/yb/tools/yb-ts-cli-test.cc
Line
Count
Source (jump to first uncovered line)
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  You may obtain a copy of the License at
8
//
9
//   http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
//
18
// The following only applies to changes made to this file as part of YugaByte development.
19
//
20
// Portions Copyright (c) YugaByte, Inc.
21
//
22
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
23
// in compliance with the License.  You may obtain a copy of the License at
24
//
25
// http://www.apache.org/licenses/LICENSE-2.0
26
//
27
// Unless required by applicable law or agreed to in writing, software distributed under the License
28
// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
29
// or implied.  See the License for the specific language governing permissions and limitations
30
// under the License.
31
//
32
// Tests for the yb-admin command-line tool.
33
34
#include <boost/assign/list_of.hpp>
35
#include <gtest/gtest.h>
36
#include <glog/logging.h>
37
38
#include "yb/consensus/consensus.pb.h"
39
#include "yb/gutil/map-util.h"
40
#include "yb/gutil/strings/join.h"
41
#include "yb/gutil/strings/split.h"
42
#include "yb/gutil/strings/substitute.h"
43
44
#include "yb/integration-tests/cluster_itest_util.h"
45
#include "yb/integration-tests/cql_test_util.h"
46
#include "yb/integration-tests/external_mini_cluster-itest-base.h"
47
#include "yb/integration-tests/test_workload.h"
48
#include "yb/integration-tests/ts_itest-base.h"
49
50
#include "yb/master/master_client.pb.h"
51
52
#include "yb/tserver/tserver.pb.h"
53
54
#include "yb/tools/admin-test-base.h"
55
#include "yb/util/path_util.h"
56
#include "yb/util/subprocess.h"
57
58
using boost::assign::list_of;
59
using strings::Split;
60
using strings::Substitute;
61
using yb::consensus::OpIdType;
62
using yb::itest::FindTabletFollowers;
63
using yb::itest::FindTabletLeader;
64
using yb::itest::TabletServerMap;
65
using yb::itest::TServerDetails;
66
using yb::itest::WaitUntilLeader;
67
68
constexpr int kTabletTimeout = 30;
69
70
namespace yb {
71
namespace tools {
72
73
static const char* const kTsCliToolName = "yb-ts-cli";
74
75
class YBTsCliTest : public ExternalMiniClusterITestBase {
76
 protected:
77
  // Figure out where the admin tool is.
78
  string GetTsCliToolPath() const;
79
};
80
81
0
string YBTsCliTest::GetTsCliToolPath() const {
82
0
  return GetToolPath(kTsCliToolName);
83
0
}
84
85
// Test deleting a tablet.
86
0
TEST_F(YBTsCliTest, TestDeleteTablet) {
87
0
  MonoDelta timeout = MonoDelta::FromSeconds(kTabletTimeout);
88
0
  std::vector<std::string> ts_flags = {
89
0
    "--enable_leader_failure_detection=false"s,
90
0
  };
91
0
  std::vector<std::string> master_flags = {
92
0
    "--catalog_manager_wait_for_new_tablets_to_elect_leader=false"s,
93
0
    "--use_create_table_leader_hint=false"s,
94
0
  };
95
0
  ASSERT_NO_FATALS(StartCluster(ts_flags, master_flags));
96
97
0
  TestWorkload workload(cluster_.get());
98
0
  workload.Setup(); // Easy way to create a new tablet.
99
100
0
  vector<tserver::ListTabletsResponsePB::StatusAndSchemaPB> tablets;
101
0
  for (const auto& entry : ts_map_) {
102
0
    TServerDetails* ts = entry.second.get();
103
0
    ASSERT_OK(itest::WaitForNumTabletsOnTS(ts, 1, timeout, &tablets));
104
0
  }
105
0
  string tablet_id = tablets[0].tablet_status().tablet_id();
106
107
0
  for (size_t i = 0; i < cluster_->num_tablet_servers(); i++) {
108
0
    ASSERT_OK(itest::WaitUntilTabletRunning(ts_map_[cluster_->tablet_server(i)->uuid()].get(),
109
0
                                            tablet_id, timeout));
110
0
  }
111
112
0
  string exe_path = GetTsCliToolPath();
113
0
  vector<string> argv;
114
0
  argv.push_back(exe_path);
115
0
  argv.push_back("--server_address");
116
0
  argv.push_back(yb::ToString(cluster_->tablet_server(0)->bound_rpc_addr()));
117
0
  argv.push_back("delete_tablet");
118
0
  argv.push_back(tablet_id);
119
0
  argv.push_back("Deleting for yb-ts-cli-test");
120
0
  ASSERT_OK(Subprocess::Call(argv));
121
122
0
  ASSERT_OK(inspect_->WaitForTabletDataStateOnTS(0, tablet_id, tablet::TABLET_DATA_TOMBSTONED));
123
0
  TServerDetails* ts = ts_map_[cluster_->tablet_server(0)->uuid()].get();
124
0
  ASSERT_OK(itest::WaitUntilTabletInState(ts, tablet_id, tablet::SHUTDOWN, timeout));
125
0
}
126
127
// Test readiness check before and after a tablet server is done bootstrapping.
128
0
TEST_F(YBTsCliTest, TestTabletServerReadiness) {
129
0
  MonoDelta timeout = MonoDelta::FromSeconds(kTabletTimeout);
130
0
  ASSERT_NO_FATALS(StartCluster({ "--TEST_tablet_bootstrap_delay_ms=2000"s }));
131
132
0
  TestWorkload workload(cluster_.get());
133
0
  workload.Setup(); // Easy way to create a new tablet.
134
135
0
  vector<tserver::ListTabletsResponsePB::StatusAndSchemaPB> tablets;
136
0
  for (const auto& entry : ts_map_) {
137
0
    TServerDetails* ts = entry.second.get();
138
0
    ASSERT_OK(itest::WaitForNumTabletsOnTS(ts, 1, timeout, &tablets));
139
0
  }
140
0
  string tablet_id = tablets[0].tablet_status().tablet_id();
141
142
0
  for (size_t i = 0; i < cluster_->num_tablet_servers(); i++) {
143
0
    ASSERT_OK(itest::WaitUntilTabletRunning(ts_map_[cluster_->tablet_server(i)->uuid()].get(),
144
0
                                            tablet_id, timeout));
145
0
  }
146
147
0
  ASSERT_NO_FATALS(cluster_->tablet_server(0)->Shutdown());
148
0
  ASSERT_OK(cluster_->tablet_server(0)->Restart());
149
150
0
  string exe_path = GetTsCliToolPath();
151
0
  vector<string> argv;
152
0
  argv.push_back(exe_path);
153
0
  argv.push_back("--server_address");
154
0
  argv.push_back(yb::ToString(cluster_->tablet_server(0)->bound_rpc_addr()));
155
0
  argv.push_back("is_server_ready");
156
0
  ASSERT_NOK(Subprocess::Call(argv));
157
158
0
  ASSERT_OK(WaitFor([&]() {
159
0
    return Subprocess::Call(argv).ok();
160
0
  }, MonoDelta::FromSeconds(10), "Wait for tablet bootstrap to finish"));
161
0
}
162
163
0
TEST_F(YBTsCliTest, TestRefreshFlags) {
164
0
  std::string gflag = "client_read_write_timeout_ms";
165
0
  std::string old_value = "120000";
166
0
  std::string new_value = "150000";
167
168
  // Write a flagfile.
169
0
  std::string flag_filename = JoinPathSegments(GetTestDataDirectory(), "flagfile.test");
170
0
  std::unique_ptr<WritableFile> flag_file;
171
0
  ASSERT_OK(Env::Default()->NewWritableFile(flag_filename, &flag_file));
172
0
  ASSERT_OK(flag_file->Append("--" + gflag + "=" + old_value));
173
0
  ASSERT_OK(flag_file->Close());
174
175
  // Start the cluster;
176
0
  vector<string> extra_flags = {"--flagfile=" + flag_filename};
177
178
0
  ASSERT_NO_FATALS(StartCluster(extra_flags, extra_flags));
179
180
  // Verify that the cluster is started with the custom GFlag value in the config.
181
0
  auto master_flag = ASSERT_RESULT(cluster_->master(0)->GetFlag(gflag));
182
0
  ASSERT_EQ(master_flag, old_value);
183
0
  auto ts_flag = ASSERT_RESULT(cluster_->tablet_server(0)->GetFlag(gflag));
184
0
  ASSERT_EQ(ts_flag, old_value);
185
186
  // Change the flagfile to have a different value for the GFlag.
187
0
  ASSERT_OK(Env::Default()->NewWritableFile(flag_filename, &flag_file));
188
0
  ASSERT_OK(flag_file->Append("--" + gflag + "=" + new_value));
189
0
  ASSERT_OK(flag_file->Close());
190
191
  // Send RefreshFlags RPC to the Master process
192
0
  string exe_path = GetTsCliToolPath();
193
0
  vector<string> argv;
194
0
  argv.push_back(exe_path);
195
0
  argv.push_back("--server_address");
196
0
  argv.push_back(yb::ToString(cluster_->master(0)->bound_rpc_addr()));
197
0
  argv.push_back("refresh_flags");
198
0
  ASSERT_OK(Subprocess::Call(argv));
199
200
  // Wait for the master process to have the updated GFlag value.
201
0
  ASSERT_OK(LoggedWaitFor([&]() -> Result<bool> {
202
0
    return VERIFY_RESULT(cluster_->master(0)->GetFlag(gflag)) == new_value;
203
0
  }, MonoDelta::FromSeconds(60), "Verify updated GFlag"));
204
205
  // The TServer should still have the old value because we didn't send it the RPC.
206
0
  ts_flag = ASSERT_RESULT(cluster_->tablet_server(0)->GetFlag(gflag));
207
0
  ASSERT_EQ(ts_flag, old_value);
208
209
  // Now, send a RefreshFlags RPC to the TServer process
210
0
  argv.clear();
211
0
  argv.push_back(exe_path);
212
0
  argv.push_back("--server_address");
213
0
  argv.push_back(yb::ToString(cluster_->tablet_server(0)->bound_rpc_addr()));
214
0
  argv.push_back("refresh_flags");
215
0
  ASSERT_OK(Subprocess::Call(argv));
216
217
  // Wait for the TS process to have the updated GFlag value.
218
0
  ASSERT_OK(LoggedWaitFor([&]() -> Result<bool> {
219
0
    return VERIFY_RESULT(cluster_->tablet_server(0)->GetFlag(gflag)) == new_value;
220
0
  }, MonoDelta::FromSeconds(60), "Verify updated GFlag"));
221
0
}
222
223
class YBTsCliUnsafeChangeTest : public AdminTestBase {
224
 public:
225
  // Figure out where the admin tool is.
226
  string GetTsCliToolPath() const;
227
};
228
229
0
string YBTsCliUnsafeChangeTest::GetTsCliToolPath() const {
230
0
  return GetToolPath(kTsCliToolName);
231
0
}
232
233
Status RunUnsafeChangeConfig(
234
    YBTsCliUnsafeChangeTest* test,
235
    const string& tablet_id,
236
    const string& dst_host,
237
0
    const vector<string>& peer_uuid_list) {
238
0
  LOG(INFO) << " Called RunUnsafeChangeConfig with " << tablet_id << " " << dst_host << " and "
239
0
            << yb::ToString(peer_uuid_list);
240
0
  std::vector<string> params;
241
0
  params.push_back(test->GetTsCliToolPath());
242
0
  params.push_back("--server_address");
243
0
  params.push_back(dst_host);
244
0
  params.push_back("unsafe_config_change");
245
0
  params.push_back(tablet_id);
246
0
  for (const auto& peer : peer_uuid_list) {
247
0
    params.push_back(peer);
248
0
  }
249
0
  VERIFY_RESULT(test->CallAdminVec(params));
250
0
  return Status::OK();
251
0
}
252
253
// Test unsafe config change when there is one follower survivor in the cluster.
254
// 1. Instantiate external mini cluster with 1 tablet having 3 replicas and 5 TS.
255
// 2. Shut down leader and follower1.
256
// 3. Trigger unsafe config change on follower2 having follower2 in the config.
257
// 4. Wait until the new config is populated on follower2(new leader) and master.
258
// 5. Bring up leader and follower1 and verify replicas are deleted.
259
// 6. Verify that new config doesn't contain old leader and follower1.
260
0
TEST_F(YBTsCliUnsafeChangeTest, TestUnsafeChangeConfigOnSingleFollower) {
261
0
  MonoDelta kTimeout = MonoDelta::FromSeconds(30);
262
0
  FLAGS_num_tablet_servers = 5;
263
0
  FLAGS_num_replicas = 3;
264
  // tserver_unresponsive_timeout_ms is useful so that master considers
265
  // the live tservers for tablet re-replication.
266
0
  std::vector<std::string> master_flags;
267
0
  std::vector<std::string> ts_flags;
268
0
  master_flags.push_back("--enable_load_balancing=true");
269
0
  BuildAndStart(ts_flags, master_flags);
270
271
0
  LOG(INFO) << "Finding tablet leader and waiting for things to start...";
272
0
  string tablet_id = tablet_replicas_.begin()->first;
273
274
  // Determine the list of tablet servers currently in the config.
275
0
  itest::TabletServerMapUnowned active_tablet_servers;
276
0
  auto iter = tablet_replicas_.equal_range(tablet_id_);
277
0
  for (auto it = iter.first; it != iter.second; ++it) {
278
0
    InsertOrDie(&active_tablet_servers, it->second->uuid(), it->second);
279
0
  }
280
281
  // Get a baseline config reported to the master.
282
0
  LOG(INFO) << "Waiting for Master to see the current replicas...";
283
0
  master::TabletLocationsPB tablet_locations;
284
0
  bool has_leader;
285
0
  ASSERT_OK(itest::WaitForReplicasReportedToMaster(cluster_.get(),
286
0
                                                   3, tablet_id_, kTimeout,
287
0
                                                   itest::WAIT_FOR_LEADER,
288
0
                                                   &has_leader, &tablet_locations));
289
0
  LOG(INFO) << "Tablet locations:\n" << yb::ToString(tablet_locations);
290
0
  ASSERT_TRUE(has_leader) << yb::ToString(tablet_locations);
291
292
0
  TServerDetails* leader_ts;
293
0
  ASSERT_OK(FindTabletLeader(active_tablet_servers, tablet_id_, kTimeout, &leader_ts));
294
0
  vector<TServerDetails*> followers;
295
0
  ASSERT_OK(FindTabletFollowers(active_tablet_servers, tablet_id_, kTimeout, &followers));
296
  // Wait for initial NO_OP to be committed by the leader.
297
0
  ASSERT_OK(WaitForOpFromCurrentTerm(leader_ts, tablet_id_, OpIdType::COMMITTED_OPID, kTimeout));
298
299
  // Shut down master so it doesn't interfere while we shut down the leader and
300
  // one of the other followers.
301
0
  cluster_->master()->Shutdown();
302
0
  cluster_->tablet_server_by_uuid(leader_ts->uuid())->Shutdown();
303
0
  cluster_->tablet_server_by_uuid(followers[1]->uuid())->Shutdown();
304
305
0
  LOG(INFO) << "Forcing unsafe config change on remaining follower " << followers[0]->uuid();
306
0
  const string& follower0_addr =
307
0
      cluster_->tablet_server_by_uuid(followers[0]->uuid())->bound_rpc_addr().ToString();
308
0
  ASSERT_OK(RunUnsafeChangeConfig(this, tablet_id_, follower0_addr, {followers[0]->uuid()}));
309
0
  ASSERT_OK(WaitUntilLeader(followers[0], tablet_id_, kTimeout));
310
0
  ASSERT_OK(WaitUntilCommittedConfigNumVotersIs(1, followers[0], tablet_id_, kTimeout));
311
312
0
  LOG(INFO) << "Restarting master...";
313
314
  // Restart master so it can re-replicate the tablet to remaining tablet servers.
315
0
  ASSERT_OK(cluster_->master()->Restart());
316
317
  // Wait for master to re-replicate.
318
0
  ASSERT_OK(WaitUntilCommittedConfigNumVotersIs(3, followers[0], tablet_id_, kTimeout));
319
0
  ASSERT_OK(itest::WaitForReplicasReportedToMaster(
320
0
      cluster_.get(), 3, tablet_id_, kTimeout, itest::WAIT_FOR_LEADER, &has_leader,
321
0
      &tablet_locations));
322
0
  OpId opid;
323
0
  ASSERT_OK(WaitForOpFromCurrentTerm(
324
0
      followers[0], tablet_id_, OpIdType::COMMITTED_OPID, kTimeout, &opid));
325
326
0
  active_tablet_servers.clear();
327
0
  std::unordered_set<string> replica_uuids;
328
0
  for (const auto& loc : tablet_locations.replicas()) {
329
0
    const string& uuid = loc.ts_info().permanent_uuid();
330
0
    InsertOrDie(&active_tablet_servers, uuid, tablet_servers_[uuid].get());
331
0
  }
332
0
  ASSERT_OK(WaitForServersToAgree(kTimeout, active_tablet_servers, tablet_id_, opid.index));
333
334
  // Verify that two new servers are part of new config and old
335
  // servers are gone.
336
0
  for (const master::TabletLocationsPB_ReplicaPB& replica : tablet_locations.replicas()) {
337
0
    ASSERT_NE(replica.ts_info().permanent_uuid(), followers[1]->uuid());
338
0
    ASSERT_NE(replica.ts_info().permanent_uuid(), leader_ts->uuid());
339
0
  }
340
341
  // Also verify that when we bring back followers[1] and leader,
342
  // we should see the tablet in TOMBSTONED state on these servers.
343
0
  ASSERT_OK(cluster_->tablet_server_by_uuid(leader_ts->uuid())->Restart());
344
0
  ASSERT_OK(cluster_->tablet_server_by_uuid(followers[1]->uuid())->Restart());
345
0
  ASSERT_OK(itest::WaitUntilTabletInState(leader_ts, tablet_id, tablet::SHUTDOWN, kTimeout));
346
0
  ASSERT_OK(itest::WaitUntilTabletInState(followers[1], tablet_id, tablet::SHUTDOWN, kTimeout));
347
0
}
348
349
// Test unsafe config change when there is one leader survivor in the cluster.
350
// 1. Instantiate external mini cluster with 1 tablet having 3 replicas and 5 TS.
351
// 2. Shut down both followers.
352
// 3. Trigger unsafe config change on leader having leader in the config.
353
// 4. Wait until the new config is populated on leader and master.
354
// 5. Verify that new config does not contain old followers.
355
0
TEST_F(YBTsCliUnsafeChangeTest, TestUnsafeChangeConfigOnSingleLeader) {
356
0
  MonoDelta kTimeout = MonoDelta::FromSeconds(30);
357
0
  FLAGS_num_tablet_servers = 5;
358
0
  FLAGS_num_replicas = 3;
359
0
  std::vector<std::string> master_flags;
360
0
  std::vector<std::string> ts_flags;
361
0
  master_flags.push_back("--enable_load_balancing=true");
362
0
  BuildAndStart(ts_flags, master_flags);
363
364
  // Determine the list of tablet servers currently in the config.
365
0
  itest::TabletServerMapUnowned active_tablet_servers;
366
0
  auto iter = tablet_replicas_.equal_range(tablet_id_);
367
0
  for (auto it = iter.first; it != iter.second; ++it) {
368
0
    InsertOrDie(&active_tablet_servers, it->second->uuid(), it->second);
369
0
  }
370
371
  // Get a baseline config reported to the master.
372
0
  LOG(INFO) << "Waiting for Master to see the current replicas...";
373
0
  master::TabletLocationsPB tablet_locations;
374
0
  bool has_leader;
375
0
  ASSERT_OK(itest::WaitForReplicasReportedToMaster(
376
0
      cluster_.get(), 3, tablet_id_, kTimeout, itest::WAIT_FOR_LEADER, &has_leader,
377
0
      &tablet_locations));
378
0
  LOG(INFO) << "Tablet locations:\n" << yb::ToString(tablet_locations);
379
0
  ASSERT_TRUE(has_leader) << yb::ToString(tablet_locations);
380
381
0
  TServerDetails* leader_ts;
382
0
  ASSERT_OK(FindTabletLeader(active_tablet_servers, tablet_id_, kTimeout, &leader_ts));
383
0
  vector<TServerDetails*> followers;
384
0
  ASSERT_OK(FindTabletFollowers(active_tablet_servers, tablet_id_, kTimeout, &followers));
385
  // Wait for initial NO_OP to be committed by the leader.
386
0
  ASSERT_OK(WaitForOpFromCurrentTerm(leader_ts, tablet_id_, OpIdType::COMMITTED_OPID, kTimeout));
387
388
  // Shut down servers follower1 and follower2,
389
  // so that we can force new config on remaining leader.
390
0
  cluster_->tablet_server_by_uuid(followers[0]->uuid())->Shutdown();
391
0
  cluster_->tablet_server_by_uuid(followers[1]->uuid())->Shutdown();
392
  // Restart master to cleanup cache of dead servers from its list of candidate
393
  // servers to trigger placement of new replicas on healthy servers.
394
0
  cluster_->master()->Shutdown();
395
0
  ASSERT_OK(cluster_->master()->Restart());
396
397
0
  LOG(INFO) << "Forcing unsafe config change on tserver " << leader_ts->uuid();
398
0
  const string& leader_addr = Substitute(
399
0
      "$0:$1",
400
0
      leader_ts->registration->common().private_rpc_addresses(0).host(),
401
0
      leader_ts->registration->common().private_rpc_addresses(0).port());
402
0
  const vector<string> peer_uuid_list = {leader_ts->uuid()};
403
0
  ASSERT_OK(RunUnsafeChangeConfig(this, tablet_id_, leader_addr, peer_uuid_list));
404
405
  // Check that new config is populated to a new follower.
406
0
  TServerDetails* new_follower = nullptr;
407
0
  vector<TServerDetails*> all_tservers = TServerDetailsVector(tablet_servers_);
408
0
  for (const auto& ts : all_tservers) {
409
0
    if (!ContainsKey(active_tablet_servers, ts->uuid())) {
410
0
      new_follower = ts;
411
0
      break;
412
0
    }
413
0
  }
414
0
  ASSERT_TRUE(new_follower != nullptr);
415
416
  // Master may try to add the servers which are down until tserver_unresponsive_timeout_ms,
417
  // so it is safer to wait until consensus metadata has 3 voters on new_follower.
418
0
  ASSERT_OK(WaitUntilCommittedConfigNumVotersIs(3, new_follower, tablet_id_, kTimeout));
419
420
  // Wait for the master to be notified of the config change.
421
0
  LOG(INFO) << "Waiting for Master to see new config...";
422
0
  ASSERT_OK(itest::WaitForReplicasReportedToMaster(
423
0
      cluster_.get(), 3, tablet_id_, kTimeout, itest::WAIT_FOR_LEADER, &has_leader,
424
0
      &tablet_locations));
425
0
  LOG(INFO) << "Tablet locations:\n" << yb::ToString(tablet_locations);
426
0
  for (const master::TabletLocationsPB_ReplicaPB& replica : tablet_locations.replicas()) {
427
0
    ASSERT_NE(replica.ts_info().permanent_uuid(), followers[0]->uuid());
428
0
    ASSERT_NE(replica.ts_info().permanent_uuid(), followers[1]->uuid());
429
0
  }
430
0
}
431
432
// Test unsafe config change when the unsafe config contains 2 nodes.
433
// 1. Instantiate external minicluster with 1 tablet having 3 replicas and 5 TS.
434
// 2. Shut down leader.
435
// 3. Trigger unsafe config change on follower1 having follower1 and follower2 in the config.
436
// 4. Wait until the new config is populated on new_leader and master.
437
// 5. Verify that new config does not contain old leader.
438
0
TEST_F(YBTsCliUnsafeChangeTest, TestUnsafeChangeConfigForConfigWithTwoNodes) {
439
0
  MonoDelta kTimeout = MonoDelta::FromSeconds(30);
440
0
  FLAGS_num_tablet_servers = 4;
441
0
  FLAGS_num_replicas = 3;
442
0
  std::vector<std::string> master_flags;
443
0
  std::vector<std::string> ts_flags;
444
0
  master_flags.push_back("--enable_load_balancing=true");
445
0
  BuildAndStart(ts_flags, master_flags);
446
447
  // Determine the list of tablet servers currently in the config.
448
0
  itest::TabletServerMapUnowned active_tablet_servers;
449
0
  auto iter = tablet_replicas_.equal_range(tablet_id_);
450
0
  for (auto it = iter.first; it != iter.second; ++it) {
451
0
    InsertOrDie(&active_tablet_servers, it->second->uuid(), it->second);
452
0
  }
453
454
  // Get a baseline config reported to the master.
455
0
  LOG(INFO) << "Waiting for Master to see the current replicas...";
456
0
  master::TabletLocationsPB tablet_locations;
457
0
  bool has_leader;
458
0
  ASSERT_OK(itest::WaitForReplicasReportedToMaster(
459
0
      cluster_.get(), 3, tablet_id_, kTimeout, itest::WAIT_FOR_LEADER, &has_leader,
460
0
      &tablet_locations));
461
0
  LOG(INFO) << "Tablet locations:\n" << yb::ToString(tablet_locations);
462
0
  ASSERT_TRUE(has_leader) << yb::ToString(tablet_locations);
463
464
0
  TServerDetails* leader_ts;
465
0
  ASSERT_OK(FindTabletLeader(active_tablet_servers, tablet_id_, kTimeout, &leader_ts));
466
0
  vector<TServerDetails*> followers;
467
0
  ASSERT_OK(FindTabletFollowers(active_tablet_servers, tablet_id_, kTimeout, &followers));
468
  // Wait for initial NO_OP to be committed by the leader.
469
0
  ASSERT_OK(WaitForOpFromCurrentTerm(leader_ts, tablet_id_, OpIdType::COMMITTED_OPID, kTimeout));
470
471
  // Shut down leader and prepare 2-node config.
472
0
  cluster_->tablet_server_by_uuid(leader_ts->uuid())->Shutdown();
473
  // Restart master to cleanup cache of dead servers from its list of candidate
474
  // servers to trigger placement of new replicas on healthy servers.
475
0
  cluster_->master()->Shutdown();
476
0
  ASSERT_OK(cluster_->master()->Restart());
477
478
0
  LOG(INFO) << "Forcing unsafe config change on tserver " << followers[1]->uuid();
479
0
  const string& follower1_addr = Substitute(
480
0
      "$0:$1",
481
0
      followers[1]->registration->common().private_rpc_addresses(0).host(),
482
0
      followers[1]->registration->common().private_rpc_addresses(0).port());
483
0
  const vector<string> peer_uuid_list = {
484
0
      followers[0]->uuid(),
485
0
      followers[1]->uuid(),
486
0
  };
487
0
  ASSERT_OK(RunUnsafeChangeConfig(this, tablet_id_, follower1_addr, peer_uuid_list));
488
489
  // Find a remaining node which will be picked for re-replication.
490
0
  vector<TServerDetails*> all_tservers = TServerDetailsVector(tablet_servers_);
491
0
  TServerDetails* new_node = nullptr;
492
0
  for (TServerDetails* ts : all_tservers) {
493
0
    if (!ContainsKey(active_tablet_servers, ts->uuid())) {
494
0
      new_node = ts;
495
0
      break;
496
0
    }
497
0
  }
498
0
  ASSERT_TRUE(new_node != nullptr);
499
500
  // Master may try to add the servers which are down until tserver_unresponsive_timeout_ms,
501
  // so it is safer to wait until consensus metadata has 3 voters on follower1.
502
0
  ASSERT_OK(WaitUntilCommittedConfigNumVotersIs(3, new_node, tablet_id_, kTimeout));
503
504
  // Wait for the master to be notified of the config change.
505
0
  LOG(INFO) << "Waiting for Master to see new config...";
506
0
  ASSERT_OK(itest::WaitForReplicasReportedToMaster(
507
0
      cluster_.get(), 3, tablet_id_, kTimeout, itest::WAIT_FOR_LEADER, &has_leader,
508
0
      &tablet_locations));
509
0
  LOG(INFO) << "Tablet locations:\n" << yb::ToString(tablet_locations);
510
0
  for (const master::TabletLocationsPB_ReplicaPB& replica : tablet_locations.replicas()) {
511
0
    ASSERT_NE(replica.ts_info().permanent_uuid(), leader_ts->uuid());
512
0
  }
513
0
}
514
515
// Test unsafe config change on a 5-replica tablet when the unsafe config contains 2 nodes.
516
// 1. Instantiate external minicluster with 1 tablet having 5 replicas and 8 TS.
517
// 2. Shut down leader and 2 followers.
518
// 3. Trigger unsafe config change on a surviving follower with those
519
//    2 surviving followers in the new config.
520
// 4. Wait until the new config is populated on new_leader and master.
521
// 5. Verify that new config does not contain old leader and old followers.
522
0
TEST_F(YBTsCliUnsafeChangeTest, TestUnsafeChangeConfigWithFiveReplicaConfig) {
523
0
  MonoDelta kTimeout = MonoDelta::FromSeconds(30);
524
0
  FLAGS_num_tablet_servers = 8;
525
0
  FLAGS_num_replicas = 5;
526
0
  std::vector<std::string> master_flags;
527
0
  std::vector<std::string> ts_flags;
528
0
  master_flags.push_back("--enable_load_balancing=true");
529
0
  BuildAndStart(ts_flags, master_flags);
530
531
0
  vector<ExternalTabletServer*> external_tservers;
532
0
  vector<TServerDetails*> tservers = TServerDetailsVector(tablet_servers_);
533
0
  for (TServerDetails* ts : tservers) {
534
0
    external_tservers.push_back(cluster_->tablet_server_by_uuid(ts->uuid()));
535
0
  }
536
537
  // Determine the list of tablet servers currently in the config.
538
0
  itest::TabletServerMapUnowned active_tablet_servers;
539
0
  auto iter = tablet_replicas_.equal_range(tablet_id_);
540
0
  for (auto it = iter.first; it != iter.second; ++it) {
541
0
    InsertOrDie(&active_tablet_servers, it->second->uuid(), it->second);
542
0
  }
543
544
  // Get a baseline config reported to the master.
545
0
  LOG(INFO) << "Waiting for Master to see the current replicas...";
546
0
  master::TabletLocationsPB tablet_locations;
547
0
  bool has_leader;
548
0
  ASSERT_OK(itest::WaitForReplicasReportedToMaster(
549
0
      cluster_.get(), 5, tablet_id_, kTimeout, itest::WAIT_FOR_LEADER, &has_leader,
550
0
      &tablet_locations));
551
0
  LOG(INFO) << "Tablet locations:\n" << yb::ToString(tablet_locations);
552
0
  ASSERT_TRUE(has_leader) << yb::ToString(tablet_locations);
553
554
0
  TServerDetails* leader_ts;
555
0
  ASSERT_OK(FindTabletLeader(active_tablet_servers, tablet_id_, kTimeout, &leader_ts));
556
0
  ASSERT_OK(itest::WaitUntilCommittedOpIdIndexIs(1, leader_ts, tablet_id_, kTimeout));
557
0
  vector<TServerDetails*> followers;
558
0
  ASSERT_OK(FindTabletFollowers(active_tablet_servers, tablet_id_, kTimeout, &followers));
559
0
  ASSERT_EQ(followers.size(), 4);
560
  // Wait for initial NO_OP to be committed by the leader.
561
0
  ASSERT_OK(WaitForOpFromCurrentTerm(leader_ts, tablet_id_, OpIdType::COMMITTED_OPID, kTimeout));
562
563
0
  cluster_->tablet_server_by_uuid(followers[2]->uuid())->Shutdown();
564
0
  cluster_->tablet_server_by_uuid(followers[3]->uuid())->Shutdown();
565
0
  cluster_->tablet_server_by_uuid(leader_ts->uuid())->Shutdown();
566
  // Restart master to cleanup cache of dead servers from its list of candidate
567
  // servers to trigger placement of new replicas on healthy servers.
568
0
  cluster_->master()->Shutdown();
569
0
  ASSERT_OK(cluster_->master()->Restart());
570
571
0
  LOG(INFO) << "Forcing unsafe config change on tserver " << followers[1]->uuid();
572
0
  const string& follower1_addr = Substitute(
573
0
      "$0:$1",
574
0
      followers[1]->registration->common().private_rpc_addresses(0).host(),
575
0
      followers[1]->registration->common().private_rpc_addresses(0).port());
576
0
  const vector<string> peer_uuid_list = {
577
0
      followers[0]->uuid(),
578
0
      followers[1]->uuid(),
579
0
  };
580
0
  ASSERT_OK(RunUnsafeChangeConfig(this, tablet_id_, follower1_addr, peer_uuid_list));
581
582
  // Find a remaining node which will be picked for re-replication.
583
0
  vector<TServerDetails*> all_tservers = TServerDetailsVector(tablet_servers_);
584
0
  TServerDetails* new_node = nullptr;
585
0
  for (TServerDetails* ts : all_tservers) {
586
0
    if (!ContainsKey(active_tablet_servers, ts->uuid())) {
587
0
      new_node = ts;
588
0
      break;
589
0
    }
590
0
  }
591
0
  ASSERT_TRUE(new_node != nullptr);
592
593
  // Master may try to add the servers which are down until tserver_unresponsive_timeout_ms,
594
  // so it is safer to wait until consensus metadata has 5 voters back on new_node.
595
0
  ASSERT_OK(WaitUntilCommittedConfigNumVotersIs(5, new_node, tablet_id_, kTimeout));
596
597
  // Wait for the master to be notified of the config change.
598
0
  LOG(INFO) << "Waiting for Master to see new config...";
599
0
  ASSERT_OK(itest::WaitForReplicasReportedToMaster(
600
0
      cluster_.get(), 5, tablet_id_, kTimeout, itest::WAIT_FOR_LEADER, &has_leader,
601
0
      &tablet_locations));
602
0
  LOG(INFO) << "Tablet locations:\n" << yb::ToString(tablet_locations);
603
0
  for (const master::TabletLocationsPB_ReplicaPB& replica : tablet_locations.replicas()) {
604
0
    ASSERT_NE(replica.ts_info().permanent_uuid(), leader_ts->uuid());
605
0
    ASSERT_NE(replica.ts_info().permanent_uuid(), followers[2]->uuid());
606
0
    ASSERT_NE(replica.ts_info().permanent_uuid(), followers[3]->uuid());
607
0
  }
608
0
}
609
610
// Test unsafe config change when there is a pending config on a surviving leader.
611
// 1. Instantiate external minicluster with 1 tablet having 3 replicas and 5 TS.
612
// 2. Shut down both the followers.
613
// 3. Trigger a regular config change on the leader which remains pending on leader.
614
// 4. Trigger unsafe config change on the surviving leader.
615
// 5. Wait until the new config is populated on leader and master.
616
// 6. Verify that new config does not contain old followers and a standby node
617
//    has populated the new config.
618
0
TEST_F(YBTsCliUnsafeChangeTest, TestUnsafeChangeConfigLeaderWithPendingConfig) {
619
0
  MonoDelta kTimeout = MonoDelta::FromSeconds(30);
620
0
  FLAGS_num_tablet_servers = 5;
621
0
  FLAGS_num_replicas = 3;
622
0
  std::vector<std::string> master_flags;
623
0
  std::vector<std::string> ts_flags;
624
0
  master_flags.push_back("--enable_load_balancing=true");
625
0
  BuildAndStart(ts_flags, master_flags);
626
627
  // Determine the list of tablet servers currently in the config.
628
0
  itest::TabletServerMapUnowned active_tablet_servers;
629
0
  auto iter = tablet_replicas_.equal_range(tablet_id_);
630
0
  for (auto it = iter.first; it != iter.second; ++it) {
631
0
    InsertOrDie(&active_tablet_servers, it->second->uuid(), it->second);
632
0
  }
633
634
  // Get a baseline config reported to the master.
635
0
  LOG(INFO) << "Waiting for Master to see the current replicas...";
636
0
  master::TabletLocationsPB tablet_locations;
637
0
  bool has_leader;
638
0
  ASSERT_OK(itest::WaitForReplicasReportedToMaster(
639
0
      cluster_.get(), 3, tablet_id_, kTimeout, itest::WAIT_FOR_LEADER, &has_leader,
640
0
      &tablet_locations));
641
0
  LOG(INFO) << "Tablet locations:\n" << yb::ToString(tablet_locations);
642
0
  ASSERT_TRUE(has_leader) << yb::ToString(tablet_locations);
643
644
0
  TServerDetails* leader_ts;
645
0
  ASSERT_OK(FindTabletLeader(active_tablet_servers, tablet_id_, kTimeout, &leader_ts));
646
0
  vector<TServerDetails*> followers;
647
0
  ASSERT_OK(FindTabletFollowers(active_tablet_servers, tablet_id_, kTimeout, &followers));
648
0
  ASSERT_EQ(followers.size(), 2);
649
  // Wait for initial NO_OP to be committed by the leader.
650
0
  ASSERT_OK(WaitForOpFromCurrentTerm(leader_ts, tablet_id_, OpIdType::COMMITTED_OPID, kTimeout));
651
652
  // Shut down servers follower1 and follower2,
653
  // so that leader can't replicate future config change ops.
654
0
  cluster_->tablet_server_by_uuid(followers[0]->uuid())->Shutdown();
655
0
  cluster_->tablet_server_by_uuid(followers[1]->uuid())->Shutdown();
656
657
  // Now try to replicate a ChangeConfig operation. This should get stuck and time out
658
  // because the server can't replicate any operations.
659
0
  Status s =
660
0
      RemoveServer(leader_ts, tablet_id_, followers[1], -1, MonoDelta::FromSeconds(2), nullptr);
661
0
  ASSERT_TRUE(s.IsTimedOut());
662
663
0
  LOG(INFO) << "Change Config Op timed out, Sending a Replace config "
664
0
            << "command when change config op is pending on the leader.";
665
0
  const string& leader_addr = Substitute(
666
0
      "$0:$1",
667
0
      leader_ts->registration->common().private_rpc_addresses(0).host(),
668
0
      leader_ts->registration->common().private_rpc_addresses(0).port());
669
0
  const vector<string> peer_uuid_list = {leader_ts->uuid()};
670
0
  ASSERT_OK(RunUnsafeChangeConfig(this, tablet_id_, leader_addr, peer_uuid_list));
671
672
  // Restart master to cleanup cache of dead servers from its list of candidate
673
  // servers to trigger placement of new replicas on healthy servers.
674
0
  cluster_->master()->Shutdown();
675
0
  ASSERT_OK(cluster_->master()->Restart());
676
677
  // Find a remaining node which will be picked for re-replication.
678
0
  vector<TServerDetails*> all_tservers = TServerDetailsVector(tablet_servers_);
679
0
  TServerDetails* new_node = nullptr;
680
0
  for (TServerDetails* ts : all_tservers) {
681
0
    if (!ContainsKey(active_tablet_servers, ts->uuid())) {
682
0
      new_node = ts;
683
0
      break;
684
0
    }
685
0
  }
686
0
  ASSERT_TRUE(new_node != nullptr);
687
688
  // Master may try to add the servers which are down until tserver_unresponsive_timeout_ms,
689
  // so it is safer to wait until consensus metadata has 3 voters on new_node.
690
0
  ASSERT_OK(WaitUntilCommittedConfigNumVotersIs(3, new_node, tablet_id_, kTimeout));
691
692
  // Wait for the master to be notified of the config change.
693
0
  LOG(INFO) << "Waiting for Master to see new config...";
694
0
  ASSERT_OK(itest::WaitForReplicasReportedToMaster(
695
0
      cluster_.get(), 3, tablet_id_, kTimeout, itest::WAIT_FOR_LEADER, &has_leader,
696
0
      &tablet_locations));
697
0
  LOG(INFO) << "Tablet locations:\n" << yb::ToString(tablet_locations);
698
0
  for (const master::TabletLocationsPB_ReplicaPB& replica : tablet_locations.replicas()) {
699
0
    ASSERT_NE(replica.ts_info().permanent_uuid(), followers[0]->uuid());
700
0
    ASSERT_NE(replica.ts_info().permanent_uuid(), followers[1]->uuid());
701
0
  }
702
0
}
703
704
// Test unsafe config change when there is a pending config on a surviving follower.
705
// 1. Instantiate external minicluster with 1 tablet having 3 replicas and 5 TS.
706
// 2. Shut down both the followers.
707
// 3. Trigger a regular config change on the leader which remains pending on leader.
708
// 4. Trigger a leader_step_down command such that leader is forced to become follower.
709
// 5. Trigger unsafe config change on the follower.
710
// 6. Wait until the new config is populated on leader and master.
711
// 7. Verify that new config does not contain old followers and a standby node
712
//    has populated the new config.
713
0
TEST_F(YBTsCliUnsafeChangeTest, TestUnsafeChangeConfigFollowerWithPendingConfig) {
714
0
  MonoDelta kTimeout = MonoDelta::FromSeconds(30);
715
0
  FLAGS_num_tablet_servers = 5;
716
0
  FLAGS_num_replicas = 3;
717
0
  std::vector<std::string> master_flags;
718
0
  std::vector<std::string> ts_flags;
719
0
  master_flags.push_back("--enable_load_balancing=true");
720
0
  BuildAndStart(ts_flags, master_flags);
721
722
  // Determine the list of tablet servers currently in the config.
723
0
  itest::TabletServerMapUnowned active_tablet_servers;
724
0
  auto iter = tablet_replicas_.equal_range(tablet_id_);
725
0
  for (auto it = iter.first; it != iter.second; ++it) {
726
0
    InsertOrDie(&active_tablet_servers, it->second->uuid(), it->second);
727
0
  }
728
729
  // Get a baseline config reported to the master.
730
0
  LOG(INFO) << "Waiting for Master to see the current replicas...";
731
0
  master::TabletLocationsPB tablet_locations;
732
0
  bool has_leader;
733
0
  ASSERT_OK(itest::WaitForReplicasReportedToMaster(
734
0
      cluster_.get(), 3, tablet_id_, kTimeout, itest::WAIT_FOR_LEADER, &has_leader,
735
0
      &tablet_locations));
736
0
  LOG(INFO) << "Tablet locations:\n" << yb::ToString(tablet_locations);
737
0
  ASSERT_TRUE(has_leader) << yb::ToString(tablet_locations);
738
739
0
  TServerDetails* leader_ts;
740
0
  ASSERT_OK(FindTabletLeader(active_tablet_servers, tablet_id_, kTimeout, &leader_ts));
741
0
  vector<TServerDetails*> followers;
742
0
  ASSERT_OK(FindTabletFollowers(active_tablet_servers, tablet_id_, kTimeout, &followers));
743
  // Wait for initial NO_OP to be committed by the leader.
744
0
  ASSERT_OK(WaitForOpFromCurrentTerm(leader_ts, tablet_id_, OpIdType::COMMITTED_OPID, kTimeout));
745
746
  // Shut down servers follower1 and follower2,
747
  // so that leader can't replicate future config change ops.
748
0
  cluster_->tablet_server_by_uuid(followers[0]->uuid())->Shutdown();
749
0
  cluster_->tablet_server_by_uuid(followers[1]->uuid())->Shutdown();
750
  // Restart master to cleanup cache of dead servers from its
751
  // list of candidate servers to place the new replicas.
752
0
  cluster_->master()->Shutdown();
753
0
  ASSERT_OK(cluster_->master()->Restart());
754
755
  // Now try to replicate a ChangeConfig operation. This should get stuck and time out
756
  // because the server can't replicate any operations.
757
0
  Status s =
758
0
      RemoveServer(leader_ts, tablet_id_, followers[1], -1, MonoDelta::FromSeconds(2), nullptr);
759
0
  ASSERT_TRUE(s.IsTimedOut());
760
761
  // Force leader to step down, best effort command since the leadership
762
  // could change anytime during cluster lifetime.
763
  //  string stderr;
764
0
  ASSERT_OK(CallAdmin("leader_stepdown", tablet_id_));
765
  //  s = Subprocess::Call({GetybCtlAbsolutePath(), "tablet", "leader_step_down",
766
  //                        cluster_->master()->bound_rpc_addr().ToString(),
767
  //                        tablet_id_},
768
  //                       "", nullptr, &stderr);
769
  //  bool not_currently_leader = stderr.find(
770
  //      Status::IllegalState("").CodeAsString()) != string::npos;
771
  //  ASSERT_TRUE(s.ok() || not_currently_leader);
772
773
0
  LOG(INFO) << "Change Config Op timed out, Sending a Replace config "
774
0
            << "command when change config op is pending on the leader.";
775
0
  const string& leader_addr = Substitute(
776
0
      "$0:$1",
777
0
      leader_ts->registration->common().private_rpc_addresses(0).host(),
778
0
      leader_ts->registration->common().private_rpc_addresses(0).port());
779
0
  const vector<string> peer_uuid_list = {leader_ts->uuid()};
780
0
  ASSERT_OK(RunUnsafeChangeConfig(this, tablet_id_, leader_addr, peer_uuid_list));
781
782
  // Find a remaining node which will be picked for re-replication.
783
0
  vector<TServerDetails*> all_tservers = TServerDetailsVector(tablet_servers_);
784
0
  TServerDetails* new_node = nullptr;
785
0
  for (TServerDetails* ts : all_tservers) {
786
0
    if (!ContainsKey(active_tablet_servers, ts->uuid())) {
787
0
      new_node = ts;
788
0
      break;
789
0
    }
790
0
  }
791
0
  ASSERT_TRUE(new_node != nullptr);
792
793
  // Master may try to add the servers which are down until tserver_unresponsive_timeout_ms,
794
  // so it is safer to wait until consensus metadata has 3 voters on new_node.
795
0
  ASSERT_OK(WaitUntilCommittedConfigNumVotersIs(3, new_node, tablet_id_, kTimeout));
796
797
  // Wait for the master to be notified of the config change.
798
0
  LOG(INFO) << "Waiting for Master to see new config...";
799
0
  ASSERT_OK(itest::WaitForReplicasReportedToMaster(
800
0
      cluster_.get(), 3, tablet_id_, kTimeout, itest::WAIT_FOR_LEADER, &has_leader,
801
0
      &tablet_locations));
802
0
  LOG(INFO) << "Tablet locations:\n" << yb::ToString(tablet_locations);
803
0
  for (const master::TabletLocationsPB_ReplicaPB& replica : tablet_locations.replicas()) {
804
0
    ASSERT_NE(replica.ts_info().permanent_uuid(), followers[1]->uuid());
805
0
    ASSERT_NE(replica.ts_info().permanent_uuid(), followers[0]->uuid());
806
0
  }
807
0
}
808
809
// Test unsafe config change when there are back to back pending configs on leader logs.
810
// 1. Instantiate external minicluster with 1 tablet having 3 replicas and 5 TS.
811
// 2. Shut down both the followers.
812
// 3. Trigger a regular config change on the leader which remains pending on leader.
813
// 4. Set a fault crash flag to trigger upon next commit of config change.
814
// 5. Trigger unsafe config change on the surviving leader which should trigger
815
//    the fault while the old config change is being committed.
816
// 6. Shutdown and restart the leader and verify that tablet bootstrapped on leader.
817
// 7. Verify that a new node has populated the new config with 3 voters.
818
0
TEST_F(YBTsCliUnsafeChangeTest, TestUnsafeChangeConfigWithPendingConfigsOnWAL) {
819
0
  MonoDelta kTimeout = MonoDelta::FromSeconds(30);
820
0
  FLAGS_num_tablet_servers = 5;
821
0
  FLAGS_num_replicas = 3;
822
0
  std::vector<std::string> master_flags;
823
0
  std::vector<std::string> ts_flags;
824
0
  master_flags.push_back("--enable_load_balancing=true");
825
0
  BuildAndStart(ts_flags, master_flags);
826
827
  // Determine the list of tablet servers currently in the config.
828
0
  itest::TabletServerMapUnowned active_tablet_servers;
829
0
  auto iter = tablet_replicas_.equal_range(tablet_id_);
830
0
  for (auto it = iter.first; it != iter.second; ++it) {
831
0
    InsertOrDie(&active_tablet_servers, it->second->uuid(), it->second);
832
0
  }
833
834
  // Get a baseline config reported to the master.
835
0
  LOG(INFO) << "Waiting for Master to see the current replicas...";
836
0
  master::TabletLocationsPB tablet_locations;
837
0
  bool has_leader;
838
0
  ASSERT_OK(itest::WaitForReplicasReportedToMaster(
839
0
      cluster_.get(), 3, tablet_id_, kTimeout, itest::WAIT_FOR_LEADER, &has_leader,
840
0
      &tablet_locations));
841
0
  LOG(INFO) << "Tablet locations:\n" << yb::ToString(tablet_locations);
842
0
  ASSERT_TRUE(has_leader) << yb::ToString(tablet_locations);
843
844
0
  TServerDetails* leader_ts;
845
0
  ASSERT_OK(FindTabletLeader(active_tablet_servers, tablet_id_, kTimeout, &leader_ts));
846
0
  vector<TServerDetails*> followers;
847
0
  ASSERT_OK(FindTabletFollowers(active_tablet_servers, tablet_id_, kTimeout, &followers));
848
  // Wait for initial NO_OP to be committed by the leader.
849
0
  ASSERT_OK(WaitForOpFromCurrentTerm(leader_ts, tablet_id_, OpIdType::COMMITTED_OPID, kTimeout));
850
851
  // Shut down servers follower1 and follower2,
852
  // so that leader can't replicate future config change ops.
853
0
  cluster_->tablet_server_by_uuid(followers[0]->uuid())->Shutdown();
854
0
  cluster_->tablet_server_by_uuid(followers[1]->uuid())->Shutdown();
855
856
  // Now try to replicate a ChangeConfig operation. This should get stuck and time out
857
  // because the server can't replicate any operations.
858
0
  Status s =
859
0
      RemoveServer(leader_ts, tablet_id_, followers[1], -1, MonoDelta::FromSeconds(2), nullptr);
860
0
  ASSERT_TRUE(s.IsTimedOut());
861
862
0
  LOG(INFO) << "Change Config Op timed out, Sending a Replace config "
863
0
            << "command when change config op is pending on the leader.";
864
0
  const string& leader_addr = Substitute(
865
0
      "$0:$1",
866
0
      leader_ts->registration->common().private_rpc_addresses(0).host(),
867
0
      leader_ts->registration->common().private_rpc_addresses(0).port());
868
0
  const vector<string> peer_uuid_list = {leader_ts->uuid()};
869
0
  ASSERT_OK(RunUnsafeChangeConfig(this, tablet_id_, leader_addr, peer_uuid_list));
870
871
  // Inject the crash via TEST_fault_crash_before_cmeta_flush flag.
872
  // Tablet will find 2 pending configs back to back during bootstrap,
873
  // one from ChangeConfig (RemoveServer) and another from UnsafeChangeConfig.
874
0
  ASSERT_OK(cluster_->SetFlag(
875
0
      cluster_->tablet_server_by_uuid(leader_ts->uuid()),
876
0
      "TEST_fault_crash_before_cmeta_flush", "1.0"));
877
878
  // Find a remaining node which will be picked for re-replication.
879
0
  vector<TServerDetails*> all_tservers = TServerDetailsVector(tablet_servers_);
880
0
  TServerDetails* new_node = nullptr;
881
0
  for (TServerDetails* ts : all_tservers) {
882
0
    if (!ContainsKey(active_tablet_servers, ts->uuid())) {
883
0
      new_node = ts;
884
0
      break;
885
0
    }
886
0
  }
887
0
  ASSERT_TRUE(new_node != nullptr);
888
  // Restart master to cleanup cache of dead servers from its list of candidate
889
  // servers to trigger placement of new replicas on healthy servers.
890
0
  cluster_->master()->Shutdown();
891
0
  ASSERT_OK(cluster_->master()->Restart());
892
893
0
  auto ts_process = cluster_->tablet_server_by_uuid(leader_ts->uuid());
894
0
  ASSERT_OK(cluster_->WaitForTSToCrash(ts_process, kTimeout));
895
  // ASSERT_OK(cluster_->tablet_server_by_uuid(leader_ts->uuid())->WaitForInjectedCrash(kTimeout));
896
897
0
  cluster_->tablet_server_by_uuid(leader_ts->uuid())->Shutdown();
898
0
  ASSERT_OK(cluster_->tablet_server_by_uuid(leader_ts->uuid())->Restart());
899
0
  vector<tserver::ListTabletsResponsePB::StatusAndSchemaPB> tablets_ignored;
900
0
  ASSERT_OK(WaitForNumTabletsOnTS(leader_ts, 1, kTimeout, &tablets_ignored));
901
0
  ASSERT_OK(WaitUntilCommittedConfigNumVotersIs(3, new_node, tablet_id_, kTimeout));
902
903
  // Wait for the master to be notified of the config change.
904
0
  ASSERT_OK(itest::WaitForReplicasReportedToMaster(
905
0
      cluster_.get(), 3, tablet_id_, kTimeout, itest::WAIT_FOR_LEADER, &has_leader,
906
0
      &tablet_locations));
907
0
  LOG(INFO) << "Tablet locations:\n" << yb::ToString(tablet_locations);
908
0
  for (const master::TabletLocationsPB_ReplicaPB& replica : tablet_locations.replicas()) {
909
0
    ASSERT_NE(replica.ts_info().permanent_uuid(), followers[0]->uuid());
910
0
    ASSERT_NE(replica.ts_info().permanent_uuid(), followers[1]->uuid());
911
0
  }
912
0
}
913
914
// Test unsafe config change on a 5-replica tablet when the mulitple pending configs
915
// on the surviving node.
916
// 1. Instantiate external minicluster with 1 tablet having 5 replicas and 9 TS.
917
// 2. Shut down all the followers.
918
// 3. Trigger unsafe config changes on the surviving leader with those
919
//    dead followers in the new config.
920
// 4. Wait until the new config is populated on the master and the new leader.
921
// 5. Verify that new config does not contain old followers.
922
0
TEST_F(YBTsCliUnsafeChangeTest, TestUnsafeChangeConfigWithMultiplePendingConfigs) {
923
0
  MonoDelta kTimeout = MonoDelta::FromSeconds(30);
924
0
  FLAGS_num_tablet_servers = 9;
925
0
  FLAGS_num_replicas = 5;
926
  // Retire the dead servers early with these settings.
927
0
  std::vector<std::string> master_flags;
928
0
  std::vector<std::string> ts_flags;
929
0
  master_flags.push_back("--enable_load_balancing=true");
930
0
  BuildAndStart(ts_flags, master_flags);
931
932
0
  vector<TServerDetails*> tservers = TServerDetailsVector(tablet_servers_);
933
0
  vector<ExternalTabletServer*> external_tservers;
934
0
  for (TServerDetails* ts : tservers) {
935
0
    external_tservers.push_back(cluster_->tablet_server_by_uuid(ts->uuid()));
936
0
  }
937
938
  // Determine the list of tablet servers currently in the config.
939
0
  itest::TabletServerMapUnowned active_tablet_servers;
940
0
  auto iter = tablet_replicas_.equal_range(tablet_id_);
941
0
  for (auto it = iter.first; it != iter.second; ++it) {
942
0
    InsertOrDie(&active_tablet_servers, it->second->uuid(), it->second);
943
0
  }
944
945
  // Get a baseline config reported to the master.
946
0
  LOG(INFO) << "Waiting for Master to see the current replicas...";
947
0
  master::TabletLocationsPB tablet_locations;
948
0
  bool has_leader;
949
0
  ASSERT_OK(itest::WaitForReplicasReportedToMaster(
950
0
      cluster_.get(), 5, tablet_id_, kTimeout, itest::WAIT_FOR_LEADER, &has_leader,
951
0
      &tablet_locations));
952
0
  LOG(INFO) << "Tablet locations:\n" << yb::ToString(tablet_locations);
953
0
  ASSERT_TRUE(has_leader) << yb::ToString(tablet_locations);
954
955
0
  TServerDetails* leader_ts;
956
0
  ASSERT_OK(FindTabletLeader(active_tablet_servers, tablet_id_, kTimeout, &leader_ts));
957
0
  vector<TServerDetails*> followers;
958
0
  for (const auto& elem : active_tablet_servers) {
959
0
    if (elem.first == leader_ts->uuid()) {
960
0
      continue;
961
0
    }
962
0
    followers.push_back(elem.second);
963
0
    cluster_->tablet_server_by_uuid(elem.first)->Shutdown();
964
0
  }
965
0
  ASSERT_EQ(4, followers.size());
966
967
  // Shutdown master to cleanup cache of dead servers from its list of candidate
968
  // servers to trigger placement of new replicas on healthy servers when we restart later.
969
0
  cluster_->master()->Shutdown();
970
971
0
  const string& leader_addr = Substitute(
972
0
      "$0:$1",
973
0
      leader_ts->registration->common().private_rpc_addresses(0).host(),
974
0
      leader_ts->registration->common().private_rpc_addresses(0).port());
975
976
  // This should keep the multiple pending configs on the node since we are
977
  // adding all the dead followers to the new config, and then eventually we write
978
  // just one surviving node to the config.
979
  // New config write sequences are: {ABCDE}, {ABCD}, {ABC}, {AB}, {A},
980
  // A being the leader node where config is written and rest of the nodes are
981
  // dead followers.
982
0
  for (int num_replicas = static_cast<int>(followers.size()); num_replicas >= 0; num_replicas--) {
983
0
    vector<string> peer_uuid_list;
984
0
    peer_uuid_list.push_back(leader_ts->uuid());
985
0
    for (int i = 0; i < num_replicas; i++) {
986
0
      peer_uuid_list.push_back(followers[i]->uuid());
987
0
    }
988
0
    ASSERT_OK(RunUnsafeChangeConfig(this, tablet_id_, leader_addr, peer_uuid_list));
989
0
  }
990
991
0
  ASSERT_OK(WaitUntilCommittedConfigNumVotersIs(1, leader_ts, tablet_id_, kTimeout));
992
0
  ASSERT_OK(cluster_->master()->Restart());
993
994
  // Find a remaining node which will be picked for re-replication.
995
0
  vector<TServerDetails*> all_tservers = TServerDetailsVector(tablet_servers_);
996
0
  TServerDetails* new_node = nullptr;
997
0
  for (TServerDetails* ts : all_tservers) {
998
0
    if (!ContainsKey(active_tablet_servers, ts->uuid())) {
999
0
      new_node = ts;
1000
0
      break;
1001
0
    }
1002
0
  }
1003
0
  ASSERT_TRUE(new_node != nullptr);
1004
1005
  // Master may try to add the servers which are down until tserver_unresponsive_timeout_ms,
1006
  // so it is safer to wait until consensus metadata has 5 voters on new_node.
1007
0
  ASSERT_OK(WaitUntilCommittedConfigNumVotersIs(5, new_node, tablet_id_, kTimeout));
1008
1009
  // Wait for the master to be notified of the config change.
1010
0
  LOG(INFO) << "Waiting for Master to see new config...";
1011
0
  ASSERT_OK(itest::WaitForReplicasReportedToMaster(
1012
0
      cluster_.get(), 5, tablet_id_, kTimeout, itest::WAIT_FOR_LEADER, &has_leader,
1013
0
      &tablet_locations));
1014
0
  LOG(INFO) << "Tablet locations:\n" << yb::ToString(tablet_locations);
1015
0
  for (const master::TabletLocationsPB_ReplicaPB& replica : tablet_locations.replicas()) {
1016
    // Verify that old followers aren't part of new config.
1017
0
    for (const auto& old_follower : followers) {
1018
0
      ASSERT_NE(replica.ts_info().permanent_uuid(), old_follower->uuid());
1019
0
    }
1020
0
  }
1021
0
}
1022
1023
} // namespace tools
1024
} // namespace yb