/**
* 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.
*/
#include "mongo/platform/basic.h"
#include "mongo/base/status.h"
#include "mongo/client/dbclientinterface.h"
#include "mongo/client/remote_command_targeter_mock.h"
#include "mongo/db/commands.h"
#include "mongo/db/dbdirectclient.h"
#include "mongo/db/repl/replication_coordinator_mock.h"
#include "mongo/db/s/collection_metadata.h"
#include "mongo/db/s/metadata_loader.h"
#include "mongo/db/server_options.h"
#include "mongo/s/catalog/dist_lock_catalog_mock.h"
#include "mongo/s/catalog/dist_lock_manager_mock.h"
#include "mongo/s/catalog/sharding_catalog_client_impl.h"
#include "mongo/s/catalog/type_chunk.h"
#include "mongo/s/catalog/type_collection.h"
#include "mongo/s/client/shard_registry.h"
#include "mongo/s/shard_server_test_fixture.h"
#include "mongo/stdx/memory.h"
#include "mongo/util/scopeguard.h"
namespace mongo {
namespace {
using std::string;
using std::unique_ptr;
using std::vector;
using unittest::assertGet;
const HostAndPort kConfigHostAndPort = HostAndPort("dummy", 123);
const NamespaceString kNss = NamespaceString("test.foo");
const NamespaceString kChunkMetadataNss = NamespaceString("config.chunks.test.foo");
const ShardId kShardId = ShardId("shard0");
class MetadataLoaderTest : public ShardServerTestFixture {
protected:
/**
* Goes throught the chunks in 'metadata' and uses a DBDirectClient to check that they are all
* persisted. Also checks that 'totalNumChunksPersisted' is correct because 'metadata' is only
* aware of a shard's chunks.
*/
void checkCollectionMetadataChunksMatchPersistedChunks(
const NamespaceString& nss,
const CollectionMetadata& metadata,
unsigned long long totalNumChunksPersisted) {
try {
DBDirectClient client(operationContext());
ASSERT_EQUALS(client.count(nss.ns()), totalNumChunksPersisted);
auto chunks = metadata.getChunks();
for (auto& chunk : chunks) {
Query query(BSON(ChunkType::minShardID() << chunk.first << ChunkType::max()
<< chunk.second.getMaxKey()));
query.readPref(ReadPreference::Nearest, BSONArray());
std::unique_ptr cursor = client.query(nss.ns().c_str(), query, 1);
ASSERT(cursor);
ASSERT(cursor->more());
BSONObj queryResult = cursor->nextSafe();
ChunkType foundChunk = assertGet(
ChunkType::fromShardBSON(queryResult, chunk.second.getVersion().epoch()));
ASSERT_BSONOBJ_EQ(chunk.first, foundChunk.getMin());
ASSERT_BSONOBJ_EQ(chunk.second.getMaxKey(), foundChunk.getMax());
}
} catch (const DBException& ex) {
ASSERT(false);
}
}
void expectFindOnConfigSendCollectionDefault() {
CollectionType collType;
collType.setNs(kNss);
collType.setKeyPattern(BSON("a" << 1));
collType.setUnique(false);
collType.setUpdatedAt(Date_t::fromMillisSinceEpoch(1));
collType.setEpoch(_maxCollVersion.epoch());
ASSERT_OK(collType.validate());
expectFindOnConfigSendBSONObjVector(std::vector{collType.toBSON()});
}
void expectFindOnConfigSendChunksDefault() {
BSONObj chunk = BSON(
ChunkType::name("test.foo-a_MinKey")
<< ChunkType::ns(kNss.ns())
<< ChunkType::min(BSON("a" << MINKEY))
<< ChunkType::max(BSON("a" << MAXKEY))
<< ChunkType::DEPRECATED_lastmod(Date_t::fromMillisSinceEpoch(_maxCollVersion.toLong()))
<< ChunkType::DEPRECATED_epoch(_maxCollVersion.epoch())
<< ChunkType::shard(kShardId.toString()));
expectFindOnConfigSendBSONObjVector(std::vector{chunk});
}
/**
* Helper to make a number of chunks that can then be manipulated in various ways in the tests.
*/
std::vector makeFourChunks() {
std::vector chunks;
std::string names[] = {
"test.foo-a_MinKey", "test.foo-a_10", "test.foo-a_50", "test.foo-a_100"};
BSONObj mins[] = {BSON("a" << MINKEY), BSON("a" << 10), BSON("a" << 50), BSON("a" << 100)};
BSONObj maxs[] = {BSON("a" << 10), BSON("a" << 50), BSON("a" << 100), BSON("a" << MAXKEY)};
for (int i = 0; i < 4; ++i) {
_maxCollVersion.incMajor();
BSONObj chunk = BSON(ChunkType::name(names[i])
<< ChunkType::ns(kNss.ns())
<< ChunkType::min(mins[i])
<< ChunkType::max(maxs[i])
<< ChunkType::DEPRECATED_lastmod(
Date_t::fromMillisSinceEpoch(_maxCollVersion.toLong()))
<< ChunkType::DEPRECATED_epoch(_maxCollVersion.epoch())
<< ChunkType::shard(kShardId.toString()));
chunks.push_back(std::move(chunk));
}
return chunks;
}
ChunkVersion getMaxCollVersion() const {
return _maxCollVersion;
}
ChunkVersion getMaxShardVersion() const {
return _maxCollVersion;
}
private:
ChunkVersion _maxCollVersion{1, 0, OID::gen()};
};
// TODO: Test config server down
// TODO: Test read of chunks with new epoch
// TODO: Test that you can properly load config using format with deprecated fields?
TEST_F(MetadataLoaderTest, DroppedColl) {
CollectionType collType;
collType.setNs(kNss);
collType.setKeyPattern(BSON("a" << 1));
collType.setUpdatedAt(Date_t());
collType.setEpoch(OID());
collType.setDropped(true);
ASSERT_OK(collType.validate());
auto future = launchAsync([this] {
CollectionMetadata metadata;
auto status = MetadataLoader::makeCollectionMetadata(operationContext(),
catalogClient(),
kNss.ns(),
kShardId.toString(),
NULL, /* no old metadata */
&metadata);
ASSERT_EQUALS(status.code(), ErrorCodes::NamespaceNotFound);
});
expectFindOnConfigSendBSONObjVector(std::vector{collType.toBSON()});
future.timed_get(kFutureTimeout);
}
TEST_F(MetadataLoaderTest, EmptyColl) {
auto future = launchAsync([this] {
CollectionMetadata metadata;
auto status = MetadataLoader::makeCollectionMetadata(operationContext(),
catalogClient(),
kNss.ns(),
kShardId.toString(),
NULL, /* no old metadata */
&metadata);
ASSERT_EQUALS(status.code(), ErrorCodes::NamespaceNotFound);
});
expectFindOnConfigSendErrorCode(ErrorCodes::NamespaceNotFound);
future.timed_get(kFutureTimeout);
}
TEST_F(MetadataLoaderTest, BadColl) {
BSONObj badCollToSend = BSON(CollectionType::fullNs(kNss.ns()));
auto future = launchAsync([this] {
CollectionMetadata metadata;
auto status = MetadataLoader::makeCollectionMetadata(operationContext(),
catalogClient(),
kNss.ns(),
kShardId.toString(),
NULL, /* no old metadata */
&metadata);
ASSERT_EQUALS(status.code(), ErrorCodes::NoSuchKey);
});
expectFindOnConfigSendBSONObjVector(std::vector{badCollToSend});
future.timed_get(kFutureTimeout);
}
TEST_F(MetadataLoaderTest, BadChunk) {
ChunkType chunkInfo;
chunkInfo.setNS(kNss.ns());
chunkInfo.setVersion(ChunkVersion(1, 0, getMaxCollVersion().epoch()));
ASSERT(!chunkInfo.validate().isOK());
auto future = launchAsync([this] {
CollectionMetadata metadata;
auto status = MetadataLoader::makeCollectionMetadata(operationContext(),
catalogClient(),
kNss.ns(),
kShardId.toString(),
NULL, /* no old metadata */
&metadata);
ASSERT_EQUALS(status.code(), ErrorCodes::NoSuchKey);
});
expectFindOnConfigSendCollectionDefault();
expectFindOnConfigSendBSONObjVector(std::vector{chunkInfo.toConfigBSON()});
future.timed_get(kFutureTimeout);
}
TEST_F(MetadataLoaderTest, NoChunksIsDropped) {
auto future = launchAsync([this] {
ON_BLOCK_EXIT([&] { Client::destroy(); });
Client::initThreadIfNotAlready("Test");
auto opCtx = cc().makeOperationContext();
CollectionMetadata metadata;
auto status = MetadataLoader::makeCollectionMetadata(opCtx.get(),
catalogClient(),
kNss.ns(),
kShardId.toString(),
NULL, /* no old metadata */
&metadata);
// This is interpreted as a dropped ns, since we drop the chunks first
ASSERT_EQUALS(status.code(), ErrorCodes::NamespaceNotFound);
checkCollectionMetadataChunksMatchPersistedChunks(kChunkMetadataNss, metadata, 0);
});
expectFindOnConfigSendCollectionDefault();
expectFindOnConfigSendBSONObjVector(std::vector{});
future.timed_get(kFutureTimeout);
}
TEST_F(MetadataLoaderTest, CheckNumChunk) {
// Need a chunk on another shard, otherwise the chunks are invalid in general and we
// can't load metadata
ChunkType chunkType;
chunkType.setNS(kNss.ns());
chunkType.setShard(ShardId("altshard"));
chunkType.setMin(BSON("a" << MINKEY));
chunkType.setMax(BSON("a" << MAXKEY));
chunkType.setVersion(ChunkVersion(1, 0, getMaxCollVersion().epoch()));
ASSERT(chunkType.validate().isOK());
auto future = launchAsync([this] {
ON_BLOCK_EXIT([&] { Client::destroy(); });
Client::initThreadIfNotAlready("Test");
auto opCtx = cc().makeOperationContext();
CollectionMetadata metadata;
auto status = MetadataLoader::makeCollectionMetadata(opCtx.get(),
catalogClient(),
kNss.ns(),
kShardId.toString(),
NULL, /* no old metadata */
&metadata);
ASSERT_OK(status);
ASSERT_EQUALS(0U, metadata.getNumChunks());
ASSERT_EQUALS(1, metadata.getCollVersion().majorVersion());
ASSERT_EQUALS(0, metadata.getShardVersion().majorVersion());
checkCollectionMetadataChunksMatchPersistedChunks(kChunkMetadataNss, metadata, 1);
});
expectFindOnConfigSendCollectionDefault();
expectFindOnConfigSendBSONObjVector(std::vector{chunkType.toConfigBSON()});
future.timed_get(kFutureTimeout);
}
TEST_F(MetadataLoaderTest, SingleChunkCheckNumChunk) {
auto future = launchAsync([this] {
ON_BLOCK_EXIT([&] { Client::destroy(); });
Client::initThreadIfNotAlready("Test");
auto opCtx = cc().makeOperationContext();
CollectionMetadata metadata;
auto status = MetadataLoader::makeCollectionMetadata(opCtx.get(),
catalogClient(),
kNss.ns(),
kShardId.toString(),
NULL, /* no old metadata */
&metadata);
ASSERT_OK(status);
ASSERT_EQUALS(1U, metadata.getNumChunks());
ASSERT_EQUALS(getMaxCollVersion(), metadata.getCollVersion());
ASSERT_EQUALS(getMaxCollVersion(), metadata.getShardVersion());
checkCollectionMetadataChunksMatchPersistedChunks(kChunkMetadataNss, metadata, 1);
});
expectFindOnConfigSendCollectionDefault();
expectFindOnConfigSendChunksDefault();
future.timed_get(kFutureTimeout);
}
TEST_F(MetadataLoaderTest, SeveralChunksCheckNumChunks) {
auto future = launchAsync([this] {
ON_BLOCK_EXIT([&] { Client::destroy(); });
Client::initThreadIfNotAlready("Test");
auto opCtx = cc().makeOperationContext();
CollectionMetadata metadata;
auto status = MetadataLoader::makeCollectionMetadata(opCtx.get(),
catalogClient(),
kNss.ns(),
kShardId.toString(),
NULL, /* no old metadata */
&metadata);
ASSERT_OK(status);
ASSERT_EQUALS(4U, metadata.getNumChunks());
ASSERT_EQUALS(getMaxCollVersion(), metadata.getCollVersion());
ASSERT_EQUALS(getMaxCollVersion(), metadata.getShardVersion());
checkCollectionMetadataChunksMatchPersistedChunks(kChunkMetadataNss, metadata, 4);
});
expectFindOnConfigSendCollectionDefault();
expectFindOnConfigSendBSONObjVector(makeFourChunks());
future.timed_get(kFutureTimeout);
}
TEST_F(MetadataLoaderTest, CollectionMetadataSetUp) {
auto future = launchAsync([this] {
ON_BLOCK_EXIT([&] { Client::destroy(); });
Client::initThreadIfNotAlready("Test");
auto opCtx = cc().makeOperationContext();
CollectionMetadata metadata;
auto status = MetadataLoader::makeCollectionMetadata(opCtx.get(),
catalogClient(),
kNss.ns(),
kShardId.toString(),
NULL, /* no old metadata */
&metadata);
ASSERT_BSONOBJ_EQ(metadata.getKeyPattern(), BSON("a" << 1));
ASSERT_TRUE(getMaxCollVersion().equals(metadata.getCollVersion()));
ASSERT_TRUE(getMaxShardVersion().equals(metadata.getShardVersion()));
checkCollectionMetadataChunksMatchPersistedChunks(kChunkMetadataNss, metadata, 1);
});
expectFindOnConfigSendCollectionDefault();
expectFindOnConfigSendChunksDefault();
future.timed_get(kFutureTimeout);
}
} // namespace
} // namespace mongo