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/bson/bsonobj.h"
34 #include "mongo/bson/bsonobjbuilder.h"
35 #include "mongo/client/query.h"
36 #include "mongo/client/remote_command_targeter_mock.h"
37 #include "mongo/db/catalog/catalog_raii.h"
38 #include "mongo/db/client.h"
39 #include "mongo/db/dbdirectclient.h"
40 #include "mongo/db/index/index_descriptor.h"
41 #include "mongo/db/keypattern.h"
42 #include "mongo/db/repl/replication_coordinator_mock.h"
43 #include "mongo/db/s/collection_sharding_state.h"
44 #include "mongo/db/s/sharding_state.h"
45 #include "mongo/s/balancer_configuration.h"
46 #include "mongo/s/chunk_version.h"
47 #include "mongo/s/client/shard_registry.h"
48 #include "mongo/s/sharding_mongod_test_fixture.h"
49 #include "mongo/unittest/unittest.h"
50 
51 namespace mongo {
52 namespace {
53 
54 using unittest::assertGet;
55 
56 using Deletion = CollectionRangeDeleter::Deletion;
57 
58 const NamespaceString kNss = NamespaceString("foo", "bar");
59 const std::string kPattern = "_id";
60 const BSONObj kKeyPattern = BSON(kPattern << 1);
61 const std::string kShardName{"a"};
62 const HostAndPort dummyHost("dummy", 123);
63 const NamespaceString kAdminSysVer = NamespaceString("admin", "system.version");
64 
65 class CollectionRangeDeleterTest : public ShardingMongodTestFixture {
66 protected:
setUp()67     void setUp() override {
68         _epoch = OID::gen();
69         serverGlobalParams.clusterRole = ClusterRole::ShardServer;
70         ShardingMongodTestFixture::setUp();
71         replicationCoordinator()->alwaysAllowWrites(true);
72         ASSERT_OK(initializeGlobalShardingStateForMongodForTest(ConnectionString(dummyHost)));
73 
74         // RemoteCommandTargeterMock::get(shardRegistry()->getConfigShard()->getTargeter())
75         //     ->setConnectionStringReturnValue(kConfigConnStr);
76 
77         configTargeter()->setFindHostReturnValue(dummyHost);
78 
79         DBDirectClient(operationContext()).createCollection(kNss.ns());
80         {
81             AutoGetCollection autoColl(operationContext(), kNss, MODE_IX);
82             auto collectionShardingState = CollectionShardingState::get(operationContext(), kNss);
83             const KeyPattern skPattern(kKeyPattern);
84             auto cm = ChunkManager::makeNew(
85                 kNss,
86                 UUID::gen(),
87                 kKeyPattern,
88                 nullptr,
89                 false,
90                 epoch(),
91                 {ChunkType(kNss,
92                            ChunkRange{skPattern.globalMin(), skPattern.globalMax()},
93                            ChunkVersion(1, 0, epoch()),
94                            ShardId("otherShard"))});
95             collectionShardingState->refreshMetadata(
96                 operationContext(),
97                 stdx::make_unique<CollectionMetadata>(cm, ShardId("thisShard")));
98         }
99     }
100 
tearDown()101     void tearDown() override {
102         {
103             AutoGetCollection autoColl(operationContext(), kNss, MODE_IX);
104             auto collectionShardingState = CollectionShardingState::get(operationContext(), kNss);
105             collectionShardingState->refreshMetadata(operationContext(), nullptr);
106         }
107 
108         ShardingMongodTestFixture::tearDown();
109     }
110 
next(CollectionRangeDeleter & rangeDeleter,int maxToDelete)111     boost::optional<Date_t> next(CollectionRangeDeleter& rangeDeleter, int maxToDelete) {
112         return CollectionRangeDeleter::cleanUpNextRange(
113             operationContext(), kNss, epoch(), maxToDelete, &rangeDeleter);
114     }
115 
configTargeter() const116     std::shared_ptr<RemoteCommandTargeterMock> configTargeter() const {
117         return RemoteCommandTargeterMock::get(shardRegistry()->getConfigShard()->getTargeter());
118     }
119 
epoch() const120     OID const& epoch() const {
121         return _epoch;
122     }
123 
makeBalancerConfiguration()124     std::unique_ptr<BalancerConfiguration> makeBalancerConfiguration() override {
125         return stdx::make_unique<BalancerConfiguration>();
126     }
127 
128 private:
129     OID _epoch;
130 };
131 
132 // Tests the case that there is nothing in the database.
TEST_F(CollectionRangeDeleterTest,EmptyDatabase)133 TEST_F(CollectionRangeDeleterTest, EmptyDatabase) {
134     CollectionRangeDeleter rangeDeleter;
135     ASSERT_FALSE(next(rangeDeleter, 1));
136 }
137 
138 // Tests the case that there is data, but it is not in a range to clean.
TEST_F(CollectionRangeDeleterTest,NoDataInGivenRangeToClean)139 TEST_F(CollectionRangeDeleterTest, NoDataInGivenRangeToClean) {
140     CollectionRangeDeleter rangeDeleter;
141     const BSONObj insertedDoc = BSON(kPattern << 25);
142     DBDirectClient dbclient(operationContext());
143     dbclient.insert(kNss.toString(), insertedDoc);
144     ASSERT_BSONOBJ_EQ(insertedDoc, dbclient.findOne(kNss.toString(), QUERY(kPattern << 25)));
145     std::list<Deletion> ranges;
146     ranges.emplace_back(Deletion{ChunkRange{BSON(kPattern << 0), BSON(kPattern << 10)}, Date_t{}});
147     auto when = rangeDeleter.add(std::move(ranges));
148     ASSERT(when && *when == Date_t{});
149     ASSERT_EQ(1u, rangeDeleter.size());
150     ASSERT_TRUE(next(rangeDeleter, 1));
151 
152     ASSERT_EQ(0u, rangeDeleter.size());
153     ASSERT_BSONOBJ_EQ(insertedDoc, dbclient.findOne(kNss.toString(), QUERY(kPattern << 25)));
154 
155     ASSERT_FALSE(next(rangeDeleter, 1));
156 }
157 
158 // Tests the case that there is a single document within a range to clean.
TEST_F(CollectionRangeDeleterTest,OneDocumentInOneRangeToClean)159 TEST_F(CollectionRangeDeleterTest, OneDocumentInOneRangeToClean) {
160     CollectionRangeDeleter rangeDeleter;
161     const BSONObj insertedDoc = BSON(kPattern << 5);
162     DBDirectClient dbclient(operationContext());
163     dbclient.insert(kNss.toString(), BSON(kPattern << 5));
164     ASSERT_BSONOBJ_EQ(insertedDoc, dbclient.findOne(kNss.toString(), QUERY(kPattern << 5)));
165 
166     std::list<Deletion> ranges;
167     auto deletion = Deletion{ChunkRange(BSON(kPattern << 0), BSON(kPattern << 10)), Date_t{}};
168     ranges.emplace_back(std::move(deletion));
169     auto when = rangeDeleter.add(std::move(ranges));
170     ASSERT(when && *when == Date_t{});
171     ASSERT_TRUE(ranges.empty());  // spliced elements out of it
172 
173     auto optNotifn = rangeDeleter.overlaps(ChunkRange(BSON(kPattern << 0), BSON(kPattern << 10)));
174     ASSERT(optNotifn);
175     auto notifn = *optNotifn;
176     ASSERT(!notifn.ready());
177     // actually delete one
178     ASSERT_TRUE(next(rangeDeleter, 1));
179     ASSERT(!notifn.ready());
180 
181     ASSERT_EQ(rangeDeleter.size(), 1u);
182     // range empty, pop range, notify
183     ASSERT_TRUE(next(rangeDeleter, 1));
184     ASSERT_TRUE(rangeDeleter.isEmpty());
185     ASSERT(notifn.ready() && notifn.waitStatus(operationContext()).isOK());
186 
187     ASSERT_TRUE(dbclient.findOne(kNss.toString(), QUERY(kPattern << 5)).isEmpty());
188     ASSERT_FALSE(next(rangeDeleter, 1));
189     ASSERT_EQUALS(0ULL, dbclient.count(kAdminSysVer.ns(), BSON(kPattern << "startRangeDeletion")));
190 }
191 
192 // Tests the case that there are multiple documents within a range to clean.
TEST_F(CollectionRangeDeleterTest,MultipleDocumentsInOneRangeToClean)193 TEST_F(CollectionRangeDeleterTest, MultipleDocumentsInOneRangeToClean) {
194     CollectionRangeDeleter rangeDeleter;
195     DBDirectClient dbclient(operationContext());
196     dbclient.insert(kNss.toString(), BSON(kPattern << 1));
197     dbclient.insert(kNss.toString(), BSON(kPattern << 2));
198     dbclient.insert(kNss.toString(), BSON(kPattern << 3));
199     ASSERT_EQUALS(3ULL, dbclient.count(kNss.toString(), BSON(kPattern << LT << 5)));
200 
201     std::list<Deletion> ranges;
202     auto deletion = Deletion{ChunkRange(BSON(kPattern << 0), BSON(kPattern << 10)), Date_t{}};
203     ranges.emplace_back(std::move(deletion));
204     auto when = rangeDeleter.add(std::move(ranges));
205     ASSERT(when && *when == Date_t{});
206 
207     ASSERT_TRUE(next(rangeDeleter, 100));
208     ASSERT_TRUE(next(rangeDeleter, 100));
209     ASSERT_EQUALS(0ULL, dbclient.count(kNss.toString(), BSON(kPattern << LT << 5)));
210     ASSERT_FALSE(next(rangeDeleter, 100));
211     ASSERT_EQUALS(0ULL, dbclient.count(kAdminSysVer.ns(), BSON(kPattern << "startRangeDeletion")));
212 }
213 
214 // Tests the case that there are multiple documents within a range to clean, and the range deleter
215 // has a max deletion rate of one document per run.
TEST_F(CollectionRangeDeleterTest,MultipleCleanupNextRangeCalls)216 TEST_F(CollectionRangeDeleterTest, MultipleCleanupNextRangeCalls) {
217     CollectionRangeDeleter rangeDeleter;
218     DBDirectClient dbclient(operationContext());
219     dbclient.insert(kNss.toString(), BSON(kPattern << 1));
220     dbclient.insert(kNss.toString(), BSON(kPattern << 2));
221     dbclient.insert(kNss.toString(), BSON(kPattern << 3));
222     ASSERT_EQUALS(3ULL, dbclient.count(kNss.toString(), BSON(kPattern << LT << 5)));
223 
224     std::list<Deletion> ranges;
225     auto deletion = Deletion{ChunkRange(BSON(kPattern << 0), BSON(kPattern << 10)), Date_t{}};
226     ranges.emplace_back(std::move(deletion));
227     auto when = rangeDeleter.add(std::move(ranges));
228     ASSERT(when && *when == Date_t{});
229 
230     ASSERT_TRUE(next(rangeDeleter, 1));
231     ASSERT_EQUALS(2ULL, dbclient.count(kNss.toString(), BSON(kPattern << LT << 5)));
232 
233     ASSERT_TRUE(next(rangeDeleter, 1));
234     ASSERT_EQUALS(1ULL, dbclient.count(kNss.toString(), BSON(kPattern << LT << 5)));
235 
236     ASSERT_TRUE(next(rangeDeleter, 1));
237     ASSERT_TRUE(next(rangeDeleter, 1));
238     ASSERT_EQUALS(0ULL, dbclient.count(kNss.toString(), BSON(kPattern << LT << 5)));
239     ASSERT_FALSE(next(rangeDeleter, 1));
240     ASSERT_EQUALS(0ULL, dbclient.count(kAdminSysVer.ns(), BSON(kPattern << "startRangeDeletion")));
241 }
242 
243 // Tests the case that there are two ranges to clean, each containing multiple documents.
TEST_F(CollectionRangeDeleterTest,MultipleDocumentsInMultipleRangesToClean)244 TEST_F(CollectionRangeDeleterTest, MultipleDocumentsInMultipleRangesToClean) {
245     CollectionRangeDeleter rangeDeleter;
246     DBDirectClient dbclient(operationContext());
247     dbclient.insert(kNss.toString(), BSON(kPattern << 1));
248     dbclient.insert(kNss.toString(), BSON(kPattern << 2));
249     dbclient.insert(kNss.toString(), BSON(kPattern << 3));
250     dbclient.insert(kNss.toString(), BSON(kPattern << 4));
251     dbclient.insert(kNss.toString(), BSON(kPattern << 5));
252     dbclient.insert(kNss.toString(), BSON(kPattern << 6));
253     ASSERT_EQUALS(6ULL, dbclient.count(kNss.toString(), BSON(kPattern << LT << 10)));
254 
255     std::list<Deletion> ranges;
256     auto later = Date_t::now();
257     ranges.emplace_back(Deletion{ChunkRange{BSON(kPattern << 0), BSON(kPattern << 3)}, later});
258     auto when = rangeDeleter.add(std::move(ranges));
259     ASSERT(when && *when == later);
260     ASSERT_TRUE(ranges.empty());  // not guaranteed by std, but failure would indicate a problem.
261 
262     std::list<Deletion> ranges2;
263     ranges2.emplace_back(Deletion{ChunkRange{BSON(kPattern << 4), BSON(kPattern << 7)}, later});
264     when = rangeDeleter.add(std::move(ranges2));
265     ASSERT(!when);
266 
267     std::list<Deletion> ranges3;
268     ranges3.emplace_back(Deletion{ChunkRange{BSON(kPattern << 3), BSON(kPattern << 4)}, Date_t{}});
269     when = rangeDeleter.add(std::move(ranges3));
270     ASSERT(when);
271 
272     auto optNotifn1 = rangeDeleter.overlaps(ChunkRange{BSON(kPattern << 0), BSON(kPattern << 3)});
273     ASSERT_TRUE(optNotifn1);
274     auto& notifn1 = *optNotifn1;
275     ASSERT_FALSE(notifn1.ready());
276 
277     auto optNotifn2 = rangeDeleter.overlaps(ChunkRange{BSON(kPattern << 4), BSON(kPattern << 7)});
278     ASSERT_TRUE(optNotifn2);
279     auto& notifn2 = *optNotifn2;
280     ASSERT_FALSE(notifn2.ready());
281 
282     auto optNotifn3 = rangeDeleter.overlaps(ChunkRange{BSON(kPattern << 3), BSON(kPattern << 4)});
283     ASSERT_TRUE(optNotifn3);
284     auto& notifn3 = *optNotifn3;
285     ASSERT_FALSE(notifn3.ready());
286 
287     // test op== on notifications
288     ASSERT_TRUE(notifn1 == *optNotifn1);
289     ASSERT_FALSE(notifn1 == *optNotifn2);
290     ASSERT_TRUE(notifn1 != *optNotifn2);
291     ASSERT_FALSE(notifn1 != *optNotifn1);
292 
293     // no op log entry yet
294     ASSERT_EQUALS(0ULL, dbclient.count(kAdminSysVer.ns(), BSON(kPattern << "startRangeDeletion")));
295 
296     ASSERT_EQUALS(6ULL, dbclient.count(kNss.ns(), BSON(kPattern << LT << 7)));
297 
298     // catch range3, [3..4) only
299     auto next1 = next(rangeDeleter, 100);
300     ASSERT_TRUE(next1);
301 
302     // no op log entry for immediate deletions
303     ASSERT_EQUALS(0ULL, dbclient.count(kAdminSysVer.ns(), BSON(kPattern << "startRangeDeletion")));
304 
305     // 3 gone
306     ASSERT_EQUALS(5ULL, dbclient.count(kNss.ns(), BSON(kPattern << LT << 7)));
307     ASSERT_EQUALS(2ULL, dbclient.count(kNss.ns(), BSON(kPattern << LT << 4)));
308 
309     ASSERT_FALSE(notifn1.ready());  // no trigger yet
310     ASSERT_FALSE(notifn2.ready());  // no trigger yet
311     ASSERT_FALSE(notifn3.ready());  // no trigger yet
312 
313     // this will find the [3..4) range empty, so pop the range and notify
314     auto next2 = next(rangeDeleter, 100);
315     ASSERT_TRUE(next2);
316 
317     // still no op log entry, because not delayed
318     ASSERT_EQUALS(0ULL, dbclient.count(kAdminSysVer.ns(), BSON(kPattern << "startRangeDeletion")));
319 
320     // deleted 1, 5 left
321     ASSERT_EQUALS(2ULL, dbclient.count(kNss.ns(), BSON(kPattern << LT << 4)));
322     ASSERT_EQUALS(5ULL, dbclient.count(kNss.ns(), BSON(kPattern << LT << 10)));
323 
324     ASSERT_FALSE(notifn1.ready());  // no trigger yet
325     ASSERT_FALSE(notifn2.ready());  // no trigger yet
326     ASSERT_TRUE(notifn3.ready());   // triggered.
327     ASSERT_OK(notifn3.waitStatus(operationContext()));
328 
329     // This will find the regular queue empty, but the [0..3) range in the delayed queue.
330     // However, the time to delete them is now, so the range is moved to the regular queue.
331     auto next3 = next(rangeDeleter, 100);
332     ASSERT_TRUE(next3);
333 
334     ASSERT_FALSE(notifn1.ready());  // no trigger yet
335     ASSERT_FALSE(notifn2.ready());  // no trigger yet
336 
337     // deleted 3, 3 left
338     ASSERT_EQUALS(3ULL, dbclient.count(kNss.ns(), BSON(kPattern << LT << 10)));
339 
340     ASSERT_EQUALS(1ULL, dbclient.count(kAdminSysVer.ns(), BSON(kPattern << "startRangeDeletion")));
341     // clang-format off
342     ASSERT_BSONOBJ_EQ(
343         BSON("_id" << "startRangeDeletion" << "ns" << kNss.ns()
344           << "epoch" << epoch() << "min" << BSON("_id" << 0) << "max" << BSON("_id" << 3)),
345         dbclient.findOne(kAdminSysVer.ns(), QUERY("_id" << "startRangeDeletion")));
346     // clang-format on
347 
348     // this will find the [0..3) range empty, so pop the range and notify
349     auto next4 = next(rangeDeleter, 100);
350     ASSERT_TRUE(next4);
351 
352     ASSERT_TRUE(notifn1.ready());
353     ASSERT_OK(notifn1.waitStatus(operationContext()));
354     ASSERT_FALSE(notifn2.ready());
355 
356     // op log entry unchanged
357     // clang-format off
358     ASSERT_BSONOBJ_EQ(
359         BSON("_id" << "startRangeDeletion" << "ns" << kNss.ns()
360           << "epoch" << epoch() << "min" << BSON("_id" << 0) << "max" << BSON("_id" << 3)),
361         dbclient.findOne(kAdminSysVer.ns(), QUERY("_id" << "startRangeDeletion")));
362     // clang-format on
363 
364     // still 3 left
365     ASSERT_EQUALS(3ULL, dbclient.count(kNss.ns(), BSON(kPattern << LT << 10)));
366 
367     // delete the remaining documents
368     auto next5 = next(rangeDeleter, 100);
369     ASSERT_TRUE(next5);
370 
371     ASSERT_FALSE(notifn2.ready());
372 
373     // Another delayed range, so logged
374     // clang-format off
375     ASSERT_BSONOBJ_EQ(
376         BSON("_id" << "startRangeDeletion" << "ns" << kNss.ns()
377           << "epoch" << epoch() << "min" << BSON("_id" << 4) << "max" << BSON("_id" << 7)),
378         dbclient.findOne(kAdminSysVer.ns(), QUERY("_id" << "startRangeDeletion")));
379     // clang-format on
380 
381     // all docs gone
382     ASSERT_EQUALS(0ULL, dbclient.count(kNss.ns(), BSON(kPattern << LT << 10)));
383 
384     // discover there are no more, pop range 2
385     auto next6 = next(rangeDeleter, 100);
386     ASSERT_TRUE(next6);
387 
388     ASSERT_TRUE(notifn2.ready());
389     ASSERT_OK(notifn2.waitStatus(operationContext()));
390 
391     // discover there are no more ranges
392     ASSERT_FALSE(next(rangeDeleter, 1));
393 }
394 
395 }  // namespace
396 }  // namespace mongo
397