summaryrefslogtreecommitdiff
path: root/src/mongo/s/util.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/s/util.h')
-rw-r--r--src/mongo/s/util.h445
1 files changed, 445 insertions, 0 deletions
diff --git a/src/mongo/s/util.h b/src/mongo/s/util.h
new file mode 100644
index 00000000000..aab63cd09ed
--- /dev/null
+++ b/src/mongo/s/util.h
@@ -0,0 +1,445 @@
+// util.h
+
+/**
+* Copyright (C) 2008 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 <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#include "mongo/pch.h"
+#include "mongo/db/jsobj.h"
+#include "mongo/util/mongoutils/str.h"
+/**
+ some generic sharding utils that can be used in mongod or mongos
+ */
+
+namespace mongo {
+
+ //
+ // ShardChunkVersions consist of a major/minor version scoped to a version epoch
+ //
+ struct ShardChunkVersion {
+ union {
+ struct {
+ int _minor;
+ int _major;
+ };
+ unsigned long long _combined;
+ };
+ OID _epoch;
+
+ ShardChunkVersion() : _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.
+ //
+
+ ShardChunkVersion( int major, int minor, const OID& epoch )
+ : _minor(minor),_major(major), _epoch(epoch) {
+ }
+
+ ShardChunkVersion( unsigned long long ll, const OID& epoch )
+ : _combined( ll ), _epoch(epoch) {
+ }
+
+ void inc( bool major ) {
+ if ( major )
+ incMajor();
+ else
+ incMinor();
+ }
+
+ 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();
+ }
+
+ string toString() const {
+ 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 ShardChunkVersion& otherVersion ) const {
+ return this->_combined > otherVersion._combined;
+ }
+
+ bool operator>=( const ShardChunkVersion& otherVersion ) const {
+ return this->_combined >= otherVersion._combined;
+ }
+
+ bool operator<( const ShardChunkVersion& otherVersion ) const {
+ return this->_combined < otherVersion._combined;
+ }
+
+ bool operator<=( const ShardChunkVersion& otherVersion ) const {
+ return this->_combined < otherVersion._combined;
+ }
+
+ //
+ // Equivalence comparison types.
+ //
+
+ // Can we write to this data and not have a problem?
+ bool isWriteCompatibleWith( const ShardChunkVersion& otherVersion ) const {
+ if( ! hasCompatibleEpoch( otherVersion ) ) return false;
+ return otherVersion._major == _major;
+ }
+
+ // Is this the same version?
+ bool isEquivalentTo( const ShardChunkVersion& otherVersion ) const {
+ if( ! hasCompatibleEpoch( otherVersion ) ) return false;
+ return otherVersion._combined == _combined;
+ }
+
+ // Is this in the same epoch?
+ bool hasCompatibleEpoch( const ShardChunkVersion& otherVersion ) const {
+ return hasCompatibleEpoch( otherVersion._epoch );
+ }
+
+ bool hasCompatibleEpoch( const OID& otherEpoch ) const {
+ // TODO : Change logic from eras are not-unequal to eras are equal
+ if( otherEpoch.isSet() && _epoch.isSet() && otherEpoch != _epoch ) return false;
+ return true;
+ }
+
+ //
+ // 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 : <TS> } and { version : [<TS>,<OID>] } format
+ //
+
+ static bool canParseBSON( const BSONElement& el, const string& prefix="" ){
+ bool canParse;
+ fromBSON( el, prefix, &canParse );
+ return canParse;
+ }
+
+ static ShardChunkVersion fromBSON( const BSONElement& el, const string& prefix="" ){
+ bool canParse;
+ return fromBSON( el, prefix, &canParse );
+ }
+
+ static ShardChunkVersion fromBSON( const BSONElement& el,
+ const string& prefix,
+ bool* canParse )
+ {
+ *canParse = true;
+
+ int type = el.type();
+
+ if( type == Array ){
+ return fromBSON( BSONArray( el.Obj() ), canParse );
+ }
+
+ if( type == jstOID ){
+ return ShardChunkVersion( 0, 0, el.OID() );
+ }
+
+ if( el.isNumber() ){
+ return ShardChunkVersion( static_cast<unsigned long long>( el.numberLong() ), OID() );
+ }
+
+ if( type == Timestamp || type == Date ){
+ return ShardChunkVersion( el._numberLong(), OID() );
+ }
+
+ // Note - we used to throw here, we can't anymore b/c debug builds will be unhappy
+ warning() << "can't load version from element type (" << (int)(el.type()) << ") "
+ << el << endl;
+
+ *canParse = false;
+
+ return ShardChunkVersion( 0, OID() );
+ }
+
+ //
+ // { version : <TS>, versionEpoch : <OID> } object format
+ //
+
+ static bool canParseBSON( const BSONObj& obj, const string& prefix="" ){
+ bool canParse;
+ fromBSON( obj, prefix, &canParse );
+ return canParse;
+ }
+
+ static ShardChunkVersion fromBSON( const BSONObj& obj, const string& prefix="" ){
+ bool canParse;
+ return fromBSON( obj, prefix, &canParse );
+ }
+
+ static ShardChunkVersion fromBSON( const BSONObj& obj,
+ const string& prefixIn,
+ bool* canParse )
+ {
+ *canParse = true;
+
+ string prefix = prefixIn;
+ if( prefixIn == "" && ! obj[ "version" ].eoo() ){
+ prefix = (string)"version";
+ }
+ else if( prefixIn == "" && ! obj[ "lastmod" ].eoo() ){
+ prefix = (string)"lastmod";
+ }
+
+ ShardChunkVersion version = fromBSON( obj[ prefix ], prefixIn, canParse );
+
+ if( obj[ prefix + "Epoch" ].type() == jstOID ){
+ version._epoch = obj[ prefix + "Epoch" ].OID();
+ *canParse = true;
+ }
+
+ return version;
+ }
+
+ //
+ // { version : [<TS>, <OID>] } format
+ //
+
+ static bool canParseBSON( const BSONArray& arr ){
+ bool canParse;
+ fromBSON( arr, &canParse );
+ return canParse;
+ }
+
+ static ShardChunkVersion fromBSON( const BSONArray& arr ){
+ bool canParse;
+ return fromBSON( arr, &canParse );
+ }
+
+ static ShardChunkVersion fromBSON( const BSONArray& arr,
+ bool* canParse )
+ {
+ *canParse = false;
+
+ ShardChunkVersion 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;
+ }
+
+ //
+ // Currently our BSON output is to two different fields, to cleanly work with older
+ // versions that know nothing about epochs.
+ //
+
+ BSONObj toBSON( const string& prefixIn="" ) const {
+ BSONObjBuilder b;
+
+ string prefix = prefixIn;
+ if( prefix == "" ) prefix = "version";
+
+ b.appendTimestamp( prefix, _combined );
+ b.append( prefix + "Epoch", _epoch );
+ return b.obj();
+ }
+
+ void addToBSON( BSONObjBuilder& b, const string& prefix="" ) const {
+ b.appendElements( toBSON( prefix ) );
+ }
+
+ void addEpochToBSON( BSONObjBuilder& b, const string& prefix="" ) const {
+ b.append( prefix + "Epoch", _epoch );
+ }
+
+ };
+
+ inline ostream& operator<<( ostream &s , const ShardChunkVersion& v) {
+ s << v.toString();
+ return s;
+ }
+
+ /**
+ * your config info for a given shard/chunk is out of date
+ */
+ class StaleConfigException : public AssertionException {
+ public:
+ StaleConfigException( const string& ns , const string& raw , int code, ShardChunkVersion received, ShardChunkVersion wanted, bool justConnection = false )
+ : AssertionException(
+ mongoutils::str::stream() << raw << " ( ns : " << ns <<
+ ", received : " << received.toString() <<
+ ", wanted : " << wanted.toString() <<
+ ", " << ( code == SendStaleConfigCode ? "send" : "recv" ) << " )",
+ code ),
+ _justConnection(justConnection) ,
+ _ns(ns),
+ _received( received ),
+ _wanted( wanted )
+ {}
+
+ // Preferred if we're rebuilding this from a thrown exception
+ StaleConfigException( const string& raw , int code, const BSONObj& error, bool justConnection = false )
+ : AssertionException( mongoutils::str::stream()
+ << raw << " ( ns : " << ( error["ns"].type() == String ? error["ns"].String() : string("<unknown>") )
+ << ", received : " << ShardChunkVersion::fromBSON( error, "vReceived" ).toString()
+ << ", wanted : " << ShardChunkVersion::fromBSON( error, "vWanted" ).toString()
+ << ", " << ( code == SendStaleConfigCode ? "send" : "recv" ) << " )",
+ code ),
+
+ _justConnection(justConnection) ,
+ // For legacy reasons, we may not always get a namespace here
+ _ns( error["ns"].type() == String ? error["ns"].String() : "" ),
+ _received( ShardChunkVersion::fromBSON( error, "vReceived" ) ),
+ _wanted( ShardChunkVersion::fromBSON( error, "vWanted" ) )
+ {}
+
+ // Needs message so when we trace all exceptions on construction we get a useful
+ // message
+ StaleConfigException() :
+ AssertionException( "initializing empty stale config exception object", 0 ) {}
+
+ virtual ~StaleConfigException() throw() {}
+
+ virtual void appendPrefix( stringstream& ss ) const { ss << "stale sharding config exception: "; }
+
+ bool justConnection() const { return _justConnection; }
+
+ string getns() const { return _ns; }
+
+ /**
+ * true if this exception would require a full reload of config data to resolve
+ */
+ bool requiresFullReload() const {
+ return ! _received.hasCompatibleEpoch( _wanted ) ||
+ _received.isSet() != _wanted.isSet();
+ }
+
+ static bool parse( const string& big , string& ns , string& raw ) {
+ string::size_type start = big.find( '[' );
+ if ( start == string::npos )
+ return false;
+ string::size_type end = big.find( ']' ,start );
+ if ( end == string::npos )
+ return false;
+
+ ns = big.substr( start + 1 , ( end - start ) - 1 );
+ raw = big.substr( end + 1 );
+ return true;
+ }
+
+ ShardChunkVersion getVersionReceived() const { return _received; }
+ ShardChunkVersion getVersionWanted() const { return _wanted; }
+
+ StaleConfigException& operator=( const StaleConfigException& elem ) {
+
+ this->_ei.msg = elem._ei.msg;
+ this->_ei.code = elem._ei.code;
+ this->_justConnection = elem._justConnection;
+ this->_ns = elem._ns;
+ this->_received = elem._received;
+ this->_wanted = elem._wanted;
+
+ return *this;
+ }
+
+ private:
+ bool _justConnection;
+ string _ns;
+ ShardChunkVersion _received;
+ ShardChunkVersion _wanted;
+ };
+
+ class SendStaleConfigException : public StaleConfigException {
+ public:
+ SendStaleConfigException( const string& ns , const string& raw , ShardChunkVersion received, ShardChunkVersion wanted, bool justConnection = false )
+ : StaleConfigException( ns, raw, SendStaleConfigCode, received, wanted, justConnection ) {}
+ SendStaleConfigException( const string& raw , const BSONObj& error, bool justConnection = false )
+ : StaleConfigException( raw, SendStaleConfigCode, error, justConnection ) {}
+ };
+
+ class RecvStaleConfigException : public StaleConfigException {
+ public:
+ RecvStaleConfigException( const string& ns , const string& raw , ShardChunkVersion received, ShardChunkVersion wanted, bool justConnection = false )
+ : StaleConfigException( ns, raw, RecvStaleConfigCode, received, wanted, justConnection ) {}
+ RecvStaleConfigException( const string& raw , const BSONObj& error, bool justConnection = false )
+ : StaleConfigException( raw, RecvStaleConfigCode, error, justConnection ) {}
+ };
+
+ class ShardConnection;
+ class DBClientBase;
+ class VersionManager {
+ public:
+ VersionManager(){};
+
+ bool isVersionableCB( DBClientBase* );
+ bool initShardVersionCB( DBClientBase*, BSONObj& );
+ bool forceRemoteCheckShardVersionCB( const string& );
+ bool checkShardVersionCB( DBClientBase*, const string&, bool, int );
+ bool checkShardVersionCB( ShardConnection*, bool, int );
+ void resetShardVersionCB( DBClientBase* );
+
+ };
+
+ extern VersionManager versionManager;
+
+}