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