mirror of https://github.com/mongodb/mongo
300 lines
11 KiB
JavaScript
300 lines
11 KiB
JavaScript
/**
|
|
* Helper functions that help get information or manipulate nodes in the fixture, whether it's a
|
|
* replica set, a sharded cluster, etc.
|
|
*/
|
|
import {isMongos} from "jstests/concurrency/fsm_workload_helpers/server_types.js";
|
|
import {DiscoverTopology} from "jstests/libs/discover_topology.js";
|
|
import {ReplSetTest} from "jstests/libs/replsettest.js";
|
|
|
|
export var FixtureHelpers = (function () {
|
|
function _getHostStringForReplSet(connectionToNodeInSet) {
|
|
const isMaster = assert.commandWorked(connectionToNodeInSet.getDB("test").isMaster());
|
|
assert(
|
|
isMaster.hasOwnProperty("setName"),
|
|
"Attempted to get replica set connection to a node that is not part of a replica set",
|
|
);
|
|
return isMaster.setName + "/" + isMaster.hosts.join(",");
|
|
}
|
|
|
|
/**
|
|
* Returns an array of connections to each data-bearing replica set in the fixture (not
|
|
* including the config servers).
|
|
*/
|
|
function getAllReplicas(db) {
|
|
let replicas = [];
|
|
if (isMongos(db)) {
|
|
const shardObjs = db.getSiblingDB("config").shards.find().sort({_id: 1});
|
|
replicas = shardObjs.map((shardObj) => new ReplSetTest(shardObj.host));
|
|
} else {
|
|
replicas = [new ReplSetTest(_getHostStringForReplSet(db.getMongo()))];
|
|
}
|
|
return replicas;
|
|
}
|
|
|
|
/**
|
|
* Returns the config server connection string.
|
|
*/
|
|
function getConfigServerConnString(routerConn) {
|
|
return assert.commandWorked(routerConn.adminCommand({getShardMap: 1})).map.config;
|
|
}
|
|
|
|
/**
|
|
* Uses ReplSetTest.awaitReplication() on each replica set in the fixture to wait for each node
|
|
* in each replica set in the fixture (besides the config servers) to reach the same op time.
|
|
* Asserts if the fixture is a standalone or if the shards are standalones.
|
|
*/
|
|
function awaitReplication(db) {
|
|
getAllReplicas(db).forEach((replSet) => replSet.awaitReplication());
|
|
}
|
|
|
|
/**
|
|
* Uses ReplSetTest.awaitLastOpCommitted() on each replica set in the fixture (besides the
|
|
* config servers) to wait for the last oplog entry on the respective primary to be visible in
|
|
* the committed snapshot view of the oplog on all secondaries.
|
|
*
|
|
* Asserts if the fixture is a standalone or if the shards are standalones.
|
|
*/
|
|
function awaitLastOpCommitted(db) {
|
|
getAllReplicas(db).forEach((replSet) => replSet.awaitLastOpCommitted());
|
|
}
|
|
|
|
/**
|
|
* Looks for an entry in the config database for the given collection, to check whether it's
|
|
* sharded.
|
|
*/
|
|
function isSharded(coll) {
|
|
const collEntry = coll.getDB().getSiblingDB("config").collections.findOne({_id: coll.getFullName()});
|
|
if (collEntry === null) {
|
|
return false;
|
|
}
|
|
return collEntry.unsplittable === null || !collEntry.unsplittable;
|
|
}
|
|
|
|
/**
|
|
* Looks for an entry in the sharding catalog for the given collection, to check whether it's
|
|
* unsplittable.
|
|
*/
|
|
function isUnsplittable(coll) {
|
|
const collEntry = coll.getDB().getSiblingDB("config").collections.findOne({_id: coll.getFullName()});
|
|
if (collEntry === null) {
|
|
return false;
|
|
}
|
|
return collEntry.unsplittable !== null && collEntry.unsplittable;
|
|
}
|
|
|
|
/**
|
|
* Looks for an entry in the sharding catalog for the given collection to check whether it is
|
|
* present.
|
|
*
|
|
* TODO (SERVER-86443): remove this utility once all collections are tracked.
|
|
*/
|
|
function isTracked(coll) {
|
|
return isSharded(coll) || isUnsplittable(coll);
|
|
}
|
|
|
|
/**
|
|
* Returns an array with the shardIds that own data for the given collection.
|
|
*/
|
|
function getShardsOwningDataForCollection(coll) {
|
|
if (isSharded(coll) || isUnsplittable(coll)) {
|
|
const res = db
|
|
.getSiblingDB("config")
|
|
.collections.aggregate([
|
|
{$match: {_id: coll.getFullName()}},
|
|
{
|
|
$lookup: {from: "chunks", localField: "uuid", foreignField: "uuid", as: "chunks"},
|
|
},
|
|
{$group: {_id: "$chunks.shard"}},
|
|
])
|
|
.toArray();
|
|
return res.map((x) => x._id).flat();
|
|
} else {
|
|
const dbMetadata = db.getSiblingDB("config").databases.findOne({_id: coll.getDB().getName()});
|
|
return dbMetadata ? [dbMetadata.primary] : [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Utility to determine whether the collections in 'collList' are colocated or not.
|
|
*/
|
|
function areCollectionsColocated(collList) {
|
|
if (!FixtureHelpers.isMongos(db)) {
|
|
return true;
|
|
}
|
|
let set = new Set();
|
|
for (const coll of collList) {
|
|
for (const shard of getShardsOwningDataForCollection(coll)) {
|
|
set.add(shard);
|
|
if (set.size > 1) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Returns the resolved view definition for 'collName' if it is a view, 'undefined' otherwise.
|
|
*/
|
|
function getViewDefinition(db, collName) {
|
|
return db.getCollectionInfos({type: "view", name: collName}).shift();
|
|
}
|
|
|
|
function getTopologyTime(db) {
|
|
const shards = db.getSiblingDB("config").shards.find({}).sort({"topologyTime": -1}).limit(1).toArray();
|
|
if (!shards.length) {
|
|
// In case we are on a replicaset config.shards is empty
|
|
return Timestamp();
|
|
}
|
|
return shards[0].topologyTime;
|
|
}
|
|
|
|
/**
|
|
* Returns the number of shards that 'coll' has any chunks on. Returns 1 if the collection is
|
|
* not sharded. Note that if the balancer is enabled then the number of shards with chunks for
|
|
* this collection can change at any moment.
|
|
*/
|
|
function numberOfShardsForCollection(coll) {
|
|
if (!isMongos(coll.getDB()) || !isSharded(coll)) {
|
|
// If we're not talking to a mongos, or the collection is not sharded, there is one
|
|
// shard.
|
|
return 1;
|
|
}
|
|
const collMetadata = db.getSiblingDB("config").collections.findOne({_id: coll.getFullName()});
|
|
if (collMetadata.timestamp) {
|
|
return db.getSiblingDB("config").chunks.distinct("shard", {uuid: collMetadata.uuid}).length;
|
|
} else {
|
|
return db.getSiblingDB("config").chunks.distinct("shard", {ns: coll.getFullName()}).length;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Runs the function given by 'func' passing the database given by 'db' from each shard nodes in
|
|
* the fixture (besides the config servers). Returns the array of return values from executed
|
|
* functions. If the fixture is a standalone, will run the function on the database directly.
|
|
*/
|
|
function mapOnEachShardNode({db, func, primaryNodeOnly}) {
|
|
function getRequestedConns(conn) {
|
|
const isMaster = conn.getDB("test").isMaster();
|
|
|
|
if (isMaster.hasOwnProperty("setName")) {
|
|
// It's a repl set.
|
|
const rs = new ReplSetTest(isMaster.me);
|
|
return primaryNodeOnly ? [rs.getPrimary()] : rs.nodes;
|
|
} else {
|
|
// It's a standalone.
|
|
return [conn];
|
|
}
|
|
}
|
|
|
|
let connList = [];
|
|
if (isMongos(db)) {
|
|
const shardObjs = db.getSiblingDB("config").shards.find().sort({_id: 1}).toArray();
|
|
|
|
for (let shardObj of shardObjs) {
|
|
connList = connList.concat(getRequestedConns(new Mongo(shardObj.host, undefined, {gRPC: false})));
|
|
}
|
|
} else {
|
|
connList = getRequestedConns(new Mongo(db.getMongo().host, undefined, {gRPC: db.getMongo().isGRPC()}));
|
|
}
|
|
|
|
return connList.map((conn) => func(conn.getDB(db.getName())));
|
|
}
|
|
|
|
/**
|
|
* Runs the command given by 'cmdObj' on the database given by 'db' on each shard nodes in
|
|
* the fixture (besides the config servers). Asserts that each command works, and returns an
|
|
* array with the responses from each shard, or with a single element if the fixture was a
|
|
* replica set. If the fixture is a standalone, will run the command directly.
|
|
*/
|
|
function runCommandOnAllShards({db, cmdObj, primaryNodeOnly}) {
|
|
return mapOnEachShardNode({
|
|
db,
|
|
func: (primaryDb) => assert.commandWorked(primaryDb.runCommand(cmdObj)),
|
|
primaryNodeOnly,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* A helper function for 'runCommandOnAllShards' to only run command on the primary nodes.
|
|
*/
|
|
function runCommandOnEachPrimary({db, cmdObj}) {
|
|
return runCommandOnAllShards({db, cmdObj, primaryNodeOnly: true});
|
|
}
|
|
|
|
/**
|
|
* Returns a connection to the replica set primary for the primary shard for the given database.
|
|
* Returns the same connection that 'db' is using if the fixture is not a sharded cluster.
|
|
*/
|
|
function getPrimaryForNodeHostingDatabase(db) {
|
|
if (!isMongos(db)) {
|
|
return db.getMongo();
|
|
}
|
|
const configDB = db.getSiblingDB("config");
|
|
let shardConn = null;
|
|
configDB.databases.find().forEach(function (dbObj) {
|
|
if (dbObj._id === db.getName()) {
|
|
const shardObj = configDB.shards.findOne({_id: dbObj.primary});
|
|
shardConn = new Mongo(shardObj.host, undefined, {gRPC: false});
|
|
}
|
|
});
|
|
assert.neq(null, shardConn, "could not find shard hosting database " + db.getName());
|
|
return shardConn;
|
|
}
|
|
|
|
/**
|
|
* Returns a collection of connections to each primary in a cluster.
|
|
*/
|
|
function getPrimaries(db) {
|
|
return getAllReplicas(db).map((replSet) => replSet.getPrimary());
|
|
}
|
|
|
|
/**
|
|
* Returns a collection of connections to secondaries in a cluster.
|
|
*/
|
|
function getSecondaries(db) {
|
|
return getAllReplicas(db).reduce((array, replSet) => {
|
|
return array.concat(replSet.getSecondaries());
|
|
}, []);
|
|
}
|
|
|
|
/**
|
|
* Returns true if we have a replica set.
|
|
*/
|
|
function isReplSet(db) {
|
|
const primaryInfo = db.isMaster();
|
|
return primaryInfo.hasOwnProperty("setName");
|
|
}
|
|
|
|
/**
|
|
* Returns true if we have a standalone mongod.
|
|
*/
|
|
function isStandalone(db) {
|
|
return !isMongos(db) && !isReplSet(db);
|
|
}
|
|
|
|
return {
|
|
isMongos: isMongos,
|
|
isSharded: isSharded,
|
|
isUnsplittable: isUnsplittable,
|
|
isTracked: isTracked,
|
|
areCollectionsColocated: areCollectionsColocated,
|
|
getShardsOwningDataForCollection: getShardsOwningDataForCollection,
|
|
getTopologyTime: getTopologyTime,
|
|
getViewDefinition: getViewDefinition,
|
|
numberOfShardsForCollection: numberOfShardsForCollection,
|
|
awaitReplication: awaitReplication,
|
|
awaitLastOpCommitted: awaitLastOpCommitted,
|
|
mapOnEachShardNode: mapOnEachShardNode,
|
|
runCommandOnAllShards: runCommandOnAllShards,
|
|
runCommandOnEachPrimary: runCommandOnEachPrimary,
|
|
getAllReplicas: getAllReplicas,
|
|
getConfigServerConnString: getConfigServerConnString,
|
|
getPrimaries: getPrimaries,
|
|
getSecondaries: getSecondaries,
|
|
getPrimaryForNodeHostingDatabase: getPrimaryForNodeHostingDatabase,
|
|
isReplSet: isReplSet,
|
|
isStandalone: isStandalone,
|
|
};
|
|
})();
|