/** * Confirms that an index build is aborted after step-up by a new primary when there are key * generation errors. This test orchestrates a scenario such that a secondary detects (and * ignores) an indexing error. After step-up, the node retries indexing the skipped record before * completing. The expected result is that the node, now primary, aborts the index build for the * entire replica set. * * @tags: [ * requires_replication, * ] */ (function() { "use strict"; load('jstests/noPassthrough/libs/index_build.js'); const rst = new ReplSetTest({ nodes: [ {}, {}, ], }); const nodes = rst.startSet(); rst.initiate(); const primary = rst.getPrimary(); const testDB = primary.getDB('test'); const coll = testDB.getCollection('test'); if (!IndexBuildTest.supportsTwoPhaseIndexBuild(primary)) { jsTestLog('Two phase index builds not enabled, skipping test.'); rst.stopSet(); return; } // Insert a document that cannot be indexed because it causes a CannotIndexParallelArrays error // code. const badDoc = { _id: 0, a: [0, 1], b: [2, 3] }; assert.commandWorked(coll.insert(badDoc)); // Start an index build on primary and secondary, but prevent the primary from scanning the // collection. Do not stop the secondary; intentionally let it scan the invalid document, which we // will resolve later. // We are using this fail point to pause the index build before it starts the collection scan. // This is important for this test because we are mutating the collection state before the index // builder is able to observe the invalid document. // By comparison, IndexBuildTest.pauseIndexBuilds() stalls the index build in the middle of the // collection scan. assert.commandWorked( testDB.adminCommand({configureFailPoint: 'hangAfterInitializingIndexBuild', mode: 'alwaysOn'})); const createIdx = IndexBuildTest.startIndexBuild(primary, coll.getFullName(), {a: 1, b: 1}); // Wait for the index build to start on the secondary. const secondary = rst.getSecondary(); const secondaryDB = secondary.getDB(testDB.getName()); const secondaryColl = secondaryDB.getCollection(coll.getName()); IndexBuildTest.waitForIndexBuildToStart(secondaryDB); IndexBuildTest.assertIndexes(secondaryColl, 2, ["_id_"], ["a_1_b_1"], {includeBuildUUIDs: true}); // Step down the primary. const stepDown = startParallelShell(() => { assert.commandWorked(db.adminCommand({"replSetStepDown": 60, "force": true})); }, primary.port); // Expect a failed createIndex command invocation in the parallel shell due to stepdown even though // the index build will continue in the background. const exitCode = createIdx({checkExitSuccess: false}); assert.neq(0, exitCode, 'expected shell to exit abnormally due to index build being terminated'); checkLog.containsJson(primary, 20441); // Wait for stepdown to complete. stepDown(); // Unblock the index build on the old primary. assert.commandWorked( testDB.adminCommand({configureFailPoint: 'hangAfterInitializingIndexBuild', mode: 'off'})); const newPrimary = rst.getPrimary(); const newPrimaryDB = newPrimary.getDB('test'); const newPrimaryColl = newPrimaryDB.getCollection('test'); // Ensure the old primary doesn't take over again. assert.neq(primary.port, newPrimary.port); // The index should not be present on the old primary after processing the abortIndexBuild oplog // entry from the new primary. jsTestLog("waiting for index build to stop on old primary"); IndexBuildTest.waitForIndexBuildToStop(testDB); IndexBuildTest.assertIndexes(coll, 1, ['_id_']); // Check that index was not built on the new primary. jsTestLog("waiting for index build to stop on new primary"); IndexBuildTest.waitForIndexBuildToStop(newPrimaryDB); IndexBuildTest.assertIndexes(newPrimaryColl, 1, ['_id_']); rst.stopSet(); })();