1
2 /**
3 * Copyright (C) 2018-present MongoDB, Inc.
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the Server Side Public License, version 1,
7 * as published by MongoDB, Inc.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * Server Side Public License for more details.
13 *
14 * You should have received a copy of the Server Side Public License
15 * along with this program. If not, see
16 * <http://www.mongodb.com/licensing/server-side-public-license>.
17 *
18 * As a special exception, the copyright holders give permission to link the
19 * code of portions of this program with the OpenSSL library under certain
20 * conditions as described in each individual source file and distribute
21 * linked combinations including the program with the OpenSSL library. You
22 * must comply with the Server Side Public License in all respects for
23 * all of the code used other than as permitted herein. If you modify file(s)
24 * with this exception, you may extend this exception to your version of the
25 * file(s), but you are not obligated to do so. If you do not wish to do so,
26 * delete this exception statement from your version. If you delete this
27 * exception statement from all source files in the program, then also delete
28 * it in the license file.
29 */
30
31 #include "mongo/platform/basic.h"
32
33 #include "mongo/db/repl/repl_set_config_checks.h"
34
35 #include <iterator>
36
37 #include "mongo/db/repl/repl_set_config.h"
38 #include "mongo/db/repl/replication_coordinator_external_state.h"
39 #include "mongo/db/service_context.h"
40 #include "mongo/util/mongoutils/str.h"
41
42 namespace mongo {
43 namespace repl {
44
45 namespace {
46 /**
47 * Finds the index of the one member configuration in "newConfig" that corresponds
48 * to the current node (as identified by "externalState").
49 *
50 * Returns an error if the current node does not appear or appears multiple times in
51 * "newConfig".
52 */
findSelfInConfig(ReplicationCoordinatorExternalState * externalState,const ReplSetConfig & newConfig,ServiceContext * ctx)53 StatusWith<int> findSelfInConfig(ReplicationCoordinatorExternalState* externalState,
54 const ReplSetConfig& newConfig,
55 ServiceContext* ctx) {
56 std::vector<ReplSetConfig::MemberIterator> meConfigs;
57 for (ReplSetConfig::MemberIterator iter = newConfig.membersBegin();
58 iter != newConfig.membersEnd();
59 ++iter) {
60 if (externalState->isSelf(iter->getHostAndPort(), ctx)) {
61 meConfigs.push_back(iter);
62 }
63 }
64 if (meConfigs.empty()) {
65 return StatusWith<int>(ErrorCodes::NodeNotFound,
66 str::stream() << "No host described in new configuration "
67 << newConfig.getConfigVersion()
68 << " for replica set "
69 << newConfig.getReplSetName()
70 << " maps to this node");
71 }
72 if (meConfigs.size() > 1) {
73 str::stream message;
74 message << "The hosts " << meConfigs.front()->getHostAndPort().toString();
75 for (size_t i = 1; i < meConfigs.size() - 1; ++i) {
76 message << ", " << meConfigs[i]->getHostAndPort().toString();
77 }
78 message << " and " << meConfigs.back()->getHostAndPort().toString()
79 << " all map to this node in new configuration version "
80 << newConfig.getConfigVersion() << " for replica set "
81 << newConfig.getReplSetName();
82 return StatusWith<int>(ErrorCodes::DuplicateKey, message);
83 }
84
85 int myIndex = std::distance(newConfig.membersBegin(), meConfigs.front());
86 invariant(myIndex >= 0 && myIndex < newConfig.getNumMembers());
87 return StatusWith<int>(myIndex);
88 }
89
90 /**
91 * Checks if the node with the given config index is electable, returning a useful
92 * status message if not.
93 */
checkElectable(const ReplSetConfig & newConfig,int configIndex)94 Status checkElectable(const ReplSetConfig& newConfig, int configIndex) {
95 const MemberConfig& myConfig = newConfig.getMemberAt(configIndex);
96 if (!myConfig.isElectable()) {
97 return Status(ErrorCodes::NodeNotElectable,
98 str::stream() << "This node, " << myConfig.getHostAndPort().toString()
99 << ", with _id "
100 << myConfig.getId()
101 << " is not electable under the new configuration version "
102 << newConfig.getConfigVersion()
103 << " for replica set "
104 << newConfig.getReplSetName());
105 }
106 return Status::OK();
107 }
108
109 /**
110 * Like findSelfInConfig, above, but also returns an error if the member configuration
111 * for this node is not electable, as this is a requirement for nodes accepting
112 * reconfig or initiate commands.
113 */
findSelfInConfigIfElectable(ReplicationCoordinatorExternalState * externalState,const ReplSetConfig & newConfig,ServiceContext * ctx)114 StatusWith<int> findSelfInConfigIfElectable(ReplicationCoordinatorExternalState* externalState,
115 const ReplSetConfig& newConfig,
116 ServiceContext* ctx) {
117 StatusWith<int> result = findSelfInConfig(externalState, newConfig, ctx);
118 if (result.isOK()) {
119 Status status = checkElectable(newConfig, result.getValue());
120 if (!status.isOK()) {
121 return StatusWith<int>(status);
122 }
123 }
124 return result;
125 }
126
127 /**
128 * Checks that the priorities of all the arbiters in the configuration are 0. If they were 1,
129 * they should have been set to 0 in MemberConfig::initialize(). Otherwise, they are illegal.
130 */
validateArbiterPriorities(const ReplSetConfig & config)131 Status validateArbiterPriorities(const ReplSetConfig& config) {
132 for (ReplSetConfig::MemberIterator iter = config.membersBegin(); iter != config.membersEnd();
133 ++iter) {
134 if (iter->isArbiter() && iter->getPriority() != 0) {
135 return Status(ErrorCodes::InvalidReplicaSetConfig,
136 str::stream() << "Member " << iter->getHostAndPort().toString()
137 << " is an arbiter but has priority "
138 << iter->getPriority()
139 << ". Arbiter priority must be 0.");
140 }
141 }
142 return Status::OK();
143 }
144
145 /**
146 * Compares two initialized and validated replica set configurations, and checks to
147 * see if "newConfig" is a legal successor configuration to "oldConfig".
148 *
149 * Returns Status::OK() if "newConfig" may replace "oldConfig", or an indicative error
150 * otherwise.
151 *
152 * The checks performed by this test are necessary, but may not be sufficient for
153 * ensuring that "newConfig" is a legal successor to "oldConfig". For example,
154 * a legal reconfiguration must typically be executed on a node that is currently
155 * primary under "oldConfig" and is electable under "newConfig". Such checks that
156 * require knowledge of which node is executing the configuration are out of scope
157 * for this function.
158 */
validateOldAndNewConfigsCompatible(const ReplSetConfig & oldConfig,const ReplSetConfig & newConfig)159 Status validateOldAndNewConfigsCompatible(const ReplSetConfig& oldConfig,
160 const ReplSetConfig& newConfig) {
161 invariant(newConfig.isInitialized());
162 invariant(oldConfig.isInitialized());
163
164 if (oldConfig.getConfigVersion() >= newConfig.getConfigVersion()) {
165 return Status(ErrorCodes::NewReplicaSetConfigurationIncompatible,
166 str::stream()
167 << "New replica set configuration version must be greater than old, but "
168 << newConfig.getConfigVersion()
169 << " is not greater than "
170 << oldConfig.getConfigVersion()
171 << " for replica set "
172 << newConfig.getReplSetName());
173 }
174
175 if (oldConfig.getReplSetName() != newConfig.getReplSetName()) {
176 return Status(ErrorCodes::NewReplicaSetConfigurationIncompatible,
177 str::stream() << "New and old configurations differ in replica set name; "
178 "old was "
179 << oldConfig.getReplSetName()
180 << ", and new is "
181 << newConfig.getReplSetName());
182 }
183
184 if (oldConfig.getReplicaSetId() != newConfig.getReplicaSetId()) {
185 return Status(ErrorCodes::NewReplicaSetConfigurationIncompatible,
186 str::stream() << "New and old configurations differ in replica set ID; "
187 "old was "
188 << oldConfig.getReplicaSetId()
189 << ", and new is "
190 << newConfig.getReplicaSetId());
191 }
192
193 if (oldConfig.isConfigServer() && !newConfig.isConfigServer()) {
194 return Status(ErrorCodes::NewReplicaSetConfigurationIncompatible,
195 str::stream() << "Cannot remove \"" << ReplSetConfig::kConfigServerFieldName
196 << "\" from replica set configuration on reconfig");
197 }
198
199 //
200 // For every member config mNew in newConfig, if there exists member config mOld
201 // in oldConfig such that mNew.getHostAndPort() == mOld.getHostAndPort(), it is required
202 // that mNew.getId() == mOld.getId().
203 //
204 // Also, one may not use reconfig to change the value of the buildIndexes or
205 // arbiterOnly flags.
206 //
207 for (ReplSetConfig::MemberIterator mNew = newConfig.membersBegin();
208 mNew != newConfig.membersEnd();
209 ++mNew) {
210 for (ReplSetConfig::MemberIterator mOld = oldConfig.membersBegin();
211 mOld != oldConfig.membersEnd();
212 ++mOld) {
213 const bool idsEqual = mOld->getId() == mNew->getId();
214 const bool hostsEqual = mOld->getHostAndPort() == mNew->getHostAndPort();
215 if (!idsEqual && !hostsEqual) {
216 continue;
217 }
218 if (hostsEqual && !idsEqual) {
219 return Status(ErrorCodes::NewReplicaSetConfigurationIncompatible,
220 str::stream() << "New and old configurations both have members with "
221 << MemberConfig::kHostFieldName
222 << " of "
223 << mOld->getHostAndPort().toString()
224 << " but in the new configuration the "
225 << MemberConfig::kIdFieldName
226 << " field is "
227 << mNew->getId()
228 << " and in the old configuration it is "
229 << mOld->getId()
230 << " for replica set "
231 << newConfig.getReplSetName());
232 }
233 // At this point, the _id and host fields are equal, so we're looking at the old and
234 // new configurations for the same member node.
235 const bool buildIndexesFlagsEqual =
236 mOld->shouldBuildIndexes() == mNew->shouldBuildIndexes();
237 if (!buildIndexesFlagsEqual) {
238 return Status(ErrorCodes::NewReplicaSetConfigurationIncompatible,
239 str::stream()
240 << "New and old configurations differ in the setting of the "
241 "buildIndexes field for member "
242 << mOld->getHostAndPort().toString()
243 << "; to make this change, remove then re-add the member");
244 }
245 const bool arbiterFlagsEqual = mOld->isArbiter() == mNew->isArbiter();
246 if (!arbiterFlagsEqual) {
247 return Status(ErrorCodes::NewReplicaSetConfigurationIncompatible,
248 str::stream()
249 << "New and old configurations differ in the setting of the "
250 "arbiterOnly field for member "
251 << mOld->getHostAndPort().toString()
252 << "; to make this change, remove then re-add the member");
253 }
254 }
255 }
256 return Status::OK();
257 }
258 } // namespace
259
validateConfigForStartUp(ReplicationCoordinatorExternalState * externalState,const ReplSetConfig & newConfig,ServiceContext * ctx)260 StatusWith<int> validateConfigForStartUp(ReplicationCoordinatorExternalState* externalState,
261 const ReplSetConfig& newConfig,
262 ServiceContext* ctx) {
263 Status status = newConfig.validate();
264 if (!status.isOK()) {
265 return StatusWith<int>(status);
266 }
267 return findSelfInConfig(externalState, newConfig, ctx);
268 }
269
validateConfigForInitiate(ReplicationCoordinatorExternalState * externalState,const ReplSetConfig & newConfig,ServiceContext * ctx)270 StatusWith<int> validateConfigForInitiate(ReplicationCoordinatorExternalState* externalState,
271 const ReplSetConfig& newConfig,
272 ServiceContext* ctx) {
273 Status status = newConfig.validate();
274 if (!status.isOK()) {
275 return StatusWith<int>(status);
276 }
277
278 status = newConfig.checkIfWriteConcernCanBeSatisfied(newConfig.getDefaultWriteConcern());
279 if (!status.isOK()) {
280 return StatusWith<int>(
281 status.code(),
282 str::stream() << "Found invalid default write concern in 'getLastErrorDefaults' field"
283 << causedBy(status.reason()));
284 }
285
286 status = validateArbiterPriorities(newConfig);
287 if (!status.isOK()) {
288 return StatusWith<int>(status);
289 }
290
291 if (newConfig.getConfigVersion() != 1) {
292 return StatusWith<int>(ErrorCodes::NewReplicaSetConfigurationIncompatible,
293 str::stream() << "Configuration used to initiate a replica set must "
294 << " have version 1, but found "
295 << newConfig.getConfigVersion());
296 }
297 return findSelfInConfigIfElectable(externalState, newConfig, ctx);
298 }
299
validateConfigForReconfig(ReplicationCoordinatorExternalState * externalState,const ReplSetConfig & oldConfig,const ReplSetConfig & newConfig,ServiceContext * ctx,bool force)300 StatusWith<int> validateConfigForReconfig(ReplicationCoordinatorExternalState* externalState,
301 const ReplSetConfig& oldConfig,
302 const ReplSetConfig& newConfig,
303 ServiceContext* ctx,
304 bool force) {
305 Status status = newConfig.validate();
306 if (!status.isOK()) {
307 return StatusWith<int>(status);
308 }
309
310 status = newConfig.checkIfWriteConcernCanBeSatisfied(newConfig.getDefaultWriteConcern());
311 if (!status.isOK()) {
312 return StatusWith<int>(
313 status.code(),
314 str::stream() << "Found invalid default write concern in 'getLastErrorDefaults' field"
315 << causedBy(status.reason()));
316 }
317
318 status = validateOldAndNewConfigsCompatible(oldConfig, newConfig);
319 if (!status.isOK()) {
320 return StatusWith<int>(status);
321 }
322
323 status = validateArbiterPriorities(newConfig);
324 if (!status.isOK()) {
325 return StatusWith<int>(status);
326 }
327
328 if (force) {
329 return findSelfInConfig(externalState, newConfig, ctx);
330 }
331
332 return findSelfInConfigIfElectable(externalState, newConfig, ctx);
333 }
334
validateConfigForHeartbeatReconfig(ReplicationCoordinatorExternalState * externalState,const ReplSetConfig & newConfig,ServiceContext * ctx)335 StatusWith<int> validateConfigForHeartbeatReconfig(
336 ReplicationCoordinatorExternalState* externalState,
337 const ReplSetConfig& newConfig,
338 ServiceContext* ctx) {
339 Status status = newConfig.validate();
340 if (!status.isOK()) {
341 return StatusWith<int>(status);
342 }
343
344 return findSelfInConfig(externalState, newConfig, ctx);
345 }
346
347 } // namespace repl
348 } // namespace mongo
349