summaryrefslogtreecommitdiff
path: root/jstests/replsets/rollback_views.js
blob: 3a030b7e916fdb618796c5cffb4d3deee2813836 (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
/*
 * Basic test of successful replica set rollback for system.views creation.
 *
 * This test sets up a 3 node set, data-bearing nodes A and B and an arbiter.
 *
 * 1. A is elected PRIMARY and inserts into "test1.coll", which is propagated to B.
 * 2. A is isolated from the rest of the set and B is elected PRIMARY.
 * 3. B creates views "test1.x" and "test2.y" (creating test[12].system.views)
 *    and collection "test3.z", which will later be undone during rollback.
 * 4. B is then isolated and A regains its connection to the arbiter.
 * 5. A inserts a document into collection "test1.x", and creates views "test2.y" and "test3.z"
 *     which B will replicate after rollback.
 * 6. B rejoins the set and goes through the rollback/recovery process.
 * 7. The contents of A and B are compared to ensure the rollback results in consistent nodes,
 *    and have the expected collections and views..
 */
load("jstests/replsets/rslib.js");

(function() {
"use strict";

// Run a command, return the result if it worked, or assert with a message otherwise.
let checkedRunCommand = (db, cmd) =>
    ((res, msg) => ((assert.commandWorked(res, msg), res)))(db.runCommand(cmd), tojson(cmd));

// Like db.getCollectionNames, but allows a filter.
let getCollectionNames = (db, filter) => checkedRunCommand(db, {listCollections: 1, filter})
                                             .cursor.firstBatch.map((entry) => entry.name)
                                             .sort();

// Function that checks that all array elements are equal, and returns the unique element.
let checkEqual = (array, what) =>
    array.reduce((x, y) => assert.eq(x, y, "nodes don't have matching " + what) || x);

// Helper function for verifying database contents at the end of the test.
let checkFinalResults = (dbs, expectedColls, expectedViews) => ({
    dbname: checkEqual(dbs, "names"),
    colls: checkEqual(
        dbs.map((db) => getCollectionNames(db, {type: "collection"})).concat([expectedColls]),
        "colls"),
    views: checkEqual(
        dbs.map((db) => getCollectionNames(db, {type: "view"})).concat([expectedViews]), "views"),
    md5: checkEqual(dbs.map((db) => checkedRunCommand(db, {dbHash: 1}).md5), "hashes")
});

let name = "rollback_views.js";
let replTest = new ReplSetTest({name: name, nodes: 3, useBridge: true});
let nodes = replTest.nodeList();

let conns = replTest.startSet();
replTest.initiate({
    "_id": name,
    "members": [
        {"_id": 0, "host": nodes[0], priority: 3},
        {"_id": 1, "host": nodes[1]},
        {"_id": 2, "host": nodes[2], arbiterOnly: true}
    ]
});

// Make sure we have a primary and that that primary is node A.
replTest.waitForState(replTest.nodes[0], ReplSetTest.State.PRIMARY);

let nodeA = conns[0];
let nodeB = conns[1];
let arbiter = conns[2];

// The default WC is majority and stopServerReplication will prevent satisfying any majority writes.
assert.commandWorked(nodeA.adminCommand(
    {setDefaultRWConcern: 1, defaultWriteConcern: {w: 1}, writeConcern: {w: "majority"}}));

let a1 = nodeA.getDB("test1");
let b1 = nodeB.getDB("test1");

// Initial data for both nodes.
assert.commandWorked(a1.coll.insert([{_id: 1, x: 1}, {_id: 2, x: 2}]));

// Wait for initial replication.
replTest.awaitReplication();

// Isolate A and wait for B to become primary.
nodeA.disconnect(nodeB);
nodeA.disconnect(arbiter);
assert.soon(() => replTest.getPrimary() == nodeB, "node B did not become primary as expected");

// Do operations on B and B alone, these will be rolled back.
// For the collection creation, first create a view with the same name, stressing rollback.
assert.commandWorked(b1.coll.remove({x: 2}));
assert.commandWorked(b1.createView("x", "coll", [{$match: {x: 1}}]));
let b2 = b1.getSiblingDB("test2");
assert.commandWorked(b2.coll.insert([{_id: 1, y: 1}, {_id: 2, y: 2}]));
assert.commandWorked(b2.createView("y", "coll", [{$match: {y: 2}}]));
let b3 = b1.getSiblingDB("test3");
assert.commandWorked(b3.createView("z", "coll", []));
assert.commandWorked(b3.adminCommand(
    {applyOps: [{op: "d", ns: b3.getName() + ".system.views", o: {_id: b3.getName() + ".z"}}]}));
assert.commandWorked(b3.z.insert([{z: 1}, {z: 2}, {z: 3}]));
assert.commandWorked(b3.z.remove({z: 1}));

// Isolate B, bring A back into contact with the arbiter, then wait for A to become primary.
// Insert new data into A, so that B will need to rollback when it reconnects to A.
nodeB.disconnect(arbiter);
replTest.awaitNoPrimary();
nodeA.reconnect(arbiter);
assert.soon(() => replTest.getPrimary() == nodeA, "nodeA did not become primary as expected");

// A is now primary and will perform writes that must be copied by B after rollback.
assert.eq(a1.coll.find().itcount(), 2, "expected two documents in test1.coll");
assert.commandWorked(a1.x.insert({_id: 3, x: "string in test1.x"}));
let a2 = a1.getSiblingDB("test2");
assert.commandWorked(a2.createView("y", "coll", [{$match: {y: 2}}]));
assert.commandWorked(a2.coll.insert([{_id: 1, y: 1}, {_id: 2, y: 2}]));
let a3 = a1.getSiblingDB("test3");
assert.commandWorked(a3.coll.insert([{z: 1}, {z: 2}, {z: 3}]));
assert.commandWorked(a3.createView("z", "coll", [{$match: {z: 3}}]));

// A is collections: test1.{coll,x}, test2.{coll,system.views}, test3.{coll,system.views}
//            views: test2.y, test3.z
// B is collections: test1.{coll,system.views}, test2.{coll,systems}, test3.{z,system.views}
//            views: test1.x, test2.y
//
// Put B back in contact with A and arbiter. A is primary, so B will rollback and catch up.
nodeB.reconnect(arbiter);
nodeA.reconnect(nodeB);

awaitOpTime(nodeB, nodeA);

// Await steady state and ensure the two nodes have the same contents.
replTest.awaitSecondaryNodes();
replTest.awaitReplication();

// Check both nodes agree with each other and with the expected set of views and collections.
print("All done, check that both nodes have the expected collections, views and md5.");
printjson(checkFinalResults([a1, b1], ["coll", "x"], []));
printjson(checkFinalResults([a2, b2], ["coll", "system.views"], ["y"]));
printjson(checkFinalResults([a3, b3], ["coll", "system.views"], ["z"]));

// Verify data consistency between nodes.
replTest.checkReplicatedDataHashes();
replTest.checkOplogs();

replTest.stopSet();
}());