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