summaryrefslogtreecommitdiff
path: root/jstests/libs/discover_topology.js
blob: 97071a8ae96c1b1d23b21169e24321440d7a0450 (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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
'use strict';

// The tojson() function that is commonly used to build up assertion messages doesn't support the
// Symbol type, so we just use unique string values instead.
var Topology = {
    kStandalone: 'stand-alone',
    kRouter: 'mongos router',
    kReplicaSet: 'replica set',
    kShardedCluster: 'sharded cluster',
};

var DiscoverTopology = (function() {
    const kDefaultConnectFn = (host) => new Mongo(host);

    function getDataMemberConnectionStrings(conn) {
        const res = assert.commandWorked(conn.adminCommand({isMaster: 1}));

        if (!res.hasOwnProperty('setName')) {
            // 'conn' represents a connection to a stand-alone mongod.
            return {type: Topology.kStandalone, mongod: conn.host};
        }

        // The "passives" field contains the list of unelectable (priority=0) secondaries
        // and is omitted from the server's response when there are none.
        res.passives = res.passives || [];
        return {
            type: Topology.kReplicaSet,
            primary: res.primary,
            nodes: [...res.hosts, ...res.passives]
        };
    }

    function findConnectedNodesViaMongos(conn, options) {
        function getConfigServerConnectionString() {
            const shardMap = conn.adminCommand({getShardMap: 1});

            if (!shardMap.hasOwnProperty('map')) {
                throw new Error(
                    'Expected "getShardMap" command to return an object with a "map" field: ' +
                    tojson(shardMap));
            }

            if (!shardMap.map.hasOwnProperty('config')) {
                throw new Error(
                    'Expected "getShardMap" command to return an object with a "map.config"' +
                    ' field: ' + tojson(shardMap));
            }

            return shardMap.map.config;
        }

        const connectFn =
            options.hasOwnProperty('connectFn') ? options.connectFn : kDefaultConnectFn;

        const configsvrConn = connectFn(getConfigServerConnectionString());
        const configsvrHosts = getDataMemberConnectionStrings(configsvrConn);

        const shards = assert.commandWorked(conn.adminCommand({listShards: 1})).shards;
        const shardHosts = {};

        for (let shardInfo of shards) {
            const shardConn = connectFn(shardInfo.host);
            shardHosts[shardInfo._id] = getDataMemberConnectionStrings(shardConn);
        }

        // Discover mongos URIs from the connection string. If a mongos is not passed in explicitly,
        // it will not be discovered.
        const mongosUris = new MongoURI("mongodb://" + conn.host);

        const mongos = {
            type: Topology.kRouter,
            nodes: mongosUris.servers.map(uriObj => uriObj.server),
        };

        return {
            type: Topology.kShardedCluster,
            configsvr: configsvrHosts,
            shards: shardHosts,
            mongos: mongos,
        };
    }

    /**
     * Returns an object describing the topology of the mongod processes reachable from 'conn'.
     * The "connectFn" property can be optionally specified to support custom retry logic when
     * making connection attempts without overriding the Mongo constructor itself.
     *
     * For a stand-alone mongod, an object of the form
     *   {type: Topology.kStandalone, mongod: <conn-string>}
     * is returned.
     *
     * For a replica set, an object of the form
     *   {
     *     type: Topology.kReplicaSet,
     *     primary: <primary-conn-string>,
     *     nodes: [<conn-string1>, <conn-string2>, ...],
     *   }
     * is returned.
     *
     * For a sharded cluster, an object of the form
     *   {
     *     type: Topology.kShardedCluster,
     *     configsvr: {nodes: [...]},
     *     shards: {
     *       <shard-name1>: {type: Topology.kStandalone, mongod: ...},
     *       <shard-name2>: {type: Topology.kReplicaSet,
     *                       primary: <primary-conn-string>,
     *                       nodes: [...]},
     *       ...
     *     },
     *     mongos: {
     *       type: Topology.kRouter,
     *       nodes: [...],
     *     }
     *   }
     * is returned, where the description for each shard depends on whether it is a stand-alone
     * shard or a replica set shard.
     */
    function findConnectedNodes(conn, options = {connectFn: kDefaultConnectFn}) {
        const isMongod = assert.commandWorked(conn.adminCommand({isMaster: 1})).msg !== 'isdbgrid';

        if (isMongod) {
            return getDataMemberConnectionStrings(conn);
        }

        return findConnectedNodesViaMongos(conn, options);
    }

    function addNonConfigNodesToList(topology, hostList) {
        if (topology.type === Topology.kStandalone) {
            hostList.push(topology.mongod);
        } else if (topology.type === Topology.kReplicaSet) {
            hostList.push(...topology.nodes);
        } else if (topology.type === Topology.kShardedCluster) {
            for (let shardName of Object.keys(topology.shards)) {
                const shardTopology = topology.shards[shardName];
                addNonConfigNodesToList(shardTopology, hostList);
            }
            hostList.push(...topology.mongos.nodes);
        } else {
            throw new Error('Unrecognized topology format: ' + tojson(topology));
        }
    }

    /**
     * Return list of nodes in the cluster given a connection NOT including config servers (if
     * there are any).
     */
    function findNonConfigNodes(conn) {
        const topology = findConnectedNodes(conn);
        let hostList = [];
        addNonConfigNodesToList(topology, hostList);
        return hostList;
    }

    return {findConnectedNodes: findConnectedNodes, findNonConfigNodes: findNonConfigNodes};
})();