summaryrefslogtreecommitdiff
path: root/jstests/noPassthrough/ttl_invalid_expire_after_secs.js
blob: a103fa3bcc69247555499088077c7a2d51f571b9 (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
/**
 * Tests TTL indexes with invalid values for 'expireAfterSeconds'.
 *
 * @tags: [
 *     requires_replication,
 * ]
 */
(function() {
'use strict';

load("jstests/libs/fail_point_util.js");
load('jstests/noPassthrough/libs/index_build.js');

function test(expireAfterSecondsVal) {
    jsTestLog("Testing expireAfterSeconds = " + expireAfterSecondsVal);

    const rst = new ReplSetTest({
        nodes: [{}, {rsConfig: {votes: 0, priority: 0}}],
        nodeOptions: {setParameter: {ttlMonitorSleepSecs: 5}},
        // Sync from primary only so that we have a well-defined node to check listIndexes behavior.
        settings: {chainingAllowed: false},
    });
    rst.startSet();
    rst.initiate();

    let primary = rst.getPrimary();
    const db = primary.getDB('test');
    const coll = db.t;

    // Insert a document before creating the index. Index builds on empty collections skip the
    // collection scan phase, which we look for using checkLog below.
    assert.commandWorked(coll.insert({_id: 0, t: ISODate()}));

    // The test cases here revolve around having a TTL index in the catalog with an invalid
    // 'expireAfterSeconds'. The current createIndexes behavior will reject index creation for
    // invalid values of expireAfterSeconds, so we use a failpoint to disable that checking to
    // simulate a value leftover from very old MongoDB versions.
    const fp = configureFailPoint(primary, 'skipTTLIndexValidationOnCreateIndex');
    const fp2 = configureFailPoint(primary,
                                   'skipTTLIndexInvalidExpireAfterSecondsValidationForCreateIndex');
    try {
        assert.commandWorked(coll.createIndex({t: 1}, {expireAfterSeconds: expireAfterSecondsVal}));
    } finally {
        fp.off();
        fp2.off();
    }

    // Log the contents of the catalog for debugging purposes in case of failure.
    let catalogContents = coll.aggregate([{$listCatalog: {}}]).toArray();
    jsTestLog("Catalog contents: " + tojson(catalogContents));

    // Wait for "Skipping TTL job due to invalid index spec" log message.
    checkLog.containsJson(primary, 6909100, {ns: coll.getFullName()});

    // TTL index should be replicated to the secondary with an invalid 'expireAfterSeconds'.
    const secondary = rst.getSecondary();
    checkLog.containsJson(secondary, 20384, {
        namespace: coll.getFullName(),
        properties: (spec) => {
            jsTestLog('TTL index on secondary: ' + tojson(spec));
            // NaN does not equal NaN, so we have to use the isNaN function for this check.
            if (isNaN(expireAfterSecondsVal))
                return isNaN(spec.expireAfterSeconds);
            else
                return spec.expireAfterSeconds == expireAfterSecondsVal;
        }
    });

    assert.eq(coll.countDocuments({}),
              1,
              'ttl index with invalid expireAfterSeconds should not remove any documents.');

    // Confirm that TTL index is replicated with a non-zero 'expireAfterSeconds' during initial
    // sync.
    const newNode = rst.add({rsConfig: {votes: 0, priority: 0}});
    rst.reInitiate();
    rst.waitForState(newNode, ReplSetTest.State.SECONDARY);
    rst.awaitReplication();
    let newNodeTestDB = newNode.getDB(db.getName());
    let newNodeColl = newNodeTestDB.getCollection(coll.getName());
    const newNodeIndexes = IndexBuildTest.assertIndexes(newNodeColl, 2, ['_id_', 't_1']);
    const newNodeSpec = newNodeIndexes.t_1;
    jsTestLog('TTL index on initial sync node: ' + tojson(newNodeSpec));
    assert(newNodeSpec.hasOwnProperty('expireAfterSeconds'),
           'Index was not replicated as a TTL index during initial sync.');
    assert.eq(
        newNodeSpec.expireAfterSeconds,
        2147483647,  // This is the "disabled" value for expireAfterSeconds
        expireAfterSecondsVal +
            ' expireAferSeconds was replicated as something other than disabled during initial sync.');

    // Check that listIndexes on the primary logged a "Fixing expire field from TTL index spec"
    // message during the invalid 'expireAfterSeconds' conversion.
    checkLog.containsJson(primary, 6835900, {namespace: coll.getFullName()});

    // Confirm that a node with an existing TTL index with an invalid 'expireAfterSeconds' will
    // convert the duration on the TTL index from the invalid value to a large positive value when
    // it becomes the primary node. When stepping down the primary, we use 'force' because there's
    // no other electable node.  Subsequently, we wait for the stepped down node to become primary
    // again.  To confirm that the TTL index has been fixed, we check the oplog for a collMod
    // operation on the TTL index that changes the `expireAfterSeconds` field from the invalid value
    // to a large positive value.
    assert.commandWorked(primary.adminCommand({replSetStepDown: 5, force: true}));
    primary = rst.waitForPrimary();

    // Log the contents of the catalog for debugging purposes in case of failure.
    let newPrimaryColl = primary.getDB(db.getName()).getCollection(coll.getName());
    const newPrimaryCatalogContents = newPrimaryColl.aggregate([{$listCatalog: {}}]).toArray();
    jsTestLog("Catalog contents on new primary: " + tojson(newPrimaryCatalogContents));

    const collModOplogEntries =
        rst.findOplog(primary,
                      {
                          op: 'c',
                          ns: newPrimaryColl.getDB().getCollection('$cmd').getFullName(),
                          'o.collMod': coll.getName(),
                          'o.index.name': 't_1',
                          'o.index.expireAfterSeconds': newNodeSpec.expireAfterSeconds
                      },
                      /*limit=*/ 1)
            .toArray();
    assert.eq(collModOplogEntries.length,
              1,
              'TTL index with ' + expireAfterSecondsVal +
                  ' expireAfterSeconds was not fixed using collMod during step-up: ' +
                  tojson(rst.findOplog(primary, {op: {$ne: 'n'}}, /*limit=*/ 10).toArray()));

    rst.stopSet();
}

test(NaN);
const maxDouble = 1.7976931348623157e+308;
test(maxDouble);
test(-maxDouble);
})();