/**
* Copyright (C) 2016 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.
*/
#include "mongo/platform/basic.h"
#include "mongo/s/client/shard_local.h"
#include "mongo/client/read_preference.h"
#include "mongo/db/catalog_raii.h"
#include "mongo/db/client.h"
#include "mongo/db/query/cursor_response.h"
#include "mongo/db/query/find_and_modify_request.h"
#include "mongo/db/repl/replication_coordinator.h"
#include "mongo/db/repl/replication_coordinator_mock.h"
#include "mongo/db/service_context_d_test_fixture.h"
#include "mongo/db/write_concern_options.h"
#include "mongo/s/client/shard_registry.h"
#include "mongo/stdx/memory.h"
namespace mongo {
namespace {
class ShardLocalTest : public ServiceContextMongoDTest {
protected:
ServiceContext::UniqueOperationContext _opCtx;
std::unique_ptr _shardLocal;
/**
* Sets up and runs a FindAndModify command with ShardLocal's runCommand. Finds a document in
* namespace "nss" that matches "find" and updates the document with "set". Upsert and new are
* set to true in the FindAndModify request.
*/
StatusWith runFindAndModifyRunCommand(NamespaceString nss,
BSONObj find,
BSONObj set);
/**
* Facilitates running a find query by supplying the redundant parameters. Finds documents in
* namespace "nss" that match "query" and returns "limit" (if there are that many) number of
* documents in "sort" order.
*/
StatusWith runFindQuery(NamespaceString nss,
BSONObj query,
BSONObj sort,
boost::optional limit);
/**
* Returns the index definitions that exist for the given collection.
*/
StatusWith> getIndexes(NamespaceString nss);
private:
void setUp() override;
void tearDown() override;
};
void ShardLocalTest::setUp() {
ServiceContextMongoDTest::setUp();
Client::initThreadIfNotAlready();
_opCtx = getGlobalServiceContext()->makeOperationContext(&cc());
serverGlobalParams.clusterRole = ClusterRole::ConfigServer;
_shardLocal = stdx::make_unique(ShardRegistry::kConfigServerShardId);
const repl::ReplSettings replSettings = {};
repl::ReplicationCoordinator::set(
getGlobalServiceContext(),
std::unique_ptr(
new repl::ReplicationCoordinatorMock(_opCtx->getServiceContext(), replSettings)));
ASSERT_OK(repl::ReplicationCoordinator::get(getGlobalServiceContext())
->setFollowerMode(repl::MemberState::RS_PRIMARY));
}
void ShardLocalTest::tearDown() {
_opCtx.reset();
ServiceContextMongoDTest::tearDown();
repl::ReplicationCoordinator::set(getGlobalServiceContext(), nullptr);
}
StatusWith ShardLocalTest::runFindAndModifyRunCommand(NamespaceString nss,
BSONObj find,
BSONObj set) {
FindAndModifyRequest findAndModifyRequest = FindAndModifyRequest::makeUpdate(nss, find, set);
findAndModifyRequest.setUpsert(true);
findAndModifyRequest.setShouldReturnNew(true);
findAndModifyRequest.setWriteConcern(WriteConcernOptions(
WriteConcernOptions::kMajority, WriteConcernOptions::SyncMode::UNSET, Seconds(15)));
return _shardLocal->runCommandWithFixedRetryAttempts(
_opCtx.get(),
ReadPreferenceSetting{ReadPreference::PrimaryOnly},
nss.db().toString(),
findAndModifyRequest.toBSON(),
Shard::RetryPolicy::kNoRetry);
}
StatusWith> ShardLocalTest::getIndexes(NamespaceString nss) {
auto response = _shardLocal->runCommandWithFixedRetryAttempts(
_opCtx.get(),
ReadPreferenceSetting{ReadPreference::PrimaryOnly},
nss.db().toString(),
BSON("listIndexes" << nss.coll().toString()),
Shard::RetryPolicy::kIdempotent);
if (!response.isOK()) {
return response.getStatus();
}
if (!response.getValue().commandStatus.isOK()) {
return response.getValue().commandStatus;
}
auto cursorResponse = CursorResponse::parseFromBSON(response.getValue().response);
if (!cursorResponse.isOK()) {
return cursorResponse.getStatus();
}
return cursorResponse.getValue().getBatch();
}
/**
* Takes a FindAndModify command's BSON response and parses it for the returned "value" field.
*/
BSONObj extractFindAndModifyNewObj(const BSONObj& responseObj) {
const auto& newDocElem = responseObj["value"];
ASSERT(!newDocElem.eoo());
ASSERT(newDocElem.isABSONObj());
return newDocElem.Obj();
}
StatusWith ShardLocalTest::runFindQuery(NamespaceString nss,
BSONObj query,
BSONObj sort,
boost::optional limit) {
return _shardLocal->exhaustiveFindOnConfig(_opCtx.get(),
ReadPreferenceSetting{ReadPreference::PrimaryOnly},
repl::ReadConcernLevel::kMajorityReadConcern,
nss,
query,
sort,
limit);
}
TEST_F(ShardLocalTest, RunCommand) {
NamespaceString nss("admin.bar");
StatusWith findAndModifyResponse = runFindAndModifyRunCommand(
nss, BSON("fooItem" << 1), BSON("$set" << BSON("fooRandom" << 254)));
Shard::CommandResponse commandResponse = unittest::assertGet(findAndModifyResponse);
BSONObj newDocument = extractFindAndModifyNewObj(commandResponse.response);
ASSERT_EQUALS(1, newDocument["fooItem"].numberInt());
ASSERT_EQUALS(254, newDocument["fooRandom"].numberInt());
}
TEST_F(ShardLocalTest, FindOneWithoutLimit) {
NamespaceString nss("admin.bar");
// Set up documents to be queried.
StatusWith findAndModifyResponse = runFindAndModifyRunCommand(
nss, BSON("fooItem" << 1), BSON("$set" << BSON("fooRandom" << 254)));
ASSERT_OK(findAndModifyResponse.getStatus());
findAndModifyResponse = runFindAndModifyRunCommand(
nss, BSON("fooItem" << 3), BSON("$set" << BSON("fooRandom" << 452)));
ASSERT_OK(findAndModifyResponse.getStatus());
// Find a single document.
StatusWith response =
runFindQuery(nss, BSON("fooItem" << 3), BSONObj(), boost::none);
Shard::QueryResponse queryResponse = unittest::assertGet(response);
std::vector docs = queryResponse.docs;
const unsigned long size = 1;
ASSERT_EQUALS(size, docs.size());
BSONObj foundDoc = docs[0];
ASSERT_EQUALS(3, foundDoc["fooItem"].numberInt());
ASSERT_EQUALS(452, foundDoc["fooRandom"].numberInt());
}
TEST_F(ShardLocalTest, FindManyWithLimit) {
NamespaceString nss("admin.bar");
// Set up documents to be queried.
StatusWith findAndModifyResponse = runFindAndModifyRunCommand(
nss, BSON("fooItem" << 1), BSON("$set" << BSON("fooRandom" << 254)));
ASSERT_OK(findAndModifyResponse.getStatus());
findAndModifyResponse = runFindAndModifyRunCommand(
nss, BSON("fooItem" << 2), BSON("$set" << BSON("fooRandom" << 444)));
ASSERT_OK(findAndModifyResponse.getStatus());
findAndModifyResponse = runFindAndModifyRunCommand(
nss, BSON("fooItem" << 3), BSON("$set" << BSON("fooRandom" << 452)));
ASSERT_OK(findAndModifyResponse.getStatus());
// Find 2 of 3 documents.
StatusWith response =
runFindQuery(nss, BSONObj(), BSON("fooItem" << 1), 2LL);
Shard::QueryResponse queryResponse = unittest::assertGet(response);
std::vector docs = queryResponse.docs;
const unsigned long size = 2;
ASSERT_EQUALS(size, docs.size());
BSONObj firstDoc = docs[0];
ASSERT_EQUALS(1, firstDoc["fooItem"].numberInt());
ASSERT_EQUALS(254, firstDoc["fooRandom"].numberInt());
BSONObj secondDoc = docs[1];
ASSERT_EQUALS(2, secondDoc["fooItem"].numberInt());
ASSERT_EQUALS(444, secondDoc["fooRandom"].numberInt());
}
TEST_F(ShardLocalTest, FindNoMatchingDocumentsEmpty) {
NamespaceString nss("admin.bar");
// Set up a document.
StatusWith findAndModifyResponse = runFindAndModifyRunCommand(
nss, BSON("fooItem" << 1), BSON("$set" << BSON("fooRandom" << 254)));
ASSERT_OK(findAndModifyResponse.getStatus());
// Run a query that won't find any results.
StatusWith response =
runFindQuery(nss, BSON("fooItem" << 3), BSONObj(), boost::none);
Shard::QueryResponse queryResponse = unittest::assertGet(response);
std::vector docs = queryResponse.docs;
const unsigned long size = 0;
ASSERT_EQUALS(size, docs.size());
}
TEST_F(ShardLocalTest, CreateIndex) {
NamespaceString nss("config.foo");
ASSERT_EQUALS(ErrorCodes::NamespaceNotFound, getIndexes(nss).getStatus());
Status status =
_shardLocal->createIndexOnConfig(_opCtx.get(), nss, BSON("a" << 1 << "b" << 1), true);
// Creating the index should implicitly create the collection
ASSERT_OK(status);
auto indexes = unittest::assertGet(getIndexes(nss));
// There should be the index we just added as well as the _id index
ASSERT_EQ(2U, indexes.size());
// Making an identical index should be a no-op.
status = _shardLocal->createIndexOnConfig(_opCtx.get(), nss, BSON("a" << 1 << "b" << 1), true);
ASSERT_OK(status);
indexes = unittest::assertGet(getIndexes(nss));
ASSERT_EQ(2U, indexes.size());
// Trying to make the same index as non-unique should fail.
status = _shardLocal->createIndexOnConfig(_opCtx.get(), nss, BSON("a" << 1 << "b" << 1), false);
ASSERT_EQUALS(ErrorCodes::IndexOptionsConflict, status);
indexes = unittest::assertGet(getIndexes(nss));
ASSERT_EQ(2U, indexes.size());
}
} // namespace
} // namespace mongo