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/db/s/migration_destination_manager.h"
34 #include "mongo/s/shard_server_test_fixture.h"
35 #include "mongo/unittest/unittest.h"
36 
37 namespace mongo {
38 namespace {
39 
40 using unittest::assertGet;
41 
42 class MigrationDestinationManagerTest : public ShardServerTestFixture {
43 protected:
44     /**
45      * Instantiates a BSON object in which both "_id" and "X" are set to value.
46      */
createDocument(int value)47     static BSONObj createDocument(int value) {
48         return BSON("_id" << value << "X" << value);
49     }
50 
51     /**
52      * Creates a list of documents to clone.
53      */
createDocumentsToClone()54     static std::vector<BSONObj> createDocumentsToClone() {
55         return {createDocument(1), createDocument(2), createDocument(3)};
56     }
57 
58     /**
59      * Creates a list of documents to clone and converts it to a BSONArray.
60      */
createDocumentsToCloneArray()61     static BSONArray createDocumentsToCloneArray() {
62         BSONArrayBuilder arrayBuilder;
63         for (auto& doc : createDocumentsToClone()) {
64             arrayBuilder.append(doc);
65         }
66         return arrayBuilder.arr();
67     }
68 };
69 
70 // Tests that documents will ferry from the fetch logic to the insert logic successfully.
TEST_F(MigrationDestinationManagerTest,CloneDocumentsFromDonorWorksCorrectly)71 TEST_F(MigrationDestinationManagerTest, CloneDocumentsFromDonorWorksCorrectly) {
72     bool ranOnce = false;
73 
74     auto fetchBatchFn = [&](OperationContext* opCtx) {
75         BSONObjBuilder fetchBatchResultBuilder;
76 
77         if (ranOnce) {
78             fetchBatchResultBuilder.append("objects", BSONObj());
79         } else {
80             ranOnce = true;
81             fetchBatchResultBuilder.append("objects", createDocumentsToCloneArray());
82         }
83 
84         return fetchBatchResultBuilder.obj();
85     };
86 
87     std::vector<BSONObj> resultDocs;
88 
89     auto insertBatchFn = [&](OperationContext* opCtx, BSONObj docs) {
90         for (auto&& docToClone : docs) {
91             resultDocs.push_back(docToClone.Obj().getOwned());
92         }
93     };
94 
95     MigrationDestinationManager::cloneDocumentsFromDonor(
96         operationContext(), insertBatchFn, fetchBatchFn);
97 
98     std::vector<BSONObj> originalDocs = createDocumentsToClone();
99 
100     ASSERT_EQ(originalDocs.size(), resultDocs.size());
101 
102     for (auto originalDocsIt = originalDocs.begin(), resultDocsIt = resultDocs.begin();
103          originalDocsIt != originalDocs.end() && resultDocsIt != resultDocs.end();
104          ++originalDocsIt, ++resultDocsIt) {
105         ASSERT_BSONOBJ_EQ(*originalDocsIt, *resultDocsIt);
106     }
107 }
108 
109 // Tests that an exception in the fetch logic will successfully throw an exception on the main
110 // thread.
TEST_F(MigrationDestinationManagerTest,CloneDocumentsThrowsFetchErrors)111 TEST_F(MigrationDestinationManagerTest, CloneDocumentsThrowsFetchErrors) {
112     bool ranOnce = false;
113 
114     auto fetchBatchFn = [&](OperationContext* opCtx) {
115         BSONObjBuilder fetchBatchResultBuilder;
116 
117         if (ranOnce) {
118             uasserted(ErrorCodes::NetworkTimeout, "network error");
119         }
120 
121         ranOnce = true;
122         fetchBatchResultBuilder.append("objects", createDocumentsToCloneArray());
123 
124         return fetchBatchResultBuilder.obj();
125     };
126 
127     auto insertBatchFn = [&](OperationContext* opCtx, BSONObj docs) {};
128 
129     ASSERT_THROWS_CODE_AND_WHAT(MigrationDestinationManager::cloneDocumentsFromDonor(
130                                     operationContext(), insertBatchFn, fetchBatchFn),
131                                 DBException,
132                                 ErrorCodes::NetworkTimeout,
133                                 "network error");
134 }
135 
136 // Tests that an exception in the insertion logic will successfully throw an exception on the
137 // main thread.
TEST_F(MigrationDestinationManagerTest,CloneDocumentsCatchesInsertErrors)138 TEST_F(MigrationDestinationManagerTest, CloneDocumentsCatchesInsertErrors) {
139     auto fetchBatchFn = [&](OperationContext* opCtx) {
140         BSONObjBuilder fetchBatchResultBuilder;
141         fetchBatchResultBuilder.append("objects", createDocumentsToCloneArray());
142         return fetchBatchResultBuilder.obj();
143     };
144 
145     auto insertBatchFn = [&](OperationContext* opCtx, BSONObj docs) {
146         uasserted(ErrorCodes::FailedToParse, "insertion error");
147     };
148 
149     // Since the error is thrown on another thread, the message becomes "operation was interrupted"
150     // on the main thread.
151 
152     ASSERT_THROWS_CODE_AND_WHAT(MigrationDestinationManager::cloneDocumentsFromDonor(
153                                     operationContext(), insertBatchFn, fetchBatchFn),
154                                 DBException,
155                                 ErrorCodes::FailedToParse,
156                                 "operation was interrupted");
157 
158     ASSERT_EQ(operationContext()->getKillStatus(), ErrorCodes::FailedToParse);
159 }
160 
161 }  // namespace
162 }  // namespace mongo
163