1 
2 /**
3  *    Copyright (C) 2018-present MongoDB, Inc.
4  *
5  *    This program is free software: you can redistribute it and/or modify
6  *    it under the terms of the Server Side Public License, version 1,
7  *    as published by MongoDB, Inc.
8  *
9  *    This program is distributed in the hope that it will be useful,
10  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *    Server Side Public License for more details.
13  *
14  *    You should have received a copy of the Server Side Public License
15  *    along with this program. If not, see
16  *    <http://www.mongodb.com/licensing/server-side-public-license>.
17  *
18  *    As a special exception, the copyright holders give permission to link the
19  *    code of portions of this program with the OpenSSL library under certain
20  *    conditions as described in each individual source file and distribute
21  *    linked combinations including the program with the OpenSSL library. You
22  *    must comply with the Server Side Public License in all respects for
23  *    all of the code used other than as permitted herein. If you modify file(s)
24  *    with this exception, you may extend this exception to your version of the
25  *    file(s), but you are not obligated to do so. If you do not wish to do so,
26  *    delete this exception statement from your version. If you delete this
27  *    exception statement from all source files in the program, then also delete
28  *    it in the license file.
29  */
30 
31 #include "mongo/platform/basic.h"
32 
33 #include "mongo/s/client/shard_local.h"
34 
35 #include "mongo/client/read_preference.h"
36 #include "mongo/db/catalog/catalog_raii.h"
37 #include "mongo/db/client.h"
38 #include "mongo/db/query/cursor_response.h"
39 #include "mongo/db/query/find_and_modify_request.h"
40 #include "mongo/db/repl/replication_coordinator_global.h"
41 #include "mongo/db/repl/replication_coordinator_mock.h"
42 #include "mongo/db/service_context_d_test_fixture.h"
43 #include "mongo/db/write_concern_options.h"
44 #include "mongo/s/client/shard_registry.h"
45 #include "mongo/stdx/memory.h"
46 
47 namespace mongo {
48 namespace {
49 
50 class ShardLocalTest : public ServiceContextMongoDTest {
51 protected:
52     ServiceContext::UniqueOperationContext _opCtx;
53     std::unique_ptr<ShardLocal> _shardLocal;
54 
55     /**
56      * Sets up and runs a FindAndModify command with ShardLocal's runCommand. Finds a document in
57      * namespace "nss" that matches "find" and updates the document with "set". Upsert and new are
58      * set to true in the FindAndModify request.
59      */
60     StatusWith<Shard::CommandResponse> runFindAndModifyRunCommand(NamespaceString nss,
61                                                                   BSONObj find,
62                                                                   BSONObj set);
63     /**
64      * Facilitates running a find query by supplying the redundant parameters. Finds documents in
65      * namespace "nss" that match "query" and returns "limit" (if there are that many) number of
66      * documents in "sort" order.
67      */
68     StatusWith<Shard::QueryResponse> runFindQuery(NamespaceString nss,
69                                                   BSONObj query,
70                                                   BSONObj sort,
71                                                   boost::optional<long long> limit);
72 
73     /**
74      * Returns the index definitions that exist for the given collection.
75      */
76     StatusWith<std::vector<BSONObj>> getIndexes(NamespaceString nss);
77 
78 private:
79     void setUp() override;
80     void tearDown() override;
81 };
82 
setUp()83 void ShardLocalTest::setUp() {
84     ServiceContextMongoDTest::setUp();
85     Client::initThreadIfNotAlready();
86     _opCtx = getGlobalServiceContext()->makeOperationContext(&cc());
87     serverGlobalParams.clusterRole = ClusterRole::ConfigServer;
88     _shardLocal = stdx::make_unique<ShardLocal>(ShardRegistry::kConfigServerShardId);
89     const repl::ReplSettings replSettings = {};
90     repl::setGlobalReplicationCoordinator(
91         new repl::ReplicationCoordinatorMock(_opCtx->getServiceContext(), replSettings));
92     ASSERT_OK(
93         repl::getGlobalReplicationCoordinator()->setFollowerMode(repl::MemberState::RS_PRIMARY));
94 }
95 
tearDown()96 void ShardLocalTest::tearDown() {
97     _opCtx.reset();
98     ServiceContextMongoDTest::tearDown();
99     repl::setGlobalReplicationCoordinator(nullptr);
100 }
101 
runFindAndModifyRunCommand(NamespaceString nss,BSONObj find,BSONObj set)102 StatusWith<Shard::CommandResponse> ShardLocalTest::runFindAndModifyRunCommand(NamespaceString nss,
103                                                                               BSONObj find,
104                                                                               BSONObj set) {
105     FindAndModifyRequest findAndModifyRequest = FindAndModifyRequest::makeUpdate(nss, find, set);
106     findAndModifyRequest.setUpsert(true);
107     findAndModifyRequest.setShouldReturnNew(true);
108     findAndModifyRequest.setWriteConcern(WriteConcernOptions(
109         WriteConcernOptions::kMajority, WriteConcernOptions::SyncMode::UNSET, Seconds(15)));
110 
111     return _shardLocal->runCommandWithFixedRetryAttempts(
112         _opCtx.get(),
113         ReadPreferenceSetting{ReadPreference::PrimaryOnly},
114         nss.db().toString(),
115         findAndModifyRequest.toBSON(),
116         Shard::RetryPolicy::kNoRetry);
117 }
118 
getIndexes(NamespaceString nss)119 StatusWith<std::vector<BSONObj>> ShardLocalTest::getIndexes(NamespaceString nss) {
120     auto response = _shardLocal->runCommandWithFixedRetryAttempts(
121         _opCtx.get(),
122         ReadPreferenceSetting{ReadPreference::PrimaryOnly},
123         nss.db().toString(),
124         BSON("listIndexes" << nss.coll().toString()),
125         Shard::RetryPolicy::kIdempotent);
126     if (!response.isOK()) {
127         return response.getStatus();
128     }
129     if (!response.getValue().commandStatus.isOK()) {
130         return response.getValue().commandStatus;
131     }
132 
133     auto cursorResponse = CursorResponse::parseFromBSON(response.getValue().response);
134     if (!cursorResponse.isOK()) {
135         return cursorResponse.getStatus();
136     }
137     return cursorResponse.getValue().getBatch();
138 }
139 
140 /**
141  * Takes a FindAndModify command's BSON response and parses it for the returned "value" field.
142  */
extractFindAndModifyNewObj(const BSONObj & responseObj)143 BSONObj extractFindAndModifyNewObj(const BSONObj& responseObj) {
144     const auto& newDocElem = responseObj["value"];
145     ASSERT(!newDocElem.eoo());
146     ASSERT(newDocElem.isABSONObj());
147     return newDocElem.Obj();
148 }
149 
runFindQuery(NamespaceString nss,BSONObj query,BSONObj sort,boost::optional<long long> limit)150 StatusWith<Shard::QueryResponse> ShardLocalTest::runFindQuery(NamespaceString nss,
151                                                               BSONObj query,
152                                                               BSONObj sort,
153                                                               boost::optional<long long> limit) {
154     return _shardLocal->exhaustiveFindOnConfig(_opCtx.get(),
155                                                ReadPreferenceSetting{ReadPreference::PrimaryOnly},
156                                                repl::ReadConcernLevel::kMajorityReadConcern,
157                                                nss,
158                                                query,
159                                                sort,
160                                                limit);
161 }
162 
TEST_F(ShardLocalTest,RunCommand)163 TEST_F(ShardLocalTest, RunCommand) {
164     NamespaceString nss("admin.bar");
165     StatusWith<Shard::CommandResponse> findAndModifyResponse = runFindAndModifyRunCommand(
166         nss, BSON("fooItem" << 1), BSON("$set" << BSON("fooRandom" << 254)));
167 
168     Shard::CommandResponse commandResponse = unittest::assertGet(findAndModifyResponse);
169     BSONObj newDocument = extractFindAndModifyNewObj(commandResponse.response);
170 
171     ASSERT_EQUALS(1, newDocument["fooItem"].numberInt());
172     ASSERT_EQUALS(254, newDocument["fooRandom"].numberInt());
173 }
174 
TEST_F(ShardLocalTest,FindOneWithoutLimit)175 TEST_F(ShardLocalTest, FindOneWithoutLimit) {
176     NamespaceString nss("admin.bar");
177 
178     // Set up documents to be queried.
179     StatusWith<Shard::CommandResponse> findAndModifyResponse = runFindAndModifyRunCommand(
180         nss, BSON("fooItem" << 1), BSON("$set" << BSON("fooRandom" << 254)));
181     ASSERT_OK(findAndModifyResponse.getStatus());
182     findAndModifyResponse = runFindAndModifyRunCommand(
183         nss, BSON("fooItem" << 3), BSON("$set" << BSON("fooRandom" << 452)));
184     ASSERT_OK(findAndModifyResponse.getStatus());
185 
186     // Find a single document.
187     StatusWith<Shard::QueryResponse> response =
188         runFindQuery(nss, BSON("fooItem" << 3), BSONObj(), boost::none);
189     Shard::QueryResponse queryResponse = unittest::assertGet(response);
190 
191     std::vector<BSONObj> docs = queryResponse.docs;
192     const unsigned long size = 1;
193     ASSERT_EQUALS(size, docs.size());
194     BSONObj foundDoc = docs[0];
195     ASSERT_EQUALS(3, foundDoc["fooItem"].numberInt());
196     ASSERT_EQUALS(452, foundDoc["fooRandom"].numberInt());
197 }
198 
TEST_F(ShardLocalTest,FindManyWithLimit)199 TEST_F(ShardLocalTest, FindManyWithLimit) {
200     NamespaceString nss("admin.bar");
201 
202     // Set up documents to be queried.
203     StatusWith<Shard::CommandResponse> findAndModifyResponse = runFindAndModifyRunCommand(
204         nss, BSON("fooItem" << 1), BSON("$set" << BSON("fooRandom" << 254)));
205     ASSERT_OK(findAndModifyResponse.getStatus());
206     findAndModifyResponse = runFindAndModifyRunCommand(
207         nss, BSON("fooItem" << 2), BSON("$set" << BSON("fooRandom" << 444)));
208     ASSERT_OK(findAndModifyResponse.getStatus());
209     findAndModifyResponse = runFindAndModifyRunCommand(
210         nss, BSON("fooItem" << 3), BSON("$set" << BSON("fooRandom" << 452)));
211     ASSERT_OK(findAndModifyResponse.getStatus());
212 
213     // Find 2 of 3 documents.
214     StatusWith<Shard::QueryResponse> response =
215         runFindQuery(nss, BSONObj(), BSON("fooItem" << 1), 2LL);
216     Shard::QueryResponse queryResponse = unittest::assertGet(response);
217 
218     std::vector<BSONObj> docs = queryResponse.docs;
219     const unsigned long size = 2;
220     ASSERT_EQUALS(size, docs.size());
221     BSONObj firstDoc = docs[0];
222     ASSERT_EQUALS(1, firstDoc["fooItem"].numberInt());
223     ASSERT_EQUALS(254, firstDoc["fooRandom"].numberInt());
224     BSONObj secondDoc = docs[1];
225     ASSERT_EQUALS(2, secondDoc["fooItem"].numberInt());
226     ASSERT_EQUALS(444, secondDoc["fooRandom"].numberInt());
227 }
228 
TEST_F(ShardLocalTest,FindNoMatchingDocumentsEmpty)229 TEST_F(ShardLocalTest, FindNoMatchingDocumentsEmpty) {
230     NamespaceString nss("admin.bar");
231 
232     // Set up a document.
233     StatusWith<Shard::CommandResponse> findAndModifyResponse = runFindAndModifyRunCommand(
234         nss, BSON("fooItem" << 1), BSON("$set" << BSON("fooRandom" << 254)));
235     ASSERT_OK(findAndModifyResponse.getStatus());
236 
237     // Run a query that won't find any results.
238     StatusWith<Shard::QueryResponse> response =
239         runFindQuery(nss, BSON("fooItem" << 3), BSONObj(), boost::none);
240     Shard::QueryResponse queryResponse = unittest::assertGet(response);
241 
242     std::vector<BSONObj> docs = queryResponse.docs;
243     const unsigned long size = 0;
244     ASSERT_EQUALS(size, docs.size());
245 }
246 
TEST_F(ShardLocalTest,CreateIndex)247 TEST_F(ShardLocalTest, CreateIndex) {
248     NamespaceString nss("config.foo");
249 
250     ASSERT_EQUALS(ErrorCodes::NamespaceNotFound, getIndexes(nss).getStatus());
251 
252     Status status =
253         _shardLocal->createIndexOnConfig(_opCtx.get(), nss, BSON("a" << 1 << "b" << 1), true);
254     // Creating the index should implicitly create the collection
255     ASSERT_OK(status);
256 
257     auto indexes = unittest::assertGet(getIndexes(nss));
258     // There should be the index we just added as well as the _id index
259     ASSERT_EQ(2U, indexes.size());
260 
261     // Making an identical index should be a no-op.
262     status = _shardLocal->createIndexOnConfig(_opCtx.get(), nss, BSON("a" << 1 << "b" << 1), true);
263     ASSERT_OK(status);
264     indexes = unittest::assertGet(getIndexes(nss));
265     ASSERT_EQ(2U, indexes.size());
266 
267     // Trying to make the same index as non-unique should fail.
268     status = _shardLocal->createIndexOnConfig(_opCtx.get(), nss, BSON("a" << 1 << "b" << 1), false);
269     ASSERT_EQUALS(ErrorCodes::IndexOptionsConflict, status);
270     indexes = unittest::assertGet(getIndexes(nss));
271     ASSERT_EQ(2U, indexes.size());
272 }
273 
274 }  // namespace
275 }  // namespace mongo
276