PageRenderTime 120ms CodeModel.GetById 20ms app.highlight 89ms RepoModel.GetById 1ms app.codeStats 0ms

/node_modules/mongoose/node_modules/mongodb-core/lib/topologies/replset.js

https://bitbucket.org/coleman333/smartsite
JavaScript | 1674 lines | 946 code | 212 blank | 516 comment | 215 complexity | 3278ebfaa4ba53e029cbb3a9594db299 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

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

Large files files are truncated, but you can click here to view the full file