/**
* Copyright (C) 2017 MongoDB 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.
*/
#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kSharding
#include "mongo/platform/basic.h"
#include "mongo/db/s/split_vector.h"
#include "mongo/db/dbdirectclient.h"
#include "mongo/s/shard_server_test_fixture.h"
#include "mongo/unittest/unittest.h"
namespace mongo {
const NamespaceString kNss = NamespaceString("foo", "bar");
const std::string kPattern = "_id";
class SplitVectorTest : public ShardServerTestFixture {
public:
void setUp() {
ShardServerTestFixture::setUp();
DBDirectClient dbclient(operationContext());
ASSERT_TRUE(dbclient.createCollection(kNss.ns()));
// Insert 100 documents into the collection so the tests can test splitting with different
// constraints.
for (int i = 0; i < 100; i++) {
BSONObjBuilder builder;
builder.append(kPattern, i);
BSONObj obj = builder.obj();
dbclient.insert(kNss.toString(), obj);
}
ASSERT_EQUALS(100ULL, dbclient.count(kNss.toString()));
}
const long long& getDocSizeBytes() {
return docSizeBytes;
}
private:
// Number of bytes in each of the same-size documents we insert into the collection.
const long long docSizeBytes = BSON(kPattern << 1).objsize();
};
namespace {
TEST_F(SplitVectorTest, SplitVectorInHalf) {
std::vector splitKeys = unittest::assertGet(splitVector(operationContext(),
kNss,
BSON(kPattern << 1),
BSON(kPattern << 0),
BSON(kPattern << 100),
false,
boost::none,
boost::none,
boost::none,
getDocSizeBytes() * 100LL));
std::vector expected = {BSON(kPattern << 50)};
ASSERT_EQ(splitKeys.size(), expected.size());
for (auto splitKeysIt = splitKeys.begin(), expectedIt = expected.begin();
splitKeysIt != splitKeys.end() && expectedIt != expected.end();
++splitKeysIt, ++expectedIt) {
ASSERT_BSONOBJ_EQ(*splitKeysIt, *expectedIt);
}
}
TEST_F(SplitVectorTest, ForceSplit) {
std::vector splitKeys = unittest::assertGet(splitVector(operationContext(),
kNss,
BSON(kPattern << 1),
BSON(kPattern << 0),
BSON(kPattern << 100),
true,
boost::none,
boost::none,
getDocSizeBytes() * 6LL,
getDocSizeBytes() * 6LL));
std::vector expected = {BSON(kPattern << 50)};
ASSERT_EQ(splitKeys.size(), expected.size());
for (auto splitKeysIt = splitKeys.begin(), expectedIt = expected.begin();
splitKeysIt != splitKeys.end() && expectedIt != expected.end();
++splitKeysIt, ++expectedIt) {
ASSERT_BSONOBJ_EQ(*splitKeysIt, *expectedIt);
}
}
TEST_F(SplitVectorTest, MaxChunkObjectsSet) {
std::vector splitKeys = unittest::assertGet(splitVector(operationContext(),
kNss,
BSON(kPattern << 1),
BSON(kPattern << 0),
BSON(kPattern << 100),
false,
boost::none,
10,
boost::none,
getDocSizeBytes() * 100LL));
// Unlike the SplitVectorInHalf test, should split at every 10th key.
std::vector expected = {BSON(kPattern << 10),
BSON(kPattern << 21),
BSON(kPattern << 32),
BSON(kPattern << 43),
BSON(kPattern << 54),
BSON(kPattern << 65),
BSON(kPattern << 76),
BSON(kPattern << 87),
BSON(kPattern << 98)};
ASSERT_EQ(splitKeys.size(), expected.size());
for (auto splitKeysIt = splitKeys.begin(), expectedIt = expected.begin();
splitKeysIt != splitKeys.end() && expectedIt != expected.end();
++splitKeysIt, ++expectedIt) {
ASSERT_BSONOBJ_EQ(*splitKeysIt, *expectedIt);
}
}
TEST_F(SplitVectorTest, SplitEveryThird) {
std::vector splitKeys = unittest::assertGet(splitVector(operationContext(),
kNss,
BSON(kPattern << 1),
BSON(kPattern << 0),
BSON(kPattern << 100),
false,
boost::none,
boost::none,
boost::none,
getDocSizeBytes() * 6LL));
std::vector expected = {
BSON(kPattern << 3), BSON(kPattern << 7), BSON(kPattern << 11), BSON(kPattern << 15),
BSON(kPattern << 19), BSON(kPattern << 23), BSON(kPattern << 27), BSON(kPattern << 31),
BSON(kPattern << 35), BSON(kPattern << 39), BSON(kPattern << 43), BSON(kPattern << 47),
BSON(kPattern << 51), BSON(kPattern << 55), BSON(kPattern << 59), BSON(kPattern << 63),
BSON(kPattern << 67), BSON(kPattern << 71), BSON(kPattern << 75), BSON(kPattern << 79),
BSON(kPattern << 83), BSON(kPattern << 87), BSON(kPattern << 91), BSON(kPattern << 95),
BSON(kPattern << 99)};
ASSERT_EQ(splitKeys.size(), expected.size());
for (auto splitKeysIt = splitKeys.begin(), expectedIt = expected.begin();
splitKeysIt != splitKeys.end() && expectedIt != expected.end();
++splitKeysIt, ++expectedIt) {
ASSERT_BSONOBJ_EQ(*splitKeysIt, *expectedIt);
}
}
TEST_F(SplitVectorTest, MaxSplitPointsSet) {
std::vector splitKeys = unittest::assertGet(splitVector(operationContext(),
kNss,
BSON(kPattern << 1),
BSON(kPattern << 0),
BSON(kPattern << 100),
false,
3,
boost::none,
boost::none,
getDocSizeBytes() * 6LL));
// Unlike the SplitEveryThird test, should only return the first 3 split points since
// maxSplitPoints is 3.
std::vector expected = {
BSON(kPattern << 3), BSON(kPattern << 7), BSON(kPattern << 11)};
ASSERT_EQ(splitKeys.size(), expected.size());
for (auto splitKeysIt = splitKeys.begin(), expectedIt = expected.begin();
splitKeysIt != splitKeys.end() || expectedIt != expected.end();
++splitKeysIt, ++expectedIt) {
ASSERT_BSONOBJ_EQ(*splitKeysIt, *expectedIt);
}
}
TEST_F(SplitVectorTest, IgnoreMaxChunkObjects) {
std::vector splitKeys = unittest::assertGet(splitVector(operationContext(),
kNss,
BSON(kPattern << 1),
BSON(kPattern << 0),
BSON(kPattern << 100),
false,
boost::none,
10,
boost::none,
getDocSizeBytes() * 6LL));
// The "maxChunkObjects"th key (10) is larger than the key count at half the maxChunkSize (3),
// so it should be ignored.
std::vector expected = {
BSON(kPattern << 3), BSON(kPattern << 7), BSON(kPattern << 11), BSON(kPattern << 15),
BSON(kPattern << 19), BSON(kPattern << 23), BSON(kPattern << 27), BSON(kPattern << 31),
BSON(kPattern << 35), BSON(kPattern << 39), BSON(kPattern << 43), BSON(kPattern << 47),
BSON(kPattern << 51), BSON(kPattern << 55), BSON(kPattern << 59), BSON(kPattern << 63),
BSON(kPattern << 67), BSON(kPattern << 71), BSON(kPattern << 75), BSON(kPattern << 79),
BSON(kPattern << 83), BSON(kPattern << 87), BSON(kPattern << 91), BSON(kPattern << 95),
BSON(kPattern << 99)};
ASSERT_EQ(splitKeys.size(), expected.size());
for (auto splitKeysIt = splitKeys.begin(), expectedIt = expected.begin();
splitKeysIt != splitKeys.end() && expectedIt != expected.end();
++splitKeysIt, ++expectedIt) {
ASSERT_BSONOBJ_EQ(*splitKeysIt, *expectedIt);
}
}
TEST_F(SplitVectorTest, NoSplit) {
std::vector splitKeys = unittest::assertGet(splitVector(operationContext(),
kNss,
BSON(kPattern << 1),
BSON(kPattern << 0),
BSON(kPattern << 100),
false,
boost::none,
boost::none,
boost::none,
getDocSizeBytes() * 1000LL));
ASSERT_EQUALS(splitKeys.size(), 0UL);
}
TEST_F(SplitVectorTest, MaxChunkSizeSpecified) {
std::vector splitKeys = unittest::assertGet(splitVector(operationContext(),
kNss,
BSON(kPattern << 1),
BSON(kPattern << 0),
BSON(kPattern << 100),
false,
boost::none,
boost::none,
1LL,
getDocSizeBytes() * 6LL));
// If both maxChunkSize and maxChunkSizeBytes are specified, maxChunkSize takes precedence.
// Since the size of the collection is not yet a megabyte, should not split.
ASSERT_EQ(splitKeys.size(), 0UL);
}
TEST_F(SplitVectorTest, NoCollection) {
auto status = splitVector(operationContext(),
NamespaceString("dummy", "collection"),
BSON(kPattern << 1),
BSON(kPattern << 0),
BSON(kPattern << 100),
false,
boost::none,
boost::none,
boost::none,
boost::none)
.getStatus();
ASSERT_EQUALS(status.code(), ErrorCodes::NamespaceNotFound);
}
TEST_F(SplitVectorTest, NoIndex) {
auto status = splitVector(operationContext(),
kNss,
BSON("foo" << 1),
BSON(kPattern << 0),
BSON(kPattern << 100),
false,
boost::none,
boost::none,
boost::none,
boost::none)
.getStatus();
ASSERT_EQUALS(status.code(), ErrorCodes::IndexNotFound);
}
TEST_F(SplitVectorTest, NoMaxChunkSize) {
auto status = splitVector(operationContext(),
kNss,
BSON(kPattern << 1),
BSON(kPattern << 0),
BSON(kPattern << 100),
false,
boost::none,
boost::none,
boost::none,
boost::none)
.getStatus();
ASSERT_EQUALS(status.code(), ErrorCodes::InvalidOptions);
}
} // namespace
} // namespace mongo