YugabyteDB (2.13.0.0-b42, bfc6a6643e7399ac8a0e81d06a3ee6d6571b33ab)

Coverage Report

Created: 2022-03-09 17:30

/Users/deen/code/yugabyte-db/src/yb/consensus/consensus_meta-test.cc
Line
Count
Source
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
#include "yb/consensus/consensus_meta.h"
33
34
#include <vector>
35
36
#include <gtest/gtest.h>
37
38
#include "yb/common/wire_protocol.h"
39
#include "yb/consensus/metadata.pb.h"
40
#include "yb/consensus/opid_util.h"
41
#include "yb/consensus/quorum_util.h"
42
#include "yb/fs/fs_manager.h"
43
#include "yb/util/net/net_util.h"
44
#include "yb/util/test_macros.h"
45
#include "yb/util/test_util.h"
46
47
#define ASSERT_VALUES_EQUAL(cmeta, opid_index, uuid, term) \
48
4
  ASSERT_NO_FATALS(AssertValuesEqual(cmeta, opid_index, uuid, term))
49
50
namespace yb {
51
namespace consensus {
52
53
using std::string;
54
using std::vector;
55
56
const char* kTabletId = "test-consensus-metadata";
57
const int64_t kInitialTerm = 3;
58
59
class ConsensusMetadataTest : public YBTest {
60
 public:
61
  ConsensusMetadataTest()
62
6
    : fs_manager_(env_.get(), GetTestPath("fs_root"), "tserver_test") {
63
6
  }
64
65
6
  void SetUp() override {
66
6
    YBTest::SetUp();
67
6
    ASSERT_OK(fs_manager_.CreateInitialFileSystemLayout());
68
6
    ASSERT_OK(fs_manager_.Open());
69
70
    // Initialize test configuration.
71
6
    config_.set_opid_index(kInvalidOpIdIndex);
72
6
    RaftPeerPB* peer = config_.add_peers();
73
6
    peer->set_permanent_uuid(fs_manager_.uuid());
74
6
    peer->set_member_type(PeerMemberType::VOTER);
75
6
    HostPortToPB(HostPort("fake-host", 0), peer->mutable_last_known_private_addr()->Add());
76
6
  }
77
78
 protected:
79
  // Assert that the given cmeta has a single configuration with the given metadata values.
80
  void AssertValuesEqual(const ConsensusMetadata& cmeta,
81
                         int64_t opid_index, const string& permanent_uuid, int64_t term);
82
83
  FsManager fs_manager_;
84
  RaftConfigPB config_;
85
};
86
87
void ConsensusMetadataTest::AssertValuesEqual(const ConsensusMetadata& cmeta,
88
                                              int64_t opid_index,
89
                                              const string& permanent_uuid,
90
4
                                              int64_t term) {
91
  // Sanity checks.
92
4
  ASSERT_EQ(1, cmeta.committed_config().peers_size());
93
94
  // Value checks.
95
4
  ASSERT_EQ(opid_index, cmeta.committed_config().opid_index());
96
4
  ASSERT_EQ(permanent_uuid, cmeta.committed_config().peers().begin()->permanent_uuid());
97
4
  ASSERT_EQ(term, cmeta.current_term());
98
4
}
99
100
// Test the basic "happy case" of creating and then loading a file.
101
1
TEST_F(ConsensusMetadataTest, TestCreateLoad) {
102
  // Create the file.
103
1
  {
104
1
    std::unique_ptr<ConsensusMetadata> cmeta;
105
1
    ASSERT_OK(ConsensusMetadata::Create(&fs_manager_, kTabletId, fs_manager_.uuid(),
106
1
                                        config_, kInitialTerm, &cmeta));
107
1
    ASSERT_VALUES_EQUAL(*cmeta, kInvalidOpIdIndex, fs_manager_.uuid(), kInitialTerm);
108
1
  }
109
110
  // Load the file.
111
1
  std::unique_ptr<ConsensusMetadata> cmeta;
112
1
  ASSERT_OK(ConsensusMetadata::Load(&fs_manager_, kTabletId, fs_manager_.uuid(), &cmeta));
113
1
  ASSERT_VALUES_EQUAL(*cmeta, kInvalidOpIdIndex, fs_manager_.uuid(), kInitialTerm);
114
1
}
115
116
// Ensure that we get an error when loading a file that doesn't exist.
117
1
TEST_F(ConsensusMetadataTest, TestFailedLoad) {
118
1
  std::unique_ptr<ConsensusMetadata> cmeta;
119
1
  Status s = ConsensusMetadata::Load(&fs_manager_, kTabletId, fs_manager_.uuid(), &cmeta);
120
2
  ASSERT_TRUE(s.IsNotFound()) << "Unexpected status: " << s.ToString();
121
1
  LOG(INFO) << "Expected failure: " << s.ToString();
122
1
}
123
124
// Check that changes are not written to disk until Flush() is called.
125
1
TEST_F(ConsensusMetadataTest, TestFlush) {
126
1
  const int64_t kNewTerm = 4;
127
1
  std::unique_ptr<ConsensusMetadata> cmeta;
128
1
  ASSERT_OK(ConsensusMetadata::Create(&fs_manager_, kTabletId, fs_manager_.uuid(),
129
1
                                      config_, kInitialTerm, &cmeta));
130
1
  cmeta->set_current_term(kNewTerm);
131
132
  // We are sort of "breaking the rules" by having multiple ConsensusMetadata
133
  // objects in flight that point to the same file, but for a test this is fine
134
  // since it's read-only.
135
1
  {
136
1
    std::unique_ptr<ConsensusMetadata> cmeta_read;
137
1
    ASSERT_OK(ConsensusMetadata::Load(&fs_manager_, kTabletId, fs_manager_.uuid(), &cmeta_read));
138
1
    ASSERT_VALUES_EQUAL(*cmeta_read, kInvalidOpIdIndex, fs_manager_.uuid(), kInitialTerm);
139
1
  }
140
141
1
  ASSERT_OK(cmeta->Flush());
142
143
1
  {
144
1
    std::unique_ptr<ConsensusMetadata> cmeta_read;
145
1
    ASSERT_OK(ConsensusMetadata::Load(&fs_manager_, kTabletId, fs_manager_.uuid(), &cmeta_read));
146
1
    ASSERT_VALUES_EQUAL(*cmeta_read, kInvalidOpIdIndex, fs_manager_.uuid(), kNewTerm);
147
1
  }
148
1
}
149
150
// Builds a distributed configuration of voters with the given uuids.
151
9
RaftConfigPB BuildConfig(const vector<string>& uuids) {
152
9
  RaftConfigPB config;
153
36
  for (const string& uuid : uuids) {
154
36
    RaftPeerPB* peer = config.add_peers();
155
36
    peer->set_permanent_uuid(uuid);
156
36
    peer->set_member_type(PeerMemberType::VOTER);
157
36
    HostPortToPB(HostPort("255.255.255.255", 0), peer->mutable_last_known_private_addr()->Add());
158
36
  }
159
9
  return config;
160
9
}
161
162
// Test ConsensusMetadata active role calculation.
163
1
TEST_F(ConsensusMetadataTest, TestActiveRole) {
164
1
  vector<string> uuids = { "a", "b", "c", "d" };
165
1
  string peer_uuid = "e";
166
1
  RaftConfigPB config1 = BuildConfig(uuids); // We aren't a member of this config...
167
1
  config1.set_opid_index(1);
168
169
1
  std::unique_ptr<ConsensusMetadata> cmeta;
170
1
  ASSERT_OK(ConsensusMetadata::Create(&fs_manager_, kTabletId, peer_uuid,
171
1
                                      config1, kInitialTerm, &cmeta));
172
173
  // Not a participant.
174
1
  ASSERT_EQ(PeerRole::NON_PARTICIPANT, cmeta->active_role());
175
176
  // Follower.
177
1
  uuids.push_back(peer_uuid);
178
1
  RaftConfigPB config2 = BuildConfig(uuids); // But we are a member of this one.
179
1
  config2.set_opid_index(1);
180
1
  cmeta->set_committed_config(config2);
181
1
  ASSERT_EQ(PeerRole::FOLLOWER, cmeta->active_role());
182
183
  // Pending should mask committed.
184
1
  cmeta->set_pending_config(config1);
185
1
  ASSERT_EQ(PeerRole::NON_PARTICIPANT, cmeta->active_role());
186
1
  cmeta->clear_pending_config();
187
1
  ASSERT_EQ(PeerRole::FOLLOWER, cmeta->active_role());
188
189
  // Leader.
190
1
  cmeta->set_leader_uuid(peer_uuid);
191
1
  ASSERT_EQ(PeerRole::LEADER, cmeta->active_role());
192
193
  // Again, pending should mask committed.
194
1
  cmeta->set_pending_config(config1);
195
1
  ASSERT_EQ(PeerRole::NON_PARTICIPANT, cmeta->active_role());
196
1
  cmeta->set_pending_config(config2); // pending == committed.
197
1
  ASSERT_EQ(PeerRole::LEADER, cmeta->active_role());
198
1
  cmeta->set_committed_config(config1); // committed now excludes this node, but is masked...
199
1
  ASSERT_EQ(PeerRole::LEADER, cmeta->active_role());
200
201
  // ... until we clear pending, then we find committed now excludes us.
202
1
  cmeta->clear_pending_config();
203
1
  ASSERT_EQ(PeerRole::NON_PARTICIPANT, cmeta->active_role());
204
1
}
205
206
// Ensure that invocations of ToConsensusStatePB() return the expected state
207
// in the returned object.
208
1
TEST_F(ConsensusMetadataTest, TestToConsensusStatePB) {
209
1
  vector<string> uuids = { "a", "b", "c", "d" };
210
1
  string peer_uuid = "e";
211
212
1
  RaftConfigPB committed_config = BuildConfig(uuids); // We aren't a member of this config...
213
1
  committed_config.set_opid_index(1);
214
1
  std::unique_ptr<ConsensusMetadata> cmeta;
215
1
  ASSERT_OK(ConsensusMetadata::Create(&fs_manager_, kTabletId, peer_uuid,
216
1
                                      committed_config, kInitialTerm, &cmeta));
217
218
1
  uuids.push_back(peer_uuid);
219
1
  RaftConfigPB pending_config = BuildConfig(uuids);
220
221
  // Set the pending configuration to be one containing the current leader (who is not
222
  // in the committed configuration). Ensure that the leader shows up when we ask for
223
  // the active consensus state.
224
1
  cmeta->set_pending_config(pending_config);
225
1
  cmeta->set_leader_uuid(peer_uuid);
226
1
  ConsensusStatePB active_cstate = cmeta->ToConsensusStatePB(CONSENSUS_CONFIG_ACTIVE);
227
1
  ASSERT_TRUE(active_cstate.has_leader_uuid());
228
1
  ASSERT_OK(VerifyConsensusState(active_cstate, UNCOMMITTED_QUORUM));
229
230
  // Without changing anything, ask for the committed consensus state.
231
  // Since the current leader is not a voter in the committed configuration, the
232
  // returned consensus state should not list a leader.
233
1
  ConsensusStatePB committed_cstate = cmeta->ToConsensusStatePB(CONSENSUS_CONFIG_COMMITTED);
234
1
  ASSERT_FALSE(committed_cstate.has_leader_uuid());
235
1
  ASSERT_OK(VerifyConsensusState(committed_cstate, COMMITTED_QUORUM));
236
237
  // Set a new leader to be a member of the committed configuration. Now the committed
238
  // consensus state should list a leader.
239
1
  cmeta->set_leader_uuid("a");
240
1
  ConsensusStatePB new_committed_cstate = cmeta->ToConsensusStatePB(CONSENSUS_CONFIG_COMMITTED);
241
1
  ASSERT_TRUE(new_committed_cstate.has_leader_uuid());
242
1
  ASSERT_OK(VerifyConsensusState(new_committed_cstate, COMMITTED_QUORUM));
243
1
}
244
245
namespace {
246
247
// Helper for TestMergeCommittedConsensusStatePB.
248
void AssertConsensusMergeExpected(const ConsensusMetadata& cmeta,
249
                                  const ConsensusStatePB& cstate,
250
                                  int64_t expected_term,
251
3
                                  const string& expected_voted_for) {
252
  // See header docs for ConsensusMetadata::MergeCommittedConsensusStatePB() for
253
  // a "spec" of these assertions.
254
3
  ASSERT_TRUE(!cmeta.has_pending_config());
255
3
  ASSERT_EQ(cmeta.committed_config().ShortDebugString(), cstate.config().ShortDebugString());
256
3
  ASSERT_EQ("", cmeta.leader_uuid());
257
3
  ASSERT_EQ(expected_term, cmeta.current_term());
258
3
  if (expected_voted_for.empty()) {
259
1
    ASSERT_FALSE(cmeta.has_voted_for());
260
2
  } else {
261
2
    ASSERT_EQ(expected_voted_for, cmeta.voted_for());
262
2
  }
263
3
}
264
265
} // namespace
266
267
// Ensure that MergeCommittedConsensusStatePB() works as advertised.
268
1
TEST_F(ConsensusMetadataTest, TestMergeCommittedConsensusStatePB) {
269
1
  vector<string> uuids = { "a", "b", "c", "d" };
270
271
1
  RaftConfigPB committed_config = BuildConfig(uuids); // We aren't a member of this config...
272
1
  committed_config.set_opid_index(1);
273
1
  std::unique_ptr<ConsensusMetadata> cmeta;
274
1
  ASSERT_OK(ConsensusMetadata::Create(&fs_manager_, kTabletId, "e",
275
1
                                      committed_config, 1, &cmeta));
276
277
1
  uuids.push_back("e");
278
1
  RaftConfigPB pending_config = BuildConfig(uuids);
279
1
  cmeta->set_pending_config(pending_config);
280
1
  cmeta->set_leader_uuid("e");
281
1
  cmeta->set_voted_for("e");
282
283
  // Keep the term and votes because the merged term is lower.
284
1
  ConsensusStatePB remote_state;
285
1
  remote_state.set_current_term(0);
286
1
  *remote_state.mutable_config() = BuildConfig({ "x", "y", "z" });
287
1
  cmeta->MergeCommittedConsensusStatePB(remote_state);
288
1
  ASSERT_NO_FATALS(AssertConsensusMergeExpected(*cmeta, remote_state, 1, "e"));
289
290
  // Same as above because the merged term is the same as the cmeta term.
291
1
  remote_state.set_current_term(1);
292
1
  *remote_state.mutable_config() = BuildConfig({ "f", "g", "h" });
293
1
  cmeta->MergeCommittedConsensusStatePB(remote_state);
294
1
  ASSERT_NO_FATALS(AssertConsensusMergeExpected(*cmeta, remote_state, 1, "e"));
295
296
  // Higher term, so wipe out the prior state.
297
1
  remote_state.set_current_term(2);
298
1
  *remote_state.mutable_config() = BuildConfig({ "i", "j", "k" });
299
1
  cmeta->set_pending_config(pending_config);
300
1
  cmeta->MergeCommittedConsensusStatePB(remote_state);
301
1
  ASSERT_NO_FATALS(AssertConsensusMergeExpected(*cmeta, remote_state, 2, ""));
302
1
}
303
304
} // namespace consensus
305
} // namespace yb