/** * Copyright (C) 2012 10gen Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the GNU Affero General Public License in all respects * for all of the code used other than as permitted herein. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you do not * wish to do so, delete this exception statement from your version. If you * delete this exception statement from all source files in the program, * then also delete it in the license file. */ #pragma once #include "mongo/db/jsobj.h" namespace mongo { /** * ChunkVersions consist of a major/minor version scoped to a version epoch * * Version configurations (format: major version, epoch): * * 1. (0, 0) - collection is dropped. * 2. (0, n), n > 0 - applicable only to shardVersion; shard has no chunk. * 3. (n, 0), n > 0 - invalid configuration. * 4. (n, m), n > 0, m > 0 - normal sharded collection version. * * TODO: This is a "manual type" but, even so, still needs to comform to what's * expected from types. */ struct ChunkVersion { union { struct { int _minor; int _major; }; unsigned long long _combined; }; OID _epoch; ChunkVersion() : _minor(0), _major(0), _epoch(OID()) {} // // Constructors shouldn't have default parameters here, since it's vital we track from // here on the epochs of versions, even if not used. // ChunkVersion(int major, int minor, const OID& epoch) : _minor(minor), _major(major), _epoch(epoch) {} static ChunkVersion DROPPED() { return ChunkVersion(0, 0, OID()); // dropped OID is zero time, zero machineId/inc } static ChunkVersion UNSHARDED() { // TODO: Distinguish between these cases return DROPPED(); } static ChunkVersion IGNORED() { ChunkVersion version = ChunkVersion(); version._epoch.init(Date_t(), true); // ignored OID is zero time, max machineId/inc return version; } static ChunkVersion fromDeprecatedLong(unsigned long long num, const OID& epoch) { ChunkVersion version(0, 0, epoch); version._combined = num; return version; } static bool isIgnoredVersion(const ChunkVersion& version) { return version.majorVersion() == 0 && version.minorVersion() == 0 && version.epoch() == IGNORED().epoch(); } void incMajor() { _major++; _minor = 0; } void incMinor() { _minor++; } // Incrementing an epoch creates a new, randomly generated identifier void incEpoch() { _epoch = OID::gen(); _major = 0; _minor = 0; } // Note: this shouldn't be used as a substitute for version except in specific cases - // epochs make versions more complex unsigned long long toLong() const { return _combined; } bool isSet() const { return _combined > 0; } bool isEpochSet() const { return _epoch.isSet(); } std::string toString() const { std::stringstream ss; // Similar to month/day/year. For the most part when debugging, we care about major // so it's first ss << _major << "|" << _minor << "||" << _epoch; return ss.str(); } int majorVersion() const { return _major; } int minorVersion() const { return _minor; } OID epoch() const { return _epoch; } // // Explicit comparison operators - versions with epochs have non-trivial comparisons. // > < operators do not check epoch cases. Generally if using == we need to handle // more complex cases. // bool operator>(const ChunkVersion& otherVersion) const { return this->_combined > otherVersion._combined; } bool operator>=(const ChunkVersion& otherVersion) const { return this->_combined >= otherVersion._combined; } bool operator<(const ChunkVersion& otherVersion) const { return this->_combined < otherVersion._combined; } bool operator<=(const ChunkVersion& otherVersion) const { return this->_combined <= otherVersion._combined; } // // Equivalence comparison types. // // Can we write to this data and not have a problem? bool isWriteCompatibleWith(const ChunkVersion& otherVersion) const { if (!hasEqualEpoch(otherVersion)) return false; return otherVersion._major == _major; } // Is this the same version? bool equals(const ChunkVersion& otherVersion) const { if (!hasEqualEpoch(otherVersion)) return false; return otherVersion._combined == _combined; } /** * Returns true if the otherVersion is the same as this version and enforces strict epoch * checking (empty epochs are not wildcards). */ bool isStrictlyEqualTo(const ChunkVersion& otherVersion) const { if (otherVersion._epoch != _epoch) return false; return otherVersion._combined == _combined; } /** * Returns true if this version is (strictly) in the same epoch as the other version and * this version is older. Returns false if we're not sure because the epochs are different * or if this version is newer. */ bool isOlderThan(const ChunkVersion& otherVersion) const { if (otherVersion._epoch != _epoch) return false; if (_major != otherVersion._major) return _major < otherVersion._major; return _minor < otherVersion._minor; } // Is this in the same epoch? bool hasEqualEpoch(const ChunkVersion& otherVersion) const { return hasEqualEpoch(otherVersion._epoch); } bool hasEqualEpoch(const OID& otherEpoch) const { return _epoch == otherEpoch; } // // BSON input/output // // The idea here is to make the BSON input style very flexible right now, so we // can then tighten it up in the next version. We can accept either a BSONObject field // with version and epoch, or version and epoch in different fields (either is optional). // In this case, epoch always is stored in a field name of the version field name + "Epoch" // // // { version : } and { version : [,] } format // static bool canParseBSON(const BSONElement& el, const std::string& prefix = "") { bool canParse; fromBSON(el, prefix, &canParse); return canParse; } static ChunkVersion fromBSON(const BSONElement& el, const std::string& prefix = "") { bool canParse; return fromBSON(el, prefix, &canParse); } static ChunkVersion fromBSON(const BSONElement& el, const std::string& prefix, bool* canParse) { *canParse = true; int type = el.type(); if (type == Array) { return fromBSON(BSONArray(el.Obj()), canParse); } if (type == jstOID) { return ChunkVersion(0, 0, el.OID()); } if (type == bsonTimestamp || type == Date) { return fromDeprecatedLong(el._numberLong(), OID()); } *canParse = false; return ChunkVersion(0, 0, OID()); } // // { version : , versionEpoch : } object format // static bool canParseBSON(const BSONObj& obj, const std::string& prefix = "") { bool canParse; fromBSON(obj, prefix, &canParse); return canParse; } static ChunkVersion fromBSON(const BSONObj& obj, const std::string& prefix = "") { bool canParse; return fromBSON(obj, prefix, &canParse); } static ChunkVersion fromBSON(const BSONObj& obj, const std::string& prefixIn, bool* canParse) { *canParse = true; std::string prefix = prefixIn; // "version" doesn't have a "cluster constanst" because that field is never // written to the config. if (prefixIn == "" && !obj["version"].eoo()) { prefix = (std::string) "version"; } // TODO: use ChunkType::DEPRECATED_lastmod() // NOTE: type_chunk.h includes this file else if (prefixIn == "" && !obj["lastmod"].eoo()) { prefix = (std::string) "lastmod"; } ChunkVersion version = fromBSON(obj[prefix], prefixIn, canParse); if (obj[prefix + "Epoch"].type() == jstOID) { version._epoch = obj[prefix + "Epoch"].OID(); *canParse = true; } return version; } // // { version : [, ] } format // static bool canParseBSON(const BSONArray& arr) { bool canParse; fromBSON(arr, &canParse); return canParse; } static ChunkVersion fromBSON(const BSONArray& arr) { bool canParse; return fromBSON(arr, &canParse); } static ChunkVersion fromBSON(const BSONArray& arr, bool* canParse) { *canParse = false; ChunkVersion version; BSONObjIterator it(arr); if (!it.more()) return version; version = fromBSON(it.next(), "", canParse); if (!(*canParse)) return version; *canParse = true; if (!it.more()) return version; BSONElement next = it.next(); if (next.type() != jstOID) return version; version._epoch = next.OID(); return version; } enum VersionChoice { VersionChoice_Local, VersionChoice_Remote, VersionChoice_Unknown }; /** * Compares a remotely-loaded version 'remoteVersion' to the latest local version of a * collection, 'localVersion', and returns the newest. * * Because it isn't clear during epoch changes which epoch is newer, the local version * before the reload occurred, 'prevLocalVersion', is used to determine whether the remote * epoch is definitely newer, or we're not sure. */ static VersionChoice chooseNewestVersion(ChunkVersion prevLocalVersion, ChunkVersion localVersion, ChunkVersion remoteVersion) { OID prevEpoch = prevLocalVersion.epoch(); OID localEpoch = localVersion.epoch(); OID remoteEpoch = remoteVersion.epoch(); // Everything changed in-flight, so we need to try again if (prevEpoch != localEpoch && localEpoch != remoteEpoch) { return VersionChoice_Unknown; } // We're in the same (zero) epoch as the latest metadata, nothing to do if (localEpoch == remoteEpoch && !remoteEpoch.isSet()) { return VersionChoice_Local; } // We're in the same (non-zero) epoch as the latest metadata, so increment the version if (localEpoch == remoteEpoch && remoteEpoch.isSet()) { // Use the newer version if possible if (localVersion < remoteVersion) { return VersionChoice_Remote; } else { return VersionChoice_Local; } } // We're now sure we're installing a new epoch and the epoch didn't change during reload dassert(prevEpoch == localEpoch && localEpoch != remoteEpoch); return VersionChoice_Remote; } // // Currently our BSON output is to two different fields, to cleanly work with older // versions that know nothing about epochs. // BSONObj toBSONWithPrefix(const std::string& prefixIn) const { BSONObjBuilder b; std::string prefix = prefixIn; if (prefix == "") prefix = "version"; b.appendTimestamp(prefix, _combined); b.append(prefix + "Epoch", _epoch); return b.obj(); } void addToBSON(BSONObjBuilder& b, const std::string& prefix = "") const { b.appendElements(toBSONWithPrefix(prefix)); } void addEpochToBSON(BSONObjBuilder& b, const std::string& prefix = "") const { b.append(prefix + "Epoch", _epoch); } BSONObj toBSON() const { // ChunkVersion wants to be an array. BSONArrayBuilder b; b.appendTimestamp(_combined); b.append(_epoch); return b.arr(); } bool parseBSON(const BSONObj& source, std::string* errMsg) { // ChunkVersion wants to be an array. BSONArray arrSource = static_cast(source); bool canParse; ChunkVersion version = fromBSON(arrSource, &canParse); if (!canParse) { *errMsg = "Could not parse version structure"; return false; } _minor = version._minor; _major = version._major; _epoch = version._epoch; return true; } void clear() { _minor = 0; _major = 0; _epoch = OID(); } void cloneTo(ChunkVersion* other) const { other->clear(); other->_minor = _minor; other->_major = _major; other->_epoch = _epoch; } }; inline std::ostream& operator<<(std::ostream& s, const ChunkVersion& v) { s << v.toString(); return s; } } // namespace mongo