/node_modules/mongoose/node_modules/mongodb-core/lib/topologies/replset.js

https://bitbucket.org/coleman333/smartsite · JavaScript · 1674 lines · 946 code · 212 blank · 516 comment · 215 complexity · 3278ebfaa4ba53e029cbb3a9594db299 MD5 · raw file

  1. 'use strict';
  2. var inherits = require('util').inherits,
  3. f = require('util').format,
  4. EventEmitter = require('events').EventEmitter,
  5. ReadPreference = require('./read_preference'),
  6. BasicCursor = require('../cursor'),
  7. retrieveBSON = require('../connection/utils').retrieveBSON,
  8. Logger = require('../connection/logger'),
  9. MongoError = require('../error').MongoError,
  10. errors = require('../error'),
  11. Server = require('./server'),
  12. ReplSetState = require('./replset_state'),
  13. clone = require('./shared').clone,
  14. Timeout = require('./shared').Timeout,
  15. Interval = require('./shared').Interval,
  16. createClientInfo = require('./shared').createClientInfo,
  17. SessionMixins = require('./shared').SessionMixins,
  18. isRetryableWritesSupported = require('./shared').isRetryableWritesSupported,
  19. getNextTransactionNumber = require('./shared').getNextTransactionNumber;
  20. var MongoCR = require('../auth/mongocr'),
  21. X509 = require('../auth/x509'),
  22. Plain = require('../auth/plain'),
  23. GSSAPI = require('../auth/gssapi'),
  24. SSPI = require('../auth/sspi'),
  25. ScramSHA1 = require('../auth/scram');
  26. var BSON = retrieveBSON();
  27. //
  28. // States
  29. var DISCONNECTED = 'disconnected';
  30. var CONNECTING = 'connecting';
  31. var CONNECTED = 'connected';
  32. var UNREFERENCED = 'unreferenced';
  33. var DESTROYED = 'destroyed';
  34. function stateTransition(self, newState) {
  35. var legalTransitions = {
  36. disconnected: [CONNECTING, DESTROYED, DISCONNECTED],
  37. connecting: [CONNECTING, DESTROYED, CONNECTED, DISCONNECTED],
  38. connected: [CONNECTED, DISCONNECTED, DESTROYED, UNREFERENCED],
  39. unreferenced: [UNREFERENCED, DESTROYED],
  40. destroyed: [DESTROYED]
  41. };
  42. // Get current state
  43. var legalStates = legalTransitions[self.state];
  44. if (legalStates && legalStates.indexOf(newState) !== -1) {
  45. self.state = newState;
  46. } else {
  47. self.s.logger.error(
  48. f(
  49. 'Pool with id [%s] failed attempted illegal state transition from [%s] to [%s] only following state allowed [%s]',
  50. self.id,
  51. self.state,
  52. newState,
  53. legalStates
  54. )
  55. );
  56. }
  57. }
  58. //
  59. // ReplSet instance id
  60. var id = 1;
  61. var handlers = ['connect', 'close', 'error', 'timeout', 'parseError'];
  62. /**
  63. * Creates a new Replset instance
  64. * @class
  65. * @param {array} seedlist A list of seeds for the replicaset
  66. * @param {boolean} options.setName The Replicaset set name
  67. * @param {boolean} [options.secondaryOnlyConnectionAllowed=false] Allow connection to a secondary only replicaset
  68. * @param {number} [options.haInterval=10000] The High availability period for replicaset inquiry
  69. * @param {boolean} [options.emitError=false] Server will emit errors events
  70. * @param {Cursor} [options.cursorFactory=Cursor] The cursor factory class used for all query cursors
  71. * @param {number} [options.size=5] Server connection pool size
  72. * @param {boolean} [options.keepAlive=true] TCP Connection keep alive enabled
  73. * @param {number} [options.keepAliveInitialDelay=0] Initial delay before TCP keep alive enabled
  74. * @param {boolean} [options.noDelay=true] TCP Connection no delay
  75. * @param {number} [options.connectionTimeout=10000] TCP Connection timeout setting
  76. * @param {number} [options.socketTimeout=0] TCP Socket timeout setting
  77. * @param {boolean} [options.singleBufferSerializtion=true] Serialize into single buffer, trade of peak memory for serialization speed
  78. * @param {boolean} [options.ssl=false] Use SSL for connection
  79. * @param {boolean|function} [options.checkServerIdentity=true] Ensure we check server identify during SSL, set to false to disable checking. Only works for Node 0.12.x or higher. You can pass in a boolean or your own checkServerIdentity override function.
  80. * @param {Buffer} [options.ca] SSL Certificate store binary buffer
  81. * @param {Buffer} [options.crl] SSL Certificate revocation store binary buffer
  82. * @param {Buffer} [options.cert] SSL Certificate binary buffer
  83. * @param {Buffer} [options.key] SSL Key file binary buffer
  84. * @param {string} [options.passphrase] SSL Certificate pass phrase
  85. * @param {string} [options.servername=null] String containing the server name requested via TLS SNI.
  86. * @param {boolean} [options.rejectUnauthorized=true] Reject unauthorized server certificates
  87. * @param {boolean} [options.promoteLongs=true] Convert Long values from the db into Numbers if they fit into 53 bits
  88. * @param {boolean} [options.promoteValues=true] Promotes BSON values to native types where possible, set to false to only receive wrapper types.
  89. * @param {boolean} [options.promoteBuffers=false] Promotes Binary BSON values to native Node Buffers.
  90. * @param {number} [options.pingInterval=5000] Ping interval to check the response time to the different servers
  91. * @param {number} [options.localThresholdMS=15] Cutoff latency point in MS for Replicaset member selection
  92. * @param {boolean} [options.domainsEnabled=false] Enable the wrapping of the callback in the current domain, disabled by default to avoid perf hit.
  93. * @return {ReplSet} A cursor instance
  94. * @fires ReplSet#connect
  95. * @fires ReplSet#ha
  96. * @fires ReplSet#joined
  97. * @fires ReplSet#left
  98. * @fires ReplSet#failed
  99. * @fires ReplSet#fullsetup
  100. * @fires ReplSet#all
  101. * @fires ReplSet#error
  102. * @fires ReplSet#serverHeartbeatStarted
  103. * @fires ReplSet#serverHeartbeatSucceeded
  104. * @fires ReplSet#serverHeartbeatFailed
  105. * @fires ReplSet#topologyOpening
  106. * @fires ReplSet#topologyClosed
  107. * @fires ReplSet#topologyDescriptionChanged
  108. * @property {string} type the topology type.
  109. * @property {string} parserType the parser type used (c++ or js).
  110. */
  111. var ReplSet = function(seedlist, options) {
  112. var self = this;
  113. options = options || {};
  114. // Validate seedlist
  115. if (!Array.isArray(seedlist)) throw new MongoError('seedlist must be an array');
  116. // Validate list
  117. if (seedlist.length === 0) throw new MongoError('seedlist must contain at least one entry');
  118. // Validate entries
  119. seedlist.forEach(function(e) {
  120. if (typeof e.host !== 'string' || typeof e.port !== 'number')
  121. throw new MongoError('seedlist entry must contain a host and port');
  122. });
  123. // Add event listener
  124. EventEmitter.call(this);
  125. // Get replSet Id
  126. this.id = id++;
  127. // Get the localThresholdMS
  128. var localThresholdMS = options.localThresholdMS || 15;
  129. // Backward compatibility
  130. if (options.acceptableLatency) localThresholdMS = options.acceptableLatency;
  131. // Create a logger
  132. var logger = Logger('ReplSet', options);
  133. // Internal state
  134. this.s = {
  135. options: Object.assign({}, options),
  136. // BSON instance
  137. bson:
  138. options.bson ||
  139. new BSON([
  140. BSON.Binary,
  141. BSON.Code,
  142. BSON.DBRef,
  143. BSON.Decimal128,
  144. BSON.Double,
  145. BSON.Int32,
  146. BSON.Long,
  147. BSON.Map,
  148. BSON.MaxKey,
  149. BSON.MinKey,
  150. BSON.ObjectId,
  151. BSON.BSONRegExp,
  152. BSON.Symbol,
  153. BSON.Timestamp
  154. ]),
  155. // Factory overrides
  156. Cursor: options.cursorFactory || BasicCursor,
  157. // Logger instance
  158. logger: logger,
  159. // Seedlist
  160. seedlist: seedlist,
  161. // Replicaset state
  162. replicaSetState: new ReplSetState({
  163. id: this.id,
  164. setName: options.setName,
  165. acceptableLatency: localThresholdMS,
  166. heartbeatFrequencyMS: options.haInterval ? options.haInterval : 10000,
  167. logger: logger
  168. }),
  169. // Current servers we are connecting to
  170. connectingServers: [],
  171. // Ha interval
  172. haInterval: options.haInterval ? options.haInterval : 10000,
  173. // Minimum heartbeat frequency used if we detect a server close
  174. minHeartbeatFrequencyMS: 500,
  175. // Disconnect handler
  176. disconnectHandler: options.disconnectHandler,
  177. // Server selection index
  178. index: 0,
  179. // Connect function options passed in
  180. connectOptions: {},
  181. // Are we running in debug mode
  182. debug: typeof options.debug === 'boolean' ? options.debug : false,
  183. // Client info
  184. clientInfo: createClientInfo(options),
  185. // Authentication context
  186. authenticationContexts: []
  187. };
  188. // Add handler for topology change
  189. this.s.replicaSetState.on('topologyDescriptionChanged', function(r) {
  190. self.emit('topologyDescriptionChanged', r);
  191. });
  192. // Log info warning if the socketTimeout < haInterval as it will cause
  193. // a lot of recycled connections to happen.
  194. if (
  195. this.s.logger.isWarn() &&
  196. this.s.options.socketTimeout !== 0 &&
  197. this.s.options.socketTimeout < this.s.haInterval
  198. ) {
  199. this.s.logger.warn(
  200. f(
  201. 'warning socketTimeout %s is less than haInterval %s. This might cause unnecessary server reconnections due to socket timeouts',
  202. this.s.options.socketTimeout,
  203. this.s.haInterval
  204. )
  205. );
  206. }
  207. // All the authProviders
  208. this.authProviders = options.authProviders || {
  209. mongocr: new MongoCR(this.s.bson),
  210. x509: new X509(this.s.bson),
  211. plain: new Plain(this.s.bson),
  212. gssapi: new GSSAPI(this.s.bson),
  213. sspi: new SSPI(this.s.bson),
  214. 'scram-sha-1': new ScramSHA1(this.s.bson)
  215. };
  216. // Add forwarding of events from state handler
  217. var types = ['joined', 'left'];
  218. types.forEach(function(x) {
  219. self.s.replicaSetState.on(x, function(t, s) {
  220. self.emit(x, t, s);
  221. });
  222. });
  223. // Connect stat
  224. this.initialConnectState = {
  225. connect: false,
  226. fullsetup: false,
  227. all: false
  228. };
  229. // Disconnected state
  230. this.state = DISCONNECTED;
  231. this.haTimeoutId = null;
  232. // Are we authenticating
  233. this.authenticating = false;
  234. // Last ismaster
  235. this.ismaster = null;
  236. // Contains the intervalId
  237. this.intervalIds = [];
  238. // Highest clusterTime seen in responses from the current deployment
  239. this.clusterTime = null;
  240. };
  241. inherits(ReplSet, EventEmitter);
  242. Object.assign(ReplSet.prototype, SessionMixins);
  243. Object.defineProperty(ReplSet.prototype, 'type', {
  244. enumerable: true,
  245. get: function() {
  246. return 'replset';
  247. }
  248. });
  249. Object.defineProperty(ReplSet.prototype, 'parserType', {
  250. enumerable: true,
  251. get: function() {
  252. return BSON.native ? 'c++' : 'js';
  253. }
  254. });
  255. Object.defineProperty(ReplSet.prototype, 'logicalSessionTimeoutMinutes', {
  256. enumerable: true,
  257. get: function() {
  258. return this.s.replicaSetState.logicalSessionTimeoutMinutes || null;
  259. }
  260. });
  261. function rexecuteOperations(self) {
  262. // If we have a primary and a disconnect handler, execute
  263. // buffered operations
  264. if (self.s.replicaSetState.hasPrimaryAndSecondary() && self.s.disconnectHandler) {
  265. self.s.disconnectHandler.execute();
  266. } else if (self.s.replicaSetState.hasPrimary() && self.s.disconnectHandler) {
  267. self.s.disconnectHandler.execute({ executePrimary: true });
  268. } else if (self.s.replicaSetState.hasSecondary() && self.s.disconnectHandler) {
  269. self.s.disconnectHandler.execute({ executeSecondary: true });
  270. }
  271. }
  272. function connectNewServers(self, servers, callback) {
  273. // Count lefts
  274. var count = servers.length;
  275. var error = null;
  276. // Handle events
  277. var _handleEvent = function(self, event) {
  278. return function(err) {
  279. var _self = this;
  280. count = count - 1;
  281. // Destroyed
  282. if (self.state === DESTROYED || self.state === UNREFERENCED) {
  283. return this.destroy({ force: true });
  284. }
  285. if (event === 'connect' && !self.authenticating) {
  286. // Destroyed
  287. if (self.state === DESTROYED || self.state === UNREFERENCED) {
  288. return _self.destroy({ force: true });
  289. }
  290. // Do we have authentication contexts that need to be applied
  291. applyAuthenticationContexts(self, _self, function() {
  292. // Destroy the instance
  293. if (self.state === DESTROYED || self.state === UNREFERENCED) {
  294. return _self.destroy({ force: true });
  295. }
  296. // Update the state
  297. var result = self.s.replicaSetState.update(_self);
  298. // Update the state with the new server
  299. if (result) {
  300. // Primary lastIsMaster store it
  301. if (_self.lastIsMaster() && _self.lastIsMaster().ismaster) {
  302. self.ismaster = _self.lastIsMaster();
  303. }
  304. // Remove the handlers
  305. for (var i = 0; i < handlers.length; i++) {
  306. _self.removeAllListeners(handlers[i]);
  307. }
  308. // Add stable state handlers
  309. _self.on('error', handleEvent(self, 'error'));
  310. _self.on('close', handleEvent(self, 'close'));
  311. _self.on('timeout', handleEvent(self, 'timeout'));
  312. _self.on('parseError', handleEvent(self, 'parseError'));
  313. // Enalbe the monitoring of the new server
  314. monitorServer(_self.lastIsMaster().me, self, {});
  315. // Rexecute any stalled operation
  316. rexecuteOperations(self);
  317. } else {
  318. _self.destroy({ force: true });
  319. }
  320. });
  321. } else if (event === 'connect' && self.authenticating) {
  322. this.destroy({ force: true });
  323. } else if (event === 'error') {
  324. error = err;
  325. }
  326. // Rexecute any stalled operation
  327. rexecuteOperations(self);
  328. // Are we done finish up callback
  329. if (count === 0) {
  330. callback(error);
  331. }
  332. };
  333. };
  334. // No new servers
  335. if (count === 0) return callback();
  336. // Execute method
  337. function execute(_server, i) {
  338. setTimeout(function() {
  339. // Destroyed
  340. if (self.state === DESTROYED || self.state === UNREFERENCED) {
  341. return;
  342. }
  343. // Create a new server instance
  344. var server = new Server(
  345. Object.assign({}, self.s.options, {
  346. host: _server.split(':')[0],
  347. port: parseInt(_server.split(':')[1], 10),
  348. authProviders: self.authProviders,
  349. reconnect: false,
  350. monitoring: false,
  351. parent: self,
  352. clientInfo: clone(self.s.clientInfo)
  353. })
  354. );
  355. // Add temp handlers
  356. server.once('connect', _handleEvent(self, 'connect'));
  357. server.once('close', _handleEvent(self, 'close'));
  358. server.once('timeout', _handleEvent(self, 'timeout'));
  359. server.once('error', _handleEvent(self, 'error'));
  360. server.once('parseError', _handleEvent(self, 'parseError'));
  361. // SDAM Monitoring events
  362. server.on('serverOpening', function(e) {
  363. self.emit('serverOpening', e);
  364. });
  365. server.on('serverDescriptionChanged', function(e) {
  366. self.emit('serverDescriptionChanged', e);
  367. });
  368. server.on('serverClosed', function(e) {
  369. self.emit('serverClosed', e);
  370. });
  371. server.connect(self.s.connectOptions);
  372. }, i);
  373. }
  374. // Create new instances
  375. for (var i = 0; i < servers.length; i++) {
  376. execute(servers[i], i);
  377. }
  378. }
  379. // Ping the server
  380. var pingServer = function(self, server, cb) {
  381. // Measure running time
  382. var start = new Date().getTime();
  383. // Emit the server heartbeat start
  384. emitSDAMEvent(self, 'serverHeartbeatStarted', { connectionId: server.name });
  385. // Execute ismaster
  386. // Set the socketTimeout for a monitoring message to a low number
  387. // Ensuring ismaster calls are timed out quickly
  388. server.command(
  389. 'admin.$cmd',
  390. {
  391. ismaster: true
  392. },
  393. {
  394. monitoring: true,
  395. socketTimeout: self.s.options.connectionTimeout || 2000
  396. },
  397. function(err, r) {
  398. if (self.state === DESTROYED || self.state === UNREFERENCED) {
  399. server.destroy({ force: true });
  400. return cb(err, r);
  401. }
  402. // Calculate latency
  403. var latencyMS = new Date().getTime() - start;
  404. // Set the last updatedTime
  405. var hrTime = process.hrtime();
  406. // Calculate the last update time
  407. server.lastUpdateTime = hrTime[0] * 1000 + Math.round(hrTime[1] / 1000);
  408. // We had an error, remove it from the state
  409. if (err) {
  410. // Emit the server heartbeat failure
  411. emitSDAMEvent(self, 'serverHeartbeatFailed', {
  412. durationMS: latencyMS,
  413. failure: err,
  414. connectionId: server.name
  415. });
  416. // Remove server from the state
  417. self.s.replicaSetState.remove(server);
  418. } else {
  419. // Update the server ismaster
  420. server.ismaster = r.result;
  421. // Check if we have a lastWriteDate convert it to MS
  422. // and store on the server instance for later use
  423. if (server.ismaster.lastWrite && server.ismaster.lastWrite.lastWriteDate) {
  424. server.lastWriteDate = server.ismaster.lastWrite.lastWriteDate.getTime();
  425. }
  426. // Do we have a brand new server
  427. if (server.lastIsMasterMS === -1) {
  428. server.lastIsMasterMS = latencyMS;
  429. } else if (server.lastIsMasterMS) {
  430. // After the first measurement, average RTT MUST be computed using an
  431. // exponentially-weighted moving average formula, with a weighting factor (alpha) of 0.2.
  432. // If the prior average is denoted old_rtt, then the new average (new_rtt) is
  433. // computed from a new RTT measurement (x) using the following formula:
  434. // alpha = 0.2
  435. // new_rtt = alpha * x + (1 - alpha) * old_rtt
  436. server.lastIsMasterMS = 0.2 * latencyMS + (1 - 0.2) * server.lastIsMasterMS;
  437. }
  438. if (self.s.replicaSetState.update(server)) {
  439. // Primary lastIsMaster store it
  440. if (server.lastIsMaster() && server.lastIsMaster().ismaster) {
  441. self.ismaster = server.lastIsMaster();
  442. }
  443. }
  444. // Server heart beat event
  445. emitSDAMEvent(self, 'serverHeartbeatSucceeded', {
  446. durationMS: latencyMS,
  447. reply: r.result,
  448. connectionId: server.name
  449. });
  450. }
  451. // Calculate the staleness for this server
  452. self.s.replicaSetState.updateServerMaxStaleness(server, self.s.haInterval);
  453. // Callback
  454. cb(err, r);
  455. }
  456. );
  457. };
  458. // Each server is monitored in parallel in their own timeout loop
  459. var monitorServer = function(host, self, options) {
  460. // If this is not the initial scan
  461. // Is this server already being monitoried, then skip monitoring
  462. if (!options.haInterval) {
  463. for (var i = 0; i < self.intervalIds.length; i++) {
  464. if (self.intervalIds[i].__host === host) {
  465. return;
  466. }
  467. }
  468. }
  469. // Get the haInterval
  470. var _process = options.haInterval ? Timeout : Interval;
  471. var _haInterval = options.haInterval ? options.haInterval : self.s.haInterval;
  472. // Create the interval
  473. var intervalId = new _process(function() {
  474. if (self.state === DESTROYED || self.state === UNREFERENCED) {
  475. // clearInterval(intervalId);
  476. intervalId.stop();
  477. return;
  478. }
  479. // Do we already have server connection available for this host
  480. var _server = self.s.replicaSetState.get(host);
  481. // Check if we have a known server connection and reuse
  482. if (_server) {
  483. // Ping the server
  484. return pingServer(self, _server, function(err) {
  485. if (err) {
  486. // NOTE: should something happen here?
  487. return;
  488. }
  489. if (self.state === DESTROYED || self.state === UNREFERENCED) {
  490. intervalId.stop();
  491. return;
  492. }
  493. // Filter out all called intervaliIds
  494. self.intervalIds = self.intervalIds.filter(function(intervalId) {
  495. return intervalId.isRunning();
  496. });
  497. // Initial sweep
  498. if (_process === Timeout) {
  499. if (
  500. self.state === CONNECTING &&
  501. ((self.s.replicaSetState.hasSecondary() &&
  502. self.s.options.secondaryOnlyConnectionAllowed) ||
  503. self.s.replicaSetState.hasPrimary())
  504. ) {
  505. self.state = CONNECTED;
  506. // Emit connected sign
  507. process.nextTick(function() {
  508. self.emit('connect', self);
  509. });
  510. // Start topology interval check
  511. topologyMonitor(self, {});
  512. }
  513. } else {
  514. if (
  515. self.state === DISCONNECTED &&
  516. ((self.s.replicaSetState.hasSecondary() &&
  517. self.s.options.secondaryOnlyConnectionAllowed) ||
  518. self.s.replicaSetState.hasPrimary())
  519. ) {
  520. self.state = CONNECTED;
  521. // Rexecute any stalled operation
  522. rexecuteOperations(self);
  523. // Emit connected sign
  524. process.nextTick(function() {
  525. self.emit('reconnect', self);
  526. });
  527. }
  528. }
  529. if (
  530. self.initialConnectState.connect &&
  531. !self.initialConnectState.fullsetup &&
  532. self.s.replicaSetState.hasPrimaryAndSecondary()
  533. ) {
  534. // Set initial connect state
  535. self.initialConnectState.fullsetup = true;
  536. self.initialConnectState.all = true;
  537. process.nextTick(function() {
  538. self.emit('fullsetup', self);
  539. self.emit('all', self);
  540. });
  541. }
  542. });
  543. }
  544. }, _haInterval);
  545. // Start the interval
  546. intervalId.start();
  547. // Add the intervalId host name
  548. intervalId.__host = host;
  549. // Add the intervalId to our list of intervalIds
  550. self.intervalIds.push(intervalId);
  551. };
  552. function topologyMonitor(self, options) {
  553. if (self.state === DESTROYED || self.state === UNREFERENCED) return;
  554. options = options || {};
  555. // Get the servers
  556. var servers = Object.keys(self.s.replicaSetState.set);
  557. // Get the haInterval
  558. var _process = options.haInterval ? Timeout : Interval;
  559. var _haInterval = options.haInterval ? options.haInterval : self.s.haInterval;
  560. if (_process === Timeout) {
  561. return connectNewServers(self, self.s.replicaSetState.unknownServers, function(err) {
  562. // Don't emit errors if the connection was already
  563. if (self.state === DESTROYED || self.state === UNREFERENCED) {
  564. return;
  565. }
  566. if (!self.s.replicaSetState.hasPrimary() && !self.s.options.secondaryOnlyConnectionAllowed) {
  567. if (err) {
  568. return self.emit('error', err);
  569. }
  570. self.emit(
  571. 'error',
  572. new MongoError('no primary found in replicaset or invalid replica set name')
  573. );
  574. return self.destroy({ force: true });
  575. } else if (
  576. !self.s.replicaSetState.hasSecondary() &&
  577. self.s.options.secondaryOnlyConnectionAllowed
  578. ) {
  579. if (err) {
  580. return self.emit('error', err);
  581. }
  582. self.emit(
  583. 'error',
  584. new MongoError('no secondary found in replicaset or invalid replica set name')
  585. );
  586. return self.destroy({ force: true });
  587. }
  588. for (var i = 0; i < servers.length; i++) {
  589. monitorServer(servers[i], self, options);
  590. }
  591. });
  592. } else {
  593. for (var i = 0; i < servers.length; i++) {
  594. monitorServer(servers[i], self, options);
  595. }
  596. }
  597. // Run the reconnect process
  598. function executeReconnect(self) {
  599. return function() {
  600. if (self.state === DESTROYED || self.state === UNREFERENCED) {
  601. return;
  602. }
  603. connectNewServers(self, self.s.replicaSetState.unknownServers, function() {
  604. var monitoringFrequencey = self.s.replicaSetState.hasPrimary()
  605. ? _haInterval
  606. : self.s.minHeartbeatFrequencyMS;
  607. // Create a timeout
  608. self.intervalIds.push(new Timeout(executeReconnect(self), monitoringFrequencey).start());
  609. });
  610. };
  611. }
  612. // Decide what kind of interval to use
  613. var intervalTime = !self.s.replicaSetState.hasPrimary()
  614. ? self.s.minHeartbeatFrequencyMS
  615. : _haInterval;
  616. self.intervalIds.push(new Timeout(executeReconnect(self), intervalTime).start());
  617. }
  618. function addServerToList(list, server) {
  619. for (var i = 0; i < list.length; i++) {
  620. if (list[i].name.toLowerCase() === server.name.toLowerCase()) return true;
  621. }
  622. list.push(server);
  623. }
  624. function handleEvent(self, event) {
  625. return function() {
  626. if (self.state === DESTROYED || self.state === UNREFERENCED) return;
  627. // Debug log
  628. if (self.s.logger.isDebug()) {
  629. self.s.logger.debug(
  630. f('handleEvent %s from server %s in replset with id %s', event, this.name, self.id)
  631. );
  632. }
  633. // Remove from the replicaset state
  634. self.s.replicaSetState.remove(this);
  635. // Are we in a destroyed state return
  636. if (self.state === DESTROYED || self.state === UNREFERENCED) return;
  637. // If no primary and secondary available
  638. if (
  639. !self.s.replicaSetState.hasPrimary() &&
  640. !self.s.replicaSetState.hasSecondary() &&
  641. self.s.options.secondaryOnlyConnectionAllowed
  642. ) {
  643. stateTransition(self, DISCONNECTED);
  644. } else if (!self.s.replicaSetState.hasPrimary()) {
  645. stateTransition(self, DISCONNECTED);
  646. }
  647. addServerToList(self.s.connectingServers, this);
  648. };
  649. }
  650. function applyAuthenticationContexts(self, server, callback) {
  651. if (self.s.authenticationContexts.length === 0) {
  652. return callback();
  653. }
  654. // Do not apply any auth contexts if it's an arbiter
  655. if (server.lastIsMaster() && server.lastIsMaster().arbiterOnly) {
  656. return callback();
  657. }
  658. // Copy contexts to ensure no modificiation in the middle of
  659. // auth process.
  660. var authContexts = self.s.authenticationContexts.slice(0);
  661. // Apply one of the contexts
  662. function applyAuth(authContexts, server, callback) {
  663. if (authContexts.length === 0) return callback();
  664. // Get the first auth context
  665. var authContext = authContexts.shift();
  666. // Copy the params
  667. var customAuthContext = authContext.slice(0);
  668. // Push our callback handler
  669. customAuthContext.push(function(/* err */) {
  670. applyAuth(authContexts, server, callback);
  671. });
  672. // Attempt authentication
  673. server.auth.apply(server, customAuthContext);
  674. }
  675. // Apply all auth contexts
  676. applyAuth(authContexts, server, callback);
  677. }
  678. function shouldTriggerConnect(self) {
  679. const isConnecting = self.state === CONNECTING;
  680. const hasPrimary = self.s.replicaSetState.hasPrimary();
  681. const hasSecondary = self.s.replicaSetState.hasSecondary();
  682. const secondaryOnlyConnectionAllowed = self.s.options.secondaryOnlyConnectionAllowed;
  683. const readPreferenceSecondary =
  684. self.s.connectOptions.readPreference &&
  685. self.s.connectOptions.readPreference.equals(ReadPreference.secondary);
  686. return (
  687. (isConnecting &&
  688. ((readPreferenceSecondary && hasSecondary) || (!readPreferenceSecondary && hasPrimary))) ||
  689. (hasSecondary && secondaryOnlyConnectionAllowed)
  690. );
  691. }
  692. function handleInitialConnectEvent(self, event) {
  693. return function() {
  694. var _this = this;
  695. // Debug log
  696. if (self.s.logger.isDebug()) {
  697. self.s.logger.debug(
  698. f(
  699. 'handleInitialConnectEvent %s from server %s in replset with id %s',
  700. event,
  701. this.name,
  702. self.id
  703. )
  704. );
  705. }
  706. // Destroy the instance
  707. if (self.state === DESTROYED || self.state === UNREFERENCED) {
  708. return this.destroy({ force: true });
  709. }
  710. // Check the type of server
  711. if (event === 'connect') {
  712. // Do we have authentication contexts that need to be applied
  713. applyAuthenticationContexts(self, _this, function() {
  714. // Destroy the instance
  715. if (self.state === DESTROYED || self.state === UNREFERENCED) {
  716. return _this.destroy({ force: true });
  717. }
  718. // Update the state
  719. var result = self.s.replicaSetState.update(_this);
  720. if (result === true) {
  721. // Primary lastIsMaster store it
  722. if (_this.lastIsMaster() && _this.lastIsMaster().ismaster) {
  723. self.ismaster = _this.lastIsMaster();
  724. }
  725. // Debug log
  726. if (self.s.logger.isDebug()) {
  727. self.s.logger.debug(
  728. f(
  729. 'handleInitialConnectEvent %s from server %s in replset with id %s has state [%s]',
  730. event,
  731. _this.name,
  732. self.id,
  733. JSON.stringify(self.s.replicaSetState.set)
  734. )
  735. );
  736. }
  737. // Remove the handlers
  738. for (var i = 0; i < handlers.length; i++) {
  739. _this.removeAllListeners(handlers[i]);
  740. }
  741. // Add stable state handlers
  742. _this.on('error', handleEvent(self, 'error'));
  743. _this.on('close', handleEvent(self, 'close'));
  744. _this.on('timeout', handleEvent(self, 'timeout'));
  745. _this.on('parseError', handleEvent(self, 'parseError'));
  746. // Do we have a primary or primaryAndSecondary
  747. if (shouldTriggerConnect(self)) {
  748. // We are connected
  749. self.state = CONNECTED;
  750. // Set initial connect state
  751. self.initialConnectState.connect = true;
  752. // Emit connect event
  753. process.nextTick(function() {
  754. self.emit('connect', self);
  755. });
  756. topologyMonitor(self, {});
  757. }
  758. } else if (result instanceof MongoError) {
  759. _this.destroy({ force: true });
  760. self.destroy({ force: true });
  761. return self.emit('error', result);
  762. } else {
  763. _this.destroy({ force: true });
  764. }
  765. });
  766. } else {
  767. // Emit failure to connect
  768. self.emit('failed', this);
  769. addServerToList(self.s.connectingServers, this);
  770. // Remove from the state
  771. self.s.replicaSetState.remove(this);
  772. }
  773. if (
  774. self.initialConnectState.connect &&
  775. !self.initialConnectState.fullsetup &&
  776. self.s.replicaSetState.hasPrimaryAndSecondary()
  777. ) {
  778. // Set initial connect state
  779. self.initialConnectState.fullsetup = true;
  780. self.initialConnectState.all = true;
  781. process.nextTick(function() {
  782. self.emit('fullsetup', self);
  783. self.emit('all', self);
  784. });
  785. }
  786. // Remove from the list from connectingServers
  787. for (var i = 0; i < self.s.connectingServers.length; i++) {
  788. if (self.s.connectingServers[i].equals(this)) {
  789. self.s.connectingServers.splice(i, 1);
  790. }
  791. }
  792. // Trigger topologyMonitor
  793. if (self.s.connectingServers.length === 0 && self.state === CONNECTING) {
  794. topologyMonitor(self, { haInterval: 1 });
  795. }
  796. };
  797. }
  798. function connectServers(self, servers) {
  799. // Update connectingServers
  800. self.s.connectingServers = self.s.connectingServers.concat(servers);
  801. // Index used to interleaf the server connects, avoiding
  802. // runtime issues on io constrained vm's
  803. var timeoutInterval = 0;
  804. function connect(server, timeoutInterval) {
  805. setTimeout(function() {
  806. // Add the server to the state
  807. if (self.s.replicaSetState.update(server)) {
  808. // Primary lastIsMaster store it
  809. if (server.lastIsMaster() && server.lastIsMaster().ismaster) {
  810. self.ismaster = server.lastIsMaster();
  811. }
  812. }
  813. // Add event handlers
  814. server.once('close', handleInitialConnectEvent(self, 'close'));
  815. server.once('timeout', handleInitialConnectEvent(self, 'timeout'));
  816. server.once('parseError', handleInitialConnectEvent(self, 'parseError'));
  817. server.once('error', handleInitialConnectEvent(self, 'error'));
  818. server.once('connect', handleInitialConnectEvent(self, 'connect'));
  819. // SDAM Monitoring events
  820. server.on('serverOpening', function(e) {
  821. self.emit('serverOpening', e);
  822. });
  823. server.on('serverDescriptionChanged', function(e) {
  824. self.emit('serverDescriptionChanged', e);
  825. });
  826. server.on('serverClosed', function(e) {
  827. self.emit('serverClosed', e);
  828. });
  829. // Start connection
  830. server.connect(self.s.connectOptions);
  831. }, timeoutInterval);
  832. }
  833. // Start all the servers
  834. while (servers.length > 0) {
  835. connect(servers.shift(), timeoutInterval++);
  836. }
  837. }
  838. /**
  839. * Emit event if it exists
  840. * @method
  841. */
  842. function emitSDAMEvent(self, event, description) {
  843. if (self.listeners(event).length > 0) {
  844. self.emit(event, description);
  845. }
  846. }
  847. /**
  848. * Initiate server connect
  849. * @method
  850. * @param {array} [options.auth=null] Array of auth options to apply on connect
  851. */
  852. ReplSet.prototype.connect = function(options) {
  853. var self = this;
  854. // Add any connect level options to the internal state
  855. this.s.connectOptions = options || {};
  856. // Set connecting state
  857. stateTransition(this, CONNECTING);
  858. // Create server instances
  859. var servers = this.s.seedlist.map(function(x) {
  860. return new Server(
  861. Object.assign({}, self.s.options, x, {
  862. authProviders: self.authProviders,
  863. reconnect: false,
  864. monitoring: false,
  865. parent: self,
  866. clientInfo: clone(self.s.clientInfo)
  867. })
  868. );
  869. });
  870. // Error out as high availbility interval must be < than socketTimeout
  871. if (
  872. this.s.options.socketTimeout > 0 &&
  873. this.s.options.socketTimeout <= this.s.options.haInterval
  874. ) {
  875. return self.emit(
  876. 'error',
  877. new MongoError(
  878. f(
  879. 'haInterval [%s] MS must be set to less than socketTimeout [%s] MS',
  880. this.s.options.haInterval,
  881. this.s.options.socketTimeout
  882. )
  883. )
  884. );
  885. }
  886. // Emit the topology opening event
  887. emitSDAMEvent(this, 'topologyOpening', { topologyId: this.id });
  888. // Start all server connections
  889. connectServers(self, servers);
  890. };
  891. /**
  892. * Destroy the server connection
  893. * @param {boolean} [options.force=false] Force destroy the pool
  894. * @method
  895. */
  896. ReplSet.prototype.destroy = function(options) {
  897. options = options || {};
  898. // Transition state
  899. stateTransition(this, DESTROYED);
  900. // Clear out any monitoring process
  901. if (this.haTimeoutId) clearTimeout(this.haTimeoutId);
  902. // Destroy the replicaset
  903. this.s.replicaSetState.destroy(options);
  904. // Clear out authentication contexts
  905. this.s.authenticationContexts = [];
  906. // Destroy all connecting servers
  907. this.s.connectingServers.forEach(function(x) {
  908. x.destroy(options);
  909. });
  910. // Clear out all monitoring
  911. for (var i = 0; i < this.intervalIds.length; i++) {
  912. this.intervalIds[i].stop();
  913. this.intervalIds[i].stop();
  914. }
  915. // Reset list of intervalIds
  916. this.intervalIds = [];
  917. // Emit toplogy closing event
  918. emitSDAMEvent(this, 'topologyClosed', { topologyId: this.id });
  919. };
  920. /**
  921. * Unref all connections belong to this server
  922. * @method
  923. */
  924. ReplSet.prototype.unref = function() {
  925. // Transition state
  926. stateTransition(this, UNREFERENCED);
  927. this.s.replicaSetState.allServers().forEach(function(x) {
  928. x.unref();
  929. });
  930. clearTimeout(this.haTimeoutId);
  931. };
  932. /**
  933. * Returns the last known ismaster document for this server
  934. * @method
  935. * @return {object}
  936. */
  937. ReplSet.prototype.lastIsMaster = function() {
  938. // If secondaryOnlyConnectionAllowed and no primary but secondary
  939. // return the secondaries ismaster result.
  940. if (
  941. this.s.options.secondaryOnlyConnectionAllowed &&
  942. !this.s.replicaSetState.hasPrimary() &&
  943. this.s.replicaSetState.hasSecondary()
  944. ) {
  945. return this.s.replicaSetState.secondaries[0].lastIsMaster();
  946. }
  947. return this.s.replicaSetState.primary
  948. ? this.s.replicaSetState.primary.lastIsMaster()
  949. : this.ismaster;
  950. };
  951. /**
  952. * All raw connections
  953. * @method
  954. * @return {Connection[]}
  955. */
  956. ReplSet.prototype.connections = function() {
  957. var servers = this.s.replicaSetState.allServers();
  958. var connections = [];
  959. for (var i = 0; i < servers.length; i++) {
  960. connections = connections.concat(servers[i].connections());
  961. }
  962. return connections;
  963. };
  964. /**
  965. * Figure out if the server is connected
  966. * @method
  967. * @param {ReadPreference} [options.readPreference] Specify read preference if command supports it
  968. * @return {boolean}
  969. */
  970. ReplSet.prototype.isConnected = function(options) {
  971. options = options || {};
  972. // If we are authenticating signal not connected
  973. // To avoid interleaving of operations
  974. if (this.authenticating) return false;
  975. // If we specified a read preference check if we are connected to something
  976. // than can satisfy this
  977. if (options.readPreference && options.readPreference.equals(ReadPreference.secondary)) {
  978. return this.s.replicaSetState.hasSecondary();
  979. }
  980. if (options.readPreference && options.readPreference.equals(ReadPreference.primary)) {
  981. return this.s.replicaSetState.hasPrimary();
  982. }
  983. if (options.readPreference && options.readPreference.equals(ReadPreference.primaryPreferred)) {
  984. return this.s.replicaSetState.hasSecondary() || this.s.replicaSetState.hasPrimary();
  985. }
  986. if (options.readPreference && options.readPreference.equals(ReadPreference.secondaryPreferred)) {
  987. return this.s.replicaSetState.hasSecondary() || this.s.replicaSetState.hasPrimary();
  988. }
  989. if (this.s.options.secondaryOnlyConnectionAllowed && this.s.replicaSetState.hasSecondary()) {
  990. return true;
  991. }
  992. return this.s.replicaSetState.hasPrimary();
  993. };
  994. /**
  995. * Figure out if the replicaset instance was destroyed by calling destroy
  996. * @method
  997. * @return {boolean}
  998. */
  999. ReplSet.prototype.isDestroyed = function() {
  1000. return this.state === DESTROYED;
  1001. };
  1002. /**
  1003. * Get server
  1004. * @method
  1005. * @param {ReadPreference} [options.readPreference] Specify read preference if command supports it
  1006. * @return {Server}
  1007. */
  1008. ReplSet.prototype.getServer = function(options) {
  1009. // Ensure we have no options
  1010. options = options || {};
  1011. // Pick the right server based on readPreference
  1012. var server = this.s.replicaSetState.pickServer(options.readPreference);
  1013. if (this.s.debug) this.emit('pickedServer', options.readPreference, server);
  1014. return server;
  1015. };
  1016. /**
  1017. * Get a direct connection
  1018. * @method
  1019. * @param {ReadPreference} [options.readPreference] Specify read preference if command supports it
  1020. * @return {Connection}
  1021. */
  1022. ReplSet.prototype.getConnection = function(options) {
  1023. var server = this.getServer(options);
  1024. if (server) return server.getConnection();
  1025. };
  1026. /**
  1027. * Get all connected servers
  1028. * @method
  1029. * @return {Server[]}
  1030. */
  1031. ReplSet.prototype.getServers = function() {
  1032. return this.s.replicaSetState.allServers();
  1033. };
  1034. //
  1035. // Execute write operation
  1036. function executeWriteOperation(args, options, callback) {
  1037. if (typeof options === 'function') (callback = options), (options = {});
  1038. options = options || {};
  1039. // TODO: once we drop Node 4, use destructuring either here or in arguments.
  1040. const self = args.self;
  1041. const op = args.op;
  1042. const ns = args.ns;
  1043. const ops = args.ops;
  1044. if (self.state === DESTROYED) return callback(new MongoError(f('topology was destroyed')));
  1045. const willRetryWrite =
  1046. !args.retrying && options.retryWrites && options.session && isRetryableWritesSupported(self);
  1047. if (!self.s.replicaSetState.hasPrimary()) {
  1048. if (self.s.disconnectHandler) {
  1049. // Not connected but we have a disconnecthandler
  1050. return self.s.disconnectHandler.add(op, ns, ops, options, callback);
  1051. } else if (!willRetryWrite) {
  1052. // No server returned we had an error
  1053. return callback(new MongoError('no primary server found'));
  1054. }
  1055. }
  1056. const handler = (err, result) => {
  1057. if (!err) return callback(null, result);
  1058. if (!(err instanceof errors.MongoNetworkError) && !err.message.match(/not master/)) {
  1059. return callback(err);
  1060. }
  1061. if (willRetryWrite) {
  1062. const newArgs = Object.assign({}, args, { retrying: true });
  1063. return executeWriteOperation(newArgs, options, callback);
  1064. }
  1065. // Per SDAM, remove primary from replicaset
  1066. self.s.replicaSetState.remove(self.s.replicaSetState.primary, { force: true });
  1067. return callback(err);
  1068. };
  1069. if (callback.operationId) {
  1070. handler.operationId = callback.operationId;
  1071. }
  1072. // increment and assign txnNumber
  1073. if (willRetryWrite) {
  1074. options.txnNumber = getNextTransactionNumber(options.session);
  1075. }
  1076. return self.s.replicaSetState.primary[op](ns, ops, options, handler);
  1077. }
  1078. /**
  1079. * Insert one or more documents
  1080. * @method
  1081. * @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
  1082. * @param {array} ops An array of documents to insert
  1083. * @param {boolean} [options.ordered=true] Execute in order or out of order
  1084. * @param {object} [options.writeConcern={}] Write concern for the operation
  1085. * @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
  1086. * @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
  1087. * @param {ClientSession} [options.session=null] Session to use for the operation
  1088. * @param {boolean} [options.retryWrites] Enable retryable writes for this operation
  1089. * @param {opResultCallback} callback A callback function
  1090. */
  1091. ReplSet.prototype.insert = function(ns, ops, options, callback) {
  1092. // Execute write operation
  1093. executeWriteOperation({ self: this, op: 'insert', ns, ops }, options, callback);
  1094. };
  1095. /**
  1096. * Perform one or more update operations
  1097. * @method
  1098. * @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
  1099. * @param {array} ops An array of updates
  1100. * @param {boolean} [options.ordered=true] Execute in order or out of order
  1101. * @param {object} [options.writeConcern={}] Write concern for the operation
  1102. * @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
  1103. * @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
  1104. * @param {ClientSession} [options.session=null] Session to use for the operation
  1105. * @param {boolean} [options.retryWrites] Enable retryable writes for this operation
  1106. * @param {opResultCallback} callback A callback function
  1107. */
  1108. ReplSet.prototype.update = function(ns, ops, options, callback) {
  1109. // Execute write operation
  1110. executeWriteOperation({ self: this, op: 'update', ns, ops }, options, callback);
  1111. };
  1112. /**
  1113. * Perform one or more remove operations
  1114. * @method
  1115. * @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
  1116. * @param {array} ops An array of removes
  1117. * @param {boolean} [options.ordered=true] Execute in order or out of order
  1118. * @param {object} [options.writeConcern={}] Write concern for the operation
  1119. * @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
  1120. * @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
  1121. * @param {ClientSession} [options.session=null] Session to use for the operation
  1122. * @param {boolean} [options.retryWrites] Enable retryable writes for this operation
  1123. * @param {opResultCallback} callback A callback function
  1124. */
  1125. ReplSet.prototype.remove = function(ns, ops, options, callback) {
  1126. // Execute write operation
  1127. executeWriteOperation({ self: this, op: 'remove', ns, ops }, options, callback);
  1128. };
  1129. /**
  1130. * Execute a command
  1131. * @method
  1132. * @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
  1133. * @param {object} cmd The command hash
  1134. * @param {ReadPreference} [options.readPreference] Specify read preference if command supports it
  1135. * @param {Connection} [options.connection] Specify connection object to execute command against
  1136. * @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
  1137. * @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
  1138. * @param {ClientSession} [options.session=null] Session to use for the operation
  1139. * @param {opResultCallback} callback A callback function
  1140. */
  1141. ReplSet.prototype.command = function(ns, cmd, options, callback) {
  1142. if (typeof options === 'function') {
  1143. (callback = options), (options = {}), (options = options || {});
  1144. }
  1145. if (this.state === DESTROYED) return callback(new MongoError(f('topology was destroyed')));
  1146. var self = this;
  1147. // Establish readPreference
  1148. var readPreference = options.readPreference ? options.readPreference : ReadPreference.primary;
  1149. // If the readPreference is primary and we have no primary, store it
  1150. if (
  1151. readPreference.preference === 'primary' &&
  1152. !this.s.replicaSetState.hasPrimary() &&
  1153. this.s.disconnectHandler != null
  1154. ) {
  1155. return this.s.disconnectHandler.add('command', ns, cmd, options, callback);
  1156. } else if (
  1157. readPreference.preference === 'secondary' &&
  1158. !this.s.replicaSetState.hasSecondary() &&
  1159. this.s.disconnectHandler != null
  1160. ) {
  1161. return this.s.disconnectHandler.add('command', ns, cmd, options, callback);
  1162. } else if (
  1163. readPreference.preference !== 'primary' &&
  1164. !this.s.replicaSetState.hasSecondary() &&
  1165. !this.s.replicaSetState.hasPrimary() &&
  1166. this.s.disconnectHandler != null
  1167. ) {
  1168. return this.s.disconnectHandler.add('command', ns, cmd, options, callback);
  1169. }
  1170. // Pick a server
  1171. var server = this.s.replicaSetState.pickServer(readPreference);
  1172. // We received an error, return it
  1173. if (!(server instanceof Server)) return callback(server);
  1174. // Emit debug event
  1175. if (self.s.debug) self.emit('pickedServer', ReadPreference.primary, server);
  1176. // No server returned we had an error
  1177. if (server == null) {
  1178. return callback(
  1179. new MongoError(
  1180. f('no server found that matches the provided readPreference %s', readPreference)
  1181. )
  1182. );
  1183. }
  1184. // Execute the command
  1185. server.command(ns, cmd, options, callback);
  1186. };
  1187. /**
  1188. * Authenticate using a specified mechanism
  1189. * @method
  1190. * @param {string} mechanism The Auth mechanism we are invoking
  1191. * @param {string} db The db we are invoking the mechanism against
  1192. * @param {...object} param Parameters for the specific mechanism
  1193. * @param {authResultCallback} callback A callback function
  1194. */
  1195. ReplSet.prototype.auth = function(mechanism, db) {
  1196. var allArgs = Array.prototype.slice.call(arguments, 0).slice(0);
  1197. var self = this;
  1198. var args = Array.prototype.slice.call(arguments, 2);
  1199. var callback = args.pop();
  1200. var currentContextIndex = 0;
  1201. // If we don't have the mechanism fail
  1202. if (this.authProviders[mechanism] == null && mechanism !== 'default') {
  1203. return callback(new MongoError(f('auth provider %s does not exist', mechanism)));
  1204. }
  1205. // Are we already authenticating, throw
  1206. if (this.authenticating) {
  1207. return callback(new MongoError('authentication or logout allready in process'));
  1208. }
  1209. // Topology is not connected, save the call in the provided store to be
  1210. // Executed at some point when the handler deems it's reconnected
  1211. if (self.s.disconnectHandler != null) {
  1212. if (!self.s.replicaSetState.hasPrimary() && !self.s.options.secondaryOnlyConnectionAllowed) {
  1213. return self.s.disconnectHandler.add('auth', db, allArgs, {}, callback);
  1214. } else if (
  1215. !self.s.replicaSetState.hasSecondary() &&
  1216. self.s.options.secondaryOnlyConnectionAllowed
  1217. ) {
  1218. return self.s.disconnectHandler.add('auth', db, allArgs, {}, callback);
  1219. }
  1220. }
  1221. // Set to authenticating
  1222. this.authenticating = true;
  1223. // All errors
  1224. var errors = [];
  1225. // Get all the servers
  1226. var servers = this.s.replicaSetState.allServers();
  1227. // No servers return
  1228. if (servers.length === 0) {
  1229. this.authenticating = false;
  1230. callback(null, true);
  1231. }
  1232. // Authenticate
  1233. function auth(server) {
  1234. // Arguments without a callback
  1235. var argsWithoutCallback = [mechanism, db].concat(args.slice(0));
  1236. // Create arguments
  1237. var finalArguments = argsWithoutCallback.concat([
  1238. function(err) {
  1239. count = count - 1;
  1240. // Save all the errors
  1241. if (err) errors.push({ name: server.name, err: err });
  1242. // We are done
  1243. if (count === 0) {
  1244. // Auth is done
  1245. self.authenticating = false;
  1246. // Return the auth error
  1247. if (errors.length) {
  1248. // Remove the entry from the stored authentication contexts
  1249. self.s.authenticationContexts.splice(currentContextIndex, 0);
  1250. // Return error
  1251. return callback(
  1252. new MongoError({
  1253. message: 'authentication fail',
  1254. errors: errors
  1255. }),
  1256. false
  1257. );
  1258. }
  1259. // Successfully authenticated session
  1260. callback(null, self);
  1261. }
  1262. }
  1263. ]);
  1264. if (!server.lastIsMaster().arbiterOnly) {
  1265. // Execute the auth only against non arbiter servers
  1266. server.auth.apply(server, finalArguments);
  1267. } else {
  1268. // If we are authenticating against an arbiter just ignore it
  1269. finalArguments.pop()(null);
  1270. }
  1271. }
  1272. // Get total count
  1273. var count = servers.length;
  1274. // Save current context index
  1275. currentContextIndex = this.s.authenticationContexts.length;
  1276. // Store the auth context and return the last index
  1277. this.s.authenticationContexts.push([mechanism, db].concat(args.slice(0)));
  1278. // Authenticate against all servers
  1279. while (servers.length > 0) {
  1280. auth(servers.shift());
  1281. }
  1282. };
  1283. /**
  1284. * Logout from a database
  1285. * @method
  1286. * @param {string} db The db we are logging out from
  1287. * @param {authResultCallback} callback A callback function
  1288. */
  1289. ReplSet.prototype.logout = function(dbName, callback) {
  1290. var self = this;
  1291. // Are we authenticating or logging out, throw
  1292. if (this.authenticating) {
  1293. throw new MongoError('authentication or logout allready in process');
  1294. }
  1295. // Ensure no new members are processed while logging out
  1296. this.authenticating = true;
  1297. // Remove from all auth providers (avoid any reaplication of the auth details)
  1298. var providers = Object.keys(this.authProviders);
  1299. for (var i = 0; i < providers.length; i++) {
  1300. this.authProviders[providers[i]].logout(dbName);
  1301. }
  1302. // Clear out any contexts associated with the db
  1303. self.s.authenticationContexts = self.s.authenticationContexts.filter(function(context) {
  1304. return context[1] !== dbName;
  1305. });
  1306. // Now logout all the servers
  1307. var servers = this.s.replicaSetState.allServers();
  1308. var count = servers.length;
  1309. if (count === 0) return callback();
  1310. var errors = [];
  1311. function logoutServer(_server, cb) {
  1312. _server.logout(dbName, function(err) {
  1313. if (err) errors.push({ name: _server.name, err: err });
  1314. cb();
  1315. });
  1316. }
  1317. // Execute logout on all server instances
  1318. for (i = 0; i < servers.length; i++) {
  1319. logoutServer(servers[i], function() {
  1320. count = count - 1;
  1321. if (count === 0) {
  1322. // Do not block new operations
  1323. self.authenticating = false;
  1324. // If we have one or more errors
  1325. if (errors.length)
  1326. return callback(
  1327. new MongoError({
  1328. message: f('logout failed against db %s', dbName),
  1329. errors: errors
  1330. }),
  1331. false
  1332. );
  1333. // No errors
  1334. callback();
  1335. }
  1336. });
  1337. }
  1338. };
  1339. /**
  1340. * Get a new cursor
  1341. * @method
  1342. * @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
  1343. * @param {object|Long} cmd Can be either a command returning a cursor or a cursorId
  1344. * @param {object} [options] Options for the cursor
  1345. * @param {object} [options.batchSize=0] Batchsize for the operation
  1346. * @param {array} [options.documents=[]] Initial documents list for cursor
  1347. * @param {ReadPreference} [options.readPreference] Specify read preference if command supports it
  1348. * @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
  1349. * @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
  1350. * @param {ClientSession} [options.session=null] Session to use for the operation
  1351. * @param {object} [options.topology] The internal topology of the created cursor
  1352. * @returns {Cursor}
  1353. */
  1354. ReplSet.prototype.cursor = function(ns, cmd, options) {
  1355. options = options || {};
  1356. const topology = options.topology || this;
  1357. // Set up final cursor type
  1358. var FinalCursor = options.cursorFactory || this.s.Cursor;
  1359. // Return the cursor
  1360. return new FinalCursor(this.s.bson, ns, cmd, options, topology, this.s.options);
  1361. };
  1362. /**
  1363. * A replset connect event, used to verify that the connection is up and running
  1364. *
  1365. * @event ReplSet#connect
  1366. * @type {ReplSet}
  1367. */
  1368. /**
  1369. * A replset reconnect event, used to verify that the topology reconnected
  1370. *
  1371. * @event ReplSet#reconnect
  1372. * @type {ReplSet}
  1373. */
  1374. /**
  1375. * A replset fullsetup event, used to signal that all topology members have been contacted.
  1376. *
  1377. * @event ReplSet#fullsetup
  1378. * @type {ReplSet}
  1379. */
  1380. /**
  1381. * A replset all event, used to signal that all topology members have been contacted.
  1382. *
  1383. * @event ReplSet#all
  1384. * @type {ReplSet}
  1385. */
  1386. /**
  1387. * A replset failed event, used to signal that initial replset connection failed.
  1388. *
  1389. * @event ReplSet#failed
  1390. * @type {ReplSet}
  1391. */
  1392. /**
  1393. * A server member left the replicaset
  1394. *
  1395. * @event ReplSet#left
  1396. * @type {function}
  1397. * @param {string} type The type of member that left (primary|secondary|arbiter)
  1398. * @param {Server} server The server object that left
  1399. */
  1400. /**
  1401. * A server member joined the replicaset
  1402. *
  1403. * @event ReplSet#joined
  1404. * @type {function}
  1405. * @param {string} type The type of member that joined (primary|secondary|arbiter)
  1406. * @param {Server} server The server object that joined
  1407. */
  1408. /**
  1409. * A server opening SDAM monitoring event
  1410. *
  1411. * @event ReplSet#serverOpening
  1412. * @type {object}
  1413. */
  1414. /**
  1415. * A server closed SDAM monitoring event
  1416. *
  1417. * @event ReplSet#serverClosed
  1418. * @type {object}
  1419. */
  1420. /**
  1421. * A server description SDAM change monitoring event
  1422. *
  1423. * @event ReplSet#serverDescriptionChanged
  1424. * @type {object}
  1425. */
  1426. /**
  1427. * A topology open SDAM event
  1428. *
  1429. * @event ReplSet#topologyOpening
  1430. * @type {object}
  1431. */
  1432. /**
  1433. * A topology closed SDAM event
  1434. *
  1435. * @event ReplSet#topologyClosed
  1436. * @type {object}
  1437. */
  1438. /**
  1439. * A topology structure SDAM change event
  1440. *
  1441. * @event ReplSet#topologyDescriptionChanged
  1442. * @type {object}
  1443. */
  1444. /**
  1445. * A topology serverHeartbeatStarted SDAM event
  1446. *
  1447. * @event ReplSet#serverHeartbeatStarted
  1448. * @type {object}
  1449. */
  1450. /**
  1451. * A topology serverHeartbeatFailed SDAM event
  1452. *
  1453. * @event ReplSet#serverHeartbeatFailed
  1454. * @type {object}
  1455. */
  1456. /**
  1457. * A topology serverHeartbeatSucceeded SDAM change event
  1458. *
  1459. * @event ReplSet#serverHeartbeatSucceeded
  1460. * @type {object}
  1461. */
  1462. module.exports = ReplSet;