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 #pragma once
32 
33 #include "mongo/db/jsobj.h"
34 #include "mongo/util/assert_util.h"
35 
36 namespace mongo {
37 
38 class BSONObj;
39 template <typename T>
40 class StatusWith;
41 
42 /**
43  * ChunkVersions consist of a major/minor version scoped to a version epoch
44  *
45  * Version configurations (format: major version, epoch):
46  *
47  * 1. (0, 0) - collection is dropped.
48  * 2. (0, n), n > 0 - applicable only to shardVersion; shard has no chunk.
49  * 3. (n, 0), n > 0 - invalid configuration.
50  * 4. (n, m), n > 0, m > 0 - normal sharded collection version.
51  *
52  * TODO: This is a "manual type" but, even so, still needs to comform to what's
53  * expected from types.
54  */
55 struct ChunkVersion {
56 public:
57     /**
58      * The name for the shard version information field, which shard-aware commands should include
59      * if they want to convey shard version.
60      */
61     static const char kShardVersionField[];
62 
ChunkVersionChunkVersion63     ChunkVersion() : _combined(0), _epoch(OID()) {}
64 
ChunkVersionChunkVersion65     ChunkVersion(uint32_t major, uint32_t minor, const OID& epoch)
66         : _combined(static_cast<uint64_t>(minor) | (static_cast<uint64_t>(major) << 32)),
67           _epoch(epoch) {}
68 
69     /**
70      * Interprets the specified BSON content as the format for commands, which is in the form:
71      *  { ..., shardVersion: [ <combined major/minor>, <OID epoch> ], ... }
72      */
73     static StatusWith<ChunkVersion> parseFromBSONForCommands(const BSONObj& obj);
74 
75     /**
76      * Parses the BSON formatted by ChunkVersion::appendWithFieldForCommands.
77      *
78      * Interprets the specified BSON content as the format for commands, which is in the form:
79      *  { ..., <field>: [ <combined major/minor>, <OID epoch> ], ... }.
80      */
81     static StatusWith<ChunkVersion> parseFromBSONWithFieldForCommands(const BSONObj& obj,
82                                                                       StringData field);
83 
84     /**
85      * Note: if possible, use ChunkVersion::parseFromBSONForCommands or
86      * ChunkVersion::parseFromBSONWithFieldForCommands instead. Phasing out this function.
87      *
88      * Interprets the specified BSON content as the format for the setShardVersion command, which
89      * is in the form:
90      *  { ..., version: [ <combined major/minor> ], versionEpoch: [ <OID epoch> ], ... }
91      */
92     static StatusWith<ChunkVersion> parseFromBSONForSetShardVersion(const BSONObj& obj);
93 
94     /**
95      * Note: if possible, use ChunkVersion::parseFromBSONForCommands or
96      * ChunkVersion::parseFromBSONWithFieldForCommands instead. Phasing out this function.
97      *
98      * Interprets the specified BSON content as the format for chunk persistence, which is in the
99      * form:
100      *  { ..., lastmod: [ <combined major/minor> ], lastmodEpoch: [ <OID epoch> ], ... }
101      */
102     static StatusWith<ChunkVersion> parseFromBSONForChunk(const BSONObj& obj);
103 
104     /**
105      * Interprets the lastmod (combined major/minor) from a BSONObj without an epoch
106      *  { ..., <field>: [ <combined major/minor> ], ... }
107      * and then sets the returned ChunkVersion's epoch field to 'epoch'.
108      */
109     static StatusWith<ChunkVersion> parseFromBSONWithFieldAndSetEpoch(const BSONObj& obj,
110                                                                       StringData field,
111                                                                       const OID& epoch);
112 
113     /**
114      * Indicates a dropped collection. All components are zeroes (OID is zero time, zero
115      * machineId/inc).
116      */
DROPPEDChunkVersion117     static ChunkVersion DROPPED() {
118         return ChunkVersion(0, 0, OID());
119     }
120 
121     /**
122      * Indicates that the collection is not sharded. Same as DROPPED.
123      */
UNSHARDEDChunkVersion124     static ChunkVersion UNSHARDED() {
125         return ChunkVersion(0, 0, OID());
126     }
127 
IGNOREDChunkVersion128     static ChunkVersion IGNORED() {
129         ChunkVersion version = ChunkVersion();
130         version._epoch.init(Date_t(), true);  // ignored OID is zero time, max machineId/inc
131         return version;
132     }
133 
fromDeprecatedLongChunkVersion134     static ChunkVersion fromDeprecatedLong(unsigned long long num, const OID& epoch) {
135         ChunkVersion version(0, 0, epoch);
136         version._combined = num;
137         return version;
138     }
139 
isIgnoredVersionChunkVersion140     static bool isIgnoredVersion(const ChunkVersion& version) {
141         return version.majorVersion() == 0 && version.minorVersion() == 0 &&
142             version.epoch() == IGNORED().epoch();
143     }
144 
incMajorChunkVersion145     void incMajor() {
146         uassert(
147             31180,
148             "The chunk major version has reached its maximum value. Manual intervention will be "
149             "required before more chunk move, split, or merge operations are allowed.",
150             majorVersion() != std::numeric_limits<uint32_t>::max());
151         _combined = static_cast<uint64_t>(majorVersion() + 1) << 32;
152     }
153 
incMinorChunkVersion154     void incMinor() {
155         uassert(
156             31181,
157             "The chunk minor version has reached its maximum value. Manual intervention will be "
158             "required before more chunk split or merge operations are allowed.",
159             minorVersion() != std::numeric_limits<uint32_t>::max());
160 
161         _combined++;
162     }
163 
164     // Note: this shouldn't be used as a substitute for version except in specific cases -
165     // epochs make versions more complex
toLongChunkVersion166     unsigned long long toLong() const {
167         return _combined;
168     }
169 
isSetChunkVersion170     bool isSet() const {
171         return _combined > 0;
172     }
173 
majorVersionChunkVersion174     uint32_t majorVersion() const {
175         return _combined >> 32;
176     }
177 
minorVersionChunkVersion178     uint32_t minorVersion() const {
179         return _combined & 0xFFFFFFFF;
180     }
181 
epochChunkVersion182     OID epoch() const {
183         return _epoch;
184     }
185 
186     //
187     // Explicit comparison operators - versions with epochs have non-trivial comparisons.
188     // > < operators do not check epoch cases.  Generally if using == we need to handle
189     // more complex cases.
190     //
191 
192     bool operator==(const ChunkVersion& otherVersion) const {
193         return equals(otherVersion);
194     }
195 
196     bool operator!=(const ChunkVersion& otherVersion) const {
197         return !(otherVersion == *this);
198     }
199 
200     bool operator>(const ChunkVersion& otherVersion) const {
201         return this->_combined > otherVersion._combined;
202     }
203 
204     bool operator>=(const ChunkVersion& otherVersion) const {
205         return this->_combined >= otherVersion._combined;
206     }
207 
208     bool operator<(const ChunkVersion& otherVersion) const {
209         return this->_combined < otherVersion._combined;
210     }
211 
212     bool operator<=(const ChunkVersion& otherVersion) const {
213         return this->_combined <= otherVersion._combined;
214     }
215 
216     //
217     // Equivalence comparison types.
218     //
219 
220     // Can we write to this data and not have a problem?
isWriteCompatibleWithChunkVersion221     bool isWriteCompatibleWith(const ChunkVersion& otherVersion) const {
222         if (!hasEqualEpoch(otherVersion))
223             return false;
224         return otherVersion.majorVersion() == majorVersion();
225     }
226 
227     // Is this the same version?
equalsChunkVersion228     bool equals(const ChunkVersion& otherVersion) const {
229         if (!hasEqualEpoch(otherVersion))
230             return false;
231         return otherVersion._combined == _combined;
232     }
233 
234     /**
235      * Returns true if the otherVersion is the same as this version and enforces strict epoch
236      * checking (empty epochs are not wildcards).
237      */
isStrictlyEqualToChunkVersion238     bool isStrictlyEqualTo(const ChunkVersion& otherVersion) const {
239         if (otherVersion._epoch != _epoch)
240             return false;
241         return otherVersion._combined == _combined;
242     }
243 
244     /**
245      * Returns true if this version is (strictly) in the same epoch as the other version and
246      * this version is older.  Returns false if we're not sure because the epochs are different
247      * or if this version is newer.
248      */
isOlderThanChunkVersion249     bool isOlderThan(const ChunkVersion& otherVersion) const {
250         if (otherVersion._epoch != _epoch)
251             return false;
252 
253         if (majorVersion() != otherVersion.majorVersion())
254             return majorVersion() < otherVersion.majorVersion();
255 
256         return minorVersion() < otherVersion.minorVersion();
257     }
258 
259     // Is this in the same epoch?
hasEqualEpochChunkVersion260     bool hasEqualEpoch(const ChunkVersion& otherVersion) const {
261         return hasEqualEpoch(otherVersion._epoch);
262     }
263 
hasEqualEpochChunkVersion264     bool hasEqualEpoch(const OID& otherEpoch) const {
265         return _epoch == otherEpoch;
266     }
267 
268     //
269     // BSON input/output
270     //
271     // The idea here is to make the BSON input style very flexible right now, so we
272     // can then tighten it up in the next version.  We can accept either a BSONObject field
273     // with version and epoch, or version and epoch in different fields (either is optional).
274     // In this case, epoch always is stored in a field name of the version field name + "Epoch"
275     //
276 
277     //
278     // { version : <TS> } and { version : [<TS>,<OID>] } format
279     //
280 
fromBSONChunkVersion281     static ChunkVersion fromBSON(const BSONElement& el, const std::string& prefix, bool* canParse) {
282         *canParse = true;
283 
284         int type = el.type();
285 
286         if (type == Array) {
287             return fromBSON(BSONArray(el.Obj()), canParse);
288         }
289 
290         if (type == jstOID) {
291             return ChunkVersion(0, 0, el.OID());
292         }
293 
294         if (type == bsonTimestamp || type == Date) {
295             return fromDeprecatedLong(el._numberLong(), OID());
296         }
297 
298         *canParse = false;
299 
300         return ChunkVersion(0, 0, OID());
301     }
302 
303     //
304     // { version : <TS>, versionEpoch : <OID> } object format
305     //
306 
307     static ChunkVersion fromBSON(const BSONObj& obj, const std::string& prefix = "") {
308         bool canParse;
309         return fromBSON(obj, prefix, &canParse);
310     }
311 
fromBSONChunkVersion312     static ChunkVersion fromBSON(const BSONObj& obj, const std::string& prefixIn, bool* canParse) {
313         *canParse = true;
314 
315         std::string prefix = prefixIn;
316         // "version" doesn't have a "cluster constanst" because that field is never
317         // written to the config.
318         if (prefixIn == "" && !obj["version"].eoo()) {
319             prefix = (std::string) "version";
320         }
321         // TODO: use ChunkType::lastmod()
322         // NOTE: type_chunk.h includes this file
323         else if (prefixIn == "" && !obj["lastmod"].eoo()) {
324             prefix = (std::string) "lastmod";
325         }
326 
327         ChunkVersion version = fromBSON(obj[prefix], prefixIn, canParse);
328 
329         if (obj[prefix + "Epoch"].type() == jstOID) {
330             version._epoch = obj[prefix + "Epoch"].OID();
331             *canParse = true;
332         }
333 
334         return version;
335     }
336 
337     //
338     // { version : [<TS>, <OID>] } format
339     //
340 
fromBSONChunkVersion341     static ChunkVersion fromBSON(const BSONArray& arr, bool* canParse) {
342         *canParse = false;
343 
344         ChunkVersion version;
345 
346         BSONObjIterator it(arr);
347         if (!it.more())
348             return version;
349 
350         version = fromBSON(it.next(), "", canParse);
351         if (!(*canParse))
352             return version;
353 
354         *canParse = true;
355 
356         if (!it.more())
357             return version;
358         BSONElement next = it.next();
359         if (next.type() != jstOID)
360             return version;
361 
362         version._epoch = next.OID();
363 
364         return version;
365     }
366 
367     //
368     // Currently our BSON output is to two different fields, to cleanly work with older
369     // versions that know nothing about epochs.
370     //
371 
toBSONWithPrefixChunkVersion372     BSONObj toBSONWithPrefix(const std::string& prefix) const {
373         invariant(!prefix.empty());
374 
375         BSONObjBuilder b;
376         b.appendTimestamp(prefix, _combined);
377         b.append(prefix + "Epoch", _epoch);
378         return b.obj();
379     }
380 
381     /**
382      * Note: if possible, use ChunkVersion::appendForCommands or
383      * ChunkVersion::appendWithFieldForCommands instead. Phasing out this function.
384      */
addToBSONChunkVersion385     void addToBSON(BSONObjBuilder& b, const std::string& prefix) const {
386         b.appendElements(toBSONWithPrefix(prefix));
387     }
388 
389     /**
390      * Appends the contents to the specified builder in the format expected by the setShardVersion
391      * command.
392      */
393     void appendForSetShardVersion(BSONObjBuilder* builder) const;
394 
395     /**
396      * Appends the contents to the specified builder in the format expected by the sharded commands.
397      */
398     void appendForCommands(BSONObjBuilder* builder) const;
399 
400     /**
401      * Appends the contents as an array to "builder" with the field name "field" in the format
402      * expected by the sharded commands.
403      *
404      * { ..., <field>: [ <combined major/minor>, <OID epoch> ], ... }
405      *
406      * Use ChunkVersion::parseFromBSONWithFieldForCommands to retrieve the ChunkVersion from the
407      * BSON created by this function.
408      */
409     void appendWithFieldForCommands(BSONObjBuilder* builder, StringData field) const;
410 
411     /**
412      * Appends the contents to the specified builder in the format expected by the chunk
413      * serialization/deserialization code.
414      */
415     void appendForChunk(BSONObjBuilder* builder) const;
416 
toStringChunkVersion417     std::string toString() const {
418         StringBuilder sb;
419         sb << majorVersion() << "|" << minorVersion() << "||" << _epoch;
420         return sb.str();
421     }
422 
423     BSONObj toBSON() const;
424 
425 private:
426     uint64_t _combined;
427 
428     OID _epoch;
429 };
430 
431 inline std::ostream& operator<<(std::ostream& s, const ChunkVersion& v) {
432     s << v.toString();
433     return s;
434 }
435 
436 }  // namespace mongo
437