summaryrefslogtreecommitdiff
path: root/jstests/concurrency/fsm_workloads/random_DDL_CRUD_operations.js
blob: 9f31c2f494cd0c5e9fb02e35d630a83dac62925a (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
173
174
175
176
177
178
'use strict';

/**
 * Performs a series of CRUD operations while DDL commands are running in the background
 * and verifies guarantees are not broken.
 *
 * @tags: [
 *   requires_sharding,
 *   # TODO (SERVER-54881): ensure the new DDL paths work with balancer, autosplit
 *   # and causal consistency.
 *   assumes_balancer_off,
 *   assumes_autosplit_off,
 *   does_not_support_causal_consistency,
 *   # TODO (SERVER-54881): ensure the new DDL paths work with add/remove shards
 *   does_not_support_add_remove_shards,
 *   # TODO (SERVER-54905): ensure all DDL are resilient.
 *   does_not_support_stepdowns,
 *   # Can be removed once PM-1965-Milestone-1 is completed.
 *   does_not_support_transactions
 *  ]
 */

var $config = (function() {
    function threadCollectionName(prefix, tid) {
        return prefix + tid;
    }

    /**
     * Used for mutual exclusion. Uses a collection to ensure atomicity on the read and update
     * operation.
     */
    function mutexLock(db, collName, tid) {
        assertAlways.soon(() => {
            let doc = db[collName].findAndModify({query: {tid: tid}, update: {$set: {mutex: 1}}});
            return doc.mutex === 0;
        });
    }

    function mutexUnlock(db, collName, tid) {
        db[collName].update({tid: tid}, {$set: {mutex: 0}});
    }

    let data = {numChunks: 20, documentsPerChunk: 5, CRUDMutex: 'CRUDMutex'};

    let states = {
        init: function(db, collName, connCache) {
            this.collName = threadCollectionName(collName, this.tid);
        },

        create: function(db, collName, connCache) {
            let tid = this.tid;
            // Pick a tid at random until we pick one that doesn't target this thread's collection.
            while (tid === this.tid)
                tid = Random.randInt(this.threadCount);

            const targetThreadColl = threadCollectionName(collName, tid);
            const coll = db[threadCollectionName(collName, tid)];
            const fullNs = coll.getFullName();
            jsTestLog('create tid:' + tid + ' currentTid: ' + this.tid);
            assertAlways.commandWorked(
                db.adminCommand({shardCollection: fullNs, key: {_id: 1}, unique: false}));
        },
        drop: function(db, collName, connCache) {
            let tid = this.tid;
            // Pick a tid at random until we pick one that doesn't target this thread's collection.
            while (tid === this.tid)
                tid = Random.randInt(this.threadCount);

            jsTestLog('drop tid:' + tid + ' currentTid: ' + this.tid);
            mutexLock(db, data.CRUDMutex, tid);
            assertAlways.eq(db[threadCollectionName(collName, tid)].drop(), true);
            mutexUnlock(db, data.CRUDMutex, tid);
        },
        rename: function(db, collName, connCache) {
            let tid = this.tid;
            // Pick a tid at random until we pick one that doesn't target this thread's collection.
            while (tid === this.tid)
                tid = Random.randInt(this.threadCount);
            const srcCollName = threadCollectionName(collName, tid);
            const srcColl = db[srcCollName];
            const numInitialDocs = srcColl.countDocuments({});
            // Rename collection
            const destCollName = threadCollectionName(collName, new Date().getTime());
            try {
                jsTestLog('rename tid:' + tid + ' currentTid: ' + this.tid);
                assertAlways.commandWorked(srcColl.renameCollection(destCollName));
            } catch (e) {
                if (e.code && e.code === ErrorCodes.NamespaceNotFound) {
                    // It is fine for a rename operation to throw NamespaceNotFound BEFORE starting
                    // (e.g. if the collection was previously dropped). Checking the changelog to
                    // assert that no such exception was thrown AFTER a rename started.
                    const dbName = db.getName();
                    let config = db.getSiblingDB('config');
                    let countRenames = config.changelog
                                           .find({
                                               what: 'renameCollection.start',
                                               details: {
                                                   source: dbName + srcCollName,
                                                   destination: dbName + destCollName
                                               }
                                           })
                                           .itcount();
                    assert.eq(0,
                              countRenames,
                              'NamespaceNotFound exception thrown during rename from ' +
                                  srcCollName + ' to ' + destCollName);
                    return;
                }
                throw e;
            }
        },
        CRUD: function(db, collName, connCache) {
            let tid = this.tid;
            // Pick a tid at random until we pick one that doesn't target this thread's collection.
            while (tid === this.tid)
                tid = Random.randInt(this.threadCount);

            const coll = db[threadCollectionName(collName, tid)];
            const fullNs = coll.getFullName();

            const generation = new Date().getTime();
            // Insert Data
            const numDocs = data.documentsPerChunk * data.numChunks;

            let insertBulkOp = coll.initializeUnorderedBulkOp();
            for (let i = 0; i < numDocs; ++i) {
                insertBulkOp.insert({generation: generation, count: i, tid: tid});
            }

            mutexLock(db, data.CRUDMutex, tid);
            jsTestLog('CRUD - Insert tid: ' + tid + ' currentTid: ' + this.tid);
            // Check if insert succeeded
            assertAlways.commandWorked(insertBulkOp.execute());
            let currentDocs = coll.countDocuments({generation: generation});
            // Check guarantees IF NO CONCURRENT DROP is running.
            // If a concurrent rename came in, then either the full operation succeded (meaning
            // there will be 0 documents left) or the insert came in first.
            assertAlways(currentDocs === numDocs || currentDocs === 0);

            jsTestLog('CRUD - Update tid: ' + tid + ' currentTid: ' + this.tid);
            assertAlways.commandWorked(
                coll.update({generation: generation}, {$set: {updated: true}}, {multi: true}));

            // Delete Data
            jsTestLog('CRUD - Remove tid: ' + tid + ' currentTid: ' + this.tid);
            // Check if delete succeeded
            coll.remove({generation: generation}, {multi: true});
            // Check guarantees IF NO CONCURRENT DROP is running.
            assertAlways.eq(coll.countDocuments({generation: generation}), 0);
            mutexUnlock(db, data.CRUDMutex, tid);
        }
    };

    let setup = function(db, collName, connCache) {
        for (let tid = 0; tid < this.threadCount; ++tid) {
            db[data.CRUDMutex].insert({tid: tid, mutex: 0});
        }
    };

    let transitions = {
        init: {create: 1.0},
        create: {create: 0.01, CRUD: 0.33, drop: 0.33, rename: 0.33},
        CRUD: {create: 0.33, CRUD: 0.01, drop: 0.33, rename: 0.33},
        drop: {create: 0.33, CRUD: 0.33, drop: 0.01, rename: 0.33},
        rename: {create: 0.33, CRUD: 0.33, drop: 0.33, rename: 0.01}
    };

    return {
        threadCount: 5,
        iterations: 50,
        startState: 'init',
        states: states,
        transitions: transitions,
        data: data,
        setup: setup,
        passConnectionCache: true
    };
})();