summaryrefslogtreecommitdiff
path: root/jstests/replsets/drop_databases_two_phase.js
blob: c3cb8ead59255196c340e520bfa1e7fb17b08d46 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
/**
 * Test to ensure that two phase drop behavior for databases on replica sets works properly.
 *
 * Uses a 3 node replica set with one arbiter to verify both phases of a 2-phase database drop:
 * the 'Collections' and 'Database' phase. Executing a 'dropDatabase' command should put that
 * database into a drop-pending state. In this state, all new collection creation requests will
 * be rejected with an error with the code ErrorCodes.DatabaseDropPending. We will exit the
 * 'Collections' phase once the last collection drop has been propagated to a majority. All
 * collections in the database will be physically dropped at this point.
 *
 * During the 'Database' phase, collection creation is still disallowed. This phase removes the
 * metadata for the database from the server and appends the 'dropDatabase' operation to the oplog.
 * Unlike the 'Collections' phase, we do not wait for the 'dropDatabase' to propagate to a majority
 * unless explicitly requested by the user with a write concern.
 */

(function() {
"use strict";

load('jstests/replsets/libs/two_phase_drops.js');  // For TwoPhaseDropCollectionTest.
load("jstests/replsets/rslib.js");
load("jstests/libs/logv2_helpers.js");
load("jstests/libs/write_concern_util.js");

// Returns a list of all collections in a given database. Use 'args' as the
// 'listCollections' command arguments.
function listCollections(database, args) {
    var args = args || {};
    var failMsg = "'listCollections' command failed";
    var res = assert.commandWorked(database.runCommand("listCollections", args), failMsg);
    return res.cursor.firstBatch;
}

// Returns a list of 'drop-pending' collections. The collection names should be of the
// format "system.drop.<optime>.<collectionName>", where 'optime' is the optime of the
// collection drop operation, encoded as a string, and 'collectionName' is the original
// collection name.
function listDropPendingCollections(database) {
    var pendingDropRegex = new RegExp("system\.drop\..*\." + collNameToDrop + "$");
    var collections = listCollections(database, {includePendingDrops: true});
    return collections.filter(c => pendingDropRegex.test(c.name));
}

// Returns a list of all collection names in a given database.
function listCollectionNames(database, args) {
    return listCollections(database, args).map(c => c.name);
}

var dbNameToDrop = 'dbToDrop';
var replTest = new ReplSetTest({nodes: [{}, {}, {arbiter: true}]});

// Initiate the replica set.
replTest.startSet();
replTest.initiate();
replTest.awaitReplication();

var primary = replTest.getPrimary();
var secondary = replTest.getSecondary();
// The default WC is majority and stopServerReplication will prevent satisfying any majority writes.
assert.commandWorked(primary.adminCommand(
    {setDefaultRWConcern: 1, defaultWriteConcern: {w: 1}, writeConcern: {w: "majority"}}));

var dbToDrop = primary.getDB(dbNameToDrop);
var collNameToDrop = "collectionToDrop";

// Create the collection that will be dropped and let it replicate.
var collToDrop = dbToDrop.getCollection(collNameToDrop);
assert.commandWorked(
    collToDrop.insert({_id: 0}, {writeConcern: {w: 2, wtimeout: replTest.kDefaultTimeoutMS}}));
assert.eq(1, collToDrop.find().itcount());

// Pause the oplog fetcher on secondary so that commit point doesn't advance, meaning that a dropped
// database on the primary will remain in 'drop-pending' state. As there isn't anything in the oplog
// buffer at this time, it is safe to pause the oplog fetcher.
jsTestLog("Pausing the oplog fetcher on the secondary node.");
stopServerReplication(secondary);

// Make sure the collection was created.
assert.contains(collNameToDrop,
                listCollectionNames(dbToDrop),
                "Collection '" + collNameToDrop + "' wasn't created properly");

/**
 * DROP DATABASE 'Collections' PHASE
 */

// Drop the collection on the primary.
var dropDatabaseFn = function() {
    var dbNameToDrop = 'dbToDrop';
    var primary = db.getMongo();
    jsTestLog('Dropping database ' + dbNameToDrop + ' on primary node ' + primary.host +
              '. This command will block because the oplog fetcher is paused on the secondary.');
    var dbToDrop = db.getSiblingDB(dbNameToDrop);
    assert.commandWorked(dbToDrop.dropDatabase());
    jsTestLog('Database ' + dbNameToDrop + ' successfully dropped on primary node ' + primary.host);
};
var dropDatabaseProcess = startParallelShell(dropDatabaseFn, primary.port);

// Check that primary has started two phase drop of the collection.
jsTestLog('Waiting for primary ' + primary.host + ' to prepare two phase drop of collection ' +
          collToDrop.getFullName());
assert.soonNoExcept(
    function() {
        return collToDrop.find().itcount() == 0;
    },
    'Primary ' + primary.host + ' failed to prepare two phase drop of collection ' +
        collToDrop.getFullName());

// 'collToDrop' is no longer visible with its original name. If 'system.drop' two phase drops
// are supported by the storage engine, check for the drop-pending namespace using
// listCollections.
const supportsDropPendingNamespaces =
    TwoPhaseDropCollectionTest.supportsDropPendingNamespaces(replTest);
if (supportsDropPendingNamespaces) {
    var dropPendingCollections = listDropPendingCollections(dbToDrop);
    assert.eq(1,
              dropPendingCollections.length,
              "Collection was not found in the 'system.drop' namespace. " +
                  "Full drop-pending collection list: " + tojson(dropPendingCollections));
    jsTestLog('Primary ' + primary.host + ' successfully started two phase drop of collection ' +
              collToDrop.getFullName());
}

// Commands that manipulate the database being dropped or perform destructive catalog operations
// should fail with the DatabaseDropPending error code while the database is in a drop-pending
// state.
assert.commandFailedWithCode(
    dbToDrop.createCollection('collectionToCreateWhileDroppingDatabase'),
    ErrorCodes.DatabaseDropPending,
    'collection creation should fail while we are in the process of dropping the database');

/**
 * DROP DATABASE 'Database' PHASE
 */

// Let the secondary apply the collection drop operation, so that the replica set commit point
// will advance, and the 'Database' phase of the database drop will complete on the primary.
jsTestLog("Restarting the oplog fetcher on the secondary node.");
restartServerReplication(secondary);

jsTestLog("Waiting for collection drop operation to replicate to all nodes.");
replTest.awaitReplication();

// Make sure the collection has been fully dropped. It should not appear as
// a normal collection or under the 'system.drop' namespace any longer. Physical collection
// drops may happen asynchronously, any time after the drop operation is committed, so we wait
// to make sure the collection is eventually dropped.
assert.soonNoExcept(function() {
    var dropPendingCollections = listDropPendingCollections(dbToDrop);
    jsTestLog('Drop pending collections: ' + tojson(dropPendingCollections));
    return dropPendingCollections.length == 0;
});

jsTestLog('Waiting for dropDatabase command on ' + primary.host + ' to complete.');
var exitCode = dropDatabaseProcess();

let db = primary.getDB(dbNameToDrop);
if (isJsonLog(db.getMongo())) {
    checkLog.contains(db.getMongo(),
                      `dropDatabase - dropping collection","attr":{"db":"${
                          dbNameToDrop}","namespace":"${dbNameToDrop}.${collNameToDrop}"`);
    checkLog.containsJson(db.getMongo(), 20336, {"db": "dbToDrop"});
} else {
    checkLog.contains(db.getMongo(), "dropping collection: " + dbNameToDrop + "." + collNameToDrop);
    checkLog.contains(db.getMongo(), "dropped 1 collection(s)");
}

assert.eq(0, exitCode, 'dropDatabase command on ' + primary.host + ' failed.');
jsTestLog('Completed dropDatabase command on ' + primary.host);

replTest.stopSet();
}());