Ohm-Management - Projektarbeit B-ME
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

replset.js 54KB

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