// Test copyDatabase command with various combinations of authed/unauthed and single node/replica
// set source and dest.

TestData.authMechanism = "SCRAM-SHA-1";                        // SERVER-11428
DB.prototype._defaultAuthenticationMechanism = "SCRAM-SHA-1";  // SERVER-11428

var baseName = "jstests_clone_copyauth";

/*
 * Helper to spawn a replica set, sharded cluster, or a single mongod and hide it all behind the
 * same interface.
 *
 * Arguments:
 *
 * clusterType - type of cluster to start.  Options are "sharded", "repl", or "single".
 * startWithAuth - whether to start the cluster with authentication.
 *
 * Member variables:
 *
 * conn - a connection to the node used to access this cluster, whether it's the mongod, a primary
 * mongod in a replica set, or a mongos.
 * connString - the full connection string used to connect to this cluster.  For a replica set this
 * is the full connection string including the replica set name.
 *
 * Member functions:
 *
 * stop() - stop and cleanup whatever nodes the helper spawned when it was created.
 */
function ClusterSpawnHelper(clusterType, startWithAuth) {
    if (clusterType === "sharded") {
        var shardingTestConfig = {
            name: baseName + "_source",
            mongos: 1,
            shards: 1,
            config: 1
        };
        if (startWithAuth) {
            shardingTestConfig.auth = "";
            shardingTestConfig.keyFile = "jstests/libs/key1";
        }
        var shardingTest = new ShardingTest(shardingTestConfig);
        this.conn = shardingTest.s;
        this.connString = this.conn.host;
    } else if (clusterType === "repl") {
        var replSetTestConfig = {
            name: baseName + "_source",
            nodes: 3,
            nodeOptions: {}
        };
        if (startWithAuth) {
            replSetTestConfig.nodeOptions.auth = "";
            replSetTestConfig.nodeOptions.keyFile = "jstests/libs/key1";
        }
        var replSetTest = new ReplSetTest(replSetTestConfig);
        replSetTest.startSet();
        replSetTest.initiate();
        if (startWithAuth) {
            authutil.asCluster(replSetTest.nodes,
                               replSetTestConfig.nodeOptions.keyFile,
                               function() {
                                   replSetTest.awaitReplication();
                               });
        } else {
            replSetTest.awaitReplication();
        }
        this.conn = replSetTest.getPrimary();
        this.connString = replSetTest.getURL();
    } else {
        var singleNodeConfig = {};
        if (startWithAuth) {
            singleNodeConfig.auth = "";
        }
        this.conn = MongoRunner.runMongod(singleNodeConfig);
        this.connString = this.conn.host;
    }

    this.stop = function() {
        if (clusterType === "sharded") {
            shardingTest.stop();
        } else if (clusterType === "repl") {
            replSetTest.stopSet();
        } else {
            MongoRunner.stopMongod(this.conn.port);
        }
    };
}

/*
 * Helper to test the running the "copydb" command between various kinds of clusters and various
 * combinations of authentication on the source and target.
 *
 * @param {Object} configObj
 *
 *   {
 *     sourceClusterType {string}: Type of cluster to use as the source of the copy.  Options are
 *         "single", "repl", "sharded".
 *     isSourceUsingAuth {bool}: Whether to use auth in the source cluster for the copy.
 *     targetClusterType {string}: Type of cluster to use as the target of the copy.  Options are
 *         "single", "repl", "sharded".
 *     isTargetUsingAuth {bool}: Whether to use auth in the target cluster for the copy.
 *   }
 */
