UNPKG

21.4 kBJavaScriptView Raw
1"use strict";
2
3var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
4
5function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
6
7var co = require('co'),
8 f = require('util').format,
9 mkdirp = require('mkdirp'),
10 rimraf = require('rimraf'),
11 Logger = require('./logger'),
12 CoreServer = require('mongodb-core').Server,
13 spawn = require('child_process').spawn;
14
15var clone = function clone(o) {
16 var obj = {};for (var name in o) {
17 obj[name] = o[name];
18 }return obj;
19};
20
21var Server = (function () {
22 function Server(binary, options, clientOptions) {
23 _classCallCheck(this, Server);
24
25 options = options || {};
26 this.options = clone(options);
27
28 // Server state
29 this.state = 'stopped';
30
31 // Create logger instance
32 this.logger = Logger('Server', options);
33
34 // Unpack default runtime information
35 this.binary = binary || 'mongod';
36 // Current process
37 this.process = null;
38 // Current command
39 this.command = null;
40 // Credentials store
41 this.credentials = [];
42 // Additional internal client options
43 this.clientOptions = clientOptions || {};
44
45 // Default values for host and port if not set
46 this.options.bind_ip = typeof this.options.bind_ip == 'string' ? this.options.bind_ip : 'localhost';
47 this.options.port = typeof this.options.port == 'number' ? this.options.port : 27017;
48 }
49
50 _createClass(Server, [{
51 key: 'discover',
52 value: function discover() {
53 var self = this;
54
55 return new Promise(function (resolve, reject) {
56 co(regeneratorRuntime.mark(function _callee() {
57 var proc, stdout, stderr;
58 return regeneratorRuntime.wrap(function _callee$(_context) {
59 while (1) {
60 switch (_context.prev = _context.next) {
61 case 0:
62 proc = spawn(self.binary, ['--version']);
63 // Variables receiving data
64
65 stdout = '';
66 stderr = '';
67 // Get the stdout
68
69 proc.stdout.on('data', function (data) {
70 stdout += data;
71 });
72 // Get the stderr
73 proc.stderr.on('data', function (data) {
74 stderr += data;
75 });
76 // Got an error
77 proc.on('error', function (err) {
78 reject(err);
79 });
80 // Process terminated
81 proc.on('close', function (code) {
82 // Perform version match
83 var versionMatch = stdout.match(/[0-9]+\.[0-9]+\.[0-9]+/);
84
85 // Check if we have ssl
86 var sslMatch = stdout.match(/ssl/i);
87
88 // Resolve the server version
89 resolve({
90 version: versionMatch.toString().split('.').map(function (x) {
91 return parseInt(x, 10);
92 }),
93 ssl: sslMatch != null
94 });
95 });
96
97 case 7:
98 case 'end':
99 return _context.stop();
100 }
101 }
102 }, _callee, this);
103 })).catch(reject);
104 });
105 }
106 }, {
107 key: 'instance',
108 value: function instance(credentials, options) {
109 var self = this;
110 options = options || {};
111 options = clone(options);
112
113 return new Promise(function (resolve, reject) {
114 co(regeneratorRuntime.mark(function _callee2() {
115 var opt, s;
116 return regeneratorRuntime.wrap(function _callee2$(_context2) {
117 while (1) {
118 switch (_context2.prev = _context2.next) {
119 case 0:
120 // Copy the basic options
121 opt = clone(self.clientOptions);
122
123 opt.host = self.options.bind_ip;
124 opt.port = self.options.port;
125 opt.connectionTimeout = 5000;
126 opt.socketTimeout = 5000;
127 opt.pool = 1;
128
129 // Ensure we only connect once and emit any error caught
130 opt.reconnect = false;
131 opt.emitError = true;
132
133 // Create an instance
134 s = new CoreServer(opt);
135
136 s.on('error', function (err) {
137 reject(err);
138 });
139
140 s.on('close', function (err) {
141 reject(err);
142 });
143
144 s.on('timeout', function (err) {
145 reject(err);
146 });
147
148 s.on('connect', function (_server) {
149 // Do we have credentials
150 var authenticate = function authenticate(_server, _credentials, _callback) {
151 if (!_credentials) return _callback();
152 // Perform authentication using provided
153 // credentials for this command
154 _server.auth(_credentials.provider, _credentials.db, _credentials.user, _credentials.password, _callback);
155 };
156
157 // Perform any necessary authentication
158 authenticate(_server, credentials, function (err) {
159 if (err) return reject(err);
160 resolve(_server);
161 });
162 });
163
164 // Connect
165 s.connect();
166
167 case 14:
168 case 'end':
169 return _context2.stop();
170 }
171 }
172 }, _callee2, this);
173 }));
174 });
175 }
176 }, {
177 key: 'executeCommand',
178 value: function executeCommand(ns, command, credentials, options) {
179 var self = this;
180 options = options || {};
181 options = clone(options);
182
183 return new Promise(function (resolve, reject) {
184 co(regeneratorRuntime.mark(function _callee3() {
185 var opt, executeCommand, s;
186 return regeneratorRuntime.wrap(function _callee3$(_context3) {
187 while (1) {
188 switch (_context3.prev = _context3.next) {
189 case 0:
190 // Copy the basic options
191 opt = clone(self.clientOptions);
192
193 opt.host = self.options.bind_ip;
194 opt.port = self.options.port;
195 opt.connectionTimeout = 5000;
196 opt.socketTimeout = 0;
197 opt.pool = 1;
198
199 // Ensure we only connect once and emit any error caught
200 opt.reconnect = false;
201 opt.emitError = true;
202
203 // Execute command
204
205 executeCommand = function executeCommand(_ns, _command, _credentials, _server) {
206 // Do we have credentials
207 var authenticate = function authenticate(_server, _credentials, _callback) {
208 if (!_credentials) return _callback();
209 // Perform authentication using provided
210 // credentials for this command
211 _server.auth(_credentials.provider, _credentials.db, _credentials.user, _credentials.password, _callback);
212 };
213
214 authenticate(_server, _credentials, function (err) {
215 if (err && options.ignoreError) return resolve({ ok: 1 });
216 // If we had an error return
217 if (err) {
218 _server.destroy();
219 return reject(err);
220 }
221
222 // Execute command
223 _server.command(_ns, _command, function (err, r) {
224 // Destroy the connection
225 _server.destroy();
226 // Return an error
227 if (err && options.ignoreError) return resolve({ ok: 1 });
228 if (err) return reject(err);
229 // Return the ismaster command
230 resolve(r.result);
231 });
232 });
233 };
234
235 // Create an instance
236
237 s = new CoreServer(opt);
238
239 s.on('error', function (err) {
240 if (options.ignoreError) return resolve({ ok: 1 });
241 if (options.reExecuteOnError) {
242 options.reExecuteOnError = false;
243 return executeCommand(ns, command, credentials, s);
244 }
245
246 reject(err);
247 });
248
249 s.on('close', function (err) {
250 if (options.ignoreError) return resolve({ ok: 1 });
251 if (options.reExecuteOnError) {
252 options.reExecuteOnError = false;
253 return executeCommand(ns, command, credentials, s);
254 }
255
256 reject(err);
257 });
258
259 s.on('timeout', function (err) {
260 if (options.ignoreError) return resolve({ ok: 1 });
261 reject(err);
262 });
263
264 s.on('connect', function (_server) {
265 executeCommand(ns, command, credentials, _server);
266 });
267
268 // Connect
269 s.connect();
270
271 case 15:
272 case 'end':
273 return _context3.stop();
274 }
275 }
276 }, _callee3, this);
277 })).catch(function (err) {
278 if (options.ignoreError) return resolve({ ok: 1 });
279 reject(err);
280 });
281 });
282 }
283 }, {
284 key: 'start',
285 value: function start() {
286 var self = this;
287
288 return new Promise(function (resolve, reject) {
289 co(regeneratorRuntime.mark(function _callee4() {
290 var result, version, errors, options, commandOptions, name, commandLine, stdout, stderr;
291 return regeneratorRuntime.wrap(function _callee4$(_context4) {
292 while (1) {
293 switch (_context4.prev = _context4.next) {
294 case 0:
295 _context4.next = 2;
296 return self.discover();
297
298 case 2:
299 result = _context4.sent;
300 version = result.version;
301
302 // All errors found during validation
303
304 errors = [];
305
306 // Ensure basic parameters
307
308 if (!self.options.dbpath) {
309 errors.push(new Error('dbpath is required'));
310 }
311
312 // Do we have any errors
313
314 if (!(errors.length > 0)) {
315 _context4.next = 8;
316 break;
317 }
318
319 return _context4.abrupt('return', reject(errors));
320
321 case 8:
322
323 // Figure out what special options we need to pass into the boot script
324 // Removing any non-compatible parameters etc.
325 if (version[0] == 3 && version[1] >= 0 && version[1] <= 2) {} else if (version[0] == 3 && version[1] >= 2) {} else if (version[0] == 2 && version[1] <= 6) {}
326
327 // Merge in all the options
328 options = clone(self.options);
329
330 // Build command options list
331
332 commandOptions = [];
333
334 // Go over all the options
335
336 for (name in options) {
337 if (options[name] == null) {
338 commandOptions.push(f('--%s', name));
339 } else {
340 commandOptions.push(f('--%s=%s', name, options[name]));
341 }
342 }
343
344 // Command line
345 commandLine = f('%s %s', self.binary, commandOptions.join(' '));
346
347 if (self.logger.isInfo()) {
348 self.logger.info(f('started mongod with [%s]', commandLine));
349 }
350
351 // Spawn a mongod process
352 self.process = spawn(self.binary, commandOptions);
353
354 // Variables receiving data
355 stdout = '';
356 stderr = '';
357
358 // Get the stdout
359
360 self.process.stdout.on('data', function (data) {
361 stdout += data.toString();
362
363 //
364 // Only emit event at start
365 if (self.state == 'stopped') {
366 if (stdout.indexOf('waiting for connections') != -1 || stdout.indexOf('connection accepted') != -1) {
367 // Mark state as running
368 self.state = 'running';
369 // Resolve
370 resolve();
371 }
372 }
373 });
374
375 // Get the stderr
376 self.process.stderr.on('data', function (data) {
377 stderr += data;
378 });
379
380 // Got an error
381 self.process.on('error', function (err) {
382 reject(new Error({ error: error, stdout: stdout, stderr: stderr }));
383 });
384
385 // Process terminated
386 self.process.on('close', function (code) {
387 if (self.state == 'stopped' && stdout == '' || code != 0) {
388 return reject(new Error(f('failed to start mongod with options %s', commandOptions)));
389 }
390
391 self.state = 'stopped';
392 });
393
394 case 21:
395 case 'end':
396 return _context4.stop();
397 }
398 }
399 }, _callee4, this);
400 })).catch(reject);
401 });
402 }
403
404 /*
405 * Retrieve the ismaster for this server
406 */
407
408 }, {
409 key: 'ismaster',
410 value: function ismaster() {
411 var self = this;
412
413 return new Promise(function (resolve, reject) {
414 co(regeneratorRuntime.mark(function _callee5() {
415 var opt, s;
416 return regeneratorRuntime.wrap(function _callee5$(_context5) {
417 while (1) {
418 switch (_context5.prev = _context5.next) {
419 case 0:
420 // Copy the basic options
421 opt = clone(self.clientOptions);
422
423 opt.host = self.options.bind_ip;
424 opt.port = self.options.port;
425 opt.connectionTimeout = 5000;
426 opt.socketTimeout = 5000;
427 opt.pool = 1;
428
429 // Ensure we only connect once and emit any error caught
430 opt.reconnect = false;
431 opt.emitError = true;
432
433 // Create an instance
434 s = new CoreServer(opt);
435 // Add listeners
436
437 s.on('error', function (err) {
438 reject(err);
439 });
440
441 s.on('close', function (err) {
442 reject(err);
443 });
444
445 s.on('timeout', function (err) {
446 reject(err);
447 });
448
449 s.on('connect', function (_server) {
450 _server.command('system.$cmd', {
451 ismaster: true
452 }, function (err, r) {
453 // Destroy the connection
454 _server.destroy();
455 // Return an error
456 if (err) return callback(err);
457 // Return the ismaster command
458 resolve(r.result);
459 });
460 });
461
462 // Connect
463 s.connect();
464
465 case 14:
466 case 'end':
467 return _context5.stop();
468 }
469 }
470 }, _callee5, this);
471 })).catch(reject);
472 });
473 }
474
475 /*
476 * Purge the db directory
477 */
478
479 }, {
480 key: 'purge',
481 value: function purge() {
482 var self = this;
483
484 return new Promise(function (resolve, reject) {
485 co(regeneratorRuntime.mark(function _callee6() {
486 return regeneratorRuntime.wrap(function _callee6$(_context6) {
487 while (1) {
488 switch (_context6.prev = _context6.next) {
489 case 0:
490 try {
491 // Delete the dbpath
492 rimraf.sync(self.options.dbpath);
493 } catch (err) {}
494
495 try {
496 // Re-Create the directory
497 mkdirp.sync(self.options.dbpath);
498 } catch (err) {}
499
500 // Return
501 resolve();
502
503 case 3:
504 case 'end':
505 return _context6.stop();
506 }
507 }
508 }, _callee6, this);
509 })).catch(reject);
510 });
511 }
512 }, {
513 key: 'stop',
514 value: function stop(signal) {
515 var self = this;
516 signal = typeof signal == 'number' ? signal : signals.SIGTERM;
517
518 return new Promise(function (resolve, reject) {
519 co(regeneratorRuntime.mark(function _callee7() {
520 return regeneratorRuntime.wrap(function _callee7$(_context7) {
521 while (1) {
522 switch (_context7.prev = _context7.next) {
523 case 0:
524 if (self.process) {
525 _context7.next = 3;
526 break;
527 }
528
529 // Set process to stopped
530 self.state = 'stopped';
531 // Return
532 return _context7.abrupt('return', resolve());
533
534 case 3:
535
536 // Wait for service to stop
537 self.process.on('close', function () {
538 // Set process to stopped
539 self.state = 'stopped';
540 // Return
541 resolve();
542 });
543
544 // Terminate the process
545 self.process.kill(signal);
546
547 case 5:
548 case 'end':
549 return _context7.stop();
550 }
551 }
552 }, _callee7, this);
553 })).catch(reject);
554 });
555 }
556 }, {
557 key: 'restart',
558 value: function restart(purge) {
559 var self = this;
560
561 return new Promise(function (resolve, reject) {
562 co(regeneratorRuntime.mark(function _callee8() {
563 return regeneratorRuntime.wrap(function _callee8$(_context8) {
564 while (1) {
565 switch (_context8.prev = _context8.next) {
566 case 0:
567 _context8.next = 2;
568 return self.stop();
569
570 case 2:
571 if (!purge) {
572 _context8.next = 5;
573 break;
574 }
575
576 _context8.next = 5;
577 return self.purge();
578
579 case 5:
580 _context8.next = 7;
581 return self.start();
582
583 case 7:
584 resolve();
585
586 case 8:
587 case 'end':
588 return _context8.stop();
589 }
590 }
591 }, _callee8, this);
592 })).catch(reject);
593 });
594 }
595 }, {
596 key: 'config',
597 get: function get() {
598 return clone(this.options);
599 }
600 }, {
601 key: 'host',
602 get: function get() {
603 return this.options.bind_ip;
604 }
605 }, {
606 key: 'port',
607 get: function get() {
608 return this.options.port;
609 }
610 }, {
611 key: 'name',
612 get: function get() {
613 return f('%s:%s', this.options.bind_ip, this.options.port);
614 }
615 }]);
616
617 return Server;
618})();
619
620module.exports = Server;
621
622// SIGHUP 1 Term Hangup detected on controlling terminal
623// or death of controlling process
624// SIGINT 2 Term Interrupt from keyboard
625// SIGQUIT 3 Core Quit from keyboard
626// SIGILL 4 Core Illegal Instruction
627// SIGABRT 6 Core Abort signal from abort(3)
628// SIGFPE 8 Core Floating point exception
629// SIGKILL 9 Term Kill signal
630// SIGSEGV 11 Core Invalid memory reference
631// SIGPIPE 13 Term Broken pipe: write to pipe with no readers
632// SIGALRM 14 Term Timer signal from alarm(2)
633// SIGTERM 15 Term Termination signal
634// Signal map
635var signals = {
636 1: 'SIGHUP',
637 2: 'SIGINT',
638 3: 'SIGQUIT',
639 4: 'SIGABRT',
640 6: 'SIGABRT',
641 8: 'SIGFPE',
642 9: 'SIGKILL',
643 11: 'SIGSEGV',
644 13: 'SIGPIPE',
645 14: 'SIGALRM',
646 15: 'SIGTERM',
647 17: 'SIGSTOP',
648 19: 'SIGSTOP',
649 23: 'SIGSTOP'
650};