a630b6ad08c4027c334e6985fcf0b6be2c703d9c
[aai/esr-gui.git] /
1 "use strict";
2
3 var EventEmitter = require('events').EventEmitter
4   , inherits = require('util').inherits
5   , f = require('util').format
6   , ServerCapabilities = require('./topology_base').ServerCapabilities
7   , MongoCR = require('mongodb-core').MongoCR
8   , MongoError = require('mongodb-core').MongoError
9   , CMongos = require('mongodb-core').Mongos
10   , Cursor = require('./cursor')
11   , AggregationCursor = require('./aggregation_cursor')
12   , CommandCursor = require('./command_cursor')
13   , Define = require('./metadata')
14   , Server = require('./server')
15   , Store = require('./topology_base').Store
16   , shallowClone = require('./utils').shallowClone
17   , MAX_JS_INT = require('./utils').MAX_JS_INT
18   , translateOptions = require('./utils').translateOptions
19   , filterOptions = require('./utils').filterOptions
20   , mergeOptions = require('./utils').mergeOptions
21   , os = require('os');
22
23 // Get package.json variable
24 var driverVersion = require(__dirname + '/../package.json').version;
25 var nodejsversion = f('Node.js %s, %s', process.version, os.endianness());
26 var type = os.type();
27 var name = process.platform;
28 var architecture = process.arch;
29 var release = os.release();
30
31 /**
32  * @fileOverview The **Mongos** class is a class that represents a Mongos Proxy topology and is
33  * used to construct connections.
34  *
35  * **Mongos Should not be used, use MongoClient.connect**
36  * @example
37  * var Db = require('mongodb').Db,
38  *   Mongos = require('mongodb').Mongos,
39  *   Server = require('mongodb').Server,
40  *   test = require('assert');
41  * // Connect using Mongos
42  * var server = new Server('localhost', 27017);
43  * var db = new Db('test', new Mongos([server]));
44  * db.open(function(err, db) {
45  *   // Get an additional db
46  *   db.close();
47  * });
48  */
49
50  // Allowed parameters
51  var legalOptionNames = ['ha', 'haInterval', 'acceptableLatencyMS'
52    , 'poolSize', 'ssl', 'checkServerIdentity', 'sslValidate'
53    , 'sslCA', 'sslCert', 'sslKey', 'sslPass', 'socketOptions', 'bufferMaxEntries'
54    , 'store', 'auto_reconnect', 'autoReconnect', 'emitError'
55    , 'keepAlive', 'noDelay', 'connectTimeoutMS', 'socketTimeoutMS'
56    , 'loggerLevel', 'logger', 'reconnectTries', 'appname', 'domainsEnabled'
57    , 'servername', 'promoteLongs', 'promoteValues', 'promoteBuffers'];
58
59 /**
60  * Creates a new Mongos instance
61  * @class
62  * @deprecated
63  * @param {Server[]} servers A seedlist of servers participating in the replicaset.
64  * @param {object} [options=null] Optional settings.
65  * @param {booelan} [options.ha=true] Turn on high availability monitoring.
66  * @param {number} [options.haInterval=5000] Time between each replicaset status check.
67  * @param {number} [options.poolSize=5] Number of connections in the connection pool for each server instance, set to 5 as default for legacy reasons.
68  * @param {number} [options.acceptableLatencyMS=15] Cutoff latency point in MS for MongoS proxy selection
69  * @param {boolean} [options.ssl=false] Use ssl connection (needs to have a mongod server with ssl support)
70  * @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.
71  * @param {object} [options.sslValidate=true] Validate mongod server certificate against ca (needs to have a mongod server with ssl support, 2.4 or higher)
72  * @param {array} [options.sslCA=null] Array of valid certificates either as Buffers or Strings (needs to have a mongod server with ssl support, 2.4 or higher)
73  * @param {(Buffer|string)} [options.sslCert=null] String or buffer containing the certificate we wish to present (needs to have a mongod server with ssl support, 2.4 or higher)
74  * @param {(Buffer|string)} [options.sslKey=null] String or buffer containing the certificate private key we wish to present (needs to have a mongod server with ssl support, 2.4 or higher)
75  * @param {(Buffer|string)} [options.sslPass=null] String or buffer containing the certificate password (needs to have a mongod server with ssl support, 2.4 or higher)
76  * @param {string} [options.servername=null] String containing the server name requested via TLS SNI.
77  * @param {object} [options.socketOptions=null] Socket options
78  * @param {boolean} [options.socketOptions.noDelay=true] TCP Socket NoDelay option.
79  * @param {number} [options.socketOptions.keepAlive=0] TCP KeepAlive on the socket with a X ms delay before start.
80  * @param {number} [options.socketOptions.connectTimeoutMS=0] TCP Connection timeout setting
81  * @param {number} [options.socketOptions.socketTimeoutMS=0] TCP Socket timeout setting
82  * @param {boolean} [options.domainsEnabled=false] Enable the wrapping of the callback in the current domain, disabled by default to avoid perf hit.
83  * @fires Mongos#connect
84  * @fires Mongos#ha
85  * @fires Mongos#joined
86  * @fires Mongos#left
87  * @fires Mongos#fullsetup
88  * @fires Mongos#open
89  * @fires Mongos#close
90  * @fires Mongos#error
91  * @fires Mongos#timeout
92  * @fires Mongos#parseError
93  * @return {Mongos} a Mongos instance.
94  */
95 var Mongos = function(servers, options) {
96   if(!(this instanceof Mongos)) return new Mongos(servers, options);
97   options = options || {};
98   var self = this;
99
100   // Filter the options
101   options = filterOptions(options, legalOptionNames);
102
103   // Ensure all the instances are Server
104   for(var i = 0; i < servers.length; i++) {
105     if(!(servers[i] instanceof Server)) {
106       throw MongoError.create({message: "all seed list instances must be of the Server type", driver:true});
107     }
108   }
109
110   // Stored options
111   var storeOptions = {
112       force: false
113     , bufferMaxEntries: typeof options.bufferMaxEntries == 'number' ? options.bufferMaxEntries : MAX_JS_INT
114   }
115
116   // Shared global store
117   var store = options.store || new Store(self, storeOptions);
118
119   // Set up event emitter
120   EventEmitter.call(this);
121
122   // Build seed list
123   var seedlist = servers.map(function(x) {
124     return {host: x.host, port: x.port}
125   });
126
127   // Get the reconnect option
128   var reconnect = typeof options.auto_reconnect == 'boolean' ? options.auto_reconnect : true;
129   reconnect = typeof options.autoReconnect == 'boolean' ? options.autoReconnect : reconnect;
130
131   // Clone options
132   var clonedOptions = mergeOptions({}, {
133     disconnectHandler: store,
134     cursorFactory: Cursor,
135     reconnect: reconnect,
136     emitError: typeof options.emitError == 'boolean' ? options.emitError : true,
137     size: typeof options.poolSize == 'number' ? options.poolSize : 5
138   });
139
140   // Translate any SSL options and other connectivity options
141   clonedOptions = translateOptions(clonedOptions, options);
142
143   // Socket options
144   var socketOptions = options.socketOptions && Object.keys(options.socketOptions).length > 0
145     ? options.socketOptions : options;
146
147   // Translate all the options to the mongodb-core ones
148   clonedOptions = translateOptions(clonedOptions, socketOptions);
149   if(typeof clonedOptions.keepAlive == 'number') {
150     clonedOptions.keepAliveInitialDelay = clonedOptions.keepAlive;
151     clonedOptions.keepAlive = clonedOptions.keepAlive > 0;
152   }
153
154   // Build default client information
155   this.clientInfo = {
156     driver: {
157       name: "nodejs",
158       version: driverVersion
159     },
160     os: {
161       type: type,
162       name: name,
163       architecture: architecture,
164       version: release
165     },
166     platform: nodejsversion
167   }
168
169   // Build default client information
170   clonedOptions.clientInfo = this.clientInfo;
171   // Do we have an application specific string
172   if(options.appname) {
173     clonedOptions.clientInfo.application = { name: options.appname };
174   }
175
176   // Create the Mongos
177   var mongos = new CMongos(seedlist, clonedOptions)
178   // Server capabilities
179   var sCapabilities = null;
180
181   // Internal state
182   this.s = {
183     // Create the Mongos
184       mongos: mongos
185     // Server capabilities
186     , sCapabilities: sCapabilities
187     // Debug turned on
188     , debug: clonedOptions.debug
189     // Store option defaults
190     , storeOptions: storeOptions
191     // Cloned options
192     , clonedOptions: clonedOptions
193     // Actual store of callbacks
194     , store: store
195     // Options
196     , options: options
197   }
198 }
199
200 var define = Mongos.define = new Define('Mongos', Mongos, false);
201
202 /**
203  * @ignore
204  */
205 inherits(Mongos, EventEmitter);
206
207 // Last ismaster
208 Object.defineProperty(Mongos.prototype, 'isMasterDoc', {
209   enumerable:true, get: function() { return this.s.mongos.lastIsMaster(); }
210 });
211
212 // BSON property
213 Object.defineProperty(Mongos.prototype, 'bson', {
214   enumerable: true, get: function() {
215     return this.s.mongos.s.bson;
216   }
217 });
218
219 Object.defineProperty(Mongos.prototype, 'haInterval', {
220   enumerable:true, get: function() { return this.s.mongos.s.haInterval; }
221 });
222
223 // Connect
224 Mongos.prototype.connect = function(db, _options, callback) {
225   var self = this;
226   if('function' === typeof _options) callback = _options, _options = {};
227   if(_options == null) _options = {};
228   if(!('function' === typeof callback)) callback = null;
229   self.s.options = _options;
230
231   // Update bufferMaxEntries
232   self.s.storeOptions.bufferMaxEntries = db.bufferMaxEntries;
233
234   // Error handler
235   var connectErrorHandler = function(event) {
236     return function(err) {
237       // Remove all event handlers
238       var events = ['timeout', 'error', 'close'];
239       events.forEach(function(e) {
240         self.removeListener(e, connectErrorHandler);
241       });
242
243       self.s.mongos.removeListener('connect', connectErrorHandler);
244
245       // Try to callback
246       try {
247         callback(err);
248       } catch(err) {
249         process.nextTick(function() { throw err; })
250       }
251     }
252   }
253
254   // Actual handler
255   var errorHandler = function(event) {
256     return function(err) {
257       if(event != 'error') {
258         self.emit(event, err);
259       }
260     }
261   }
262
263   // Error handler
264   var reconnectHandler = function(err) {
265     self.emit('reconnect');
266     self.s.store.execute();
267   }
268
269   // relay the event
270   var relay = function(event) {
271     return function(t, server) {
272       self.emit(event, t, server);
273     }
274   }
275
276   // Connect handler
277   var connectHandler = function() {
278     // Clear out all the current handlers left over
279     ["timeout", "error", "close", 'serverOpening', 'serverDescriptionChanged', 'serverHeartbeatStarted',
280       'serverHeartbeatSucceeded', 'serverHeartbeatFailed', 'serverClosed', 'topologyOpening',
281       'topologyClosed', 'topologyDescriptionChanged'].forEach(function(e) {
282       self.s.mongos.removeAllListeners(e);
283     });
284
285     // Set up listeners
286     self.s.mongos.once('timeout', errorHandler('timeout'));
287     self.s.mongos.once('error', errorHandler('error'));
288     self.s.mongos.once('close', errorHandler('close'));
289
290     // Set up SDAM listeners
291     self.s.mongos.on('serverDescriptionChanged', relay('serverDescriptionChanged'));
292     self.s.mongos.on('serverHeartbeatStarted', relay('serverHeartbeatStarted'));
293     self.s.mongos.on('serverHeartbeatSucceeded', relay('serverHeartbeatSucceeded'));
294     self.s.mongos.on('serverHeartbeatFailed', relay('serverHeartbeatFailed'));
295     self.s.mongos.on('serverOpening', relay('serverOpening'));
296     self.s.mongos.on('serverClosed', relay('serverClosed'));
297     self.s.mongos.on('topologyOpening', relay('topologyOpening'));
298     self.s.mongos.on('topologyClosed', relay('topologyClosed'));
299     self.s.mongos.on('topologyDescriptionChanged', relay('topologyDescriptionChanged'));
300
301     // Set up serverConfig listeners
302     self.s.mongos.on('fullsetup', relay('fullsetup'));
303
304     // Emit open event
305     self.emit('open', null, self);
306
307     // Return correctly
308     try {
309       callback(null, self);
310     } catch(err) {
311       process.nextTick(function() { throw err; })
312     }
313   }
314
315   // Set up listeners
316   self.s.mongos.once('timeout', connectErrorHandler('timeout'));
317   self.s.mongos.once('error', connectErrorHandler('error'));
318   self.s.mongos.once('close', connectErrorHandler('close'));
319   self.s.mongos.once('connect', connectHandler);
320   // Join and leave events
321   self.s.mongos.on('joined', relay('joined'));
322   self.s.mongos.on('left', relay('left'));
323
324   // Reconnect server
325   self.s.mongos.on('reconnect', reconnectHandler);
326
327   // Start connection
328   self.s.mongos.connect(_options);
329 }
330
331 // Server capabilities
332 Mongos.prototype.capabilities = function() {
333   if(this.s.sCapabilities) return this.s.sCapabilities;
334   if(this.s.mongos.lastIsMaster() == null) return null;
335   this.s.sCapabilities = new ServerCapabilities(this.s.mongos.lastIsMaster());
336   return this.s.sCapabilities;
337 }
338
339 define.classMethod('capabilities', {callback: false, promise:false, returns: [ServerCapabilities]});
340
341 // Command
342 Mongos.prototype.command = function(ns, cmd, options, callback) {
343   this.s.mongos.command(ns, cmd, options, callback);
344 }
345
346 define.classMethod('command', {callback: true, promise:false});
347
348 // Insert
349 Mongos.prototype.insert = function(ns, ops, options, callback) {
350   this.s.mongos.insert(ns, ops, options, function(e, m) {
351     callback(e, m)
352   });
353 }
354
355 define.classMethod('insert', {callback: true, promise:false});
356
357 // Update
358 Mongos.prototype.update = function(ns, ops, options, callback) {
359   this.s.mongos.update(ns, ops, options, callback);
360 }
361
362 define.classMethod('update', {callback: true, promise:false});
363
364 // Remove
365 Mongos.prototype.remove = function(ns, ops, options, callback) {
366   this.s.mongos.remove(ns, ops, options, callback);
367 }
368
369 define.classMethod('remove', {callback: true, promise:false});
370
371 // Destroyed
372 Mongos.prototype.isDestroyed = function() {
373   return this.s.mongos.isDestroyed();
374 }
375
376 // IsConnected
377 Mongos.prototype.isConnected = function() {
378   return this.s.mongos.isConnected();
379 }
380
381 define.classMethod('isConnected', {callback: false, promise:false, returns: [Boolean]});
382
383 // Insert
384 Mongos.prototype.cursor = function(ns, cmd, options) {
385   options.disconnectHandler = this.s.store;
386   return this.s.mongos.cursor(ns, cmd, options);
387 }
388
389 define.classMethod('cursor', {callback: false, promise:false, returns: [Cursor, AggregationCursor, CommandCursor]});
390
391 Mongos.prototype.lastIsMaster = function() {
392   return this.s.mongos.lastIsMaster();
393 }
394
395 Mongos.prototype.close = function(forceClosed) {
396   this.s.mongos.destroy();
397   // We need to wash out all stored processes
398   if(forceClosed == true) {
399     this.s.storeOptions.force = forceClosed;
400     this.s.store.flush();
401   }
402 }
403
404 define.classMethod('close', {callback: false, promise:false});
405
406 Mongos.prototype.auth = function() {
407   var args = Array.prototype.slice.call(arguments, 0);
408   this.s.mongos.auth.apply(this.s.mongos, args);
409 }
410
411 define.classMethod('auth', {callback: true, promise:false});
412
413 Mongos.prototype.logout = function() {
414   var args = Array.prototype.slice.call(arguments, 0);
415   this.s.mongos.logout.apply(this.s.mongos, args);
416 }
417
418 define.classMethod('logout', {callback: true, promise:false});
419
420 /**
421  * All raw connections
422  * @method
423  * @return {array}
424  */
425 Mongos.prototype.connections = function() {
426   return this.s.mongos.connections();
427 }
428
429 define.classMethod('connections', {callback: false, promise:false, returns:[Array]});
430
431 /**
432  * A mongos connect event, used to verify that the connection is up and running
433  *
434  * @event Mongos#connect
435  * @type {Mongos}
436  */
437
438 /**
439  * The mongos high availability event
440  *
441  * @event Mongos#ha
442  * @type {function}
443  * @param {string} type The stage in the high availability event (start|end)
444  * @param {boolean} data.norepeat This is a repeating high availability process or a single execution only
445  * @param {number} data.id The id for this high availability request
446  * @param {object} data.state An object containing the information about the current replicaset
447  */
448
449 /**
450  * A server member left the mongos set
451  *
452  * @event Mongos#left
453  * @type {function}
454  * @param {string} type The type of member that left (primary|secondary|arbiter)
455  * @param {Server} server The server object that left
456  */
457
458 /**
459  * A server member joined the mongos set
460  *
461  * @event Mongos#joined
462  * @type {function}
463  * @param {string} type The type of member that joined (primary|secondary|arbiter)
464  * @param {Server} server The server object that joined
465  */
466
467 /**
468  * Mongos fullsetup event, emitted when all proxies in the topology have been connected to.
469  *
470  * @event Mongos#fullsetup
471  * @type {Mongos}
472  */
473
474 /**
475  * Mongos open event, emitted when mongos can start processing commands.
476  *
477  * @event Mongos#open
478  * @type {Mongos}
479  */
480
481 /**
482  * Mongos close event
483  *
484  * @event Mongos#close
485  * @type {object}
486  */
487
488 /**
489  * Mongos error event, emitted if there is an error listener.
490  *
491  * @event Mongos#error
492  * @type {MongoError}
493  */
494
495 /**
496  * Mongos timeout event
497  *
498  * @event Mongos#timeout
499  * @type {object}
500  */
501
502 /**
503  * Mongos parseError event
504  *
505  * @event Mongos#parseError
506  * @type {object}
507  */
508
509 module.exports = Mongos;