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.

server.js 36KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143
  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. Logger = require('../connection/logger'),
  7. debugOptions = require('../connection/utils').debugOptions,
  8. retrieveBSON = require('../connection/utils').retrieveBSON,
  9. Pool = require('../connection/pool'),
  10. Query = require('../connection/commands').Query,
  11. MongoError = require('../error').MongoError,
  12. MongoNetworkError = require('../error').MongoNetworkError,
  13. TwoSixWireProtocolSupport = require('../wireprotocol/2_6_support'),
  14. ThreeTwoWireProtocolSupport = require('../wireprotocol/3_2_support'),
  15. BasicCursor = require('../cursor'),
  16. sdam = require('./shared'),
  17. createClientInfo = require('./shared').createClientInfo,
  18. createCompressionInfo = require('./shared').createCompressionInfo,
  19. resolveClusterTime = require('./shared').resolveClusterTime,
  20. SessionMixins = require('./shared').SessionMixins,
  21. relayEvents = require('../utils').relayEvents;
  22. const collationNotSupported = require('../utils').collationNotSupported;
  23. function getSaslSupportedMechs(options) {
  24. if (!options) {
  25. return {};
  26. }
  27. const authArray = options.auth || [];
  28. const authMechanism = authArray[0] || options.authMechanism;
  29. const authSource = authArray[1] || options.authSource || options.dbName || 'admin';
  30. const user = authArray[2] || options.user;
  31. if (typeof authMechanism === 'string' && authMechanism.toUpperCase() !== 'DEFAULT') {
  32. return {};
  33. }
  34. if (!user) {
  35. return {};
  36. }
  37. return { saslSupportedMechs: `${authSource}.${user}` };
  38. }
  39. function getDefaultAuthMechanism(ismaster) {
  40. if (ismaster) {
  41. // If ismaster contains saslSupportedMechs, use scram-sha-256
  42. // if it is available, else scram-sha-1
  43. if (Array.isArray(ismaster.saslSupportedMechs)) {
  44. return ismaster.saslSupportedMechs.indexOf('SCRAM-SHA-256') >= 0
  45. ? 'scram-sha-256'
  46. : 'scram-sha-1';
  47. }
  48. // Fallback to legacy selection method. If wire version >= 3, use scram-sha-1
  49. if (ismaster.maxWireVersion >= 3) {
  50. return 'scram-sha-1';
  51. }
  52. }
  53. // Default for wireprotocol < 3
  54. return 'mongocr';
  55. }
  56. function extractIsMasterError(err, result) {
  57. if (err) {
  58. return err;
  59. }
  60. if (result && result.result && result.result.ok === 0) {
  61. return new MongoError(result.result);
  62. }
  63. }
  64. // Used for filtering out fields for loggin
  65. var debugFields = [
  66. 'reconnect',
  67. 'reconnectTries',
  68. 'reconnectInterval',
  69. 'emitError',
  70. 'cursorFactory',
  71. 'host',
  72. 'port',
  73. 'size',
  74. 'keepAlive',
  75. 'keepAliveInitialDelay',
  76. 'noDelay',
  77. 'connectionTimeout',
  78. 'checkServerIdentity',
  79. 'socketTimeout',
  80. 'singleBufferSerializtion',
  81. 'ssl',
  82. 'ca',
  83. 'crl',
  84. 'cert',
  85. 'key',
  86. 'rejectUnauthorized',
  87. 'promoteLongs',
  88. 'promoteValues',
  89. 'promoteBuffers',
  90. 'servername'
  91. ];
  92. // Server instance id
  93. var id = 0;
  94. var serverAccounting = false;
  95. var servers = {};
  96. var BSON = retrieveBSON();
  97. /**
  98. * Creates a new Server instance
  99. * @class
  100. * @param {boolean} [options.reconnect=true] Server will attempt to reconnect on loss of connection
  101. * @param {number} [options.reconnectTries=30] Server attempt to reconnect #times
  102. * @param {number} [options.reconnectInterval=1000] Server will wait # milliseconds between retries
  103. * @param {number} [options.monitoring=true] Enable the server state monitoring (calling ismaster at monitoringInterval)
  104. * @param {number} [options.monitoringInterval=5000] The interval of calling ismaster when monitoring is enabled.
  105. * @param {Cursor} [options.cursorFactory=Cursor] The cursor factory class used for all query cursors
  106. * @param {string} options.host The server host
  107. * @param {number} options.port The server port
  108. * @param {number} [options.size=5] Server connection pool size
  109. * @param {boolean} [options.keepAlive=true] TCP Connection keep alive enabled
  110. * @param {number} [options.keepAliveInitialDelay=300000] Initial delay before TCP keep alive enabled
  111. * @param {boolean} [options.noDelay=true] TCP Connection no delay
  112. * @param {number} [options.connectionTimeout=30000] TCP Connection timeout setting
  113. * @param {number} [options.socketTimeout=360000] TCP Socket timeout setting
  114. * @param {boolean} [options.ssl=false] Use SSL for connection
  115. * @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.
  116. * @param {Buffer} [options.ca] SSL Certificate store binary buffer
  117. * @param {Buffer} [options.crl] SSL Certificate revocation store binary buffer
  118. * @param {Buffer} [options.cert] SSL Certificate binary buffer
  119. * @param {Buffer} [options.key] SSL Key file binary buffer
  120. * @param {string} [options.passphrase] SSL Certificate pass phrase
  121. * @param {boolean} [options.rejectUnauthorized=true] Reject unauthorized server certificates
  122. * @param {string} [options.servername=null] String containing the server name requested via TLS SNI.
  123. * @param {boolean} [options.promoteLongs=true] Convert Long values from the db into Numbers if they fit into 53 bits
  124. * @param {boolean} [options.promoteValues=true] Promotes BSON values to native types where possible, set to false to only receive wrapper types.
  125. * @param {boolean} [options.promoteBuffers=false] Promotes Binary BSON values to native Node Buffers.
  126. * @param {string} [options.appname=null] Application name, passed in on ismaster call and logged in mongod server logs. Maximum size 128 bytes.
  127. * @param {boolean} [options.domainsEnabled=false] Enable the wrapping of the callback in the current domain, disabled by default to avoid perf hit.
  128. * @param {boolean} [options.monitorCommands=false] Enable command monitoring for this topology
  129. * @return {Server} A cursor instance
  130. * @fires Server#connect
  131. * @fires Server#close
  132. * @fires Server#error
  133. * @fires Server#timeout
  134. * @fires Server#parseError
  135. * @fires Server#reconnect
  136. * @fires Server#reconnectFailed
  137. * @fires Server#serverHeartbeatStarted
  138. * @fires Server#serverHeartbeatSucceeded
  139. * @fires Server#serverHeartbeatFailed
  140. * @fires Server#topologyOpening
  141. * @fires Server#topologyClosed
  142. * @fires Server#topologyDescriptionChanged
  143. * @property {string} type the topology type.
  144. * @property {string} parserType the parser type used (c++ or js).
  145. */
  146. var Server = function(options) {
  147. options = options || {};
  148. // Add event listener
  149. EventEmitter.call(this);
  150. // Server instance id
  151. this.id = id++;
  152. // Internal state
  153. this.s = {
  154. // Options
  155. options: options,
  156. // Logger
  157. logger: Logger('Server', options),
  158. // Factory overrides
  159. Cursor: options.cursorFactory || BasicCursor,
  160. // BSON instance
  161. bson:
  162. options.bson ||
  163. new BSON([
  164. BSON.Binary,
  165. BSON.Code,
  166. BSON.DBRef,
  167. BSON.Decimal128,
  168. BSON.Double,
  169. BSON.Int32,
  170. BSON.Long,
  171. BSON.Map,
  172. BSON.MaxKey,
  173. BSON.MinKey,
  174. BSON.ObjectId,
  175. BSON.BSONRegExp,
  176. BSON.Symbol,
  177. BSON.Timestamp
  178. ]),
  179. // Pool
  180. pool: null,
  181. // Disconnect handler
  182. disconnectHandler: options.disconnectHandler,
  183. // Monitor thread (keeps the connection alive)
  184. monitoring: typeof options.monitoring === 'boolean' ? options.monitoring : true,
  185. // Is the server in a topology
  186. inTopology: !!options.parent,
  187. // Monitoring timeout
  188. monitoringInterval:
  189. typeof options.monitoringInterval === 'number' ? options.monitoringInterval : 5000,
  190. // Topology id
  191. topologyId: -1,
  192. compression: { compressors: createCompressionInfo(options) },
  193. // Optional parent topology
  194. parent: options.parent
  195. };
  196. // If this is a single deployment we need to track the clusterTime here
  197. if (!this.s.parent) {
  198. this.s.clusterTime = null;
  199. }
  200. // Curent ismaster
  201. this.ismaster = null;
  202. // Current ping time
  203. this.lastIsMasterMS = -1;
  204. // The monitoringProcessId
  205. this.monitoringProcessId = null;
  206. // Initial connection
  207. this.initialConnect = true;
  208. // Wire protocol handler, default to oldest known protocol handler
  209. // this gets changed when the first ismaster is called.
  210. this.wireProtocolHandler = new TwoSixWireProtocolSupport();
  211. // Default type
  212. this._type = 'server';
  213. // Set the client info
  214. this.clientInfo = createClientInfo(options);
  215. // Max Stalleness values
  216. // last time we updated the ismaster state
  217. this.lastUpdateTime = 0;
  218. // Last write time
  219. this.lastWriteDate = 0;
  220. // Stalleness
  221. this.staleness = 0;
  222. };
  223. inherits(Server, EventEmitter);
  224. Object.assign(Server.prototype, SessionMixins);
  225. Object.defineProperty(Server.prototype, 'type', {
  226. enumerable: true,
  227. get: function() {
  228. return this._type;
  229. }
  230. });
  231. Object.defineProperty(Server.prototype, 'parserType', {
  232. enumerable: true,
  233. get: function() {
  234. return BSON.native ? 'c++' : 'js';
  235. }
  236. });
  237. Object.defineProperty(Server.prototype, 'logicalSessionTimeoutMinutes', {
  238. enumerable: true,
  239. get: function() {
  240. if (!this.ismaster) return null;
  241. return this.ismaster.logicalSessionTimeoutMinutes || null;
  242. }
  243. });
  244. // In single server deployments we track the clusterTime directly on the topology, however
  245. // in Mongos and ReplSet deployments we instead need to delegate the clusterTime up to the
  246. // tracking objects so we can ensure we are gossiping the maximum time received from the
  247. // server.
  248. Object.defineProperty(Server.prototype, 'clusterTime', {
  249. enumerable: true,
  250. set: function(clusterTime) {
  251. const settings = this.s.parent ? this.s.parent : this.s;
  252. resolveClusterTime(settings, clusterTime);
  253. },
  254. get: function() {
  255. const settings = this.s.parent ? this.s.parent : this.s;
  256. return settings.clusterTime || null;
  257. }
  258. });
  259. Server.enableServerAccounting = function() {
  260. serverAccounting = true;
  261. servers = {};
  262. };
  263. Server.disableServerAccounting = function() {
  264. serverAccounting = false;
  265. };
  266. Server.servers = function() {
  267. return servers;
  268. };
  269. Object.defineProperty(Server.prototype, 'name', {
  270. enumerable: true,
  271. get: function() {
  272. return this.s.options.host + ':' + this.s.options.port;
  273. }
  274. });
  275. function isSupportedServer(response) {
  276. return response && typeof response.maxWireVersion === 'number' && response.maxWireVersion >= 2;
  277. }
  278. function configureWireProtocolHandler(self, ismaster) {
  279. // 3.2 wire protocol handler
  280. if (ismaster.maxWireVersion >= 4) {
  281. return new ThreeTwoWireProtocolSupport();
  282. }
  283. // default to 2.6 wire protocol handler
  284. return new TwoSixWireProtocolSupport();
  285. }
  286. function disconnectHandler(self, type, ns, cmd, options, callback) {
  287. // Topology is not connected, save the call in the provided store to be
  288. // Executed at some point when the handler deems it's reconnected
  289. if (
  290. !self.s.pool.isConnected() &&
  291. self.s.options.reconnect &&
  292. self.s.disconnectHandler != null &&
  293. !options.monitoring
  294. ) {
  295. self.s.disconnectHandler.add(type, ns, cmd, options, callback);
  296. return true;
  297. }
  298. // If we have no connection error
  299. if (!self.s.pool.isConnected()) {
  300. callback(new MongoError(f('no connection available to server %s', self.name)));
  301. return true;
  302. }
  303. }
  304. function monitoringProcess(self) {
  305. return function() {
  306. // Pool was destroyed do not continue process
  307. if (self.s.pool.isDestroyed()) return;
  308. // Emit monitoring Process event
  309. self.emit('monitoring', self);
  310. // Perform ismaster call
  311. // Query options
  312. var queryOptions = { numberToSkip: 0, numberToReturn: -1, checkKeys: false, slaveOk: true };
  313. // Create a query instance
  314. var query = new Query(self.s.bson, 'admin.$cmd', { ismaster: true }, queryOptions);
  315. // Get start time
  316. var start = new Date().getTime();
  317. // Execute the ismaster query
  318. self.s.pool.write(
  319. query,
  320. {
  321. socketTimeout:
  322. typeof self.s.options.connectionTimeout !== 'number'
  323. ? 2000
  324. : self.s.options.connectionTimeout,
  325. monitoring: true
  326. },
  327. function(err, result) {
  328. // Set initial lastIsMasterMS
  329. self.lastIsMasterMS = new Date().getTime() - start;
  330. if (self.s.pool.isDestroyed()) return;
  331. // Update the ismaster view if we have a result
  332. if (result) {
  333. self.ismaster = result.result;
  334. }
  335. // Re-schedule the monitoring process
  336. self.monitoringProcessId = setTimeout(monitoringProcess(self), self.s.monitoringInterval);
  337. }
  338. );
  339. };
  340. }
  341. var eventHandler = function(self, event) {
  342. return function(err) {
  343. // Log information of received information if in info mode
  344. if (self.s.logger.isInfo()) {
  345. var object = err instanceof MongoError ? JSON.stringify(err) : {};
  346. self.s.logger.info(
  347. f('server %s fired event %s out with message %s', self.name, event, object)
  348. );
  349. }
  350. // Handle connect event
  351. if (event === 'connect') {
  352. // Issue an ismaster command at connect
  353. // Query options
  354. var queryOptions = { numberToSkip: 0, numberToReturn: -1, checkKeys: false, slaveOk: true };
  355. // Create a query instance
  356. var compressors =
  357. self.s.compression && self.s.compression.compressors ? self.s.compression.compressors : [];
  358. var query = new Query(
  359. self.s.bson,
  360. 'admin.$cmd',
  361. Object.assign(
  362. { ismaster: true, client: self.clientInfo, compression: compressors },
  363. getSaslSupportedMechs(self.s.options)
  364. ),
  365. queryOptions
  366. );
  367. // Get start time
  368. var start = new Date().getTime();
  369. // Execute the ismaster query
  370. self.s.pool.write(
  371. query,
  372. {
  373. socketTimeout: self.s.options.connectionTimeout || 2000
  374. },
  375. function(err, result) {
  376. // Set initial lastIsMasterMS
  377. self.lastIsMasterMS = new Date().getTime() - start;
  378. const serverError = extractIsMasterError(err, result);
  379. if (serverError) {
  380. self.destroy();
  381. return self.emit('error', serverError);
  382. }
  383. if (!isSupportedServer(result.result)) {
  384. self.destroy();
  385. const latestSupportedVersion = '2.6';
  386. const message =
  387. 'Server at ' +
  388. self.s.options.host +
  389. ':' +
  390. self.s.options.port +
  391. ' reports wire version ' +
  392. (result.result.maxWireVersion || 0) +
  393. ', but this version of Node.js Driver requires at least 2 (MongoDB' +
  394. latestSupportedVersion +
  395. ').';
  396. return self.emit('error', new MongoError(message), self);
  397. }
  398. // Determine whether the server is instructing us to use a compressor
  399. if (result.result && result.result.compression) {
  400. for (var i = 0; i < self.s.compression.compressors.length; i++) {
  401. if (result.result.compression.indexOf(self.s.compression.compressors[i]) > -1) {
  402. self.s.pool.options.agreedCompressor = self.s.compression.compressors[i];
  403. break;
  404. }
  405. }
  406. if (self.s.compression.zlibCompressionLevel) {
  407. self.s.pool.options.zlibCompressionLevel = self.s.compression.zlibCompressionLevel;
  408. }
  409. }
  410. // Ensure no error emitted after initial connect when reconnecting
  411. self.initialConnect = false;
  412. // Save the ismaster
  413. self.ismaster = result.result;
  414. // It's a proxy change the type so
  415. // the wireprotocol will send $readPreference
  416. if (self.ismaster.msg === 'isdbgrid') {
  417. self._type = 'mongos';
  418. }
  419. // Add the correct wire protocol handler
  420. self.wireProtocolHandler = configureWireProtocolHandler(self, self.ismaster);
  421. // Have we defined self monitoring
  422. if (self.s.monitoring) {
  423. self.monitoringProcessId = setTimeout(
  424. monitoringProcess(self),
  425. self.s.monitoringInterval
  426. );
  427. }
  428. // Emit server description changed if something listening
  429. sdam.emitServerDescriptionChanged(self, {
  430. address: self.name,
  431. arbiters: [],
  432. hosts: [],
  433. passives: [],
  434. type: sdam.getTopologyType(self)
  435. });
  436. if (!self.s.inTopology) {
  437. // Emit topology description changed if something listening
  438. sdam.emitTopologyDescriptionChanged(self, {
  439. topologyType: 'Single',
  440. servers: [
  441. {
  442. address: self.name,
  443. arbiters: [],
  444. hosts: [],
  445. passives: [],
  446. type: sdam.getTopologyType(self)
  447. }
  448. ]
  449. });
  450. }
  451. // Log the ismaster if available
  452. if (self.s.logger.isInfo()) {
  453. self.s.logger.info(
  454. f('server %s connected with ismaster [%s]', self.name, JSON.stringify(self.ismaster))
  455. );
  456. }
  457. // Emit connect
  458. self.emit('connect', self);
  459. }
  460. );
  461. } else if (
  462. event === 'error' ||
  463. event === 'parseError' ||
  464. event === 'close' ||
  465. event === 'timeout' ||
  466. event === 'reconnect' ||
  467. event === 'attemptReconnect' ||
  468. 'reconnectFailed'
  469. ) {
  470. // Remove server instance from accounting
  471. if (
  472. serverAccounting &&
  473. ['close', 'timeout', 'error', 'parseError', 'reconnectFailed'].indexOf(event) !== -1
  474. ) {
  475. // Emit toplogy opening event if not in topology
  476. if (!self.s.inTopology) {
  477. self.emit('topologyOpening', { topologyId: self.id });
  478. }
  479. delete servers[self.id];
  480. }
  481. if (event === 'close') {
  482. // Closing emits a server description changed event going to unknown.
  483. sdam.emitServerDescriptionChanged(self, {
  484. address: self.name,
  485. arbiters: [],
  486. hosts: [],
  487. passives: [],
  488. type: 'Unknown'
  489. });
  490. }
  491. // Reconnect failed return error
  492. if (event === 'reconnectFailed') {
  493. self.emit('reconnectFailed', err);
  494. // Emit error if any listeners
  495. if (self.listeners('error').length > 0) {
  496. self.emit('error', err);
  497. }
  498. // Terminate
  499. return;
  500. }
  501. // On first connect fail
  502. if (
  503. self.s.pool.state === 'disconnected' &&
  504. self.initialConnect &&
  505. ['close', 'timeout', 'error', 'parseError'].indexOf(event) !== -1
  506. ) {
  507. self.initialConnect = false;
  508. return self.emit(
  509. 'error',
  510. new MongoNetworkError(
  511. f('failed to connect to server [%s] on first connect [%s]', self.name, err)
  512. )
  513. );
  514. }
  515. // Reconnect event, emit the server
  516. if (event === 'reconnect') {
  517. // Reconnecting emits a server description changed event going from unknown to the
  518. // current server type.
  519. sdam.emitServerDescriptionChanged(self, {
  520. address: self.name,
  521. arbiters: [],
  522. hosts: [],
  523. passives: [],
  524. type: sdam.getTopologyType(self)
  525. });
  526. return self.emit(event, self);
  527. }
  528. // Emit the event
  529. self.emit(event, err);
  530. }
  531. };
  532. };
  533. /**
  534. * Initiate server connect
  535. * @method
  536. * @param {array} [options.auth=null] Array of auth options to apply on connect
  537. */
  538. Server.prototype.connect = function(options) {
  539. var self = this;
  540. options = options || {};
  541. // Set the connections
  542. if (serverAccounting) servers[this.id] = this;
  543. // Do not allow connect to be called on anything that's not disconnected
  544. if (self.s.pool && !self.s.pool.isDisconnected() && !self.s.pool.isDestroyed()) {
  545. throw new MongoError(f('server instance in invalid state %s', self.s.pool.state));
  546. }
  547. // Create a pool
  548. self.s.pool = new Pool(this, Object.assign(self.s.options, options, { bson: this.s.bson }));
  549. // Set up listeners
  550. self.s.pool.on('close', eventHandler(self, 'close'));
  551. self.s.pool.on('error', eventHandler(self, 'error'));
  552. self.s.pool.on('timeout', eventHandler(self, 'timeout'));
  553. self.s.pool.on('parseError', eventHandler(self, 'parseError'));
  554. self.s.pool.on('connect', eventHandler(self, 'connect'));
  555. self.s.pool.on('reconnect', eventHandler(self, 'reconnect'));
  556. self.s.pool.on('reconnectFailed', eventHandler(self, 'reconnectFailed'));
  557. // Set up listeners for command monitoring
  558. relayEvents(self.s.pool, self, ['commandStarted', 'commandSucceeded', 'commandFailed']);
  559. // Emit toplogy opening event if not in topology
  560. if (!self.s.inTopology) {
  561. this.emit('topologyOpening', { topologyId: self.id });
  562. }
  563. // Emit opening server event
  564. self.emit('serverOpening', {
  565. topologyId: self.s.topologyId !== -1 ? self.s.topologyId : self.id,
  566. address: self.name
  567. });
  568. // Connect with optional auth settings
  569. if (options.auth) {
  570. self.s.pool.connect.apply(self.s.pool, options.auth);
  571. } else {
  572. self.s.pool.connect();
  573. }
  574. };
  575. /**
  576. * Get the server description
  577. * @method
  578. * @return {object}
  579. */
  580. Server.prototype.getDescription = function() {
  581. var ismaster = this.ismaster || {};
  582. var description = {
  583. type: sdam.getTopologyType(this),
  584. address: this.name
  585. };
  586. // Add fields if available
  587. if (ismaster.hosts) description.hosts = ismaster.hosts;
  588. if (ismaster.arbiters) description.arbiters = ismaster.arbiters;
  589. if (ismaster.passives) description.passives = ismaster.passives;
  590. if (ismaster.setName) description.setName = ismaster.setName;
  591. return description;
  592. };
  593. /**
  594. * Returns the last known ismaster document for this server
  595. * @method
  596. * @return {object}
  597. */
  598. Server.prototype.lastIsMaster = function() {
  599. return this.ismaster;
  600. };
  601. /**
  602. * Unref all connections belong to this server
  603. * @method
  604. */
  605. Server.prototype.unref = function() {
  606. this.s.pool.unref();
  607. };
  608. /**
  609. * Figure out if the server is connected
  610. * @method
  611. * @return {boolean}
  612. */
  613. Server.prototype.isConnected = function() {
  614. if (!this.s.pool) return false;
  615. return this.s.pool.isConnected();
  616. };
  617. /**
  618. * Figure out if the server instance was destroyed by calling destroy
  619. * @method
  620. * @return {boolean}
  621. */
  622. Server.prototype.isDestroyed = function() {
  623. if (!this.s.pool) return false;
  624. return this.s.pool.isDestroyed();
  625. };
  626. function basicWriteValidations(self) {
  627. if (!self.s.pool) return new MongoError('server instance is not connected');
  628. if (self.s.pool.isDestroyed()) return new MongoError('server instance pool was destroyed');
  629. }
  630. function basicReadValidations(self, options) {
  631. basicWriteValidations(self, options);
  632. if (options.readPreference && !(options.readPreference instanceof ReadPreference)) {
  633. throw new Error('readPreference must be an instance of ReadPreference');
  634. }
  635. }
  636. /**
  637. * Execute a command
  638. * @method
  639. * @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
  640. * @param {object} cmd The command hash
  641. * @param {ReadPreference} [options.readPreference] Specify read preference if command supports it
  642. * @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
  643. * @param {Boolean} [options.checkKeys=false] Specify if the bson parser should validate keys.
  644. * @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
  645. * @param {Boolean} [options.fullResult=false] Return the full envelope instead of just the result document.
  646. * @param {ClientSession} [options.session=null] Session to use for the operation
  647. * @param {opResultCallback} callback A callback function
  648. */
  649. Server.prototype.command = function(ns, cmd, options, callback) {
  650. var self = this;
  651. if (typeof options === 'function') {
  652. (callback = options), (options = {}), (options = options || {});
  653. }
  654. var result = basicReadValidations(self, options);
  655. if (result) return callback(result);
  656. // Clone the options
  657. options = Object.assign({}, options, { wireProtocolCommand: false });
  658. // Debug log
  659. if (self.s.logger.isDebug())
  660. self.s.logger.debug(
  661. f(
  662. 'executing command [%s] against %s',
  663. JSON.stringify({
  664. ns: ns,
  665. cmd: cmd,
  666. options: debugOptions(debugFields, options)
  667. }),
  668. self.name
  669. )
  670. );
  671. // If we are not connected or have a disconnectHandler specified
  672. if (disconnectHandler(self, 'command', ns, cmd, options, callback)) return;
  673. // error if collation not supported
  674. if (collationNotSupported(this, cmd)) {
  675. return callback(new MongoError(`server ${this.name} does not support collation`));
  676. }
  677. // Are we executing against a specific topology
  678. var topology = options.topology || {};
  679. // Create the query object
  680. var query = self.wireProtocolHandler.command(self.s.bson, ns, cmd, {}, topology, options);
  681. if (query instanceof MongoError) {
  682. return callback(query, null);
  683. }
  684. // Set slave OK of the query
  685. query.slaveOk = options.readPreference ? options.readPreference.slaveOk() : false;
  686. // Write options
  687. var writeOptions = {
  688. raw: typeof options.raw === 'boolean' ? options.raw : false,
  689. promoteLongs: typeof options.promoteLongs === 'boolean' ? options.promoteLongs : true,
  690. promoteValues: typeof options.promoteValues === 'boolean' ? options.promoteValues : true,
  691. promoteBuffers: typeof options.promoteBuffers === 'boolean' ? options.promoteBuffers : false,
  692. command: true,
  693. monitoring: typeof options.monitoring === 'boolean' ? options.monitoring : false,
  694. fullResult: typeof options.fullResult === 'boolean' ? options.fullResult : false,
  695. requestId: query.requestId,
  696. socketTimeout: typeof options.socketTimeout === 'number' ? options.socketTimeout : null,
  697. session: options.session || null
  698. };
  699. // Write the operation to the pool
  700. self.s.pool.write(query, writeOptions, callback);
  701. };
  702. /**
  703. * Insert one or more documents
  704. * @method
  705. * @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
  706. * @param {array} ops An array of documents to insert
  707. * @param {boolean} [options.ordered=true] Execute in order or out of order
  708. * @param {object} [options.writeConcern={}] Write concern for the operation
  709. * @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
  710. * @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
  711. * @param {ClientSession} [options.session=null] Session to use for the operation
  712. * @param {opResultCallback} callback A callback function
  713. */
  714. Server.prototype.insert = function(ns, ops, options, callback) {
  715. var self = this;
  716. if (typeof options === 'function') {
  717. (callback = options), (options = {}), (options = options || {});
  718. }
  719. var result = basicWriteValidations(self, options);
  720. if (result) return callback(result);
  721. // If we are not connected or have a disconnectHandler specified
  722. if (disconnectHandler(self, 'insert', ns, ops, options, callback)) return;
  723. // Setup the docs as an array
  724. ops = Array.isArray(ops) ? ops : [ops];
  725. // Execute write
  726. return self.wireProtocolHandler.insert(self.s.pool, ns, self.s.bson, ops, options, callback);
  727. };
  728. /**
  729. * Perform one or more update operations
  730. * @method
  731. * @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
  732. * @param {array} ops An array of updates
  733. * @param {boolean} [options.ordered=true] Execute in order or out of order
  734. * @param {object} [options.writeConcern={}] Write concern for the operation
  735. * @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
  736. * @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
  737. * @param {ClientSession} [options.session=null] Session to use for the operation
  738. * @param {opResultCallback} callback A callback function
  739. */
  740. Server.prototype.update = function(ns, ops, options, callback) {
  741. var self = this;
  742. if (typeof options === 'function') {
  743. (callback = options), (options = {}), (options = options || {});
  744. }
  745. var result = basicWriteValidations(self, options);
  746. if (result) return callback(result);
  747. // If we are not connected or have a disconnectHandler specified
  748. if (disconnectHandler(self, 'update', ns, ops, options, callback)) return;
  749. // error if collation not supported
  750. if (collationNotSupported(this, options)) {
  751. return callback(new MongoError(`server ${this.name} does not support collation`));
  752. }
  753. // Setup the docs as an array
  754. ops = Array.isArray(ops) ? ops : [ops];
  755. // Execute write
  756. return self.wireProtocolHandler.update(self.s.pool, ns, self.s.bson, ops, options, callback);
  757. };
  758. /**
  759. * Perform one or more remove operations
  760. * @method
  761. * @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
  762. * @param {array} ops An array of removes
  763. * @param {boolean} [options.ordered=true] Execute in order or out of order
  764. * @param {object} [options.writeConcern={}] Write concern for the operation
  765. * @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
  766. * @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
  767. * @param {ClientSession} [options.session=null] Session to use for the operation
  768. * @param {opResultCallback} callback A callback function
  769. */
  770. Server.prototype.remove = function(ns, ops, options, callback) {
  771. var self = this;
  772. if (typeof options === 'function') {
  773. (callback = options), (options = {}), (options = options || {});
  774. }
  775. var result = basicWriteValidations(self, options);
  776. if (result) return callback(result);
  777. // If we are not connected or have a disconnectHandler specified
  778. if (disconnectHandler(self, 'remove', ns, ops, options, callback)) return;
  779. // error if collation not supported
  780. if (collationNotSupported(this, options)) {
  781. return callback(new MongoError(`server ${this.name} does not support collation`));
  782. }
  783. // Setup the docs as an array
  784. ops = Array.isArray(ops) ? ops : [ops];
  785. // Execute write
  786. return self.wireProtocolHandler.remove(self.s.pool, ns, self.s.bson, ops, options, callback);
  787. };
  788. /**
  789. * Get a new cursor
  790. * @method
  791. * @param {string} ns The MongoDB fully qualified namespace (ex: db1.collection1)
  792. * @param {object|Long} cmd Can be either a command returning a cursor or a cursorId
  793. * @param {object} [options] Options for the cursor
  794. * @param {object} [options.batchSize=0] Batchsize for the operation
  795. * @param {array} [options.documents=[]] Initial documents list for cursor
  796. * @param {ReadPreference} [options.readPreference] Specify read preference if command supports it
  797. * @param {Boolean} [options.serializeFunctions=false] Specify if functions on an object should be serialized.
  798. * @param {Boolean} [options.ignoreUndefined=false] Specify if the BSON serializer should ignore undefined fields.
  799. * @param {ClientSession} [options.session=null] Session to use for the operation
  800. * @param {object} [options.topology] The internal topology of the created cursor
  801. * @returns {Cursor}
  802. */
  803. Server.prototype.cursor = function(ns, cmd, options) {
  804. options = options || {};
  805. const topology = options.topology || this;
  806. // Set up final cursor type
  807. var FinalCursor = options.cursorFactory || this.s.Cursor;
  808. // Return the cursor
  809. return new FinalCursor(this.s.bson, ns, cmd, options, topology, this.s.options);
  810. };
  811. /**
  812. * Logout from a database
  813. * @method
  814. * @param {string} db The db we are logging out from
  815. * @param {authResultCallback} callback A callback function
  816. */
  817. Server.prototype.logout = function(dbName, callback) {
  818. this.s.pool.logout(dbName, callback);
  819. };
  820. /**
  821. * Authenticate using a specified mechanism
  822. * @method
  823. * @param {string} mechanism The Auth mechanism we are invoking
  824. * @param {string} db The db we are invoking the mechanism against
  825. * @param {...object} param Parameters for the specific mechanism
  826. * @param {authResultCallback} callback A callback function
  827. */
  828. Server.prototype.auth = function(mechanism, db) {
  829. var self = this;
  830. if (mechanism === 'default') {
  831. mechanism = getDefaultAuthMechanism(self.ismaster);
  832. }
  833. // Slice all the arguments off
  834. var args = Array.prototype.slice.call(arguments, 0);
  835. // Set the mechanism
  836. args[0] = mechanism;
  837. // Get the callback
  838. var callback = args[args.length - 1];
  839. // If we are not connected or have a disconnectHandler specified
  840. if (disconnectHandler(self, 'auth', db, args, {}, callback)) {
  841. return;
  842. }
  843. // Do not authenticate if we are an arbiter
  844. if (this.lastIsMaster() && this.lastIsMaster().arbiterOnly) {
  845. return callback(null, true);
  846. }
  847. // Apply the arguments to the pool
  848. self.s.pool.auth.apply(self.s.pool, args);
  849. };
  850. /**
  851. * Compare two server instances
  852. * @method
  853. * @param {Server} server Server to compare equality against
  854. * @return {boolean}
  855. */
  856. Server.prototype.equals = function(server) {
  857. if (typeof server === 'string') return this.name.toLowerCase() === server.toLowerCase();
  858. if (server.name) return this.name.toLowerCase() === server.name.toLowerCase();
  859. return false;
  860. };
  861. /**
  862. * All raw connections
  863. * @method
  864. * @return {Connection[]}
  865. */
  866. Server.prototype.connections = function() {
  867. return this.s.pool.allConnections();
  868. };
  869. /**
  870. * Selects a server
  871. * @return {Server}
  872. */
  873. Server.prototype.selectServer = function(selector, options, callback) {
  874. if (typeof selector === 'function' && typeof callback === 'undefined')
  875. (callback = selector), (selector = undefined), (options = {});
  876. if (typeof options === 'function')
  877. (callback = options), (options = selector), (selector = undefined);
  878. callback(null, this);
  879. };
  880. var listeners = ['close', 'error', 'timeout', 'parseError', 'connect'];
  881. /**
  882. * Destroy the server connection
  883. * @method
  884. * @param {boolean} [options.emitClose=false] Emit close event on destroy
  885. * @param {boolean} [options.emitDestroy=false] Emit destroy event on destroy
  886. * @param {boolean} [options.force=false] Force destroy the pool
  887. */
  888. Server.prototype.destroy = function(options) {
  889. if (this._destroyed) return;
  890. options = options || {};
  891. var self = this;
  892. // Set the connections
  893. if (serverAccounting) delete servers[this.id];
  894. // Destroy the monitoring process if any
  895. if (this.monitoringProcessId) {
  896. clearTimeout(this.monitoringProcessId);
  897. }
  898. // No pool, return
  899. if (!self.s.pool) {
  900. this._destroyed = true;
  901. return;
  902. }
  903. // Emit close event
  904. if (options.emitClose) {
  905. self.emit('close', self);
  906. }
  907. // Emit destroy event
  908. if (options.emitDestroy) {
  909. self.emit('destroy', self);
  910. }
  911. // Remove all listeners
  912. listeners.forEach(function(event) {
  913. self.s.pool.removeAllListeners(event);
  914. });
  915. // Emit opening server event
  916. if (self.listeners('serverClosed').length > 0)
  917. self.emit('serverClosed', {
  918. topologyId: self.s.topologyId !== -1 ? self.s.topologyId : self.id,
  919. address: self.name
  920. });
  921. // Emit toplogy opening event if not in topology
  922. if (self.listeners('topologyClosed').length > 0 && !self.s.inTopology) {
  923. self.emit('topologyClosed', { topologyId: self.id });
  924. }
  925. if (self.s.logger.isDebug()) {
  926. self.s.logger.debug(f('destroy called on server %s', self.name));
  927. }
  928. // Destroy the pool
  929. this.s.pool.destroy(options.force);
  930. this._destroyed = true;
  931. };
  932. /**
  933. * A server connect event, used to verify that the connection is up and running
  934. *
  935. * @event Server#connect
  936. * @type {Server}
  937. */
  938. /**
  939. * A server reconnect event, used to verify that the server topology has reconnected
  940. *
  941. * @event Server#reconnect
  942. * @type {Server}
  943. */
  944. /**
  945. * A server opening SDAM monitoring event
  946. *
  947. * @event Server#serverOpening
  948. * @type {object}
  949. */
  950. /**
  951. * A server closed SDAM monitoring event
  952. *
  953. * @event Server#serverClosed
  954. * @type {object}
  955. */
  956. /**
  957. * A server description SDAM change monitoring event
  958. *
  959. * @event Server#serverDescriptionChanged
  960. * @type {object}
  961. */
  962. /**
  963. * A topology open SDAM event
  964. *
  965. * @event Server#topologyOpening
  966. * @type {object}
  967. */
  968. /**
  969. * A topology closed SDAM event
  970. *
  971. * @event Server#topologyClosed
  972. * @type {object}
  973. */
  974. /**
  975. * A topology structure SDAM change event
  976. *
  977. * @event Server#topologyDescriptionChanged
  978. * @type {object}
  979. */
  980. /**
  981. * Server reconnect failed
  982. *
  983. * @event Server#reconnectFailed
  984. * @type {Error}
  985. */
  986. /**
  987. * Server connection pool closed
  988. *
  989. * @event Server#close
  990. * @type {object}
  991. */
  992. /**
  993. * Server connection pool caused an error
  994. *
  995. * @event Server#error
  996. * @type {Error}
  997. */
  998. /**
  999. * Server destroyed was called
  1000. *
  1001. * @event Server#destroy
  1002. * @type {Server}
  1003. */
  1004. module.exports = Server;