topology_description.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.TopologyDescription = void 0;
  4. const WIRE_CONSTANTS = require("../cmap/wire_protocol/constants");
  5. const error_1 = require("../error");
  6. const utils_1 = require("../utils");
  7. const common_1 = require("./common");
  8. const server_description_1 = require("./server_description");
  9. // constants related to compatibility checks
  10. const MIN_SUPPORTED_SERVER_VERSION = WIRE_CONSTANTS.MIN_SUPPORTED_SERVER_VERSION;
  11. const MAX_SUPPORTED_SERVER_VERSION = WIRE_CONSTANTS.MAX_SUPPORTED_SERVER_VERSION;
  12. const MIN_SUPPORTED_WIRE_VERSION = WIRE_CONSTANTS.MIN_SUPPORTED_WIRE_VERSION;
  13. const MAX_SUPPORTED_WIRE_VERSION = WIRE_CONSTANTS.MAX_SUPPORTED_WIRE_VERSION;
  14. const MONGOS_OR_UNKNOWN = new Set([common_1.ServerType.Mongos, common_1.ServerType.Unknown]);
  15. const MONGOS_OR_STANDALONE = new Set([common_1.ServerType.Mongos, common_1.ServerType.Standalone]);
  16. const NON_PRIMARY_RS_MEMBERS = new Set([
  17. common_1.ServerType.RSSecondary,
  18. common_1.ServerType.RSArbiter,
  19. common_1.ServerType.RSOther
  20. ]);
  21. /**
  22. * Representation of a deployment of servers
  23. * @public
  24. */
  25. class TopologyDescription {
  26. /**
  27. * Create a TopologyDescription
  28. */
  29. constructor(topologyType, serverDescriptions, setName, maxSetVersion, maxElectionId, commonWireVersion, options) {
  30. var _a, _b;
  31. options = options !== null && options !== void 0 ? options : {};
  32. // TODO: consider assigning all these values to a temporary value `s` which
  33. // we use `Object.freeze` on, ensuring the internal state of this type
  34. // is immutable.
  35. this.type = topologyType !== null && topologyType !== void 0 ? topologyType : common_1.TopologyType.Unknown;
  36. this.servers = serverDescriptions !== null && serverDescriptions !== void 0 ? serverDescriptions : new Map();
  37. this.stale = false;
  38. this.compatible = true;
  39. this.heartbeatFrequencyMS = (_a = options.heartbeatFrequencyMS) !== null && _a !== void 0 ? _a : 0;
  40. this.localThresholdMS = (_b = options.localThresholdMS) !== null && _b !== void 0 ? _b : 0;
  41. if (setName) {
  42. this.setName = setName;
  43. }
  44. if (maxSetVersion) {
  45. this.maxSetVersion = maxSetVersion;
  46. }
  47. if (maxElectionId) {
  48. this.maxElectionId = maxElectionId;
  49. }
  50. if (commonWireVersion) {
  51. this.commonWireVersion = commonWireVersion;
  52. }
  53. // determine server compatibility
  54. for (const serverDescription of this.servers.values()) {
  55. // Load balancer mode is always compatible.
  56. if (serverDescription.type === common_1.ServerType.Unknown ||
  57. serverDescription.type === common_1.ServerType.LoadBalancer) {
  58. continue;
  59. }
  60. if (serverDescription.minWireVersion > MAX_SUPPORTED_WIRE_VERSION) {
  61. this.compatible = false;
  62. this.compatibilityError = `Server at ${serverDescription.address} requires wire version ${serverDescription.minWireVersion}, but this version of the driver only supports up to ${MAX_SUPPORTED_WIRE_VERSION} (MongoDB ${MAX_SUPPORTED_SERVER_VERSION})`;
  63. }
  64. if (serverDescription.maxWireVersion < MIN_SUPPORTED_WIRE_VERSION) {
  65. this.compatible = false;
  66. this.compatibilityError = `Server at ${serverDescription.address} reports wire version ${serverDescription.maxWireVersion}, but this version of the driver requires at least ${MIN_SUPPORTED_WIRE_VERSION} (MongoDB ${MIN_SUPPORTED_SERVER_VERSION}).`;
  67. break;
  68. }
  69. }
  70. // Whenever a client updates the TopologyDescription from a hello response, it MUST set
  71. // TopologyDescription.logicalSessionTimeoutMinutes to the smallest logicalSessionTimeoutMinutes
  72. // value among ServerDescriptions of all data-bearing server types. If any have a null
  73. // logicalSessionTimeoutMinutes, then TopologyDescription.logicalSessionTimeoutMinutes MUST be
  74. // set to null.
  75. this.logicalSessionTimeoutMinutes = undefined;
  76. for (const [, server] of this.servers) {
  77. if (server.isReadable) {
  78. if (server.logicalSessionTimeoutMinutes == null) {
  79. // If any of the servers have a null logicalSessionsTimeout, then the whole topology does
  80. this.logicalSessionTimeoutMinutes = undefined;
  81. break;
  82. }
  83. if (this.logicalSessionTimeoutMinutes == null) {
  84. // First server with a non null logicalSessionsTimeout
  85. this.logicalSessionTimeoutMinutes = server.logicalSessionTimeoutMinutes;
  86. continue;
  87. }
  88. // Always select the smaller of the:
  89. // current server logicalSessionsTimeout and the topologies logicalSessionsTimeout
  90. this.logicalSessionTimeoutMinutes = Math.min(this.logicalSessionTimeoutMinutes, server.logicalSessionTimeoutMinutes);
  91. }
  92. }
  93. }
  94. /**
  95. * Returns a new TopologyDescription based on the SrvPollingEvent
  96. * @internal
  97. */
  98. updateFromSrvPollingEvent(ev, srvMaxHosts = 0) {
  99. /** The SRV addresses defines the set of addresses we should be using */
  100. const incomingHostnames = ev.hostnames();
  101. const currentHostnames = new Set(this.servers.keys());
  102. const hostnamesToAdd = new Set(incomingHostnames);
  103. const hostnamesToRemove = new Set();
  104. for (const hostname of currentHostnames) {
  105. // filter hostnamesToAdd (made from incomingHostnames) down to what is *not* present in currentHostnames
  106. hostnamesToAdd.delete(hostname);
  107. if (!incomingHostnames.has(hostname)) {
  108. // If the SRV Records no longer include this hostname
  109. // we have to stop using it
  110. hostnamesToRemove.add(hostname);
  111. }
  112. }
  113. if (hostnamesToAdd.size === 0 && hostnamesToRemove.size === 0) {
  114. // No new hosts to add and none to remove
  115. return this;
  116. }
  117. const serverDescriptions = new Map(this.servers);
  118. for (const removedHost of hostnamesToRemove) {
  119. serverDescriptions.delete(removedHost);
  120. }
  121. if (hostnamesToAdd.size > 0) {
  122. if (srvMaxHosts === 0) {
  123. // Add all!
  124. for (const hostToAdd of hostnamesToAdd) {
  125. serverDescriptions.set(hostToAdd, new server_description_1.ServerDescription(hostToAdd));
  126. }
  127. }
  128. else if (serverDescriptions.size < srvMaxHosts) {
  129. // Add only the amount needed to get us back to srvMaxHosts
  130. const selectedHosts = (0, utils_1.shuffle)(hostnamesToAdd, srvMaxHosts - serverDescriptions.size);
  131. for (const selectedHostToAdd of selectedHosts) {
  132. serverDescriptions.set(selectedHostToAdd, new server_description_1.ServerDescription(selectedHostToAdd));
  133. }
  134. }
  135. }
  136. return new TopologyDescription(this.type, serverDescriptions, this.setName, this.maxSetVersion, this.maxElectionId, this.commonWireVersion, { heartbeatFrequencyMS: this.heartbeatFrequencyMS, localThresholdMS: this.localThresholdMS });
  137. }
  138. /**
  139. * Returns a copy of this description updated with a given ServerDescription
  140. * @internal
  141. */
  142. update(serverDescription) {
  143. const address = serverDescription.address;
  144. // potentially mutated values
  145. let { type: topologyType, setName, maxSetVersion, maxElectionId, commonWireVersion } = this;
  146. if (serverDescription.setName && setName && serverDescription.setName !== setName) {
  147. serverDescription = new server_description_1.ServerDescription(address, undefined);
  148. }
  149. const serverType = serverDescription.type;
  150. const serverDescriptions = new Map(this.servers);
  151. // update common wire version
  152. if (serverDescription.maxWireVersion !== 0) {
  153. if (commonWireVersion == null) {
  154. commonWireVersion = serverDescription.maxWireVersion;
  155. }
  156. else {
  157. commonWireVersion = Math.min(commonWireVersion, serverDescription.maxWireVersion);
  158. }
  159. }
  160. // update the actual server description
  161. serverDescriptions.set(address, serverDescription);
  162. if (topologyType === common_1.TopologyType.Single) {
  163. // once we are defined as single, that never changes
  164. return new TopologyDescription(common_1.TopologyType.Single, serverDescriptions, setName, maxSetVersion, maxElectionId, commonWireVersion, { heartbeatFrequencyMS: this.heartbeatFrequencyMS, localThresholdMS: this.localThresholdMS });
  165. }
  166. if (topologyType === common_1.TopologyType.Unknown) {
  167. if (serverType === common_1.ServerType.Standalone && this.servers.size !== 1) {
  168. serverDescriptions.delete(address);
  169. }
  170. else {
  171. topologyType = topologyTypeForServerType(serverType);
  172. }
  173. }
  174. if (topologyType === common_1.TopologyType.Sharded) {
  175. if (!MONGOS_OR_UNKNOWN.has(serverType)) {
  176. serverDescriptions.delete(address);
  177. }
  178. }
  179. if (topologyType === common_1.TopologyType.ReplicaSetNoPrimary) {
  180. if (MONGOS_OR_STANDALONE.has(serverType)) {
  181. serverDescriptions.delete(address);
  182. }
  183. if (serverType === common_1.ServerType.RSPrimary) {
  184. const result = updateRsFromPrimary(serverDescriptions, serverDescription, setName, maxSetVersion, maxElectionId);
  185. topologyType = result[0];
  186. setName = result[1];
  187. maxSetVersion = result[2];
  188. maxElectionId = result[3];
  189. }
  190. else if (NON_PRIMARY_RS_MEMBERS.has(serverType)) {
  191. const result = updateRsNoPrimaryFromMember(serverDescriptions, serverDescription, setName);
  192. topologyType = result[0];
  193. setName = result[1];
  194. }
  195. }
  196. if (topologyType === common_1.TopologyType.ReplicaSetWithPrimary) {
  197. if (MONGOS_OR_STANDALONE.has(serverType)) {
  198. serverDescriptions.delete(address);
  199. topologyType = checkHasPrimary(serverDescriptions);
  200. }
  201. else if (serverType === common_1.ServerType.RSPrimary) {
  202. const result = updateRsFromPrimary(serverDescriptions, serverDescription, setName, maxSetVersion, maxElectionId);
  203. topologyType = result[0];
  204. setName = result[1];
  205. maxSetVersion = result[2];
  206. maxElectionId = result[3];
  207. }
  208. else if (NON_PRIMARY_RS_MEMBERS.has(serverType)) {
  209. topologyType = updateRsWithPrimaryFromMember(serverDescriptions, serverDescription, setName);
  210. }
  211. else {
  212. topologyType = checkHasPrimary(serverDescriptions);
  213. }
  214. }
  215. return new TopologyDescription(topologyType, serverDescriptions, setName, maxSetVersion, maxElectionId, commonWireVersion, { heartbeatFrequencyMS: this.heartbeatFrequencyMS, localThresholdMS: this.localThresholdMS });
  216. }
  217. get error() {
  218. const descriptionsWithError = Array.from(this.servers.values()).filter((sd) => sd.error);
  219. if (descriptionsWithError.length > 0) {
  220. return descriptionsWithError[0].error;
  221. }
  222. }
  223. /**
  224. * Determines if the topology description has any known servers
  225. */
  226. get hasKnownServers() {
  227. return Array.from(this.servers.values()).some((sd) => sd.type !== common_1.ServerType.Unknown);
  228. }
  229. /**
  230. * Determines if this topology description has a data-bearing server available.
  231. */
  232. get hasDataBearingServers() {
  233. return Array.from(this.servers.values()).some((sd) => sd.isDataBearing);
  234. }
  235. /**
  236. * Determines if the topology has a definition for the provided address
  237. * @internal
  238. */
  239. hasServer(address) {
  240. return this.servers.has(address);
  241. }
  242. }
  243. exports.TopologyDescription = TopologyDescription;
  244. function topologyTypeForServerType(serverType) {
  245. switch (serverType) {
  246. case common_1.ServerType.Standalone:
  247. return common_1.TopologyType.Single;
  248. case common_1.ServerType.Mongos:
  249. return common_1.TopologyType.Sharded;
  250. case common_1.ServerType.RSPrimary:
  251. return common_1.TopologyType.ReplicaSetWithPrimary;
  252. case common_1.ServerType.RSOther:
  253. case common_1.ServerType.RSSecondary:
  254. return common_1.TopologyType.ReplicaSetNoPrimary;
  255. default:
  256. return common_1.TopologyType.Unknown;
  257. }
  258. }
  259. // TODO: improve these docs when ObjectId is properly typed
  260. function compareObjectId(oid1, oid2) {
  261. if (oid1 == null) {
  262. return -1;
  263. }
  264. if (oid2 == null) {
  265. return 1;
  266. }
  267. if (oid1.id instanceof Buffer && oid2.id instanceof Buffer) {
  268. const oid1Buffer = oid1.id;
  269. const oid2Buffer = oid2.id;
  270. return oid1Buffer.compare(oid2Buffer);
  271. }
  272. const oid1String = oid1.toString();
  273. const oid2String = oid2.toString();
  274. return oid1String.localeCompare(oid2String);
  275. }
  276. function updateRsFromPrimary(serverDescriptions, serverDescription, setName, maxSetVersion, maxElectionId) {
  277. setName = setName || serverDescription.setName;
  278. if (setName !== serverDescription.setName) {
  279. serverDescriptions.delete(serverDescription.address);
  280. return [checkHasPrimary(serverDescriptions), setName, maxSetVersion, maxElectionId];
  281. }
  282. const electionId = serverDescription.electionId ? serverDescription.electionId : null;
  283. if (serverDescription.setVersion && electionId) {
  284. if (maxSetVersion && maxElectionId) {
  285. if (maxSetVersion > serverDescription.setVersion ||
  286. compareObjectId(maxElectionId, electionId) > 0) {
  287. // this primary is stale, we must remove it
  288. serverDescriptions.set(serverDescription.address, new server_description_1.ServerDescription(serverDescription.address));
  289. return [checkHasPrimary(serverDescriptions), setName, maxSetVersion, maxElectionId];
  290. }
  291. }
  292. maxElectionId = serverDescription.electionId;
  293. }
  294. if (serverDescription.setVersion != null &&
  295. (maxSetVersion == null || serverDescription.setVersion > maxSetVersion)) {
  296. maxSetVersion = serverDescription.setVersion;
  297. }
  298. // We've heard from the primary. Is it the same primary as before?
  299. for (const [address, server] of serverDescriptions) {
  300. if (server.type === common_1.ServerType.RSPrimary && server.address !== serverDescription.address) {
  301. // Reset old primary's type to Unknown.
  302. serverDescriptions.set(address, new server_description_1.ServerDescription(server.address));
  303. // There can only be one primary
  304. break;
  305. }
  306. }
  307. // Discover new hosts from this primary's response.
  308. serverDescription.allHosts.forEach((address) => {
  309. if (!serverDescriptions.has(address)) {
  310. serverDescriptions.set(address, new server_description_1.ServerDescription(address));
  311. }
  312. });
  313. // Remove hosts not in the response.
  314. const currentAddresses = Array.from(serverDescriptions.keys());
  315. const responseAddresses = serverDescription.allHosts;
  316. currentAddresses
  317. .filter((addr) => responseAddresses.indexOf(addr) === -1)
  318. .forEach((address) => {
  319. serverDescriptions.delete(address);
  320. });
  321. return [checkHasPrimary(serverDescriptions), setName, maxSetVersion, maxElectionId];
  322. }
  323. function updateRsWithPrimaryFromMember(serverDescriptions, serverDescription, setName) {
  324. if (setName == null) {
  325. // TODO(NODE-3483): should be an appropriate runtime error
  326. throw new error_1.MongoRuntimeError('Argument "setName" is required if connected to a replica set');
  327. }
  328. if (setName !== serverDescription.setName ||
  329. (serverDescription.me && serverDescription.address !== serverDescription.me)) {
  330. serverDescriptions.delete(serverDescription.address);
  331. }
  332. return checkHasPrimary(serverDescriptions);
  333. }
  334. function updateRsNoPrimaryFromMember(serverDescriptions, serverDescription, setName) {
  335. const topologyType = common_1.TopologyType.ReplicaSetNoPrimary;
  336. setName = setName || serverDescription.setName;
  337. if (setName !== serverDescription.setName) {
  338. serverDescriptions.delete(serverDescription.address);
  339. return [topologyType, setName];
  340. }
  341. serverDescription.allHosts.forEach((address) => {
  342. if (!serverDescriptions.has(address)) {
  343. serverDescriptions.set(address, new server_description_1.ServerDescription(address));
  344. }
  345. });
  346. if (serverDescription.me && serverDescription.address !== serverDescription.me) {
  347. serverDescriptions.delete(serverDescription.address);
  348. }
  349. return [topologyType, setName];
  350. }
  351. function checkHasPrimary(serverDescriptions) {
  352. for (const serverDescription of serverDescriptions.values()) {
  353. if (serverDescription.type === common_1.ServerType.RSPrimary) {
  354. return common_1.TopologyType.ReplicaSetWithPrimary;
  355. }
  356. }
  357. return common_1.TopologyType.ReplicaSetNoPrimary;
  358. }
  359. //# sourceMappingURL=topology_description.js.map