This is an automated email from the ASF dual-hosted git repository.
alexey pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/kudu.git
The following commit(s) were added to refs/heads/master by this push:
new e44e0d489 [client] add ScanTokenStaleRaftMembershipTest
e44e0d489 is described below
commit e44e0d4892b0e2469a18aefb78062f5aa2e1799c
Author: Alexey Serbin <[email protected]>
AuthorDate: Thu Jul 11 19:40:36 2024 -0700
[client] add ScanTokenStaleRaftMembershipTest
This patch adds a new test scenario TabletLeaderChange into the newly
added ScanTokenStaleRaftMembershipTest fixture. The motivation for this
patch was a request to clarify on the Kudu C++ client's behavior in
particular scenarios, which on itself was in the context of a follow-up
to KUDU-3349.
Change-Id: I6ce3d549d4ab2502c58deae1250b49ba16bbc914
Reviewed-on: http://gerrit.cloudera.org:8080/21580
Reviewed-by: Ashwani Raina <[email protected]>
Reviewed-by: Abhishek Chennaka <[email protected]>
Tested-by: Alexey Serbin <[email protected]>
---
src/kudu/client/scan_token-test.cc | 166 +++++++++++++++++++++++++++++++++++++
1 file changed, 166 insertions(+)
diff --git a/src/kudu/client/scan_token-test.cc
b/src/kudu/client/scan_token-test.cc
index 0caba95ec..169baeefd 100644
--- a/src/kudu/client/scan_token-test.cc
+++ b/src/kudu/client/scan_token-test.cc
@@ -24,6 +24,7 @@
#include <string>
#include <thread>
#include <type_traits>
+#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
@@ -53,6 +54,7 @@
#include "kudu/gutil/ref_counted.h"
#include "kudu/gutil/stl_util.h"
#include "kudu/gutil/strings/substitute.h"
+#include "kudu/integration-tests/cluster_itest_util.h"
#include "kudu/integration-tests/test_workload.h"
#include "kudu/master/catalog_manager.h"
#include "kudu/master/master.h"
@@ -84,6 +86,8 @@ using kudu::client::KuduTableCreator;
using kudu::client::sp::shared_ptr;
using kudu::cluster::InternalMiniCluster;
using kudu::cluster::InternalMiniClusterOptions;
+using kudu::itest::TServerDetails;
+using kudu::itest::TabletServerMap;
using kudu::master::CatalogManager;
using kudu::master::TabletInfo;
using kudu::tablet::TabletReplica;
@@ -1578,6 +1582,168 @@ TEST_F(ScanTokenTest, TestMasterRequestsNoMetadata) {
ASSERT_EQ(init_location_requests + 1, NumGetTableLocationsRequests());
}
+class ScanTokenStaleRaftMembershipTest : public ScanTokenTest {
+ protected:
+ void SetUp() override {
+ NO_FATALS(KuduTest::SetUp());
+
+ InternalMiniClusterOptions opt;
+ opt.num_tablet_servers = 3;
+ // Set up the mini cluster
+ cluster_.reset(new InternalMiniCluster(env_, std::move(opt)));
+ ASSERT_OK(cluster_->Start());
+ ASSERT_OK(cluster_->CreateClient(nullptr, &client_));
+ }
+};
+
+// A test scenario to verify how the client's metacache behaves when a leader
+// replica changes, given that the metacache was originally populated
+// from a scan token. The information on the replicas' Raft configuration
+// becomes outdated by the time a write request is sent out, so the client
+// should find a newly elected leader replica and retry the write request
+// with the new leader replica.
+TEST_F(ScanTokenStaleRaftMembershipTest, TabletLeaderChange) {
+ constexpr const char* const kTableName = "scan-token-stale-tablet-config";
+ constexpr const char* const kKey = "key";
+
+ KuduSchema schema;
+ {
+ KuduSchemaBuilder builder;
+
builder.AddColumn(kKey)->NotNull()->Type(KuduColumnSchema::INT64)->PrimaryKey();
+ builder.AddColumn("col")->Nullable()->Type(KuduColumnSchema::INT64);
+ ASSERT_OK(builder.Build(&schema));
+ }
+
+ shared_ptr<KuduTable> table;
+ {
+ // Create a table of RF=3 and a single range partition [-100, 100).
+ unique_ptr<KuduTableCreator> tc(client_->NewTableCreator());
+ tc->table_name(kTableName);
+ tc->schema(&schema);
+ tc->num_replicas(3);
+ tc->set_range_partition_columns({ kKey });
+
+ {
+ unique_ptr<KuduPartialRow> lb(schema.NewRow());
+ ASSERT_OK(lb->SetInt64(kKey, -100));
+ unique_ptr<KuduPartialRow> ub(schema.NewRow());
+ ASSERT_OK(ub->SetInt64(kKey, 100));
+ tc->add_range_partition(lb.release(), ub.release());
+ }
+ ASSERT_OK(tc->Create());
+ ASSERT_OK(client_->OpenTable(kTableName, &table));
+ }
+
+ unique_ptr<KuduScanToken> token;
+ {
+ // Build scan token(s), embedding information on the tablet locations and
+ // replicas' Raft roles.
+ vector<KuduScanToken*> tokens;
+ KuduScanTokenBuilder builder(table.get());
+ ASSERT_OK(builder.IncludeTableMetadata(true));
+ ASSERT_OK(builder.IncludeTabletMetadata(true));
+ ASSERT_OK(builder.Build(&tokens));
+ ASSERT_EQ(1, tokens.size());
+ token.reset(tokens.front());
+ }
+
+ shared_ptr<KuduClient> new_client;
+ ASSERT_OK(cluster_->CreateClient(nullptr, &new_client));
+
+ {
+ // List the tables to prevent counting initialization RPCs.
+ vector<string> tables;
+ ASSERT_OK(new_client->ListTables(&tables));
+ }
+
+ const auto init_schema_requests = NumGetTableSchemaRequests();
+ const auto init_location_requests = NumGetTableLocationsRequests();
+
+ unique_ptr<KuduScanner> scanner;
+ ASSERT_OK(IntoUniqueScanner(new_client.get(), *token, &scanner));
+ ASSERT_OK(scanner->Open());
+ KuduScanBatch batch;
+ ASSERT_OK(scanner->NextBatch(&batch));
+
+ // Hydrating a scan token, opening the table, and fetching a batch of rows
+ // should result neither in GetTableSchema, nor in GetTableLocations request.
+ ASSERT_EQ(init_schema_requests, NumGetTableSchemaRequests());
+ ASSERT_EQ(init_location_requests, NumGetTableLocationsRequests());
+
+ const auto& tablet = token->tablet();
+ const auto& tablet_id = tablet.id();
+ const auto& replicas = tablet.replicas();
+ const KuduTabletServer* ts = nullptr;
+ string leader_uuid = "";
+ for (const auto& r : replicas) {
+ if (r->is_leader()) {
+ ts = &r->ts();
+ leader_uuid = ts->uuid();
+ break;
+ }
+ }
+ ASSERT_NE(nullptr, ts);
+ ASSERT_FALSE(leader_uuid.empty());
+
+ const auto kRaftTimeout = MonoDelta::FromSeconds(30);
+ TabletServerMap ts_map;
+ ASSERT_OK(CreateTabletServerMap(cluster_->master_proxy(),
cluster_->messenger(), &ts_map));
+ const TServerDetails* leader_tsd = FindPtrOrNull(ts_map, leader_uuid);
+ ASSERT_NE(nullptr, leader_tsd);
+
+ // Make the current leader replica identified by 'leader_uuid' to step down
+ // abruptly, inducing Raft election round.
+ ASSERT_OK(LeaderStepDown(leader_tsd, tablet_id, kRaftTimeout));
+
+ // After initiating a change in the tablet's Raft leadership, try to insert
+ // a single row into the table. During the Raft election round,
WriteRequestPB
+ // RPCs should be rejected with REPLICA_NOT_LEADER error code by every
replica
+ // of the tablet. However, the write request should eventually succeed once
+ // a new leader replica is elected.
+ shared_ptr<KuduSession> session = client_->NewSession();
+ {
+ unique_ptr<KuduInsert> insert(table->NewInsert());
+ ASSERT_OK(insert->mutable_row()->SetInt64(kKey, 0));
+ ASSERT_OK(session->Apply(insert.release()));
+ ASSERT_OK(session->Flush());
+ }
+
+ // At this point the new leader replica for the tablet has already been
+ // established to accommodate the write request above. As an extra sanity
+ // check that's crucial for the essence of this test scenario, make sure
+ // the newly elected leader replica is indeed hosted by other tablet server.
+ //
+ // NOTE: for existing coverage, see RaftConsensusElectionITest.LeaderStepDown
+ // in raft_consensus_election-itest.cc
+ TServerDetails* new_leader_tsd = nullptr;
+ ASSERT_OK(FindTabletLeader(ts_map, tablet_id, kRaftTimeout,
&new_leader_tsd));
+ ASSERT_NE(nullptr, new_leader_tsd);
+ ASSERT_NE(new_leader_tsd->uuid(), leader_tsd->uuid());
+
+ // The version of the table's schema hasn't changed, so no new GetTableSchema
+ // requests should be sent by the client's metacache.
+ ASSERT_EQ(init_schema_requests, NumGetTableSchemaRequests());
+
+ // GetTableLocations requests should be issued by the client's metacache
+ // to find the location of the new leader replica.
+ const auto location_requests_post_election = NumGetTableLocationsRequests();
+ ASSERT_LT(init_location_requests, location_requests_post_election);
+
+ // Write one more row into the tablet to let the metacache do its job again:
+ // that's necessary for the assertion on NumGetTableLocationsRequests()
below.
+ {
+ unique_ptr<KuduInsert> insert(table->NewInsert());
+ ASSERT_OK(insert->mutable_row()->SetInt64(kKey, 1));
+ ASSERT_OK(session->Apply(insert.release()));
+ ASSERT_OK(session->Flush());
+ }
+
+ // Make sure no new GetTableLocations requests were sent upon writing into
+ // the tablet once the new leader replica is found and that information
+ // is stored in the metacache.
+ ASSERT_EQ(location_requests_post_election, NumGetTableLocationsRequests());
+}
+
enum FirstRangeChangeMode {
BEGIN = 0,
RANGE_DROPPED = 0,