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