'use strict'; var inherits = require('util').inherits, f = require('util').format, diff = require('./shared').diff, EventEmitter = require('events').EventEmitter, Logger = require('../connection/logger'), ReadPreference = require('./read_preference'), MongoError = require('../error').MongoError, Buffer = require('safe-buffer').Buffer; var TopologyType = { Single: 'Single', ReplicaSetNoPrimary: 'ReplicaSetNoPrimary', ReplicaSetWithPrimary: 'ReplicaSetWithPrimary', Sharded: 'Sharded', Unknown: 'Unknown' }; var ServerType = { Standalone: 'Standalone', Mongos: 'Mongos', PossiblePrimary: 'PossiblePrimary', RSPrimary: 'RSPrimary', RSSecondary: 'RSSecondary', RSArbiter: 'RSArbiter', RSOther: 'RSOther', RSGhost: 'RSGhost', Unknown: 'Unknown' }; var ReplSetState = function(options) { options = options || {}; // Add event listener EventEmitter.call(this); // Topology state this.topologyType = TopologyType.ReplicaSetNoPrimary; this.setName = options.setName; // Server set this.set = {}; // Unpacked options this.id = options.id; this.setName = options.setName; // Replicaset logger this.logger = options.logger || Logger('ReplSet', options); // Server selection index this.index = 0; // Acceptable latency this.acceptableLatency = options.acceptableLatency || 15; // heartbeatFrequencyMS this.heartbeatFrequencyMS = options.heartbeatFrequencyMS || 10000; // Server side this.primary = null; this.secondaries = []; this.arbiters = []; this.passives = []; this.ghosts = []; // Current unknown hosts this.unknownServers = []; // In set status this.set = {}; // Status this.maxElectionId = null; this.maxSetVersion = 0; // Description of the Replicaset this.replicasetDescription = { topologyType: 'Unknown', servers: [] }; this.logicalSessionTimeoutMinutes = undefined; }; inherits(ReplSetState, EventEmitter); ReplSetState.prototype.hasPrimaryAndSecondary = function() { return this.primary != null && this.secondaries.length > 0; }; ReplSetState.prototype.hasPrimaryOrSecondary = function() { return this.hasPrimary() || this.hasSecondary(); }; ReplSetState.prototype.hasPrimary = function() { return this.primary != null; }; ReplSetState.prototype.hasSecondary = function() { return this.secondaries.length > 0; }; ReplSetState.prototype.get = function(host) { var servers = this.allServers(); for (var i = 0; i < servers.length; i++) { if (servers[i].name.toLowerCase() === host.toLowerCase()) { return servers[i]; } } return null; }; ReplSetState.prototype.allServers = function(options) { options = options || {}; var servers = this.primary ? [this.primary] : []; servers = servers.concat(this.secondaries); if (!options.ignoreArbiters) servers = servers.concat(this.arbiters); servers = servers.concat(this.passives); return servers; }; ReplSetState.prototype.destroy = function(options) { // Destroy all sockets if (this.primary) this.primary.destroy(options); this.secondaries.forEach(function(x) { x.destroy(options); }); this.arbiters.forEach(function(x) { x.destroy(options); }); this.passives.forEach(function(x) { x.destroy(options); }); this.ghosts.forEach(function(x) { x.destroy(options); }); // Clear out the complete state this.secondaries = []; this.arbiters = []; this.passives = []; this.ghosts = []; this.unknownServers = []; this.set = {}; this.primary = null; // Emit the topology changed emitTopologyDescriptionChanged(this); }; ReplSetState.prototype.remove = function(server, options) { options = options || {}; // Get the server name and lowerCase it var serverName = server.name.toLowerCase(); // Only remove if the current server is not connected var servers = this.primary ? [this.primary] : []; servers = servers.concat(this.secondaries); servers = servers.concat(this.arbiters); servers = servers.concat(this.passives); // Check if it's active and this is just a failed connection attempt for (var i = 0; i < servers.length; i++) { if ( !options.force && servers[i].equals(server) && servers[i].isConnected && servers[i].isConnected() ) { return; } } // If we have it in the set remove it if (this.set[serverName]) { this.set[serverName].type = ServerType.Unknown; this.set[serverName].electionId = null; this.set[serverName].setName = null; this.set[serverName].setVersion = null; } // Remove type var removeType = null; // Remove from any lists if (this.primary && this.primary.equals(server)) { this.primary = null; this.topologyType = TopologyType.ReplicaSetNoPrimary; removeType = 'primary'; } // Remove from any other server lists removeType = removeFrom(server, this.secondaries) ? 'secondary' : removeType; removeType = removeFrom(server, this.arbiters) ? 'arbiter' : removeType; removeType = removeFrom(server, this.passives) ? 'secondary' : removeType; removeFrom(server, this.ghosts); removeFrom(server, this.unknownServers); // Push to unknownServers this.unknownServers.push(serverName); // Do we have a removeType if (removeType) { this.emit('left', removeType, server); } }; const isArbiter = ismaster => ismaster.arbiterOnly && ismaster.setName; ReplSetState.prototype.update = function(server) { var self = this; // Get the current ismaster var ismaster = server.lastIsMaster(); // Get the server name and lowerCase it var serverName = server.name.toLowerCase(); // // Add any hosts // if (ismaster) { // Join all the possible new hosts var hosts = Array.isArray(ismaster.hosts) ? ismaster.hosts : []; hosts = hosts.concat(Array.isArray(ismaster.arbiters) ? ismaster.arbiters : []); hosts = hosts.concat(Array.isArray(ismaster.passives) ? ismaster.passives : []); hosts = hosts.map(function(s) { return s.toLowerCase(); }); // Add all hosts as unknownServers for (var i = 0; i < hosts.length; i++) { // Add to the list of unknown server if ( this.unknownServers.indexOf(hosts[i]) === -1 && (!this.set[hosts[i]] || this.set[hosts[i]].type === ServerType.Unknown) ) { this.unknownServers.push(hosts[i].toLowerCase()); } if (!this.set[hosts[i]]) { this.set[hosts[i]] = { type: ServerType.Unknown, electionId: null, setName: null, setVersion: null }; } } } // // Unknown server // if (!ismaster && !inList(ismaster, server, this.unknownServers)) { self.set[serverName] = { type: ServerType.Unknown, setVersion: null, electionId: null, setName: null }; // Update set information about the server instance self.set[serverName].type = ServerType.Unknown; self.set[serverName].electionId = ismaster ? ismaster.electionId : ismaster; self.set[serverName].setName = ismaster ? ismaster.setName : ismaster; self.set[serverName].setVersion = ismaster ? ismaster.setVersion : ismaster; if (self.unknownServers.indexOf(server.name) === -1) { self.unknownServers.push(serverName); } // Set the topology return false; } // Update logicalSessionTimeoutMinutes if (ismaster.logicalSessionTimeoutMinutes !== undefined && !isArbiter(ismaster)) { if ( self.logicalSessionTimeoutMinutes === undefined || ismaster.logicalSessionTimeoutMinutes === null ) { self.logicalSessionTimeoutMinutes = ismaster.logicalSessionTimeoutMinutes; } else { self.logicalSessionTimeoutMinutes = Math.min( self.logicalSessionTimeoutMinutes, ismaster.logicalSessionTimeoutMinutes ); } } // // Is this a mongos // if (ismaster && ismaster.msg === 'isdbgrid') { return false; } // A RSOther instance if ( (ismaster.setName && ismaster.hidden) || (ismaster.setName && !ismaster.ismaster && !ismaster.secondary && !ismaster.arbiterOnly && !ismaster.passive) ) { self.set[serverName] = { type: ServerType.RSOther, setVersion: null, electionId: null, setName: ismaster.setName }; // Set the topology this.topologyType = this.primary ? TopologyType.ReplicaSetWithPrimary : TopologyType.ReplicaSetNoPrimary; if (ismaster.setName) this.setName = ismaster.setName; return false; } // A RSGhost instance if (ismaster.isreplicaset) { self.set[serverName] = { type: ServerType.RSGhost, setVersion: null, electionId: null, setName: null }; // Set the topology this.topologyType = this.primary ? TopologyType.ReplicaSetWithPrimary : TopologyType.ReplicaSetNoPrimary; if (ismaster.setName) this.setName = ismaster.setName; // Set the topology return false; } // // Standalone server, destroy and return // if (ismaster && ismaster.ismaster && !ismaster.setName) { this.topologyType = this.primary ? TopologyType.ReplicaSetWithPrimary : TopologyType.Unknown; this.remove(server, { force: true }); return false; } // // Server in maintanance mode // if (ismaster && !ismaster.ismaster && !ismaster.secondary && !ismaster.arbiterOnly) { this.remove(server, { force: true }); return false; } // // If the .me field does not match the passed in server // if (ismaster.me && ismaster.me.toLowerCase() !== serverName) { if (this.logger.isWarn()) { this.logger.warn( f( 'the seedlist server was removed due to its address %s not matching its ismaster.me address %s', server.name, ismaster.me ) ); } // Delete from the set delete this.set[serverName]; // Delete unknown servers removeFrom(server, self.unknownServers); // Destroy the instance server.destroy(); // Set the type of topology we have if (this.primary && !this.primary.equals(server)) { this.topologyType = TopologyType.ReplicaSetWithPrimary; } else { this.topologyType = TopologyType.ReplicaSetNoPrimary; } // // We have a potential primary // if (!this.primary && ismaster.primary) { this.set[ismaster.primary.toLowerCase()] = { type: ServerType.PossiblePrimary, setName: null, electionId: null, setVersion: null }; } return false; } // // Primary handling // if (!this.primary && ismaster.ismaster && ismaster.setName) { var ismasterElectionId = server.lastIsMaster().electionId; if (this.setName && this.setName !== ismaster.setName) { this.topologyType = TopologyType.ReplicaSetNoPrimary; return new MongoError( f( 'setName from ismaster does not match provided connection setName [%s] != [%s]', ismaster.setName, this.setName ) ); } if (!this.maxElectionId && ismasterElectionId) { this.maxElectionId = ismasterElectionId; } else if (this.maxElectionId && ismasterElectionId) { var result = compareObjectIds(this.maxElectionId, ismasterElectionId); // Get the electionIds var ismasterSetVersion = server.lastIsMaster().setVersion; if (result === 1) { this.topologyType = TopologyType.ReplicaSetNoPrimary; return false; } else if (result === 0 && ismasterSetVersion) { if (ismasterSetVersion < this.maxSetVersion) { this.topologyType = TopologyType.ReplicaSetNoPrimary; return false; } } this.maxSetVersion = ismasterSetVersion; this.maxElectionId = ismasterElectionId; } // Hande normalization of server names var normalizedHosts = ismaster.hosts.map(function(x) { return x.toLowerCase(); }); var locationIndex = normalizedHosts.indexOf(serverName); // Validate that the server exists in the host list if (locationIndex !== -1) { self.primary = server; self.set[serverName] = { type: ServerType.RSPrimary, setVersion: ismaster.setVersion, electionId: ismaster.electionId, setName: ismaster.setName }; // Set the topology this.topologyType = TopologyType.ReplicaSetWithPrimary; if (ismaster.setName) this.setName = ismaster.setName; removeFrom(server, self.unknownServers); removeFrom(server, self.secondaries); removeFrom(server, self.passives); self.emit('joined', 'primary', server); } else { this.topologyType = TopologyType.ReplicaSetNoPrimary; } emitTopologyDescriptionChanged(self); return true; } else if (ismaster.ismaster && ismaster.setName) { // Get the electionIds var currentElectionId = self.set[self.primary.name.toLowerCase()].electionId; var currentSetVersion = self.set[self.primary.name.toLowerCase()].setVersion; var currentSetName = self.set[self.primary.name.toLowerCase()].setName; ismasterElectionId = server.lastIsMaster().electionId; ismasterSetVersion = server.lastIsMaster().setVersion; var ismasterSetName = server.lastIsMaster().setName; // Is it the same server instance if (this.primary.equals(server) && currentSetName === ismasterSetName) { return false; } // If we do not have the same rs name if (currentSetName && currentSetName !== ismasterSetName) { if (!this.primary.equals(server)) { this.topologyType = TopologyType.ReplicaSetWithPrimary; } else { this.topologyType = TopologyType.ReplicaSetNoPrimary; } return false; } // Check if we need to replace the server if (currentElectionId && ismasterElectionId) { result = compareObjectIds(currentElectionId, ismasterElectionId); if (result === 1) { return false; } else if (result === 0 && currentSetVersion > ismasterSetVersion) { return false; } } else if (!currentElectionId && ismasterElectionId && ismasterSetVersion) { if (ismasterSetVersion < this.maxSetVersion) { return false; } } if (!this.maxElectionId && ismasterElectionId) { this.maxElectionId = ismasterElectionId; } else if (this.maxElectionId && ismasterElectionId) { result = compareObjectIds(this.maxElectionId, ismasterElectionId); if (result === 1) { return false; } else if (result === 0 && currentSetVersion && ismasterSetVersion) { if (ismasterSetVersion < this.maxSetVersion) { return false; } } else { if (ismasterSetVersion < this.maxSetVersion) { return false; } } this.maxElectionId = ismasterElectionId; this.maxSetVersion = ismasterSetVersion; } else { this.maxSetVersion = ismasterSetVersion; } // Modify the entry to unknown self.set[self.primary.name.toLowerCase()] = { type: ServerType.Unknown, setVersion: null, electionId: null, setName: null }; // Signal primary left self.emit('left', 'primary', this.primary); // Destroy the instance self.primary.destroy(); // Set the new instance self.primary = server; // Set the set information self.set[serverName] = { type: ServerType.RSPrimary, setVersion: ismaster.setVersion, electionId: ismaster.electionId, setName: ismaster.setName }; // Set the topology this.topologyType = TopologyType.ReplicaSetWithPrimary; if (ismaster.setName) this.setName = ismaster.setName; removeFrom(server, self.unknownServers); removeFrom(server, self.secondaries); removeFrom(server, self.passives); self.emit('joined', 'primary', server); emitTopologyDescriptionChanged(self); return true; } // A possible instance if (!this.primary && ismaster.primary) { self.set[ismaster.primary.toLowerCase()] = { type: ServerType.PossiblePrimary, setVersion: null, electionId: null, setName: null }; } // // Secondary handling // if ( ismaster.secondary && ismaster.setName && !inList(ismaster, server, this.secondaries) && this.setName && this.setName === ismaster.setName ) { addToList(self, ServerType.RSSecondary, ismaster, server, this.secondaries); // Set the topology this.topologyType = this.primary ? TopologyType.ReplicaSetWithPrimary : TopologyType.ReplicaSetNoPrimary; if (ismaster.setName) this.setName = ismaster.setName; removeFrom(server, self.unknownServers); // Remove primary if (this.primary && this.primary.name.toLowerCase() === serverName) { server.destroy(); this.primary = null; self.emit('left', 'primary', server); } // Emit secondary joined replicaset self.emit('joined', 'secondary', server); emitTopologyDescriptionChanged(self); return true; } // // Arbiter handling // if ( isArbiter(ismaster) && !inList(ismaster, server, this.arbiters) && this.setName && this.setName === ismaster.setName ) { addToList(self, ServerType.RSArbiter, ismaster, server, this.arbiters); // Set the topology this.topologyType = this.primary ? TopologyType.ReplicaSetWithPrimary : TopologyType.ReplicaSetNoPrimary; if (ismaster.setName) this.setName = ismaster.setName; removeFrom(server, self.unknownServers); self.emit('joined', 'arbiter', server); emitTopologyDescriptionChanged(self); return true; } // // Passive handling // if ( ismaster.passive && ismaster.setName && !inList(ismaster, server, this.passives) && this.setName && this.setName === ismaster.setName ) { addToList(self, ServerType.RSSecondary, ismaster, server, this.passives); // Set the topology this.topologyType = this.primary ? TopologyType.ReplicaSetWithPrimary : TopologyType.ReplicaSetNoPrimary; if (ismaster.setName) this.setName = ismaster.setName; removeFrom(server, self.unknownServers); // Remove primary if (this.primary && this.primary.name.toLowerCase() === serverName) { server.destroy(); this.primary = null; self.emit('left', 'primary', server); } self.emit('joined', 'secondary', server); emitTopologyDescriptionChanged(self); return true; } // // Remove the primary // if (this.set[serverName] && this.set[serverName].type === ServerType.RSPrimary) { self.emit('left', 'primary', this.primary); this.primary.destroy(); this.primary = null; this.topologyType = TopologyType.ReplicaSetNoPrimary; return false; } this.topologyType = this.primary ? TopologyType.ReplicaSetWithPrimary : TopologyType.ReplicaSetNoPrimary; return false; }; /** * Recalculate single server max staleness * @method */ ReplSetState.prototype.updateServerMaxStaleness = function(server, haInterval) { // Locate the max secondary lastwrite var max = 0; // Go over all secondaries for (var i = 0; i < this.secondaries.length; i++) { max = Math.max(max, this.secondaries[i].lastWriteDate); } // Perform this servers staleness calculation if (server.ismaster.maxWireVersion >= 5 && server.ismaster.secondary && this.hasPrimary()) { server.staleness = server.lastUpdateTime - server.lastWriteDate - (this.primary.lastUpdateTime - this.primary.lastWriteDate) + haInterval; } else if (server.ismaster.maxWireVersion >= 5 && server.ismaster.secondary) { server.staleness = max - server.lastWriteDate + haInterval; } }; /** * Recalculate all the staleness values for secodaries * @method */ ReplSetState.prototype.updateSecondariesMaxStaleness = function(haInterval) { for (var i = 0; i < this.secondaries.length; i++) { this.updateServerMaxStaleness(this.secondaries[i], haInterval); } }; /** * Pick a server by the passed in ReadPreference * @method * @param {ReadPreference} readPreference The ReadPreference instance to use */ ReplSetState.prototype.pickServer = function(readPreference) { // If no read Preference set to primary by default readPreference = readPreference || ReadPreference.primary; // maxStalenessSeconds is not allowed with a primary read if (readPreference.preference === 'primary' && readPreference.maxStalenessSeconds != null) { return new MongoError('primary readPreference incompatible with maxStalenessSeconds'); } // Check if we have any non compatible servers for maxStalenessSeconds var allservers = this.primary ? [this.primary] : []; allservers = allservers.concat(this.secondaries); // Does any of the servers not support the right wire protocol version // for maxStalenessSeconds when maxStalenessSeconds specified on readPreference. Then error out if (readPreference.maxStalenessSeconds != null) { for (var i = 0; i < allservers.length; i++) { if (allservers[i].ismaster.maxWireVersion < 5) { return new MongoError( 'maxStalenessSeconds not supported by at least one of the replicaset members' ); } } } // Do we have the nearest readPreference if (readPreference.preference === 'nearest' && readPreference.maxStalenessSeconds == null) { return pickNearest(this, readPreference); } else if ( readPreference.preference === 'nearest' && readPreference.maxStalenessSeconds != null ) { return pickNearestMaxStalenessSeconds(this, readPreference); } // Get all the secondaries var secondaries = this.secondaries; // Check if we can satisfy and of the basic read Preferences if (readPreference.equals(ReadPreference.secondary) && secondaries.length === 0) { return new MongoError('no secondary server available'); } if ( readPreference.equals(ReadPreference.secondaryPreferred) && secondaries.length === 0 && this.primary == null ) { return new MongoError('no secondary or primary server available'); } if (readPreference.equals(ReadPreference.primary) && this.primary == null) { return new MongoError('no primary server available'); } // Secondary preferred or just secondaries if ( readPreference.equals(ReadPreference.secondaryPreferred) || readPreference.equals(ReadPreference.secondary) ) { if (secondaries.length > 0 && readPreference.maxStalenessSeconds == null) { // Pick nearest of any other servers available var server = pickNearest(this, readPreference); // No server in the window return primary if (server) { return server; } } else if (secondaries.length > 0 && readPreference.maxStalenessSeconds != null) { // Pick nearest of any other servers available server = pickNearestMaxStalenessSeconds(this, readPreference); // No server in the window return primary if (server) { return server; } } if (readPreference.equals(ReadPreference.secondaryPreferred)) { return this.primary; } return null; } // Primary preferred if (readPreference.equals(ReadPreference.primaryPreferred)) { server = null; // We prefer the primary if it's available if (this.primary) { return this.primary; } // Pick a secondary if (secondaries.length > 0 && readPreference.maxStalenessSeconds == null) { server = pickNearest(this, readPreference); } else if (secondaries.length > 0 && readPreference.maxStalenessSeconds != null) { server = pickNearestMaxStalenessSeconds(this, readPreference); } // Did we find a server if (server) return server; } // Return the primary return this.primary; }; // // Filter serves by tags var filterByTags = function(readPreference, servers) { if (readPreference.tags == null) return servers; var filteredServers = []; var tagsArray = Array.isArray(readPreference.tags) ? readPreference.tags : [readPreference.tags]; // Iterate over the tags for (var j = 0; j < tagsArray.length; j++) { var tags = tagsArray[j]; // Iterate over all the servers for (var i = 0; i < servers.length; i++) { var serverTag = servers[i].lastIsMaster().tags || {}; // Did we find the a matching server var found = true; // Check if the server is valid for (var name in tags) { if (serverTag[name] !== tags[name]) { found = false; } } // Add to candidate list if (found) { filteredServers.push(servers[i]); } } } // Returned filtered servers return filteredServers; }; function pickNearestMaxStalenessSeconds(self, readPreference) { // Only get primary and secondaries as seeds var servers = []; // Get the maxStalenessMS var maxStalenessMS = readPreference.maxStalenessSeconds * 1000; // Check if the maxStalenessMS > 90 seconds if (maxStalenessMS < 90 * 1000) { return new MongoError('maxStalenessSeconds must be set to at least 90 seconds'); } // Add primary to list if not a secondary read preference if ( self.primary && readPreference.preference !== 'secondary' && readPreference.preference !== 'secondaryPreferred' ) { servers.push(self.primary); } // Add all the secondaries for (var i = 0; i < self.secondaries.length; i++) { servers.push(self.secondaries[i]); } // If we have a secondaryPreferred readPreference and no server add the primary if (self.primary && servers.length === 0 && readPreference.preference !== 'secondaryPreferred') { servers.push(self.primary); } // Filter by tags servers = filterByTags(readPreference, servers); // Filter by latency servers = servers.filter(function(s) { return s.staleness <= maxStalenessMS; }); // Sort by time servers.sort(function(a, b) { return a.lastIsMasterMS - b.lastIsMasterMS; }); // No servers, default to primary if (servers.length === 0) { return null; } // Ensure index does not overflow the number of available servers self.index = self.index % servers.length; // Get the server var server = servers[self.index]; // Add to the index self.index = self.index + 1; // Return the first server of the sorted and filtered list return server; } function pickNearest(self, readPreference) { // Only get primary and secondaries as seeds var servers = []; // Add primary to list if not a secondary read preference if ( self.primary && readPreference.preference !== 'secondary' && readPreference.preference !== 'secondaryPreferred' ) { servers.push(self.primary); } // Add all the secondaries for (var i = 0; i < self.secondaries.length; i++) { servers.push(self.secondaries[i]); } // If we have a secondaryPreferred readPreference and no server add the primary if (servers.length === 0 && self.primary && readPreference.preference !== 'secondaryPreferred') { servers.push(self.primary); } // Filter by tags servers = filterByTags(readPreference, servers); // Sort by time servers.sort(function(a, b) { return a.lastIsMasterMS - b.lastIsMasterMS; }); // Locate lowest time (picked servers are lowest time + acceptable Latency margin) var lowest = servers.length > 0 ? servers[0].lastIsMasterMS : 0; // Filter by latency servers = servers.filter(function(s) { return s.lastIsMasterMS <= lowest + self.acceptableLatency; }); // No servers, default to primary if (servers.length === 0) { return null; } // Ensure index does not overflow the number of available servers self.index = self.index % servers.length; // Get the server var server = servers[self.index]; // Add to the index self.index = self.index + 1; // Return the first server of the sorted and filtered list return server; } function inList(ismaster, server, list) { for (var i = 0; i < list.length; i++) { if (list[i] && list[i].name && list[i].name.toLowerCase() === server.name.toLowerCase()) return true; } return false; } function addToList(self, type, ismaster, server, list) { var serverName = server.name.toLowerCase(); // Update set information about the server instance self.set[serverName].type = type; self.set[serverName].electionId = ismaster ? ismaster.electionId : ismaster; self.set[serverName].setName = ismaster ? ismaster.setName : ismaster; self.set[serverName].setVersion = ismaster ? ismaster.setVersion : ismaster; // Add to the list list.push(server); } function compareObjectIds(id1, id2) { var a = Buffer.from(id1.toHexString(), 'hex'); var b = Buffer.from(id2.toHexString(), 'hex'); if (a === b) { return 0; } if (typeof Buffer.compare === 'function') { return Buffer.compare(a, b); } var x = a.length; var y = b.length; var len = Math.min(x, y); for (var i = 0; i < len; i++) { if (a[i] !== b[i]) { break; } } if (i !== len) { x = a[i]; y = b[i]; } return x < y ? -1 : y < x ? 1 : 0; } function removeFrom(server, list) { for (var i = 0; i < list.length; i++) { if (list[i].equals && list[i].equals(server)) { list.splice(i, 1); return true; } else if (typeof list[i] === 'string' && list[i].toLowerCase() === server.name.toLowerCase()) { list.splice(i, 1); return true; } } return false; } function emitTopologyDescriptionChanged(self) { if (self.listeners('topologyDescriptionChanged').length > 0) { var topology = 'Unknown'; var setName = self.setName; if (self.hasPrimaryAndSecondary()) { topology = 'ReplicaSetWithPrimary'; } else if (!self.hasPrimary() && self.hasSecondary()) { topology = 'ReplicaSetNoPrimary'; } // Generate description var description = { topologyType: topology, setName: setName, servers: [] }; // Add the primary to the list if (self.hasPrimary()) { var desc = self.primary.getDescription(); desc.type = 'RSPrimary'; description.servers.push(desc); } // Add all the secondaries description.servers = description.servers.concat( self.secondaries.map(function(x) { var description = x.getDescription(); description.type = 'RSSecondary'; return description; }) ); // Add all the arbiters description.servers = description.servers.concat( self.arbiters.map(function(x) { var description = x.getDescription(); description.type = 'RSArbiter'; return description; }) ); // Add all the passives description.servers = description.servers.concat( self.passives.map(function(x) { var description = x.getDescription(); description.type = 'RSSecondary'; return description; }) ); // Get the diff var diffResult = diff(self.replicasetDescription, description); // Create the result var result = { topologyId: self.id, previousDescription: self.replicasetDescription, newDescription: description, diff: diffResult }; // Emit the topologyDescription change // if(diffResult.servers.length > 0) { self.emit('topologyDescriptionChanged', result); // } // Set the new description self.replicasetDescription = description; } } module.exports = ReplSetState;