/Users/deen/code/yugabyte-db/src/yb/tserver/remote_bootstrap_service-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 | | |
33 | | #include <limits> |
34 | | |
35 | | #include <gflags/gflags.h> |
36 | | |
37 | | #include "yb/common/wire_protocol.h" |
38 | | |
39 | | #include "yb/consensus/log_anchor_registry.h" |
40 | | #include "yb/consensus/log_reader.h" |
41 | | #include "yb/consensus/log_util.h" |
42 | | #include "yb/consensus/metadata.pb.h" |
43 | | #include "yb/consensus/opid_util.h" |
44 | | |
45 | | #include "yb/rpc/messenger.h" |
46 | | #include "yb/rpc/rpc_controller.h" |
47 | | #include "yb/rpc/rpc_header.pb.h" |
48 | | |
49 | | #include "yb/tserver/mini_tablet_server.h" |
50 | | #include "yb/tserver/remote_bootstrap-test-base.h" |
51 | | #include "yb/tserver/remote_bootstrap.pb.h" |
52 | | #include "yb/tserver/remote_bootstrap.proxy.h" |
53 | | |
54 | | #include "yb/util/crc.h" |
55 | | #include "yb/util/env_util.h" |
56 | | #include "yb/util/monotime.h" |
57 | | #include "yb/util/stopwatch.h" |
58 | | #include "yb/util/test_util.h" |
59 | | |
60 | | #define ASSERT_REMOTE_ERROR(status, err, code, str) \ |
61 | 9 | ASSERT_NO_FATALS(AssertRemoteError(status, err, code, str)) |
62 | | |
63 | | DECLARE_uint64(remote_bootstrap_idle_timeout_ms); |
64 | | DECLARE_uint64(remote_bootstrap_timeout_poll_period_ms); |
65 | | |
66 | | namespace yb { |
67 | | namespace tserver { |
68 | | |
69 | | using consensus::MaximumOpId; |
70 | | using consensus::MinimumOpId; |
71 | | using consensus::OpIdEquals; |
72 | | using env_util::ReadFully; |
73 | | using log::ReadableLogSegment; |
74 | | using rpc::ErrorStatusPB; |
75 | | using rpc::RpcController; |
76 | | |
77 | | class RemoteBootstrapServiceTest : public RemoteBootstrapTest { |
78 | | public: |
79 | 7 | RemoteBootstrapServiceTest() { |
80 | | // Poll for session expiration every 10 ms for the session timeout test. |
81 | 7 | FLAGS_remote_bootstrap_timeout_poll_period_ms = 10; |
82 | 7 | } |
83 | | |
84 | | protected: |
85 | 7 | void SetUp() override { |
86 | 7 | RemoteBootstrapTest::SetUp(); |
87 | 7 | remote_bootstrap_proxy_.reset( |
88 | 7 | new RemoteBootstrapServiceProxy( |
89 | 7 | proxy_cache_.get(), HostPort::FromBoundEndpoint(mini_server_->bound_rpc_addr()))); |
90 | 7 | } |
91 | | |
92 | | Status DoBeginRemoteBootstrapSession(const string& tablet_id, |
93 | | const string& requestor_uuid, |
94 | | BeginRemoteBootstrapSessionResponsePB* resp, |
95 | 7 | RpcController* controller) { |
96 | 7 | controller->set_timeout(MonoDelta::FromSeconds(1.0)); |
97 | 7 | BeginRemoteBootstrapSessionRequestPB req; |
98 | 7 | req.set_tablet_id(tablet_id); |
99 | 7 | req.set_requestor_uuid(requestor_uuid); |
100 | 7 | return UnwindRemoteError( |
101 | 7 | remote_bootstrap_proxy_->BeginRemoteBootstrapSession(req, resp, controller), controller); |
102 | 7 | } |
103 | | |
104 | | Status DoBeginValidRemoteBootstrapSession( |
105 | | string* session_id, |
106 | | tablet::RaftGroupReplicaSuperBlockPB* superblock = nullptr, |
107 | | uint64_t* idle_timeout_millis = nullptr, |
108 | 6 | uint64_t* first_sequence_number = nullptr) { |
109 | 6 | BeginRemoteBootstrapSessionResponsePB resp; |
110 | 6 | RpcController controller; |
111 | 6 | RETURN_NOT_OK(DoBeginRemoteBootstrapSession(GetTabletId(), GetLocalUUID(), &resp, &controller)); |
112 | 6 | *session_id = resp.session_id(); |
113 | 6 | if (superblock) { |
114 | 2 | *superblock = resp.superblock(); |
115 | 2 | } |
116 | 6 | if (idle_timeout_millis) { |
117 | 2 | *idle_timeout_millis = resp.session_idle_timeout_millis(); |
118 | 2 | } |
119 | 6 | if (first_sequence_number) { |
120 | 2 | *first_sequence_number = resp.first_wal_segment_seqno(); |
121 | 2 | } |
122 | 6 | return Status::OK(); |
123 | 6 | } |
124 | | |
125 | | Status DoCheckSessionActive(const string& session_id, |
126 | | CheckRemoteBootstrapSessionActiveResponsePB* resp, |
127 | 2 | RpcController* controller) { |
128 | 2 | controller->set_timeout(MonoDelta::FromSeconds(1.0)); |
129 | 2 | CheckRemoteBootstrapSessionActiveRequestPB req; |
130 | 2 | req.set_session_id(session_id); |
131 | 2 | return UnwindRemoteError( |
132 | 2 | remote_bootstrap_proxy_->CheckSessionActive(req, resp, controller), controller); |
133 | 2 | } |
134 | | |
135 | | Status DoFetchData(const string& session_id, const DataIdPB& data_id, |
136 | | uint64_t* offset, int64_t* max_length, |
137 | | FetchDataResponsePB* resp, |
138 | 7 | RpcController* controller) { |
139 | 7 | controller->set_timeout(MonoDelta::FromSeconds(1.0)); |
140 | 7 | FetchDataRequestPB req; |
141 | 7 | req.set_session_id(session_id); |
142 | 7 | req.mutable_data_id()->CopyFrom(data_id); |
143 | 7 | if (offset) { |
144 | 0 | req.set_offset(*offset); |
145 | 0 | } |
146 | 7 | if (max_length) { |
147 | 0 | req.set_max_length(*max_length); |
148 | 0 | } |
149 | 7 | return UnwindRemoteError( |
150 | 7 | remote_bootstrap_proxy_->FetchData(req, resp, controller), controller); |
151 | 7 | } |
152 | | |
153 | | Status DoEndRemoteBootstrapSession(const string& session_id, bool is_success, |
154 | | const Status* error_msg, |
155 | | EndRemoteBootstrapSessionResponsePB* resp, |
156 | 3 | RpcController* controller) { |
157 | 3 | controller->set_timeout(MonoDelta::FromSeconds(1.0)); |
158 | 3 | EndRemoteBootstrapSessionRequestPB req; |
159 | 3 | req.set_session_id(session_id); |
160 | 3 | req.set_is_success(is_success); |
161 | 3 | if (error_msg) { |
162 | 0 | StatusToPB(*error_msg, req.mutable_error()); |
163 | 0 | } |
164 | 3 | return UnwindRemoteError( |
165 | 3 | remote_bootstrap_proxy_->EndRemoteBootstrapSession(req, resp, controller), controller); |
166 | 3 | } |
167 | | |
168 | | // Decode the remote error into a Status object. |
169 | 18 | Status ExtractRemoteError(const ErrorStatusPB* remote_error) { |
170 | 18 | const RemoteBootstrapErrorPB& error = |
171 | 18 | remote_error->GetExtension(RemoteBootstrapErrorPB::remote_bootstrap_error_ext); |
172 | 18 | return StatusFromPB(error.status()); |
173 | 18 | } |
174 | | |
175 | | // Enhance a RemoteError Status message with additional details from the remote. |
176 | 19 | Status UnwindRemoteError(Status status, const RpcController* controller) { |
177 | 19 | if (!status.IsRemoteError()) { |
178 | 10 | return status; |
179 | 10 | } |
180 | 9 | Status remote_error = ExtractRemoteError(controller->error_response()); |
181 | 9 | return status.CloneAndPrepend(remote_error.ToString()); |
182 | 9 | } |
183 | | |
184 | | void AssertRemoteError(Status status, const ErrorStatusPB* remote_error, |
185 | | const RemoteBootstrapErrorPB::Code app_code, |
186 | 9 | const string& status_code_string) { |
187 | 18 | ASSERT_TRUE(status.IsRemoteError()) << "Unexpected status code: " << status.ToString() |
188 | 18 | << ", app code: " |
189 | 18 | << RemoteBootstrapErrorPB::Code_Name(app_code) |
190 | 18 | << ", status code string: " << status_code_string; |
191 | 9 | const Status app_status = ExtractRemoteError(remote_error); |
192 | 9 | const RemoteBootstrapErrorPB& error = |
193 | 9 | remote_error->GetExtension(RemoteBootstrapErrorPB::remote_bootstrap_error_ext); |
194 | 18 | ASSERT_EQ(app_code, error.code()) << error.ShortDebugString(); |
195 | 18 | ASSERT_EQ(status_code_string, app_status.CodeAsString()) << app_status.ToString(); |
196 | 9 | LOG(INFO) << app_status.ToString(); |
197 | 9 | } |
198 | | |
199 | | // Wrap given file name in the protobuf format suitable for a FetchData() call. |
200 | 0 | static DataIdPB AsDataTypeId(const string& file_name) { |
201 | 0 | DataIdPB data_id; |
202 | 0 | data_id.set_type(DataIdPB::ROCKSDB_FILE); |
203 | 0 | data_id.set_file_name(file_name); |
204 | 0 | return data_id; |
205 | 0 | } |
206 | | |
207 | | std::unique_ptr<RemoteBootstrapServiceProxy> remote_bootstrap_proxy_; |
208 | | }; |
209 | | |
210 | | // Test beginning and ending a remote bootstrap session. |
211 | 1 | TEST_F(RemoteBootstrapServiceTest, TestSimpleBeginEndSession) { |
212 | 1 | string session_id; |
213 | 1 | tablet::RaftGroupReplicaSuperBlockPB superblock; |
214 | 1 | uint64_t idle_timeout_millis; |
215 | 1 | uint64_t first_segment_seqno; |
216 | 1 | ASSERT_OK(DoBeginValidRemoteBootstrapSession(&session_id, |
217 | 1 | &superblock, |
218 | 1 | &idle_timeout_millis, |
219 | 1 | &first_segment_seqno)); |
220 | | // Basic validation of returned params. |
221 | 1 | ASSERT_FALSE(session_id.empty()); |
222 | 1 | ASSERT_EQ(FLAGS_remote_bootstrap_idle_timeout_ms, idle_timeout_millis); |
223 | 1 | ASSERT_TRUE(superblock.IsInitialized()); |
224 | 1 | ASSERT_EQ(1, first_segment_seqno); |
225 | | |
226 | 1 | EndRemoteBootstrapSessionResponsePB resp; |
227 | 1 | RpcController controller; |
228 | 1 | ASSERT_OK(DoEndRemoteBootstrapSession(session_id, true, nullptr, &resp, &controller)); |
229 | 1 | } |
230 | | |
231 | | // Test starting two sessions. The current implementation will silently only create one. |
232 | 1 | TEST_F(RemoteBootstrapServiceTest, TestBeginTwice) { |
233 | | // Second time through should silently succeed. |
234 | 3 | for (int i = 0; i < 2; i++) { |
235 | 2 | string session_id; |
236 | 2 | ASSERT_OK(DoBeginValidRemoteBootstrapSession(&session_id)); |
237 | 2 | ASSERT_FALSE(session_id.empty()); |
238 | 2 | } |
239 | 1 | } |
240 | | |
241 | | // Test bad session id error condition. |
242 | 1 | TEST_F(RemoteBootstrapServiceTest, TestInvalidSessionId) { |
243 | 1 | vector<string> bad_session_ids; |
244 | 1 | bad_session_ids.push_back("hodor"); |
245 | 1 | bad_session_ids.push_back(GetLocalUUID()); |
246 | | |
247 | | // Fetch a block for a non-existent session. |
248 | 2 | for (const string& session_id : bad_session_ids) { |
249 | 2 | FetchDataResponsePB resp; |
250 | 2 | RpcController controller; |
251 | 2 | DataIdPB data_id; |
252 | 2 | data_id.set_type(DataIdPB::LOG_SEGMENT); |
253 | 2 | data_id.set_wal_segment_seqno(1); |
254 | 2 | Status status = DoFetchData(session_id, data_id, nullptr, nullptr, &resp, &controller); |
255 | 2 | ASSERT_REMOTE_ERROR(status, controller.error_response(), RemoteBootstrapErrorPB::NO_SESSION, |
256 | 2 | STATUS(NotFound, "").CodeAsString()); |
257 | 2 | } |
258 | | |
259 | | // End a non-existent session. |
260 | 2 | for (const string& session_id : bad_session_ids) { |
261 | 2 | EndRemoteBootstrapSessionResponsePB resp; |
262 | 2 | RpcController controller; |
263 | 2 | Status status = DoEndRemoteBootstrapSession(session_id, true, nullptr, &resp, &controller); |
264 | 2 | ASSERT_REMOTE_ERROR(status, controller.error_response(), RemoteBootstrapErrorPB::NO_SESSION, |
265 | 2 | STATUS(NotFound, "").CodeAsString()); |
266 | 2 | } |
267 | 1 | } |
268 | | |
269 | | // Test bad tablet id error condition. |
270 | 1 | TEST_F(RemoteBootstrapServiceTest, TestInvalidTabletId) { |
271 | 1 | BeginRemoteBootstrapSessionResponsePB resp; |
272 | 1 | RpcController controller; |
273 | 1 | Status status = |
274 | 1 | DoBeginRemoteBootstrapSession("some-unknown-tablet", GetLocalUUID(), &resp, &controller); |
275 | 1 | ASSERT_REMOTE_ERROR(status, controller.error_response(), RemoteBootstrapErrorPB::TABLET_NOT_FOUND, |
276 | 1 | STATUS(NotFound, "").CodeAsString()); |
277 | 1 | } |
278 | | |
279 | | // Test DataIdPB validation. |
280 | 1 | TEST_F(RemoteBootstrapServiceTest, TestInvalidBlockOrOpId) { |
281 | 1 | string session_id; |
282 | 1 | ASSERT_OK(DoBeginValidRemoteBootstrapSession(&session_id)); |
283 | | |
284 | | // Invalid Segment Sequence Number for log fetch. |
285 | 1 | { |
286 | 1 | FetchDataResponsePB resp; |
287 | 1 | RpcController controller; |
288 | 1 | DataIdPB data_id; |
289 | 1 | data_id.set_type(DataIdPB::LOG_SEGMENT); |
290 | 1 | data_id.set_wal_segment_seqno(31337); |
291 | 1 | Status status = DoFetchData(session_id, data_id, nullptr, nullptr, &resp, &controller); |
292 | 1 | ASSERT_REMOTE_ERROR(status, controller.error_response(), |
293 | 1 | RemoteBootstrapErrorPB::WAL_SEGMENT_NOT_FOUND, |
294 | 1 | STATUS(NotFound, "").CodeAsString()); |
295 | 1 | } |
296 | | |
297 | | // Invalid file name for rocksdb file fetch. |
298 | 1 | { |
299 | 1 | FetchDataResponsePB resp; |
300 | 1 | RpcController controller; |
301 | 1 | DataIdPB data_id; |
302 | 1 | data_id.set_type(DataIdPB::ROCKSDB_FILE); |
303 | 1 | data_id.set_file_name("random_file_name"); |
304 | 1 | Status status = DoFetchData(session_id, data_id, nullptr, nullptr, &resp, &controller); |
305 | 1 | ASSERT_REMOTE_ERROR(status, controller.error_response(), |
306 | 1 | RemoteBootstrapErrorPB::ROCKSDB_FILE_NOT_FOUND, |
307 | 1 | STATUS(NotFound, "").CodeAsString()); |
308 | 1 | } |
309 | | |
310 | | // Empty data type id (no Segment Sequence Number and no RocksDB file); |
311 | 1 | { |
312 | 1 | FetchDataResponsePB resp; |
313 | 1 | RpcController controller; |
314 | 1 | DataIdPB data_id; |
315 | 1 | data_id.set_type(DataIdPB::LOG_SEGMENT); |
316 | 1 | Status status = DoFetchData(session_id, data_id, nullptr, nullptr, &resp, &controller); |
317 | 1 | ASSERT_REMOTE_ERROR(status, controller.error_response(), |
318 | 1 | RemoteBootstrapErrorPB::INVALID_REMOTE_BOOTSTRAP_REQUEST, |
319 | 1 | STATUS(InvalidArgument, "").CodeAsString()); |
320 | 1 | } |
321 | | |
322 | | // Both RocksDB file and Segment Sequence Number in the same "union" PB (illegal). |
323 | 1 | { |
324 | 1 | FetchDataResponsePB resp; |
325 | 1 | RpcController controller; |
326 | 1 | DataIdPB data_id; |
327 | 1 | data_id.set_type(DataIdPB::LOG_SEGMENT); |
328 | 1 | data_id.set_wal_segment_seqno(0); |
329 | 1 | data_id.set_file_name("dummy_file_name"); |
330 | 1 | Status status = DoFetchData(session_id, data_id, nullptr, nullptr, &resp, &controller); |
331 | 1 | ASSERT_REMOTE_ERROR(status, controller.error_response(), |
332 | 1 | RemoteBootstrapErrorPB::INVALID_REMOTE_BOOTSTRAP_REQUEST, |
333 | 1 | STATUS(InvalidArgument, "").CodeAsString()); |
334 | 1 | } |
335 | 1 | } |
336 | | |
337 | | // Test that we are able to fetch log segments. |
338 | 1 | TEST_F(RemoteBootstrapServiceTest, TestFetchLog) { |
339 | 1 | string session_id; |
340 | 1 | tablet::RaftGroupReplicaSuperBlockPB superblock; |
341 | 1 | uint64_t idle_timeout_millis; |
342 | 1 | uint64_t segment_seqno; |
343 | 1 | ASSERT_OK(DoBeginValidRemoteBootstrapSession(&session_id, |
344 | 1 | &superblock, |
345 | 1 | &idle_timeout_millis, |
346 | 1 | &segment_seqno)); |
347 | | |
348 | 1 | ASSERT_EQ(1, segment_seqno); |
349 | | |
350 | | // Fetch the remote data. |
351 | 1 | FetchDataResponsePB resp; |
352 | 1 | RpcController controller; |
353 | 1 | DataIdPB data_id; |
354 | 1 | data_id.set_type(DataIdPB::LOG_SEGMENT); |
355 | 1 | data_id.set_wal_segment_seqno(segment_seqno); |
356 | 1 | ASSERT_OK(DoFetchData(session_id, data_id, nullptr, nullptr, &resp, &controller)); |
357 | | |
358 | | // Fetch the local data. |
359 | 1 | log::SegmentSequence local_segments; |
360 | 1 | ASSERT_OK(tablet_peer_->log()->GetLogReader()->GetSegmentsSnapshot(&local_segments)); |
361 | | |
362 | 1 | uint64_t first_seg_seqno = (*local_segments.begin())->header().sequence_number(); |
363 | | |
364 | | |
365 | 2 | ASSERT_EQ(segment_seqno, first_seg_seqno) |
366 | 2 | << "Expected equal sequence numbers: " << segment_seqno |
367 | 2 | << " and " << first_seg_seqno; |
368 | 1 | const scoped_refptr<ReadableLogSegment>& segment = local_segments[0]; |
369 | 1 | faststring scratch; |
370 | 1 | int64_t size = ASSERT_RESULT(segment->readable_file_checkpoint()->Size()); |
371 | 1 | scratch.resize(size); |
372 | 1 | Slice slice; |
373 | 1 | ASSERT_OK(ReadFully(segment->readable_file_checkpoint().get(), 0, size, &slice, scratch.data())); |
374 | | |
375 | 1 | AssertDataEqual(slice.data(), slice.size(), resp.chunk()); |
376 | 1 | } |
377 | | |
378 | | // Test that the remote bootstrap session timeout works properly. |
379 | 1 | TEST_F(RemoteBootstrapServiceTest, TestSessionTimeout) { |
380 | | // This flag should be seen by the service due to TSO. |
381 | | // We have also reduced the timeout polling frequency in SetUp(). |
382 | 1 | FLAGS_remote_bootstrap_idle_timeout_ms = 1; // Expire the session almost immediately. |
383 | | |
384 | | // Start session. |
385 | 1 | string session_id; |
386 | 1 | ASSERT_OK(DoBeginValidRemoteBootstrapSession(&session_id)); |
387 | | |
388 | 1 | MonoTime start_time = MonoTime::Now(); |
389 | 1 | CheckRemoteBootstrapSessionActiveResponsePB resp; |
390 | | |
391 | 2 | do { |
392 | 2 | RpcController controller; |
393 | 2 | ASSERT_OK(DoCheckSessionActive(session_id, &resp, &controller)); |
394 | 2 | if (!resp.session_is_active()) { |
395 | 1 | break; |
396 | 1 | } |
397 | 1 | SleepFor(MonoDelta::FromMilliseconds(1)); // 1 ms |
398 | 1 | } while (MonoTime::Now().GetDeltaSince(start_time).ToSeconds() < 10); |
399 | | |
400 | 2 | ASSERT_FALSE(resp.session_is_active()) << "Remote bootstrap session did not time out!"; |
401 | 1 | } |
402 | | |
403 | | } // namespace tserver |
404 | | } // namespace yb |