From 3cc16161de9e097c34f8992bd7376203af86182e Mon Sep 17 00:00:00 2001 From: Alberto Lerner Date: Mon, 19 Nov 2012 18:49:17 -0500 Subject: [PATCH] SERVER-939 Introduced types for config.databases and config.chunks collections. --- src/mongo/s/SConscript | 8 +- src/mongo/s/field_parser.cpp | 18 +++ src/mongo/s/field_parser.h | 5 + src/mongo/s/field_parser_test.cpp | 118 ++++++++++------- src/mongo/s/type_chunk.cpp | 196 +++++++++++++++++++++++++++++ src/mongo/s/type_chunk.h | 152 ++++++++++++++++++++++ src/mongo/s/type_chunk_test.cpp | 162 ++++++++++++++++++++++++ src/mongo/s/type_database.cpp | 117 +++++++++++++++++ src/mongo/s/type_database.h | 135 ++++++++++++++++++++ src/mongo/s/type_database_test.cpp | 65 ++++++++++ 10 files changed, 928 insertions(+), 48 deletions(-) create mode 100644 src/mongo/s/type_chunk.cpp create mode 100644 src/mongo/s/type_chunk.h create mode 100644 src/mongo/s/type_chunk_test.cpp create mode 100644 src/mongo/s/type_database.cpp create mode 100644 src/mongo/s/type_database.h create mode 100644 src/mongo/s/type_database_test.cpp diff --git a/src/mongo/s/SConscript b/src/mongo/s/SConscript index 5fef9f141118d..7a273dfeb3356 100644 --- a/src/mongo/s/SConscript +++ b/src/mongo/s/SConscript @@ -4,10 +4,16 @@ Import("env") env.StaticLibrary('config', ['cluster_constants.cpp', 'field_parser.cpp', - 'type_collection.cpp'], + 'type_chunk.cpp', + 'type_collection.cpp', + 'type_database.cpp'], LIBDEPS=['$BUILD_DIR/mongo/base/base', '$BUILD_DIR/mongo/bson']) env.CppUnitTest('field_parser_test', 'field_parser_test.cpp', LIBDEPS=['config']) +env.CppUnitTest('type_chunk_test', 'type_chunk_test.cpp', LIBDEPS=['config']) + env.CppUnitTest('type_collection_test', 'type_collection_test.cpp', LIBDEPS=['config']) + +env.CppUnitTest('type_database_test', 'type_database_test.cpp', LIBDEPS=['config']) diff --git a/src/mongo/s/field_parser.cpp b/src/mongo/s/field_parser.cpp index 1d285ce7e29ac..9d5080b16c5c9 100644 --- a/src/mongo/s/field_parser.cpp +++ b/src/mongo/s/field_parser.cpp @@ -36,6 +36,24 @@ namespace mongo { return false; } + bool FieldParser::extract(BSONObj doc, + const BSONField& field, + const BSONArray& def, + BSONArray* out) { + BSONElement elem = doc[field.name()]; + if (elem.eoo()) { + *out = def; + return true; + } + + if (elem.type() == Array) { + *out = BSONArray(elem.embeddedObject()); + return true; + } + + return false; + } + bool FieldParser::extract(BSONObj doc, const BSONField& field, const BSONObj& def, diff --git a/src/mongo/s/field_parser.h b/src/mongo/s/field_parser.h index b6d3f682820f0..218ea0f89bf26 100644 --- a/src/mongo/s/field_parser.h +++ b/src/mongo/s/field_parser.h @@ -43,6 +43,11 @@ namespace mongo { bool def, bool* out); + static bool extract(BSONObj doc, + const BSONField& field, + const BSONArray& def, + BSONArray* out); + static bool extract(BSONObj doc, const BSONField& field, const BSONObj& def, diff --git a/src/mongo/s/field_parser_test.cpp b/src/mongo/s/field_parser_test.cpp index 09fe9c3e61f7c..ffa43ad13c148 100644 --- a/src/mongo/s/field_parser_test.cpp +++ b/src/mongo/s/field_parser_test.cpp @@ -24,30 +24,42 @@ namespace { + using mongo::BSONArray; + using mongo::BSONField; + using mongo::BSONObj; + using mongo::Date_t; + using mongo::FieldParser; + using mongo::OID; + using std::string; + class ExtractionFixture : public mongo::unittest::Test { protected: - mongo::BSONObj doc; + BSONObj doc; bool valBool; - mongo::BSONObj valObj; - mongo::Date_t valDate; - std::string valString; - mongo::OID valOID; - - static mongo::BSONField aBool; - static mongo::BSONField anObj; - static mongo::BSONField aDate; - static mongo::BSONField aString; - static mongo::BSONField anOID; + BSONArray valArray; + BSONObj valObj; + Date_t valDate; + string valString; + OID valOID; + + static BSONField aBool; + static BSONField anArray; + static BSONField anObj; + static BSONField aDate; + static BSONField aString; + static BSONField anOID; void setUp() { valBool = true; + valArray = BSON_ARRAY(1 << 2 << 3); valObj = BSON("a" << 1); valDate = 1ULL; valString = "a string"; - valOID = mongo::OID::gen(); + valOID = OID::gen(); doc = BSON(aBool(valBool) << + anArray(valArray) << anObj(valObj) << aDate(valDate) << aString(valString) << @@ -58,66 +70,78 @@ namespace { } }; - mongo::BSONField ExtractionFixture::aBool("aBool"); - mongo::BSONField ExtractionFixture::anObj("anObj"); - mongo::BSONField ExtractionFixture::aDate("aDate"); - mongo::BSONField ExtractionFixture::aString("aString"); - mongo::BSONField ExtractionFixture::anOID("anOID"); + BSONField ExtractionFixture::aBool("aBool"); + BSONField ExtractionFixture::anArray("anArray"); + BSONField ExtractionFixture::anObj("anObj"); + BSONField ExtractionFixture::aDate("aDate"); + BSONField ExtractionFixture::aString("aString"); + BSONField ExtractionFixture::anOID("anOID"); TEST_F(ExtractionFixture, GetBool) { - mongo::BSONField notThere("otherBool"); - mongo::BSONField wrongType(aString.name()); + BSONField notThere("otherBool"); + BSONField wrongType(anObj.name()); bool val; - ASSERT_TRUE(mongo::FieldParser::extract(doc, aBool, false, &val)); + ASSERT_TRUE(FieldParser::extract(doc, aBool, false, &val)); ASSERT_EQUALS(val, valBool); - ASSERT_TRUE(mongo::FieldParser::extract(doc, notThere, true, &val)); + ASSERT_TRUE(FieldParser::extract(doc, notThere, true, &val)); ASSERT_EQUALS(val, true); - ASSERT_FALSE(mongo::FieldParser::extract(doc, wrongType, true, &val)); + ASSERT_FALSE(FieldParser::extract(doc, wrongType, true, &val)); + } + + TEST_F(ExtractionFixture, GetBSONArray) { + BSONField notThere("otherArray"); + BSONField wrongType(aString.name()); + BSONArray val; + ASSERT_TRUE(FieldParser::extract(doc, anArray, BSONArray(), &val)); + ASSERT_EQUALS(val, valArray); + ASSERT_TRUE(FieldParser::extract(doc, notThere, BSON_ARRAY("a" << "b"), &val)); + ASSERT_EQUALS(val, BSON_ARRAY("a" << "b")); + ASSERT_FALSE(FieldParser::extract(doc, wrongType, BSON_ARRAY("a" << "b"), &val)); } TEST_F(ExtractionFixture, GetBSONObj) { - mongo::BSONField notThere("otherObj"); - mongo::BSONField wrongType(aString.name()); - mongo::BSONObj val; - ASSERT_TRUE(mongo::FieldParser::extract(doc, anObj, mongo::BSONObj(), &val)); + BSONField notThere("otherObj"); + BSONField wrongType(aString.name()); + BSONObj val; + ASSERT_TRUE(FieldParser::extract(doc, anObj, BSONObj(), &val)); ASSERT_EQUALS(val, valObj); - ASSERT_TRUE(mongo::FieldParser::extract(doc, notThere, BSON("b" << 1), &val)); + ASSERT_TRUE(FieldParser::extract(doc, notThere, BSON("b" << 1), &val)); ASSERT_EQUALS(val, BSON("b" << 1)); - ASSERT_FALSE(mongo::FieldParser::extract(doc, wrongType, BSON("b" << 1), &val)); + ASSERT_FALSE(FieldParser::extract(doc, wrongType, BSON("b" << 1), &val)); } TEST_F(ExtractionFixture, GetDate) { - mongo::BSONField notThere("otherDate"); - mongo::BSONField wrongType(aString.name()); - mongo::Date_t val; - ASSERT_TRUE(mongo::FieldParser::extract(doc, aDate, time(0), &val)); + BSONField notThere("otherDate"); + BSONField wrongType(aString.name()); + Date_t val; + ASSERT_TRUE(FieldParser::extract(doc, aDate, time(0), &val)); ASSERT_EQUALS(val, valDate); - ASSERT_TRUE(mongo::FieldParser::extract(doc, notThere, 99ULL, &val)); + ASSERT_TRUE(FieldParser::extract(doc, notThere, 99ULL, &val)); ASSERT_EQUALS(val, 99ULL); - ASSERT_FALSE(mongo::FieldParser::extract(doc, wrongType, 99ULL, &val)); + ASSERT_FALSE(FieldParser::extract(doc, wrongType, 99ULL, &val)); } TEST_F(ExtractionFixture, GetString) { - mongo::BSONField notThere("otherString"); - mongo::BSONField wrongType(aBool.name()); - std::string val; - ASSERT_TRUE(mongo::FieldParser::extract(doc, aString, "", &val)); + BSONField notThere("otherString"); + BSONField wrongType(aBool.name()); + string val; + ASSERT_TRUE(FieldParser::extract(doc, aString, "", &val)); ASSERT_EQUALS(val, valString); - ASSERT_TRUE(mongo::FieldParser::extract(doc, notThere, "abc", &val)); + ASSERT_TRUE(FieldParser::extract(doc, notThere, "abc", &val)); ASSERT_EQUALS(val, "abc"); - ASSERT_FALSE(mongo::FieldParser::extract(doc, wrongType, "abc", &val)); + ASSERT_FALSE(FieldParser::extract(doc, wrongType, "abc", &val)); } TEST_F(ExtractionFixture, GetOID) { - mongo::BSONField notThere("otherOID"); - mongo::BSONField wrongType(aString.name()); - mongo::OID defOID = mongo::OID::gen(); - mongo::OID val; - ASSERT_TRUE(mongo::FieldParser::extract(doc, anOID, mongo::OID(), &val)); + BSONField notThere("otherOID"); + BSONField wrongType(aString.name()); + OID defOID = OID::gen(); + OID val; + ASSERT_TRUE(FieldParser::extract(doc, anOID, OID(), &val)); ASSERT_EQUALS(val, valOID); - ASSERT_TRUE(mongo::FieldParser::extract(doc, notThere, defOID, &val)); + ASSERT_TRUE(FieldParser::extract(doc, notThere, defOID, &val)); ASSERT_EQUALS(val, defOID); - ASSERT_FALSE(mongo::FieldParser::extract(doc, wrongType, defOID, &val)); + ASSERT_FALSE(FieldParser::extract(doc, wrongType, defOID, &val)); } } // unnamed namespace diff --git a/src/mongo/s/type_chunk.cpp b/src/mongo/s/type_chunk.cpp new file mode 100644 index 0000000000000..306f722a74a8c --- /dev/null +++ b/src/mongo/s/type_chunk.cpp @@ -0,0 +1,196 @@ +/** + * 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 . + */ + +#include // for strcmp + +#include "mongo/s/type_chunk.h" + +#include "mongo/s/field_parser.h" +#include "mongo/util/mongoutils/str.h" + +namespace mongo { + + using mongoutils::str::stream; + + const std::string ChunkType::ConfigNS = "config.chunks"; + + BSONField ChunkType::name("_id"); + BSONField ChunkType::ns("ns"); + BSONField ChunkType::min("min"); + BSONField ChunkType::max("max"); + BSONField ChunkType::version("version"); + BSONField ChunkType::shard("shard"); + BSONField ChunkType::jumbo("jumbo"); + + BSONField ChunkType::DEPRECATED_lastmod("lastmod"); + BSONField ChunkType::DEPRECATED_epoch("lastmodEpoch"); + + ChunkType::ChunkType() { + clear(); + } + + ChunkType::~ChunkType() { + } + + bool ChunkType::isValid(std::string* errMsg) const { + std::string dummy; + if (errMsg == NULL) { + errMsg = &dummy; + } + + // All the mandatory fields must be present. + if (_name.empty()) { + *errMsg = stream() << "missing " << name.name() << " field"; + return false; + } + if (_ns.empty()) { + *errMsg = stream() << "missing " << ns.name() << " field"; + return false; + } + if (! _min.nFields()) { + *errMsg = stream() << "missing " << min.name() << " field"; + return false; + } + if (! _max.nFields()) { + *errMsg = stream() << "missing " << max.name() << " field"; + return false; + } + if (_version._combined == 0ULL) { + *errMsg = stream() << "missing " << version.name() << " field"; + return false; + } + if (_shard.empty()) { + *errMsg = stream() << "missing " << shard.name() << " field"; + return false; + } + + // 'min' and 'max' must share the same fields. + if (_min.nFields() != _max.nFields()) { + *errMsg = stream() << "min and max have a different number of keys"; + return false; + } + BSONObjIterator minIt(_min); + BSONObjIterator maxIt(_max); + while (minIt.more() && maxIt.more()) { + BSONElement minElem = minIt.next(); + BSONElement maxElem = maxIt.next(); + if (strcmp(minElem.fieldName(), maxElem.fieldName())) { + *errMsg = stream() << "min and max must have the same set of keys"; + return false; + } + } + + // 'max' should be greater than 'min'. + if (_min.woCompare(_max) >= 0) { + *errMsg = stream() << "max key must be greater than min key"; + return false; + } + + return true; + } + + BSONObj ChunkType::toBSON() const { + BSONObjBuilder builder; + + if (!_name.empty()) builder.append(name(), _name); + if (!_ns.empty()) builder.append(ns(), _ns); + if (_min.nFields()) builder.append(min(), _min); + if (_max.nFields()) builder.append(max(), _max); + + if (_version._combined != 0ULL) { + BSONArrayBuilder arrBuilder(builder.subarrayStart(version())); + arrBuilder.appendTimestamp(_version._major, _version._minor); + arrBuilder.append(_version._epoch); + arrBuilder.done(); + } + + if (!_shard.empty()) builder.append(shard(), _shard); + if (_jumbo) builder.append(jumbo(), _jumbo); + + return builder.obj(); + } + + void ChunkType::parseBSON(BSONObj source) { + clear(); + + bool ok = true; + ok &= FieldParser::extract(source, name, "", &_name); + ok &= FieldParser::extract(source, ns, "", &_ns); + ok &= FieldParser::extract(source, min, BSONObj(), &_min); + ok &= FieldParser::extract(source, max, BSONObj(), &_max); + ok &= FieldParser::extract(source, shard, "", &_shard); + ok &= FieldParser::extract(source, jumbo, false, &_jumbo); + if (! ok) { + clear(); + return; + } + + // + // ShardChunkVersion backward compatibility logic + // + + // ShardChunkVersion is currently encoded as { 'version': [,] } + BSONArray arrVersion; + ok = FieldParser::extract(source, version, BSONArray(), &arrVersion); + if (! ok) { + clear(); + return; + } + else if (arrVersion.nFields()) { + bool okVersion; + _version = ShardChunkVersion::fromBSON(arrVersion, &okVersion); + if (! okVersion) { + clear(); + } + return; + } + + // If we haven't found the current format try parsing the deprecated format + // { lastmod: , lastmodEpoch: }. + Date_t lastmod; + OID epoch; + ok = FieldParser::extract(source, DEPRECATED_lastmod, time(0), &lastmod); + ok &= FieldParser::extract(source, DEPRECATED_epoch, OID(), &epoch); + if (! ok) { + clear(); + } + else { + _version = ShardChunkVersion(lastmod.millis, epoch); + } + } + + void ChunkType::clear() { + _name.clear(); + _ns.clear(); + _min = BSONObj(); + _max = BSONObj(); + _version = ShardChunkVersion(); + _shard.clear(); + _jumbo = false; + } + + void ChunkType::cloneTo(ChunkType* other) { + other->clear(); + other->_name = _name; + other->_ns = _ns; + other->_min = _min; + other->_max = _max; + other->_version = _version; + other->_shard = _shard; + other->_jumbo = _jumbo; + } + +} // namespace mongo diff --git a/src/mongo/s/type_chunk.h b/src/mongo/s/type_chunk.h new file mode 100644 index 0000000000000..a10fdfa206e2d --- /dev/null +++ b/src/mongo/s/type_chunk.h @@ -0,0 +1,152 @@ +/** + * 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 . + */ + +#pragma once + +#include + +#include "mongo/base/disallow_copying.h" +#include "mongo/base/string_data.h" +#include "mongo/db/jsobj.h" +#include "mongo/s/util.h" // for ShardChunkVersion + +namespace mongo { + + /** + * This class represents the layout and contents of documents contained in the + * config.chunks collection. All manipulation of documents coming from that + * collection should be done with this class. + * + * Usage Example: + * + * // Contact the config. 'conn' has been obtained before. + * DBClientBase* conn; + * unique_ptr cursor; + * BSONObj query = QUERY(ChunkType::ns("mydb.mycoll")); + * cursor.reset(conn->query(ChunkType::ConfigNS, query, ...)); + * + * // Process the response. + * while (cursor->more()) { + * chunkDoc = cursor->next(); + * ChunkType chunk; + * chunk.fromBSON(dbDoc); + * if (! chunk.isValid()) { + * // Can't use 'chunk'. Take action. + * } + * // use 'chunk' + * } + */ + class ChunkType { + MONGO_DISALLOW_COPYING(ChunkType); + public: + + // + // schema declarations + // + + // Name of the chunk collection in the config server. + static const std::string ConfigNS; + + // Field names and types in the chunk collection type. + static BSONField name; + static BSONField ns; + static BSONField min; + static BSONField max; + static BSONField version; + static BSONField shard; + static BSONField jumbo; + + // Transition to new format, 2.2 -> 2.4 + // 2.2 can read both lastmod + lastmodEpoch format and 2.4 [ lastmod, OID ] formats. + static BSONField DEPRECATED_lastmod; // major | minor versions + static BSONField DEPRECATED_epoch; // disambiguates collection incarnations + + // + // chunk type methods + // + + ChunkType(); + ~ChunkType(); + + /** + * Returns true if all the mandatory fields are present and have valid + * representations. Otherwise returns false and fills in the optional 'errMsg' string. + */ + bool isValid(std::string* errMsg) const; + + /** + * Returns the BSON representation of the entry. + */ + BSONObj toBSON() const; + + /** + * Clears and populates the internal state using the 'source' BSON object if the + * latter contains valid values. Otherwise clear the internal state. + */ + void parseBSON(BSONObj source); + + /** + * Clears the internal state. + */ + void clear(); + + /** + * Copies all the fields present in 'this' to 'other'. + */ + void cloneTo(ChunkType* other); + + /** + * Returns a string representation of the current internal state. + */ + std::string toString() const; + + // + // individual field accessors + // + + void setName(const StringData& name) { _name = std::string(name.data(), name.size()); } + const std::string& getName() const { return _name; } + + void setNS(const StringData& ns) { _ns = std::string(ns.data(), ns.size()); } + const std::string& getNS() const { return _ns; } + + void setMin(const BSONObj& min) { _min = min.getOwned(); } + BSONObj getMin() const { return _min; } + + void setMax(const BSONObj& max) { _max = max.getOwned(); } + BSONObj getMax() const { return _max; } + + void setVersion(const ShardChunkVersion& version) { _version = version; } + const ShardChunkVersion& getVersion() const { return _version; } + + void setShard(const StringData& shard) { _shard=std::string(shard.data(), shard.size()); } + const std::string& getShard() const { return _shard; } + + void setJumbo(bool jumbo) { _jumbo = jumbo; } + bool getJumbo() const { return _jumbo; } + + private: + // Convention: (M)andatory, (O)ptional, (S)pecial rule. + string _name; // (M) chunk's id + string _ns; // (M) collection this chunk is in + BSONObj _min; // (M) first key of the range, inclusive + BSONObj _max; // (M) last key of the range, non-inclusive + ShardChunkVersion _version; // (M) version of this chunk + string _shard; // (M) shard this chunk lives in + bool _jumbo; // (O) too big to move? + }; + +} // namespace mongo diff --git a/src/mongo/s/type_chunk_test.cpp b/src/mongo/s/type_chunk_test.cpp new file mode 100644 index 0000000000000..b2e5928befb4f --- /dev/null +++ b/src/mongo/s/type_chunk_test.cpp @@ -0,0 +1,162 @@ +/** + * 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 . + */ + +#include "mongo/pch.h" + +#include "mongo/bson/oid.h" +#include "mongo/bson/util/misc.h" // for Date_t +#include "mongo/s/type_chunk.h" +#include "mongo/s/util.h" // for ShardChunkVersion +#include "mongo/unittest/unittest.h" + +namespace { + + using mongo::BSONArray; + using mongo::BSONObj; + using mongo::ChunkType; + using mongo::Date_t; + using mongo::OID; + using mongo::ShardChunkVersion; + + TEST(Validity, MissingFields) { + ChunkType chunk; + BSONArray version = BSON_ARRAY(Date_t(1) << OID::gen()); + + BSONObj objModNS = BSON(ChunkType::name("test.mycol-a_MinKey") << + ChunkType::min(BSON("a" << 10 << "b" << 10)) << + ChunkType::max(BSON("a" << 20)) << + ChunkType::version(version) << + ChunkType::shard("shard0001")); + chunk.parseBSON(objModNS); + ASSERT_FALSE(chunk.isValid(NULL)); + + BSONObj objModName = BSON(ChunkType::ns("test.mycol") << + ChunkType::min(BSON("a" << 10 << "b" << 10)) << + ChunkType::max(BSON("a" << 20)) << + ChunkType::version(version) << + ChunkType::shard("shard0001")); + chunk.parseBSON(objModName); + ASSERT_FALSE(chunk.isValid(NULL)); + + BSONObj objModKeys = BSON(ChunkType::name("test.mycol-a_MinKey") << + ChunkType::ns("test.mycol") << + ChunkType::version(version) << + ChunkType::shard("shard0001")); + chunk.parseBSON(objModKeys); + ASSERT_FALSE(chunk.isValid(NULL)); + + BSONObj objModVersion = BSON(ChunkType::name("test.mycol-a_MinKey") << + ChunkType::ns("test.mycol") << + ChunkType::min(BSON("a" << 10 << "b" << 10)) << + ChunkType::max(BSON("a" << 20)) << + ChunkType::shard("shard0001")); + chunk.parseBSON(objModVersion); + ASSERT_FALSE(chunk.isValid(NULL)); + + BSONObj objModShard = BSON(ChunkType::name("test.mycol-a_MinKey") << + ChunkType::ns("test.mycol") << + ChunkType::min(BSON("a" << 10 << "b" << 10)) << + ChunkType::max(BSON("a" << 20)) << + ChunkType::version(version) << + ChunkType::shard("shard0001")); + chunk.parseBSON(objModShard); + ASSERT_FALSE(chunk.isValid(NULL)); + } + + TEST(MinMaxValidity, DifferentNumberOfColumns) { + ChunkType chunk; + BSONArray version = BSON_ARRAY(Date_t(1) << OID::gen()); + BSONObj obj = BSON(ChunkType::name("test.mycol-a_MinKey") << + ChunkType::ns("test.mycol") << + ChunkType::min(BSON("a" << 10 << "b" << 10)) << + ChunkType::max(BSON("a" << 20)) << + ChunkType::version(version) << + ChunkType::shard("shard0001")); + chunk.parseBSON(obj); + ASSERT_FALSE(chunk.isValid(NULL)); + } + + TEST(MinMaxValidity, DifferentColumns) { + ChunkType chunk; + BSONArray version = BSON_ARRAY(Date_t(1) << OID::gen()); + BSONObj obj = BSON(ChunkType::name("test.mycol-a_MinKey") << + ChunkType::ns("test.mycol") << + ChunkType::min(BSON("a" << 10)) << + ChunkType::max(BSON("b" << 20)) << + ChunkType::version(version) << + ChunkType::shard("shard0001")); + chunk.parseBSON(obj); + ASSERT_FALSE(chunk.isValid(NULL)); + } + + TEST(MinMaxValidity, NotAscending) { + ChunkType chunk; + BSONArray version = BSON_ARRAY(Date_t(1) << OID::gen()); + BSONObj obj = BSON(ChunkType::name("test.mycol-a_MinKey") << + ChunkType::ns("test.mycol") << + ChunkType::min(BSON("a" << 20)) << + ChunkType::max(BSON("a" << 10)) << + ChunkType::version(version) << + ChunkType::shard("shard0001")); + chunk.parseBSON(obj); + ASSERT_FALSE(chunk.isValid(NULL)); + } + + TEST(Compatibility, NewFormatVersion) { + ChunkType chunk; + OID epoch = OID::gen(); + BSONArray version = BSON_ARRAY(Date_t(1) << epoch); + BSONObj obj = BSON(ChunkType::name("test.mycol-a_MinKey") << + ChunkType::ns("test.mycol") << + ChunkType::min(BSON("a" << 10)) << + ChunkType::max(BSON("a" << 20)) << + ChunkType::version(version) << + ChunkType::shard("shard0001")); + chunk.parseBSON(obj); + ASSERT_TRUE(chunk.isValid(NULL)); + ASSERT_EQUALS(chunk.getName(), "test.mycol-a_MinKey"); + ASSERT_EQUALS(chunk.getNS(), "test.mycol"); + ASSERT_EQUALS(chunk.getMin(), BSON("a" << 10)); + ASSERT_EQUALS(chunk.getMax(), BSON("a" << 20)); + ShardChunkVersion fetchedVersion = chunk.getVersion(); + ASSERT_EQUALS(fetchedVersion._combined, 1ULL); + ASSERT_EQUALS(fetchedVersion._epoch, epoch); + ASSERT_EQUALS(chunk.getShard(), "shard0001"); + } + + TEST(Compatibility, OldFormatVersion) { + ChunkType chunk; + OID epoch = OID::gen(); + BSONObj obj = BSON(ChunkType::name("test.mycol-a_MinKey") << + ChunkType::ns("test.mycol") << + ChunkType::min(BSON("a" << 10)) << + ChunkType::max(BSON("a" << 20)) << + ChunkType::DEPRECATED_lastmod(Date_t(1)) << + ChunkType::DEPRECATED_epoch(epoch) << + ChunkType::shard("shard0001")); + chunk.parseBSON(obj); + ASSERT_TRUE(chunk.isValid(NULL)); + ASSERT_EQUALS(chunk.getName(), "test.mycol-a_MinKey"); + ASSERT_EQUALS(chunk.getNS(), "test.mycol"); + ASSERT_EQUALS(chunk.getMin(), BSON("a" << 10)); + ASSERT_EQUALS(chunk.getMax(), BSON("a" << 20)); + ShardChunkVersion fetchedVersion = chunk.getVersion(); + ASSERT_EQUALS(fetchedVersion._combined, 1ULL); + ASSERT_EQUALS(fetchedVersion._epoch, epoch); + ASSERT_EQUALS(chunk.getShard(), "shard0001"); + } + +} // unnamed namespace diff --git a/src/mongo/s/type_database.cpp b/src/mongo/s/type_database.cpp new file mode 100644 index 0000000000000..8ed2a9762c12a --- /dev/null +++ b/src/mongo/s/type_database.cpp @@ -0,0 +1,117 @@ +/** + * 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 . + */ + +#include "mongo/s/type_database.h" + +#include "mongo/s/field_parser.h" +#include "mongo/util/mongoutils/str.h" + +namespace mongo { + + using mongoutils::str::stream; + + const std::string DatabaseType::ConfigNS = "config.databases"; + + BSONField DatabaseType::name("_id"); + BSONField DatabaseType::primary("primary"); + BSONField DatabaseType::scattered("scatterCollections"); + BSONField DatabaseType::draining("draining"); + + BSONField DatabaseType::DEPRECATED_partitioned("partitioned"); + BSONField DatabaseType::DEPRECATED_name("name"); + BSONField DatabaseType::DEPRECATED_sharded("sharded"); + + DatabaseType::DatabaseType() { + clear(); + } + + DatabaseType::~DatabaseType() { + } + + bool DatabaseType::isValid(std::string* errMsg) const { + std::string dummy; + if (errMsg == NULL) { + errMsg = &dummy; + } + + // All the mandatory fields must be present. + if (_name.empty()) { + *errMsg = stream() << "missing " << name.name() << " field"; + return false; + } + if (_primary.empty()) { + *errMsg = stream() << "missing " << primary.name() << " field"; + return false; + } + + return true; + } + + BSONObj DatabaseType::toBSON() const { + BSONObjBuilder builder; + if (!_name.empty()) builder.append(name(), _name); + if (!_primary.empty()) builder.append(primary(), _primary); + if (_scattered) builder.append(scattered(), _scattered); + if (_draining) builder.append(draining(), _draining); + return builder.obj(); + } + + void DatabaseType::parseBSON(BSONObj source) { + clear(); + + bool ok = true; + ok &= FieldParser::extract(source, name, "", &_name); + ok &= FieldParser::extract(source, primary, "", &_primary); + ok &= FieldParser::extract(source, scattered, false, &_scattered ); + ok &= FieldParser::extract(source, draining, false, &_draining); + if (! ok) { + clear(); + return; + } + + // + // backward compatibility + // + + // There used to be a flag called "partitioned" that would allow collections + // under that database to be sharded. We don't require that anymore. + bool partitioned; + if (! FieldParser::extract(source, DEPRECATED_partitioned, false, &partitioned)) { + clear(); + return; + } + } + + void DatabaseType::clear() { + _name.clear(); + _primary.clear(); + _scattered = false; + _draining = false; + } + + void DatabaseType::cloneTo(DatabaseType* other) { + other->clear(); + other->_name = _name; + other->_primary = _primary; + other->_scattered = _scattered; + other->_draining = _draining; + } + + std::string DatabaseType::toString() const { + return toBSON().toString(); + } + +} // namespace mongo diff --git a/src/mongo/s/type_database.h b/src/mongo/s/type_database.h new file mode 100644 index 0000000000000..37c98d05c015c --- /dev/null +++ b/src/mongo/s/type_database.h @@ -0,0 +1,135 @@ +/** + * 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 . + */ + +#pragma once + +#include + +#include "mongo/base/disallow_copying.h" +#include "mongo/base/string_data.h" +#include "mongo/db/jsobj.h" + +namespace mongo { + + /** + * This class represents the layout and contents of documents contained in the + * config.database collection. All manipulation of documents coming from that + * collection should be done with this class. + * + * Usage Example: + * + * // Contact the config. 'conn' has been obtained before. + * DBClientBase* conn; + * BSONObj query = QUERY(DatabaseType::name("mydb")); + * dbDoc = conn->findOne(DatbaseType::ConfigNS, query); + * + * // Process the response. + * DatabaseType db; + * db.fromBSON(dbDoc); + * if (! db.isValid()) { + * // Can't use 'db'. Take action. + * } + * // use 'db' + * + */ + class DatabaseType { + MONGO_DISALLOW_COPYING(DatabaseType); + public: + + // + // schema declarations + // + + // Name of the database collection in the config server. + static const std::string ConfigNS; + + // Field names and types in the database collection type. + static BSONField name; + static BSONField primary; + static BSONField scattered; + static BSONField draining; + + // This field was last used in 2.2 series (version 3). + static BSONField DEPRECATED_partitioned; + + // These fields were last used in 1.4 series (version 2). + static BSONField DEPRECATED_name; + static BSONField DEPRECATED_sharded; + + // + // database type methods + // + + DatabaseType(); + ~DatabaseType(); + + /** + * Returns true if all the mandatory fields are present and have valid + * representations. Otherwise returns false and fills in the optional 'errMsg' string. + */ + bool isValid(std::string* errMsg) const; + + /** + * Returns the BSON representation of the entry. + */ + BSONObj toBSON() const; + + /** + * Clears and populates the internal state using the 'source' BSON object if the + * latter contains valid values. Otherwise clear the internal state. + */ + void parseBSON(BSONObj source); + + /** + * Clears the internal state. + */ + void clear(); + + /** + * Copies all the fields present in 'this' to 'other'. + */ + void cloneTo(DatabaseType* other); + + /** + * Returns a string representation of the current internal state. + */ + std::string toString() const; + + // + // individual field accessors + // + + void setName(const StringData& name) { _name = std::string(name.data(), name.size()); } + const std::string& getName() const { return _name; } + + void setPrimary(const StringData& shard) {_primary=std::string(shard.data(), shard.size());} + const std::string& getPrimary() const { return _primary; } + + void setScattered(bool scattered) { _scattered = scattered; } + bool getScattered() { return _scattered; } + + void setDrainig(bool draining) { _draining = draining; } + bool getDraining() const { return _draining; } + + private: + // Convention: (M)andatory, (O)ptional, (S)pecial rule. + string _name; // (M) database name + string _primary; // (M) primary shard for the database + bool _scattered; // (O) can db collections live outside the primary? + bool _draining; // (O) is this database about to be deleted? + }; + +} // namespace mongo diff --git a/src/mongo/s/type_database_test.cpp b/src/mongo/s/type_database_test.cpp new file mode 100644 index 0000000000000..14d439cd5cb44 --- /dev/null +++ b/src/mongo/s/type_database_test.cpp @@ -0,0 +1,65 @@ +/** + * 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 . + */ + +#include "mongo/pch.h" + +#include "mongo/bson/oid.h" +#include "mongo/bson/util/misc.h" // for Date_t +#include "mongo/s/type_database.h" +#include "mongo/unittest/unittest.h" + +namespace { + + using mongo::DatabaseType; + using mongo::BSONObj; + + TEST(Validity, Empty) { + DatabaseType db; + BSONObj emptyObj = BSONObj(); + db.parseBSON(emptyObj); + ASSERT_FALSE(db.isValid(NULL)); + } + + TEST(Validity, NonScatteredDatabase) { + DatabaseType db; + BSONObj obj = BSON(DatabaseType::name("mydb") << + DatabaseType::primary("shard")); + db.parseBSON(obj); + ASSERT_TRUE(db.isValid(NULL)); + } + + TEST(Validity, ScatteredDatabase) { + DatabaseType db; + BSONObj obj = BSON(DatabaseType::name("mydb") << + DatabaseType::primary("shard") << + DatabaseType::scattered(true)); + db.parseBSON(obj); + ASSERT_TRUE(db.isValid(NULL)); + } + + TEST(Compatibility, PartitionedIsIrrelevant) { + DatabaseType db; + BSONObj obj = BSON(DatabaseType::name("mydb") << + DatabaseType::primary("shard") << + DatabaseType::DEPRECATED_partitioned(true)); + db.parseBSON(obj); + ASSERT_EQUALS(db.getName(), "mydb"); + ASSERT_EQUALS(db.getPrimary(), "shard"); + ASSERT_EQUALS(db.getScattered(), false); + ASSERT_EQUALS(db.getDraining(), false); + } + +} // unnamed namespace