YugabyteDB (2.13.0.0-b42, bfc6a6643e7399ac8a0e81d06a3ee6d6571b33ab)

Coverage Report

Created: 2022-03-09 17:30

/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