function copydbBetweenClustersTest(configObj) {
    // First sanity check the arguments in our configObj
    var requiredKeys =
        ['sourceClusterType', 'isSourceUsingAuth', 'targetClusterType', 'isTargetUsingAuth'];

    var i;
    for (i = 0; i < requiredKeys.length; i++) {
        assert(configObj.hasOwnProperty(requiredKeys[i]),
               "Missing required key: " + requiredKeys[i] + " in config object");
    }

    // 1. Get a connection to the source database, insert data and setup auth if applicable
    source = new ClusterSpawnHelper(configObj.sourceClusterType, configObj.isSourceUsingAuth);

    if (configObj.isSourceUsingAuth) {
        // Create a super user so we can create a regular user and not be locked out afterwards
        source.conn.getDB("admin")
            .createUser({user: "sourceSuperUser", pwd: "sourceSuperUser", roles: ["root"]});
        source.conn.getDB("admin").auth("sourceSuperUser", "sourceSuperUser");

        source.conn.getDB(baseName)[baseName].save({i: 1});
        assert.eq(1, source.conn.getDB(baseName)[baseName].count());
        assert.eq(1, source.conn.getDB(baseName)[baseName].findOne().i);

        // Insert a document and create a regular user that we will use for the target
        // authenticating with the source
        source.conn.getDB(baseName).createUser({user: "foo", pwd: "bar", roles: ["dbOwner"]});

        source.conn.getDB("admin").logout();
        assert.throws(function() {
            source.conn.getDB(baseName)[baseName].findOne();
        });
    } else {
        source.conn.getDB(baseName)[baseName].save({i: 1});
        assert.eq(1, source.conn.getDB(baseName)[baseName].count());
        assert.eq(1, source.conn.getDB(baseName)[baseName].findOne().i);
    }

    // 2. Get a connection to the target database, and set up auth if necessary
    target = new ClusterSpawnHelper(configObj.targetClusterType, configObj.isTargetUsingAuth);

    if (configObj.isTargetUsingAuth) {
        target.conn.getDB("admin")
            .createUser({user: "targetSuperUser", pwd: "targetSuperUser", roles: ["root"]});
        assert.throws(function() {
            target.conn.getDB(baseName)[baseName].findOne();
        });
        target.conn.getDB("admin").auth("targetSuperUser", "targetSuperUser");
    }

    // 3. Run the copydb command
    target.conn.getDB(baseName).dropDatabase();
    assert.eq(0, target.conn.getDB(baseName)[baseName].count());
    if (configObj.isSourceUsingAuth) {
        // We only need to pass username and password if the target has to send authentication
        // information to the source cluster
        assert.commandWorked(target.conn.getDB(baseName).copyDatabase(
            baseName, baseName, source.connString, "foo", "bar"));
    } else {
        // We are copying from a cluster with no auth
        assert.commandWorked(
            target.conn.getDB(baseName).copyDatabase(baseName, baseName, source.connString));
    }
    assert.eq(1, target.conn.getDB(baseName)[baseName].count());
    assert.eq(1, target.conn.getDB(baseName)[baseName].findOne().i);

    // 4. Do any necessary cleanup
    source.stop();
    target.stop();
}

(function() {
    "use strict";

    var sourceClusterTypeValues = ["single", "repl", "sharded"];
    var isSourceUsingAuthValues = [true, false];
    var targetClusterTypeValues = ["single", "repl", "sharded"];
    var isTargetUsingAuthValues = [true, false];
    for (var i = 0; i < sourceClusterTypeValues.length; i++) {
        for (var j = 0; j < isSourceUsingAuthValues.length; j++) {
            for (var k = 0; k < targetClusterTypeValues.length; k++) {
                for (var l = 0; l < isTargetUsingAuthValues.length; l++) {
                    if (sourceClusterTypeValues[i] === "sharded" &&
                        targetClusterTypeValues[k] === "sharded") {
                        // SERVER-13112
                        continue;
                    }
                    if (sourceClusterTypeValues[i] === "repl" &&
                        targetClusterTypeValues[k] === "repl") {
                        // SERVER-13077
                        continue;
                    }
                    if (isSourceUsingAuthValues[j] === true &&
                        targetClusterTypeValues[k] === "sharded") {
                        // SERVER-6427
                        continue;
                    }
                    if (sourceClusterTypeValues[i] === "repl" &&
                        isSourceUsingAuthValues[j] === false &&
                        targetClusterTypeValues[k] === "sharded" &&
                        isTargetUsingAuthValues[l] == true) {
                        // SERVER-18103
                        continue;
                    }
                    var testCase = {
                        'sourceClusterType': sourceClusterTypeValues[i],
                        'isSourceUsingAuth': isSourceUsingAuthValues[j],
                        'targetClusterType': targetClusterTypeValues[k],
                        'isTargetUsingAuth': isTargetUsingAuthValues[l]
                    };
                    print("Running copydb with auth test:");
                    printjson(testCase);
                    copydbBetweenClustersTest(testCase);
                }
            }
        }
    }
}());
print(baseName + " success!");
