server_description.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.ServerDescription = void 0;
  4. exports.parseServerType = parseServerType;
  5. exports.compareTopologyVersion = compareTopologyVersion;
  6. const bson_1 = require("../bson");
  7. const error_1 = require("../error");
  8. const utils_1 = require("../utils");
  9. const common_1 = require("./common");
  10. const WRITABLE_SERVER_TYPES = new Set([
  11. common_1.ServerType.RSPrimary,
  12. common_1.ServerType.Standalone,
  13. common_1.ServerType.Mongos,
  14. common_1.ServerType.LoadBalancer
  15. ]);
  16. const DATA_BEARING_SERVER_TYPES = new Set([
  17. common_1.ServerType.RSPrimary,
  18. common_1.ServerType.RSSecondary,
  19. common_1.ServerType.Mongos,
  20. common_1.ServerType.Standalone,
  21. common_1.ServerType.LoadBalancer
  22. ]);
  23. /**
  24. * The client's view of a single server, based on the most recent hello outcome.
  25. *
  26. * Internal type, not meant to be directly instantiated
  27. * @public
  28. */
  29. class ServerDescription {
  30. /**
  31. * Create a ServerDescription
  32. * @internal
  33. *
  34. * @param address - The address of the server
  35. * @param hello - An optional hello response for this server
  36. */
  37. constructor(address, hello, options = {}) {
  38. if (address == null || address === '') {
  39. throw new error_1.MongoRuntimeError('ServerDescription must be provided with a non-empty address');
  40. }
  41. this.address =
  42. typeof address === 'string'
  43. ? utils_1.HostAddress.fromString(address).toString() // Use HostAddress to normalize
  44. : address.toString();
  45. this.type = parseServerType(hello, options);
  46. this.hosts = hello?.hosts?.map((host) => host.toLowerCase()) ?? [];
  47. this.passives = hello?.passives?.map((host) => host.toLowerCase()) ?? [];
  48. this.arbiters = hello?.arbiters?.map((host) => host.toLowerCase()) ?? [];
  49. this.tags = hello?.tags ?? {};
  50. this.minWireVersion = hello?.minWireVersion ?? 0;
  51. this.maxWireVersion = hello?.maxWireVersion ?? 0;
  52. this.roundTripTime = options?.roundTripTime ?? -1;
  53. this.minRoundTripTime = options?.minRoundTripTime ?? 0;
  54. this.lastUpdateTime = (0, utils_1.now)();
  55. this.lastWriteDate = hello?.lastWrite?.lastWriteDate ?? 0;
  56. // NOTE: This actually builds the stack string instead of holding onto the getter and all its
  57. // associated references. This is done to prevent a memory leak.
  58. this.error = options.error ?? null;
  59. this.error?.stack;
  60. // TODO(NODE-2674): Preserve int64 sent from MongoDB
  61. this.topologyVersion = this.error?.topologyVersion ?? hello?.topologyVersion ?? null;
  62. this.setName = hello?.setName ?? null;
  63. this.setVersion = hello?.setVersion ?? null;
  64. this.electionId = hello?.electionId ?? null;
  65. this.logicalSessionTimeoutMinutes = hello?.logicalSessionTimeoutMinutes ?? null;
  66. this.maxMessageSizeBytes = hello?.maxMessageSizeBytes ?? null;
  67. this.maxWriteBatchSize = hello?.maxWriteBatchSize ?? null;
  68. this.maxBsonObjectSize = hello?.maxBsonObjectSize ?? null;
  69. this.primary = hello?.primary ?? null;
  70. this.me = hello?.me?.toLowerCase() ?? null;
  71. this.$clusterTime = hello?.$clusterTime ?? null;
  72. this.iscryptd = Boolean(hello?.iscryptd);
  73. }
  74. get hostAddress() {
  75. return utils_1.HostAddress.fromString(this.address);
  76. }
  77. get allHosts() {
  78. return this.hosts.concat(this.arbiters).concat(this.passives);
  79. }
  80. /** Is this server available for reads*/
  81. get isReadable() {
  82. return this.type === common_1.ServerType.RSSecondary || this.isWritable;
  83. }
  84. /** Is this server data bearing */
  85. get isDataBearing() {
  86. return DATA_BEARING_SERVER_TYPES.has(this.type);
  87. }
  88. /** Is this server available for writes */
  89. get isWritable() {
  90. return WRITABLE_SERVER_TYPES.has(this.type);
  91. }
  92. get host() {
  93. const chopLength = `:${this.port}`.length;
  94. return this.address.slice(0, -chopLength);
  95. }
  96. get port() {
  97. const port = this.address.split(':').pop();
  98. return port ? Number.parseInt(port, 10) : 27017;
  99. }
  100. /**
  101. * Determines if another `ServerDescription` is equal to this one per the rules defined in the SDAM specification.
  102. * @see https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring.md
  103. */
  104. equals(other) {
  105. // Despite using the comparator that would determine a nullish topologyVersion as greater than
  106. // for equality we should only always perform direct equality comparison
  107. const topologyVersionsEqual = this.topologyVersion === other?.topologyVersion ||
  108. compareTopologyVersion(this.topologyVersion, other?.topologyVersion) === 0;
  109. const electionIdsEqual = this.electionId != null && other?.electionId != null
  110. ? (0, utils_1.compareObjectId)(this.electionId, other.electionId) === 0
  111. : this.electionId === other?.electionId;
  112. return (other != null &&
  113. other.iscryptd === this.iscryptd &&
  114. (0, utils_1.errorStrictEqual)(this.error, other.error) &&
  115. this.type === other.type &&
  116. this.minWireVersion === other.minWireVersion &&
  117. (0, utils_1.arrayStrictEqual)(this.hosts, other.hosts) &&
  118. tagsStrictEqual(this.tags, other.tags) &&
  119. this.setName === other.setName &&
  120. this.setVersion === other.setVersion &&
  121. electionIdsEqual &&
  122. this.primary === other.primary &&
  123. this.logicalSessionTimeoutMinutes === other.logicalSessionTimeoutMinutes &&
  124. topologyVersionsEqual);
  125. }
  126. }
  127. exports.ServerDescription = ServerDescription;
  128. // Parses a `hello` message and determines the server type
  129. function parseServerType(hello, options) {
  130. if (options?.loadBalanced) {
  131. return common_1.ServerType.LoadBalancer;
  132. }
  133. if (!hello || !hello.ok) {
  134. return common_1.ServerType.Unknown;
  135. }
  136. if (hello.isreplicaset) {
  137. return common_1.ServerType.RSGhost;
  138. }
  139. if (hello.msg && hello.msg === 'isdbgrid') {
  140. return common_1.ServerType.Mongos;
  141. }
  142. if (hello.setName) {
  143. if (hello.hidden) {
  144. return common_1.ServerType.RSOther;
  145. }
  146. else if (hello.isWritablePrimary) {
  147. return common_1.ServerType.RSPrimary;
  148. }
  149. else if (hello.secondary) {
  150. return common_1.ServerType.RSSecondary;
  151. }
  152. else if (hello.arbiterOnly) {
  153. return common_1.ServerType.RSArbiter;
  154. }
  155. else {
  156. return common_1.ServerType.RSOther;
  157. }
  158. }
  159. return common_1.ServerType.Standalone;
  160. }
  161. function tagsStrictEqual(tags, tags2) {
  162. const tagsKeys = Object.keys(tags);
  163. const tags2Keys = Object.keys(tags2);
  164. return (tagsKeys.length === tags2Keys.length &&
  165. tagsKeys.every((key) => tags2[key] === tags[key]));
  166. }
  167. /**
  168. * Compares two topology versions.
  169. *
  170. * 1. If the response topologyVersion is unset or the ServerDescription's
  171. * topologyVersion is null, the client MUST assume the response is more recent.
  172. * 1. If the response's topologyVersion.processId is not equal to the
  173. * ServerDescription's, the client MUST assume the response is more recent.
  174. * 1. If the response's topologyVersion.processId is equal to the
  175. * ServerDescription's, the client MUST use the counter field to determine
  176. * which topologyVersion is more recent.
  177. *
  178. * ```ts
  179. * currentTv < newTv === -1
  180. * currentTv === newTv === 0
  181. * currentTv > newTv === 1
  182. * ```
  183. */
  184. function compareTopologyVersion(currentTv, newTv) {
  185. if (currentTv == null || newTv == null) {
  186. return -1;
  187. }
  188. if (!currentTv.processId.equals(newTv.processId)) {
  189. return -1;
  190. }
  191. // TODO(NODE-2674): Preserve int64 sent from MongoDB
  192. const currentCounter = typeof currentTv.counter === 'bigint'
  193. ? bson_1.Long.fromBigInt(currentTv.counter)
  194. : bson_1.Long.isLong(currentTv.counter)
  195. ? currentTv.counter
  196. : bson_1.Long.fromNumber(currentTv.counter);
  197. const newCounter = typeof newTv.counter === 'bigint'
  198. ? bson_1.Long.fromBigInt(newTv.counter)
  199. : bson_1.Long.isLong(newTv.counter)
  200. ? newTv.counter
  201. : bson_1.Long.fromNumber(newTv.counter);
  202. return currentCounter.compare(newCounter);
  203. }
  204. //# sourceMappingURL=server_description.js.map