3 var inherits = require('util').inherits,
4 f = require('util').format,
5 EventEmitter = require('events').EventEmitter,
6 Logger = require('../connection/logger'),
7 ReadPreference = require('./read_preference'),
8 MongoError = require('../error');
11 'Single': 'Single', 'ReplicaSetNoPrimary': 'ReplicaSetNoPrimary',
12 'ReplicaSetWithPrimary': 'ReplicaSetWithPrimary', 'Sharded': 'Sharded',
17 'Standalone': 'Standalone', 'Mongos': 'Mongos', 'PossiblePrimary': 'PossiblePrimary',
18 'RSPrimary': 'RSPrimary', 'RSSecondary': 'RSSecondary', 'RSArbiter': 'RSArbiter',
19 'RSOther': 'RSOther', 'RSGhost': 'RSGhost', 'Unknown': 'Unknown'
22 var ReplSetState = function(options) {
23 options = options || {};
25 EventEmitter.call(this);
27 this.topologyType = TopologyType.ReplicaSetNoPrimary;
28 this.setName = options.setName;
35 this.setName = options.setName;
38 this.logger = options.logger || Logger('ReplSet', options);
40 // Server selection index
43 this.acceptableLatency = options.acceptableLatency || 15;
45 // heartbeatFrequencyMS
46 this.heartbeatFrequencyMS = options.heartbeatFrequencyMS || 10000;
50 this.secondaries = [];
54 // Current unknown hosts
55 this.unknownServers = [];
59 this.maxElectionId = null;
60 this.maxSetVersion = 0;
61 // Description of the Replicaset
62 this.replicasetDescription = {
63 "topologyType": "Unknown", "servers": []
67 inherits(ReplSetState, EventEmitter);
69 ReplSetState.prototype.hasPrimaryAndSecondary = function() {
70 return this.primary != null && this.secondaries.length > 0;
73 ReplSetState.prototype.hasPrimary = function() {
74 return this.primary != null;
77 ReplSetState.prototype.hasSecondary = function() {
78 return this.secondaries.length > 0;
81 ReplSetState.prototype.allServers = function(options) {
82 options = options || {};
83 var servers = this.primary ? [this.primary] : [];
84 servers = servers.concat(this.secondaries);
85 if(!options.ignoreArbiters) servers = servers.concat(this.arbiters);
86 servers = servers.concat(this.passives);
90 ReplSetState.prototype.destroy = function(options) {
91 // Destroy all sockets
92 if(this.primary) this.primary.destroy(options);
93 this.secondaries.forEach(function(x) { x.destroy(options); });
94 this.arbiters.forEach(function(x) { x.destroy(options); });
95 this.passives.forEach(function(x) { x.destroy(options); });
96 this.ghosts.forEach(function(x) { x.destroy(options); });
97 // Clear out the complete state
98 this.secondaries = [];
102 this.unknownServers = [];
106 ReplSetState.prototype.remove = function(server, options) {
107 options = options || {};
109 // Only remove if the current server is not connected
110 var servers = this.primary ? [this.primary] : [];
111 servers = servers.concat(this.secondaries);
112 servers = servers.concat(this.arbiters);
113 servers = servers.concat(this.passives);
115 // Check if it's active and this is just a failed connection attempt
116 for(var i = 0; i < servers.length; i++) {
117 if(!options.force && servers[i].equals(server) && servers[i].isConnected && servers[i].isConnected()) {
122 // If we have it in the set remove it
123 if(this.set[server.name.toLowerCase()]) {
124 this.set[server.name.toLowerCase()].type = ServerType.Unknown;
125 this.set[server.name.toLowerCase()].electionId = null;
126 this.set[server.name.toLowerCase()].setName = null;
127 this.set[server.name.toLowerCase()].setVersion = null;
131 var removeType = null;
133 // Remove from any lists
134 if(this.primary && this.primary.equals(server)) {
136 this.topologyType = TopologyType.ReplicaSetNoPrimary;
137 removeType = 'primary';
140 // Remove from any other server lists
141 removeType = removeFrom(server, this.secondaries) ? 'secondary' : removeType;
142 removeType = removeFrom(server, this.arbiters) ? 'arbiter' : removeType;
143 removeType = removeFrom(server, this.passives) ? 'secondary' : removeType;
144 removeFrom(server, this.ghosts);
145 removeFrom(server, this.unknownServers);
147 // Do we have a removeType
149 this.emit('left', removeType, server);
153 ReplSetState.prototype.update = function(server) {
155 // Get the current ismaster
156 var ismaster = server.lastIsMaster();
162 // Join all the possible new hosts
163 var hosts = Array.isArray(ismaster.hosts) ? ismaster.hosts : [];
164 hosts = hosts.concat(Array.isArray(ismaster.arbiters) ? ismaster.arbiters : []);
165 hosts = hosts.concat(Array.isArray(ismaster.passives) ? ismaster.passives : []);
167 // Add all hosts as unknownServers
168 for(var i = 0; i < hosts.length; i++) {
169 // Add to the list of unknown server
170 if(this.unknownServers.indexOf(hosts[i]) == -1
171 && (!this.set[hosts[i].toLowerCase()] || this.set[hosts[i].toLowerCase()].type == ServerType.Unknown)) {
172 this.unknownServers.push(hosts[i]);
175 if(!this.set[hosts[i].toLowerCase()]) {
176 this.set[hosts[i].toLowerCase()] = {
177 type: ServerType.Unknown,
189 if(!ismaster && !inList(ismaster, server, this.unknownServers)) {
190 self.set[server.name.toLowerCase()] = {
191 type: ServerType.Unknown, setVersion: null, electionId: null, setName: null
193 // Update set information about the server instance
194 self.set[server.name.toLowerCase()].type = ServerType.Unknown;
195 self.set[server.name.toLowerCase()].electionId = ismaster ? ismaster.electionId : ismaster;
196 self.set[server.name.toLowerCase()].setName = ismaster ? ismaster.setName : ismaster;
197 self.set[server.name.toLowerCase()].setVersion = ismaster ? ismaster.setVersion : ismaster;
199 if(self.unknownServers.indexOf(server.name) == -1) {
200 self.unknownServers.push(server.name);
210 if(ismaster && ismaster.msg == 'isdbgrid') {
214 // A RSOther instance
215 if((ismaster.setName && ismaster.hidden)
216 || (ismaster.setName && !ismaster.ismaster && !ismaster.secondary && !ismaster.arbiterOnly && !ismaster.passive)) {
217 self.set[server.name.toLowerCase()] = {
218 type: ServerType.RSOther, setVersion: null,
219 electionId: null, setName: ismaster.setName
222 this.topologyType = this.primary ? TopologyType.ReplicaSetWithPrimary : TopologyType.ReplicaSetNoPrimary;
223 if(ismaster.setName) this.setName = ismaster.setName;
227 // A RSGhost instance
228 if(ismaster.isreplicaset) {
229 self.set[server.name.toLowerCase()] = {
230 type: ServerType.RSGhost, setVersion: null,
231 electionId: null, setName: null
235 this.topologyType = this.primary ? TopologyType.ReplicaSetWithPrimary : TopologyType.ReplicaSetNoPrimary;
236 if(ismaster.setName) this.setName = ismaster.setName;
243 // Standalone server, destroy and return
245 if(ismaster && ismaster.ismaster && !ismaster.setName) {
246 this.topologyType = this.primary ? TopologyType.ReplicaSetWithPrimary : TopologyType.Unknown;
247 this.remove(server, {force:true});
252 // Server in maintanance mode
254 if(ismaster && !ismaster.ismaster && !ismaster.secondary && !ismaster.arbiterOnly) {
255 this.remove(server, {force:true});
260 // If the .me field does not match the passed in server
262 if(ismaster.me && ismaster.me != server.name) {
263 if(this.logger.isWarn()) {
264 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));
267 // Delete from the set
268 delete this.set[server.name.toLowerCase()];
270 // Set the type of topology we have
271 if(this.primary && !this.primary.equals(server)) {
272 this.topologyType = TopologyType.ReplicaSetWithPrimary;
274 this.topologyType = TopologyType.ReplicaSetNoPrimary;
278 // We have a potential primary
280 if(!this.primary && ismaster.primary) {
281 this.set[ismaster.primary.toLowerCase()] = {
282 type: ServerType.PossiblePrimary,
295 if(!this.primary && ismaster.ismaster && ismaster.setName) {
296 var ismasterElectionId = server.lastIsMaster().electionId;
297 if(this.setName && this.setName != ismaster.setName) {
298 this.topologyType = TopologyType.ReplicaSetNoPrimary;
299 return new MongoError(f('setName from ismaster does not match provided connection setName [%s] != [%s]', ismaster.setName, this.setName));
302 if(!this.maxElectionId && ismasterElectionId) {
303 this.maxElectionId = ismasterElectionId;
304 } else if(this.maxElectionId && ismasterElectionId) {
305 var result = compareObjectIds(this.maxElectionId, ismasterElectionId);
306 // Get the electionIds
307 var ismasterSetVersion = server.lastIsMaster().setVersion;
309 // if(result == 1 || result == 0) {
311 this.topologyType = TopologyType.ReplicaSetNoPrimary;
313 } else if(result == 0 && ismasterSetVersion) {
314 if(ismasterSetVersion < this.maxSetVersion) {
315 this.topologyType = TopologyType.ReplicaSetNoPrimary;
320 this.maxSetVersion = ismasterSetVersion;
321 this.maxElectionId = ismasterElectionId;
324 // Hande normalization of server names
325 var normalizedHosts = ismaster.hosts.map(function(x) { return x.toLowerCase() });
326 var locationIndex = normalizedHosts.indexOf(server.name.toLowerCase());
328 // Validate that the server exists in the host list
329 if(locationIndex != -1) {
330 self.primary = server;
331 self.set[server.name.toLowerCase()] = {
332 type: ServerType.RSPrimary,
333 setVersion: ismaster.setVersion,
334 electionId: ismaster.electionId,
335 setName: ismaster.setName
339 this.topologyType = TopologyType.ReplicaSetWithPrimary;
340 if(ismaster.setName) this.setName = ismaster.setName;
341 removeFrom(server, self.unknownServers);
342 removeFrom(server, self.secondaries);
343 removeFrom(server, self.passives);
344 self.emit('joined', 'primary', server);
346 this.topologyType = TopologyType.ReplicaSetNoPrimary;
349 emitTopologyDescriptionChanged(self);
351 } else if(ismaster.ismaster && ismaster.setName) {
352 // Get the electionIds
353 var currentElectionId = self.set[self.primary.name.toLowerCase()].electionId;
354 var currentSetVersion = self.set[self.primary.name.toLowerCase()].setVersion;
355 var currentSetName = self.set[self.primary.name.toLowerCase()].setName;
356 ismasterElectionId = server.lastIsMaster().electionId;
357 ismasterSetVersion = server.lastIsMaster().setVersion;
358 var ismasterSetName = server.lastIsMaster().setName;
360 // Is it the same server instance
361 if(this.primary.equals(server)
362 && currentSetName == ismasterSetName) {
366 // If we do not have the same rs name
367 if(currentSetName && currentSetName != ismasterSetName) {
368 if(!this.primary.equals(server)) {
369 this.topologyType = TopologyType.ReplicaSetWithPrimary;
371 this.topologyType = TopologyType.ReplicaSetNoPrimary;
377 // Check if we need to replace the server
378 if(currentElectionId && ismasterElectionId) {
379 result = compareObjectIds(currentElectionId, ismasterElectionId);
383 } else if(result == 0 && (currentSetVersion > ismasterSetVersion)) {
386 } else if(!currentElectionId && ismasterElectionId
387 && ismasterSetVersion) {
388 if(ismasterSetVersion < this.maxSetVersion) {
393 if(!this.maxElectionId && ismasterElectionId) {
394 this.maxElectionId = ismasterElectionId;
395 } else if(this.maxElectionId && ismasterElectionId) {
396 result = compareObjectIds(this.maxElectionId, ismasterElectionId);
400 } else if(result == 0 && currentSetVersion && ismasterSetVersion) {
401 if(ismasterSetVersion < this.maxSetVersion) {
405 if(ismasterSetVersion < this.maxSetVersion) {
410 this.maxElectionId = ismasterElectionId;
411 this.maxSetVersion = ismasterSetVersion;
413 this.maxSetVersion = ismasterSetVersion;
416 // Modify the entry to unknown
417 self.set[self.primary.name.toLowerCase()] = {
418 type: ServerType.Unknown, setVersion: null,
419 electionId: null, setName: null
422 // Signal primary left
423 self.emit('left', 'primary', this.primary);
424 // Destroy the instance
425 self.primary.destroy();
426 // Set the new instance
427 self.primary = server;
428 // Set the set information
429 self.set[server.name.toLowerCase()] = {
430 type: ServerType.RSPrimary, setVersion: ismaster.setVersion,
431 electionId: ismaster.electionId, setName: ismaster.setName
435 this.topologyType = TopologyType.ReplicaSetWithPrimary;
436 if(ismaster.setName) this.setName = ismaster.setName;
437 removeFrom(server, self.unknownServers);
438 removeFrom(server, self.secondaries);
439 removeFrom(server, self.passives);
440 self.emit('joined', 'primary', server);
441 emitTopologyDescriptionChanged(self);
445 // A possible instance
446 if(!this.primary && ismaster.primary) {
447 self.set[ismaster.primary.toLowerCase()] = {
448 type: ServerType.PossiblePrimary, setVersion: null,
449 electionId: null, setName: null
454 // Secondary handling
456 if(ismaster.secondary && ismaster.setName
457 && !inList(ismaster, server, this.secondaries)
458 && this.setName && this.setName == ismaster.setName) {
459 addToList(self, ServerType.RSSecondary, ismaster, server, this.secondaries);
461 this.topologyType = this.primary ? TopologyType.ReplicaSetWithPrimary : TopologyType.ReplicaSetNoPrimary;
462 if(ismaster.setName) this.setName = ismaster.setName;
463 removeFrom(server, self.unknownServers);
466 if(this.primary && this.primary.name == server.name) {
469 self.emit('left', 'primary', server);
472 self.emit('joined', 'secondary', server);
473 emitTopologyDescriptionChanged(self);
480 if(ismaster.arbiterOnly && ismaster.setName
481 && !inList(ismaster, server, this.arbiters)
482 && this.setName && this.setName == ismaster.setName) {
483 addToList(self, ServerType.RSArbiter, ismaster, server, this.arbiters);
485 this.topologyType = this.primary ? TopologyType.ReplicaSetWithPrimary : TopologyType.ReplicaSetNoPrimary;
486 if(ismaster.setName) this.setName = ismaster.setName;
487 removeFrom(server, self.unknownServers);
488 self.emit('joined', 'arbiter', server);
489 emitTopologyDescriptionChanged(self);
496 if(ismaster.passive && ismaster.setName
497 && !inList(ismaster, server, this.passives)
498 && this.setName && this.setName == ismaster.setName) {
499 addToList(self, ServerType.RSSecondary, ismaster, server, this.passives);
501 this.topologyType = this.primary ? TopologyType.ReplicaSetWithPrimary : TopologyType.ReplicaSetNoPrimary;
502 if(ismaster.setName) this.setName = ismaster.setName;
503 removeFrom(server, self.unknownServers);
506 if(this.primary && this.primary.name == server.name) {
509 self.emit('left', 'primary', server);
512 self.emit('joined', 'secondary', server);
513 emitTopologyDescriptionChanged(self);
518 // Remove the primary
520 if(this.set[server.name.toLowerCase()] && this.set[server.name.toLowerCase()].type == ServerType.RSPrimary) {
521 self.emit('left', 'primary', this.primary);
522 this.primary.destroy();
524 this.topologyType = TopologyType.ReplicaSetNoPrimary;
528 this.topologyType = this.primary ? TopologyType.ReplicaSetWithPrimary : TopologyType.ReplicaSetNoPrimary;
533 * Recalculate single server max staleness
536 ReplSetState.prototype.updateServerMaxStaleness = function(server, haInterval) {
537 // Locate the max secondary lastwrite
539 // Go over all secondaries
540 for(var i = 0; i < this.secondaries.length; i++) {
541 max = Math.max(max, this.secondaries[i].lastWriteDate);
544 // Perform this servers staleness calculation
545 if(server.ismaster.maxWireVersion >= 5
546 && server.ismaster.secondary
547 && this.hasPrimary()) {
548 server.staleness = (server.lastUpdateTime - server.lastWriteDate)
549 - (this.primary.lastUpdateTime - this.primary.lastWriteDate)
551 } else if(server.ismaster.maxWireVersion >= 5
552 && server.ismaster.secondary){
553 server.staleness = max - server.lastWriteDate + haInterval;
558 * Recalculate all the stalness values for secodaries
561 ReplSetState.prototype.updateSecondariesMaxStaleness = function(haInterval) {
562 for(var i = 0; i < this.secondaries.length; i++) {
563 this.updateServerMaxStaleness(this.secondaries[i], haInterval);
568 * Pick a server by the passed in ReadPreference
570 * @param {ReadPreference} readPreference The ReadPreference instance to use
572 ReplSetState.prototype.pickServer = function(readPreference) {
573 // If no read Preference set to primary by default
574 readPreference = readPreference || ReadPreference.primary;
576 // maxStalenessSeconds is not allowed with a primary read
577 if(readPreference.preference == 'primary' && readPreference.maxStalenessSeconds != null) {
578 return new MongoError('primary readPreference incompatible with maxStalenessSeconds');
581 // Check if we have any non compatible servers for maxStalenessSeconds
582 var allservers = this.primary ? [this.primary] : [];
583 allservers = allservers.concat(this.secondaries);
585 // Does any of the servers not support the right wire protocol version
586 // for maxStalenessSeconds when maxStalenessSeconds specified on readPreference. Then error out
587 if(readPreference.maxStalenessSeconds != null) {
588 for(var i = 0; i < allservers.length; i++) {
589 if(allservers[i].ismaster.maxWireVersion < 5) {
590 return new MongoError('maxStalenessSeconds not supported by at least one of the replicaset members');
595 // Do we have the nearest readPreference
596 if(readPreference.preference == 'nearest' && readPreference.maxStalenessSeconds == null) {
597 return pickNearest(this, readPreference);
598 } else if(readPreference.preference == 'nearest' && readPreference.maxStalenessSeconds != null) {
599 return pickNearestMaxStalenessSeconds(this, readPreference);
602 // Get all the secondaries
603 var secondaries = this.secondaries;
605 // Check if we can satisfy and of the basic read Preferences
606 if(readPreference.equals(ReadPreference.secondary)
607 && secondaries.length == 0) {
608 return new MongoError("no secondary server available");
611 if(readPreference.equals(ReadPreference.secondaryPreferred)
612 && secondaries.length == 0
613 && this.primary == null) {
614 return new MongoError("no secondary or primary server available");
617 if(readPreference.equals(ReadPreference.primary)
618 && this.primary == null) {
619 return new MongoError("no primary server available");
622 // Secondary preferred or just secondaries
623 if(readPreference.equals(ReadPreference.secondaryPreferred)
624 || readPreference.equals(ReadPreference.secondary)) {
626 if(secondaries.length > 0 && readPreference.maxStalenessSeconds == null) {
627 // Pick nearest of any other servers available
628 var server = pickNearest(this, readPreference);
629 // No server in the window return primary
633 } else if(secondaries.length > 0 && readPreference.maxStalenessSeconds != null) {
634 // Pick nearest of any other servers available
635 server = pickNearestMaxStalenessSeconds(this, readPreference);
636 // No server in the window return primary
642 if(readPreference.equals(ReadPreference.secondaryPreferred)){
650 if(readPreference.equals(ReadPreference.primaryPreferred)) {
653 // We prefer the primary if it's available
659 if(secondaries.length > 0 && readPreference.maxStalenessSeconds == null) {
660 server = pickNearest(this, readPreference);
661 } else if(secondaries.length > 0 && readPreference.maxStalenessSeconds != null) {
662 server = pickNearestMaxStalenessSeconds(this, readPreference);
665 // Did we find a server
666 if(server) return server;
669 // Return the primary
674 // Filter serves by tags
675 var filterByTags = function(readPreference, servers) {
676 if(readPreference.tags == null) return servers;
677 var filteredServers = [];
678 var tagsArray = Array.isArray(readPreference.tags) ? readPreference.tags : [readPreference.tags];
680 // Iterate over the tags
681 for(var j = 0; j < tagsArray.length; j++) {
682 var tags = tagsArray[j];
684 // Iterate over all the servers
685 for(var i = 0; i < servers.length; i++) {
686 var serverTag = servers[i].lastIsMaster().tags || {};
688 // Did we find the a matching server
690 // Check if the server is valid
691 for(var name in tags) {
692 if(serverTag[name] != tags[name]) {
697 // Add to candidate list
699 filteredServers.push(servers[i]);
704 // Returned filtered servers
705 return filteredServers;
708 function pickNearestMaxStalenessSeconds(self, readPreference) {
709 // Only get primary and secondaries as seeds
711 var heartbeatFrequencyMS = self.heartbeatFrequencyMS;
713 // Get the maxStalenessMS
714 var maxStalenessMS = readPreference.maxStalenessSeconds * 1000;
716 // Check if the maxStalenessMS > 90 seconds
717 if(maxStalenessMS < 90 * 1000) {
718 return new MongoError('maxStalenessSeconds must be set to at least 90 seconds');
721 // Add primary to list if not a secondary read preference
722 if(self.primary && readPreference.preference != 'secondary') {
723 servers.push(self.primary);
726 // Add all the secondaries
727 for(var i = 0; i < self.secondaries.length; i++) {
728 servers.push(self.secondaries[i]);
732 servers = filterByTags(readPreference, servers);
735 // Locate lowest time (picked servers are lowest time + acceptable Latency margin)
736 // var lowest = servers.length > 0 ? servers[0].lastIsMasterMS : 0;
739 servers = servers.filter(function(s) {
740 return s.staleness <= maxStalenessMS;
744 servers.sort(function(a, b) {
745 // return a.time > b.time;
746 return a.lastIsMasterMS > b.lastIsMasterMS
749 // No servers, default to primary
750 if(servers.length == 0) {
754 // Ensure index does not overflow the number of available servers
755 self.index = self.index % servers.length;
758 var server = servers[self.index];
760 self.index = self.index + 1;
761 // Return the first server of the sorted and filtered list
765 function pickNearest(self, readPreference) {
766 // Only get primary and secondaries as seeds
769 // Add primary to list if not a secondary read preference
770 if(self.primary && readPreference.preference != 'secondary') {
771 servers.push(self.primary);
774 // Add all the secondaries
775 for(var i = 0; i < self.secondaries.length; i++) {
776 servers.push(self.secondaries[i]);
780 servers = filterByTags(readPreference, servers);
783 servers.sort(function(a, b) {
784 // return a.time > b.time;
785 return a.lastIsMasterMS > b.lastIsMasterMS
788 // Locate lowest time (picked servers are lowest time + acceptable Latency margin)
789 var lowest = servers.length > 0 ? servers[0].lastIsMasterMS : 0;
792 servers = servers.filter(function(s) {
793 return s.lastIsMasterMS <= lowest + self.acceptableLatency;
796 // No servers, default to primary
797 if(servers.length == 0) {
801 // Ensure index does not overflow the number of available servers
802 self.index = self.index % servers.length;
804 var server = servers[self.index];
806 self.index = self.index + 1;
807 // Return the first server of the sorted and filtered list
811 function inList(ismaster, server, list) {
812 for(var i = 0; i < list.length; i++) {
813 if(list[i].name == server.name) return true;
819 function addToList(self, type, ismaster, server, list) {
820 // Update set information about the server instance
821 self.set[server.name.toLowerCase()].type = type;
822 self.set[server.name.toLowerCase()].electionId = ismaster ? ismaster.electionId : ismaster;
823 self.set[server.name.toLowerCase()].setName = ismaster ? ismaster.setName : ismaster;
824 self.set[server.name.toLowerCase()].setVersion = ismaster ? ismaster.setVersion : ismaster;
829 function compareObjectIds(id1, id2) {
830 var a = new Buffer(id1.toHexString(), 'hex');
831 var b = new Buffer(id2.toHexString(), 'hex');
837 if(typeof Buffer.compare === 'function') {
838 return Buffer.compare(a, b);
843 var len = Math.min(x, y);
845 for (var i = 0; i < len; i++) {
856 return x < y ? -1 : y < x ? 1 : 0;
859 function removeFrom(server, list) {
860 for(var i = 0; i < list.length; i++) {
861 if(list[i].equals && list[i].equals(server)) {
864 } else if(typeof list[i] == 'string' && list[i] == server.name) {
873 function emitTopologyDescriptionChanged(self) {
874 if(self.listeners('topologyDescriptionChanged').length > 0) {
875 var topology = 'Unknown';
876 var setName = self.setName;
878 if(self.hasPrimaryAndSecondary()) {
879 topology = 'ReplicaSetWithPrimary';
880 } else if(!self.hasPrimary() && self.hasSecondary()) {
881 topology = 'ReplicaSetNoPrimary';
884 // Generate description
886 topologyType: topology,
891 // Add the primary to the list
892 if(self.hasPrimary()) {
893 var desc = self.primary.getDescription();
894 desc.type = 'RSPrimary';
895 description.servers.push(desc);
898 // Add all the secondaries
899 description.servers = description.servers.concat(self.secondaries.map(function(x) {
900 var description = x.getDescription();
901 description.type = 'RSSecondary';
905 // Add all the arbiters
906 description.servers = description.servers.concat(self.arbiters.map(function(x) {
907 var description = x.getDescription();
908 description.type = 'RSArbiter';
912 // Add all the passives
913 description.servers = description.servers.concat(self.passives.map(function(x) {
914 var description = x.getDescription();
915 description.type = 'RSSecondary';
922 previousDescription: self.replicasetDescription,
923 newDescription: description,
924 diff: diff(self.replicasetDescription, description)
927 // Emit the topologyDescription change
928 self.emit('topologyDescriptionChanged', result);
930 // Set the new description
931 self.replicasetDescription = description;
935 function diff(previous, current) {
936 // Difference document
943 previous = { servers: [] };
946 // Got through all the servers
947 for(var i = 0; i < previous.servers.length; i++) {
948 var prevServer = previous.servers[i];
950 // Go through all current servers
951 for(var j = 0; j < current.servers.length; j++) {
952 var currServer = current.servers[j];
955 if(prevServer.address === currServer.address) {
956 // We had a change in state
957 if(prevServer.type != currServer.type) {
959 address: prevServer.address,
960 from: prevServer.type,
972 module.exports = ReplSetState;