/**
 * Tests that initial sync will continue if the syncing node is removed during syncing.
 * This behavior is desired because transient DNS failures can cause the node to falsely believe
 * that it is removed.
 *
 * @tags: [
 * ]
 */
(function() {
"use strict";

load("jstests/libs/fail_point_util.js");

const testName = TestData.testName;
const rst = new ReplSetTest({name: testName, nodes: [{}]});
const nodes = rst.startSet();
rst.initiate();

const primary = rst.getPrimary();
const primaryDb = primary.getDB("test");
const initialSyncSource = primary;

// Add some data to be cloned.
assert.commandWorked(primaryDb.test.insert([{a: 1}, {b: 2}, {c: 3}]));
rst.awaitReplication();

jsTest.log("Adding the initial sync destination node to the replica set");
const initialSyncNode = rst.add({
    rsConfig: {priority: 0, votes: 0},
    setParameter: {
        'failpoint.initialSyncHangBeforeCopyingDatabases': tojson({mode: 'alwaysOn'}),
        'numInitialSyncAttempts': 1,
        'logComponentVerbosity': tojson({replication: {verbosity: 1}}),
        'failpoint.forceSyncSourceCandidate':
            tojson({mode: 'alwaysOn', data: {hostAndPort: initialSyncSource.host}})
    }
});
rst.reInitiate();
rst.waitForState(initialSyncNode, ReplSetTest.State.STARTUP_2);

// Set us up to hang before finish so we can check status.
const beforeFinishFailPoint = configureFailPoint(initialSyncNode, "initialSyncHangBeforeFinish");

jsTestLog("Waiting to reach cloning phase of initial sync");
assert.commandWorked(initialSyncNode.adminCommand({
    waitForFailPoint: "initialSyncHangBeforeCopyingDatabases",
    timesEntered: 1,
    maxTimeMS: kDefaultWaitForFailPointTimeout
}));
jsTestLog("Removing initial sync node");
// Avoid closing the connection when the node transitions to REMOVED.
assert.commandWorked(initialSyncNode.adminCommand({hello: 1, hangUpOnStepDown: false}));

let config = rst.getReplSetConfigFromNode();
const origHost = config.members[1].host;
// This host will never resolve.
config.members[1].host = "always.invalid:27017";
config.version++;
assert.commandWorked(primary.adminCommand({replSetReconfig: config, force: 1}));
jsTestLog("Waiting for initial sync node to realize it is removed.");
assert.soonNoExcept(function() {
    assert.commandFailedWithCode(initialSyncNode.adminCommand({replSetGetStatus: 1}),
                                 ErrorCodes.InvalidReplicaSetConfig);
    return true;
});

// Release the initial failpoint.
assert.commandWorked(initialSyncNode.adminCommand(
    {configureFailPoint: "initialSyncHangBeforeCopyingDatabases", mode: "off"}));

jsTestLog("Waiting for initial sync to complete.");
beforeFinishFailPoint.wait();

jsTestLog("Initial sync complete.  Re-adding node to check initial sync status.");
config.members[1].host = origHost;
config.version++;
assert.commandWorked(primary.adminCommand({replSetReconfig: config, force: 1}));
rst.waitForState(initialSyncNode, ReplSetTest.State.STARTUP_2);
const res = assert.commandWorked(initialSyncNode.adminCommand({replSetGetStatus: 1}));
printjson(res.initialSyncStatus);
assert.eq(res.initialSyncStatus.failedInitialSyncAttempts, 0);

jsTestLog("Re-removing node.");
config.members[1].host = "always.invalid:27017";
config.version++;
assert.commandWorked(primary.adminCommand({replSetReconfig: config, force: 1}));
jsTestLog("Waiting for initial sync node to realize it is removed again.");
assert.soonNoExcept(function() {
    assert.commandFailedWithCode(initialSyncNode.adminCommand({replSetGetStatus: 1}),
                                 ErrorCodes.InvalidReplicaSetConfig);
    return true;
});

// Add some more data that must be cloned during steady-state replication.
assert.commandWorked(primaryDb.test.insert([{d: 4}, {e: 5}, {f: 6}]));
beforeFinishFailPoint.off();

// Wait until initial sync completion routine is finished.
checkLog.containsJson(initialSyncNode, 4853000);

// Make sure the node is still REMOVED.
assert.commandFailedWithCode(initialSyncNode.adminCommand({replSetGetStatus: 1}),
                             ErrorCodes.InvalidReplicaSetConfig);

jsTestLog("Re-adding initial sync node a final time");
config.members[1].host = origHost;
config.version++;
assert.commandWorked(primary.adminCommand({replSetReconfig: config, force: 1}));
rst.waitForState(initialSyncNode, ReplSetTest.State.SECONDARY);

rst.stopSet();
})();
