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