/Users/deen/code/yugabyte-db/src/yb/integration-tests/master_path_handlers-itest.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) YugaByte, Inc. |
2 | | // |
3 | | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except |
4 | | // in compliance with the License. You may obtain a copy of the License at |
5 | | // |
6 | | // http://www.apache.org/licenses/LICENSE-2.0 |
7 | | // |
8 | | // Unless required by applicable law or agreed to in writing, software distributed under the License |
9 | | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
10 | | // or implied. See the License for the specific language governing permissions and limitations |
11 | | // under the License. |
12 | | // |
13 | | |
14 | | #include <chrono> |
15 | | |
16 | | #include "yb/client/schema.h" |
17 | | #include "yb/client/table.h" |
18 | | #include "yb/client/table_creator.h" |
19 | | #include "yb/client/yb_table_name.h" |
20 | | |
21 | | #include "yb/common/partition.h" |
22 | | |
23 | | #include "yb/integration-tests/external_mini_cluster.h" |
24 | | #include "yb/integration-tests/mini_cluster.h" |
25 | | #include "yb/integration-tests/yb_mini_cluster_test_base.h" |
26 | | |
27 | | #include "yb/master/master-path-handlers.h" |
28 | | #include "yb/master/mini_master.h" |
29 | | |
30 | | #include "yb/tools/yb-admin_client.h" |
31 | | |
32 | | #include "yb/tserver/mini_tablet_server.h" |
33 | | |
34 | | #include "yb/util/curl_util.h" |
35 | | #include "yb/util/jsonreader.h" |
36 | | #include "yb/util/random_util.h" |
37 | | #include "yb/util/result.h" |
38 | | #include "yb/util/test_macros.h" |
39 | | |
40 | | DECLARE_int32(tserver_unresponsive_timeout_ms); |
41 | | DECLARE_int32(heartbeat_interval_ms); |
42 | | DECLARE_string(TEST_master_extra_list_host_port); |
43 | | |
44 | | DECLARE_int32(follower_unavailable_considered_failed_sec); |
45 | | |
46 | | namespace yb { |
47 | | namespace master { |
48 | | |
49 | | using std::string; |
50 | | |
51 | | const std::string kKeyspaceName("my_keyspace"); |
52 | | const uint kNumMasters(3); |
53 | | const uint kNumTablets(3); |
54 | | |
55 | | template <class T> |
56 | | class MasterPathHandlersBaseItest : public YBMiniClusterTestBase<T> { |
57 | | public: |
58 | 3 | void DoTearDown() override { |
59 | 3 | cluster_->Shutdown(); |
60 | 3 | } _ZN2yb6master27MasterPathHandlersBaseItestINS_11MiniClusterEE10DoTearDownEv Line | Count | Source | 58 | 2 | void DoTearDown() override { | 59 | 2 | cluster_->Shutdown(); | 60 | 2 | } |
_ZN2yb6master27MasterPathHandlersBaseItestINS_19ExternalMiniClusterEE10DoTearDownEv Line | Count | Source | 58 | 1 | void DoTearDown() override { | 59 | 1 | cluster_->Shutdown(); | 60 | 1 | } |
|
61 | | |
62 | | protected: |
63 | 1 | void TestUrl(const string& query_path, faststring* result) { |
64 | 1 | const string tables_url = master_http_url_ + query_path; |
65 | 1 | EasyCurl curl; |
66 | 1 | ASSERT_OK(curl.FetchURL(tables_url, result)); |
67 | 1 | } Unexecuted instantiation: _ZN2yb6master27MasterPathHandlersBaseItestINS_11MiniClusterEE7TestUrlERKNSt3__112basic_stringIcNS4_11char_traitsIcEENS4_9allocatorIcEEEEPNS_10faststringE _ZN2yb6master27MasterPathHandlersBaseItestINS_19ExternalMiniClusterEE7TestUrlERKNSt3__112basic_stringIcNS4_11char_traitsIcEENS4_9allocatorIcEEEEPNS_10faststringE Line | Count | Source | 63 | 1 | void TestUrl(const string& query_path, faststring* result) { | 64 | 1 | const string tables_url = master_http_url_ + query_path; | 65 | 1 | EasyCurl curl; | 66 | 1 | ASSERT_OK(curl.FetchURL(tables_url, result)); | 67 | 1 | } |
|
68 | | |
69 | 5 | virtual int num_masters() const { |
70 | 5 | return kNumMasters; |
71 | 5 | } _ZNK2yb6master27MasterPathHandlersBaseItestINS_11MiniClusterEE11num_mastersEv Line | Count | Source | 69 | 4 | virtual int num_masters() const { | 70 | 4 | return kNumMasters; | 71 | 4 | } |
_ZNK2yb6master27MasterPathHandlersBaseItestINS_19ExternalMiniClusterEE11num_mastersEv Line | Count | Source | 69 | 1 | virtual int num_masters() const { | 70 | 1 | return kNumMasters; | 71 | 1 | } |
|
72 | | |
73 | | using YBMiniClusterTestBase<T>::cluster_; |
74 | | string master_http_url_; |
75 | | }; |
76 | | |
77 | | class MasterPathHandlersItest : public MasterPathHandlersBaseItest<MiniCluster> { |
78 | | public: |
79 | 5 | void SetUp() override { |
80 | 5 | YBMiniClusterTestBase::SetUp(); |
81 | 5 | MiniClusterOptions opts; |
82 | | // Set low heartbeat timeout. |
83 | 5 | FLAGS_tserver_unresponsive_timeout_ms = 5000; |
84 | 5 | opts.num_tablet_servers = kNumTablets; |
85 | 5 | opts.num_masters = num_masters(); |
86 | 5 | cluster_.reset(new MiniCluster(opts)); |
87 | 5 | ASSERT_OK(cluster_->Start()); |
88 | | |
89 | 0 | Endpoint master_http_endpoint = |
90 | 0 | ASSERT_RESULT(cluster_->GetLeaderMiniMaster())->bound_http_addr(); |
91 | 0 | master_http_url_ = "http://" + AsString(master_http_endpoint); |
92 | 0 | } |
93 | | }; |
94 | | |
95 | 0 | bool verifyTServersAlive(int n, const string& result) { |
96 | 0 | size_t pos = 0; |
97 | 0 | for (int i = 0; i < n; i++) { |
98 | 0 | pos = result.find(kTserverAlive, pos + 1); |
99 | 0 | if (pos == string::npos) { |
100 | 0 | return false; |
101 | 0 | } |
102 | 0 | } |
103 | 0 | return result.find(kTserverAlive, pos + 1) == string::npos; |
104 | 0 | } |
105 | | |
106 | 0 | TEST_F(MasterPathHandlersItest, TestMasterPathHandlers) { |
107 | 0 | faststring result; |
108 | 0 | TestUrl("/table?id=1", &result); |
109 | 0 | TestUrl("/tablet-servers", &result); |
110 | 0 | TestUrl("/tables", &result); |
111 | 0 | TestUrl("/dump-entities", &result); |
112 | 0 | TestUrl("/cluster-config", &result); |
113 | 0 | TestUrl("/tablet-replication", &result); |
114 | 0 | } |
115 | | |
116 | 0 | TEST_F(MasterPathHandlersItest, TestDeadTServers) { |
117 | | // Shutdown tserver and wait for heartbeat timeout. |
118 | 0 | cluster_->mini_tablet_server(0)->Shutdown(); |
119 | 0 | std::this_thread::sleep_for(std::chrono::milliseconds(2 * FLAGS_tserver_unresponsive_timeout_ms)); |
120 | | |
121 | | // Check UI page. |
122 | 0 | faststring result; |
123 | 0 | TestUrl("/tablet-servers", &result); |
124 | 0 | const string &result_str = result.ToString(); |
125 | 0 | ASSERT_TRUE(verifyTServersAlive(2, result_str)); |
126 | | |
127 | | // Now verify dead. |
128 | 0 | size_t pos = result_str.find(kTserverDead, 0); |
129 | 0 | ASSERT_TRUE(pos != string::npos); |
130 | 0 | ASSERT_TRUE(result_str.find(kTserverDead, pos + 1) == string::npos); |
131 | | |
132 | | // Startup the tserver and wait for heartbeats. |
133 | 0 | ASSERT_OK(cluster_->mini_tablet_server(0)->Start()); |
134 | |
|
135 | 0 | ASSERT_OK(WaitFor([&]() -> bool { |
136 | 0 | TestUrl("/tablet-servers", &result); |
137 | 0 | return verifyTServersAlive(3, result.ToString()); |
138 | 0 | }, MonoDelta::FromSeconds(10), "Waiting for tserver heartbeat to master")); |
139 | 0 | } |
140 | | |
141 | 0 | TEST_F(MasterPathHandlersItest, TestTabletReplicationEndpoint) { |
142 | 0 | auto client = ASSERT_RESULT(cluster_->CreateClient()); |
143 | 0 | ASSERT_OK(client->CreateNamespaceIfNotExists(kKeyspaceName)); |
144 | | |
145 | | // Create table. |
146 | | // TODO(5016): Consolidate into some standardized helper code. |
147 | 0 | client::YBTableName table_name(YQL_DATABASE_CQL, kKeyspaceName, "test_table"); |
148 | 0 | client::YBSchema schema; |
149 | 0 | client::YBSchemaBuilder b; |
150 | 0 | b.AddColumn("key")->Type(INT32)->NotNull()->PrimaryKey(); |
151 | 0 | b.AddColumn("int_val")->Type(INT32)->NotNull(); |
152 | 0 | b.AddColumn("string_val")->Type(STRING)->NotNull(); |
153 | 0 | ASSERT_OK(b.Build(&schema)); |
154 | 0 | std::unique_ptr<client::YBTableCreator> table_creator(client->NewTableCreator()); |
155 | 0 | ASSERT_OK(table_creator->table_name(table_name) |
156 | 0 | .schema(&schema) |
157 | 0 | .hash_schema(YBHashSchema::kMultiColumnHash) |
158 | 0 | .Create()); |
159 | 0 | std::shared_ptr<client::YBTable> table; |
160 | 0 | ASSERT_OK(client->OpenTable(table_name, &table)); |
161 | | |
162 | | // Choose a tablet to orphan and take note of the servers which are leaders/followers for this |
163 | | // tablet. |
164 | 0 | google::protobuf::RepeatedPtrField<TabletLocationsPB> tablets; |
165 | 0 | ASSERT_OK(client->GetTabletsFromTableId(table->id(), kNumTablets, &tablets)); |
166 | 0 | std::vector<yb::tserver::MiniTabletServer *> followers; |
167 | 0 | yb::tserver::MiniTabletServer* leader = nullptr; |
168 | 0 | auto orphan_tablet = tablets.Get(0); |
169 | 0 | for (const auto& replica : orphan_tablet.replicas()) { |
170 | 0 | const auto uuid = replica.ts_info().permanent_uuid(); |
171 | 0 | auto* tserver = cluster_->find_tablet_server(uuid); |
172 | 0 | ASSERT_NOTNULL(tserver); |
173 | 0 | if (replica.role() == PeerRole::LEADER) { |
174 | 0 | leader = tserver; |
175 | 0 | } else { |
176 | 0 | followers.push_back(tserver); |
177 | 0 | } |
178 | | // Shutdown all tservers. |
179 | 0 | tserver->Shutdown(); |
180 | 0 | } |
181 | 0 | ASSERT_NOTNULL(leader); |
182 | | |
183 | | // Restart the server which was previously the leader of the now orphaned tablet. |
184 | 0 | ASSERT_OK(leader->Start()); |
185 | | // Sleep here to give the master's catalog_manager time to receive heartbeat from "leader". |
186 | 0 | std::this_thread::sleep_for(std::chrono::milliseconds(6 * FLAGS_heartbeat_interval_ms)); |
187 | | |
188 | | // Call endpoint and validate format of response. |
189 | 0 | faststring result; |
190 | 0 | TestUrl("/api/v1/tablet-replication", &result); |
191 | |
|
192 | 0 | JsonReader r(result.ToString()); |
193 | 0 | ASSERT_OK(r.Init()); |
194 | 0 | const rapidjson::Value* json_obj = nullptr; |
195 | 0 | EXPECT_OK(r.ExtractObject(r.root(), NULL, &json_obj)); |
196 | 0 | EXPECT_EQ(rapidjson::kObjectType, CHECK_NOTNULL(json_obj)->GetType()); |
197 | 0 | EXPECT_TRUE(json_obj->HasMember("leaderless_tablets")); |
198 | 0 | EXPECT_EQ(rapidjson::kArrayType, (*json_obj)["leaderless_tablets"].GetType()); |
199 | 0 | const rapidjson::Value::ConstArray tablets_json = (*json_obj)["leaderless_tablets"].GetArray(); |
200 | 0 | std::vector<std::string> leaderless_tablet_uuids; |
201 | 0 | for (const auto& tablet_json : tablets_json) { |
202 | 0 | EXPECT_EQ(rapidjson::kObjectType, tablet_json.GetType()); |
203 | 0 | EXPECT_TRUE(tablet_json.HasMember("table_uuid")); |
204 | 0 | EXPECT_EQ(rapidjson::kStringType, tablet_json["table_uuid"].GetType()); |
205 | 0 | EXPECT_TRUE(tablet_json.HasMember("tablet_uuid")); |
206 | 0 | EXPECT_EQ(rapidjson::kStringType, tablet_json["tablet_uuid"].GetType()); |
207 | 0 | } |
208 | |
|
209 | 0 | auto has_orphan_tablet_result = std::any_of( |
210 | 0 | tablets_json.begin(), tablets_json.end(), |
211 | 0 | [&orphan_tablet](const auto& tablet_json) { |
212 | 0 | return tablet_json["tablet_uuid"].GetString() == orphan_tablet.tablet_id(); |
213 | 0 | }); |
214 | 0 | EXPECT_TRUE(has_orphan_tablet_result) << "Expected to find orphan_tablet in leaderless tablets."; |
215 | | |
216 | | // YBMiniClusterTestBase test-end verification will fail if the cluster is up with stopped nodes. |
217 | 0 | cluster_->Shutdown(); |
218 | 0 | } |
219 | | |
220 | 0 | TEST_F(MasterPathHandlersItest, TestTabletUnderReplicationEndpoint) { |
221 | | // Set test specific flag |
222 | 0 | FLAGS_follower_unavailable_considered_failed_sec = 30; |
223 | |
|
224 | 0 | auto client = ASSERT_RESULT(cluster_->CreateClient()); |
225 | 0 | ASSERT_OK(client->CreateNamespaceIfNotExists(kKeyspaceName)); |
226 | | |
227 | | // Create table. |
228 | 0 | client::YBTableName table_name(YQL_DATABASE_CQL, kKeyspaceName, "test_table"); |
229 | 0 | client::YBSchema schema; |
230 | 0 | client::YBSchemaBuilder b; |
231 | 0 | b.AddColumn("key")->Type(INT32)->NotNull()->PrimaryKey(); |
232 | 0 | b.AddColumn("int_val")->Type(INT32)->NotNull(); |
233 | 0 | b.AddColumn("string_val")->Type(STRING)->NotNull(); |
234 | 0 | ASSERT_OK(b.Build(&schema)); |
235 | 0 | std::unique_ptr<client::YBTableCreator> table_creator(client->NewTableCreator()); |
236 | 0 | ASSERT_OK(table_creator->table_name(table_name) |
237 | 0 | .schema(&schema) |
238 | 0 | .hash_schema(YBHashSchema::kMultiColumnHash) |
239 | 0 | .Create()); |
240 | 0 | std::shared_ptr<client::YBTable> table; |
241 | 0 | ASSERT_OK(client->OpenTable(table_name, &table)); |
242 | | |
243 | | // Get all the tablets of this table and store them |
244 | 0 | google::protobuf::RepeatedPtrField<TabletLocationsPB> tablets; |
245 | 0 | ASSERT_OK(client->GetTabletsFromTableId(table->id(), kNumTablets, &tablets)); |
246 | |
|
247 | 0 | std::vector<std::string> tIds; |
248 | 0 | bool isTestTrue = true; |
249 | |
|
250 | 0 | int numTablets = tablets.size(); |
251 | 0 | for(int i = 0; i < numTablets; i++) { |
252 | 0 | auto tablet = tablets.Get(i); |
253 | 0 | tIds.push_back(tablet.tablet_id()); |
254 | 0 | } |
255 | | |
256 | | // Now kill one of the servers |
257 | | // Since the replication factor is 3 and the number |
258 | | // of nodes is also 3, all the tablets |
259 | | // of this table should become under replicated |
260 | 0 | cluster_->mini_tablet_server(0)->Shutdown(); |
261 | | // Wait for 3*30 secs just to be safe |
262 | 0 | std::this_thread::sleep_for(std::chrono::milliseconds( |
263 | 0 | 3 * FLAGS_follower_unavailable_considered_failed_sec * 1000)); |
264 | | |
265 | | // Call endpoint and validate format of response. |
266 | 0 | faststring result; |
267 | 0 | TestUrl("/api/v1/tablet-under-replication", &result); |
268 | |
|
269 | 0 | JsonReader r(result.ToString()); |
270 | 0 | ASSERT_OK(r.Init()); |
271 | 0 | const rapidjson::Value* json_obj = nullptr; |
272 | 0 | EXPECT_OK(r.ExtractObject(r.root(), NULL, &json_obj)); |
273 | 0 | EXPECT_EQ(rapidjson::kObjectType, CHECK_NOTNULL(json_obj)->GetType()); |
274 | 0 | EXPECT_TRUE(json_obj->HasMember("underreplicated_tablets")); |
275 | 0 | EXPECT_EQ(rapidjson::kArrayType, (*json_obj)["underreplicated_tablets"].GetType()); |
276 | 0 | const rapidjson::Value::ConstArray tablets_json = |
277 | 0 | (*json_obj)["underreplicated_tablets"].GetArray(); |
278 | |
|
279 | 0 | for (const auto& tablet_json : tablets_json) { |
280 | 0 | EXPECT_EQ(rapidjson::kObjectType, tablet_json.GetType()); |
281 | 0 | EXPECT_TRUE(tablet_json.HasMember("table_uuid")); |
282 | 0 | EXPECT_EQ(rapidjson::kStringType, tablet_json["table_uuid"].GetType()); |
283 | 0 | EXPECT_TRUE(tablet_json.HasMember("tablet_uuid")); |
284 | 0 | EXPECT_EQ(rapidjson::kStringType, tablet_json["tablet_uuid"].GetType()); |
285 | 0 | } |
286 | | |
287 | | // These tablets should be present in the json response |
288 | 0 | for(const std::string &id : tIds) { |
289 | 0 | isTestTrue = isTestTrue && std::any_of(tablets_json.begin(), tablets_json.end(), |
290 | 0 | [&id](const auto &tablet_json) { |
291 | 0 | return tablet_json["tablet_uuid"].GetString() == id; |
292 | 0 | }); |
293 | 0 | } |
294 | |
|
295 | 0 | EXPECT_TRUE(isTestTrue) << "Expected to find under-replicated tablets."; |
296 | | |
297 | | // YBMiniClusterTestBase test-end verification will fail if the cluster is up with stopped nodes. |
298 | 0 | cluster_->Shutdown(); |
299 | 0 | } |
300 | | |
301 | | class MultiMasterPathHandlersItest : public MasterPathHandlersItest { |
302 | | public: |
303 | 1 | int num_masters() const override { |
304 | 1 | return 3; |
305 | 1 | } |
306 | | }; |
307 | | |
308 | 0 | TEST_F_EX(MasterPathHandlersItest, Forward, MultiMasterPathHandlersItest) { |
309 | 0 | FLAGS_TEST_master_extra_list_host_port = RandomHumanReadableString(16) + ".com"; |
310 | 0 | EasyCurl curl; |
311 | 0 | faststring content; |
312 | 0 | for (size_t i = 0; i != cluster_->num_masters(); ++i) { |
313 | 0 | auto url = Format("http://$0/tablet-servers", cluster_->mini_master(i)->bound_http_addr()); |
314 | 0 | content.clear(); |
315 | 0 | ASSERT_OK(curl.FetchURL(url, &content)); |
316 | 0 | } |
317 | 0 | } |
318 | | |
319 | | class MasterPathHandlersExternalItest : public MasterPathHandlersBaseItest<ExternalMiniCluster> { |
320 | | public: |
321 | 1 | void SetUp() override { |
322 | 1 | YBMiniClusterTestBase::SetUp(); |
323 | 1 | ExternalMiniClusterOptions opts; |
324 | | // Set low heartbeat timeout. |
325 | 1 | FLAGS_tserver_unresponsive_timeout_ms = 5000; |
326 | 1 | opts.num_tablet_servers = kNumTablets; |
327 | 1 | opts.num_masters = num_masters(); |
328 | 1 | cluster_.reset(new ExternalMiniCluster(opts)); |
329 | 1 | ASSERT_OK(cluster_->Start()); |
330 | | |
331 | 1 | HostPort master_http_endpoint = cluster_->master(0)->bound_http_hostport(); |
332 | 1 | master_http_url_ = "http://" + ToString(master_http_endpoint); |
333 | 1 | } |
334 | | }; |
335 | | |
336 | 1 | TEST_F_EX(MasterPathHandlersItest, TestTablePlacementInfo, MasterPathHandlersExternalItest) { |
337 | 1 | auto client = ASSERT_RESULT(cluster_->CreateClient()); |
338 | 1 | ASSERT_OK(client->CreateNamespaceIfNotExists(kKeyspaceName)); |
339 | | |
340 | | // Create table. |
341 | | // TODO(5016): Consolidate into some standardized helper code. |
342 | 1 | client::YBTableName table_name(YQL_DATABASE_CQL, kKeyspaceName, "test_table"); |
343 | 1 | client::YBSchema schema; |
344 | 1 | client::YBSchemaBuilder b; |
345 | 1 | b.AddColumn("key")->Type(INT32)->NotNull()->PrimaryKey(); |
346 | 1 | b.AddColumn("int_val")->Type(INT32)->NotNull(); |
347 | 1 | b.AddColumn("string_val")->Type(STRING)->NotNull(); |
348 | 1 | ASSERT_OK(b.Build(&schema)); |
349 | 1 | std::unique_ptr<client::YBTableCreator> table_creator(client->NewTableCreator()); |
350 | 1 | ASSERT_OK(table_creator->table_name(table_name) |
351 | 1 | .schema(&schema) |
352 | 1 | .hash_schema(YBHashSchema::kMultiColumnHash) |
353 | 1 | .Create()); |
354 | 1 | std::shared_ptr<client::YBTable> table; |
355 | 1 | ASSERT_OK(client->OpenTable(table_name, &table)); |
356 | | |
357 | | // Verify replication info is empty. |
358 | 1 | faststring result; |
359 | 1 | auto url = Format("/table?id=$0", table->id()); |
360 | 1 | TestUrl(url, &result); |
361 | 1 | const string& result_str = result.ToString(); |
362 | 1 | size_t pos = result_str.find("Replication Info", 0); |
363 | 1 | ASSERT_NE(pos, string::npos); |
364 | 1 | ASSERT_EQ(result_str.find("live_replicas", pos + 1), string::npos); |
365 | | |
366 | | // Verify cluster level replication info. |
367 | 1 | auto yb_admin_client_ = std::make_unique<yb::tools::enterprise::ClusterAdminClient>( |
368 | 1 | cluster_->GetMasterAddresses(), MonoDelta::FromSeconds(30)); |
369 | 1 | ASSERT_OK(yb_admin_client_->Init()); |
370 | 1 | ASSERT_OK(yb_admin_client_->ModifyPlacementInfo("cloud.region.zone", 3, "table_uuid")); |
371 | 0 | TestUrl(url, &result); |
372 | 0 | const string& cluster_str = result.ToString(); |
373 | 0 | pos = cluster_str.find("Replication Info", 0); |
374 | 0 | ASSERT_NE(pos, string::npos); |
375 | 0 | pos = cluster_str.find("placement_zone", pos + 1); |
376 | 0 | ASSERT_NE(pos, string::npos); |
377 | 0 | ASSERT_EQ(cluster_str.substr(pos + 17, 4), "zone"); |
378 | | |
379 | | // Verify table level replication info. |
380 | 0 | ASSERT_OK(yb_admin_client_->ModifyTablePlacementInfo( |
381 | 0 | table->name(), "cloud.region.anotherzone", 3, "table_uuid")); |
382 | 0 | TestUrl(url, &result); |
383 | 0 | const string& table_str = result.ToString(); |
384 | 0 | pos = table_str.find("Replication Info", 0); |
385 | 0 | ASSERT_NE(pos, string::npos); |
386 | 0 | pos = table_str.find("placement_zone", pos + 1); |
387 | 0 | ASSERT_NE(pos, string::npos); |
388 | 0 | ASSERT_EQ(table_str.substr(pos + 17, 11), "anotherzone"); |
389 | 0 | } |
390 | | |
391 | | } // namespace master |
392 | | } // namespace yb |