600e5540c610eee39ff75e60e5e89ca86dea94bf
[aai/esr-gui.git] /
1 "use strict"
2
3 var inherits = require('util').inherits,
4   f = require('util').format,
5   EventEmitter = require('events').EventEmitter,
6   Logger = require('../connection/logger'),
7   ObjectId = require('bson').ObjectId,
8   ReadPreference = require('./read_preference'),
9   MongoError = require('../error');
10
11 var TopologyType = {
12   'Single': 'Single', 'ReplicaSetNoPrimary': 'ReplicaSetNoPrimary',
13   'ReplicaSetWithPrimary': 'ReplicaSetWithPrimary', 'Sharded': 'Sharded',
14   'Unknown': 'Unknown'
15 };
16
17 var ServerType = {
18   'Standalone': 'Standalone', 'Mongos': 'Mongos', 'PossiblePrimary': 'PossiblePrimary',
19   'RSPrimary': 'RSPrimary', 'RSSecondary': 'RSSecondary', 'RSArbiter': 'RSArbiter',
20   'RSOther': 'RSOther', 'RSGhost': 'RSGhost', 'Unknown': 'Unknown'
21 };
22
23 var ReplSetState = function(options) {
24   options = options || {};
25   // Add event listener
26   EventEmitter.call(this);
27   // Topology state
28   this.topologyType = TopologyType.ReplicaSetNoPrimary;
29   this.setName = options.setName;
30
31   // Server set
32   this.set = {};
33
34   // Unpacked options
35   this.id = options.id;
36   this.setName = options.setName;
37
38   // Replicaset logger
39   this.logger = options.logger || Logger('ReplSet', options);
40
41   // Server selection index
42   this.index = 0;
43   // Acceptable latency
44   this.acceptableLatency = options.acceptableLatency || 15;
45
46   // heartbeatFrequencyMS
47   this.heartbeatFrequencyMS = options.heartbeatFrequencyMS || 10000;
48
49   // Server side
50   this.primary = null;
51   this.secondaries = [];
52   this.arbiters = [];
53   this.passives = [];
54   this.ghosts = [];
55   // Current unknown hosts
56   this.unknownServers = [];
57   // In set status
58   this.set = {};
59   // Status
60   this.maxElectionId = null;
61   this.maxSetVersion = 0;
62   // Description of the Replicaset
63   this.replicasetDescription = {
64     "topologyType": "Unknown", "servers": []
65   };
66 }
67
68 inherits(ReplSetState, EventEmitter);
69
70 ReplSetState.prototype.hasPrimaryAndSecondary = function(server) {
71   return this.primary != null && this.secondaries.length > 0;
72 }
73
74 ReplSetState.prototype.hasPrimary = function(server) {
75   return this.primary != null;
76 }
77
78 ReplSetState.prototype.hasSecondary = function(server) {
79   return this.secondaries.length > 0;
80 }
81
82 ReplSetState.prototype.allServers = function(options) {
83   options = options || {};
84   var servers = this.primary ? [this.primary] : [];
85   servers = servers.concat(this.secondaries);
86   if(!options.ignoreArbiters) servers = servers.concat(this.arbiters);
87   servers = servers.concat(this.passives);
88   return servers;
89 }
90
91 ReplSetState.prototype.destroy = function() {
92   // Destroy all sockets
93   if(this.primary) this.primary.destroy();
94   this.secondaries.forEach(function(x) { x.destroy(); });
95   this.arbiters.forEach(function(x) { x.destroy(); });
96   this.passives.forEach(function(x) { x.destroy(); });
97   this.ghosts.forEach(function(x) { x.destroy(); });
98   // Clear out the complete state
99   this.secondaries = [];
100   this.arbiters = [];
101   this.passives = [];
102   this.ghosts = [];
103   this.unknownServers = [];
104   this.set = {};
105 }
106
107 ReplSetState.prototype.remove = function(server, options) {
108   options = options || {};
109
110   // Only remove if the current server is not connected
111   var servers = this.primary ? [this.primary] : [];
112   servers = servers.concat(this.secondaries);
113   servers = servers.concat(this.arbiters);
114   servers = servers.concat(this.passives);
115
116   // Check if it's active and this is just a failed connection attempt
117   for(var i = 0; i < servers.length; i++) {
118     if(!options.force && servers[i].equals(server) && servers[i].isConnected && servers[i].isConnected()) {
119       return;
120     }
121   }
122
123   // If we have it in the set remove it
124   if(this.set[server.name]) {
125     this.set[server.name].type = ServerType.Unknown;
126     this.set[server.name].electionId = null;
127     this.set[server.name].setName = null;
128     this.set[server.name].setVersion = null;
129   }
130
131   // Remove type
132   var removeType = null;
133
134   // Remove from any lists
135   if(this.primary && this.primary.equals(server)) {
136     this.primary = null;
137     this.topologyType = TopologyType.ReplicaSetNoPrimary;
138     removeType = 'primary';
139   }
140
141   // Remove from any other server lists
142   removeType = removeFrom(server, this.secondaries) ? 'secondary' : removeType;
143   removeType = removeFrom(server, this.arbiters) ? 'arbiter' : removeType;
144   removeType = removeFrom(server, this.passives) ? 'secondary' : removeType;
145   removeFrom(server, this.ghosts);
146   removeFrom(server, this.unknownServers);
147
148   // Do we have a removeType
149   if(removeType) {
150     this.emit('left', removeType, server);
151   }
152 }
153
154 ReplSetState.prototype.update = function(server) {
155   var self = this;
156   // Get the current ismaster
157   var ismaster = server.lastIsMaster();
158
159   //
160   // Add any hosts
161   //
162   if(ismaster) {
163     // Join all the possible new hosts
164     var hosts = Array.isArray(ismaster.hosts) ? ismaster.hosts : [];
165     hosts = hosts.concat(Array.isArray(ismaster.arbiters) ? ismaster.arbiters : []);
166     hosts = hosts.concat(Array.isArray(ismaster.passives) ? ismaster.passives : []);
167
168     // Add all hosts as unknownServers
169     for(var i = 0; i < hosts.length; i++) {
170       // Add to the list of unknown server
171       if(this.unknownServers.indexOf(hosts[i]) == -1
172         && (!this.set[hosts[i]] || this.set[hosts[i]].type == ServerType.Unknown)) {
173         this.unknownServers.push(hosts[i]);
174       }
175
176       if(!this.set[hosts[i]]) {
177         this.set[hosts[i]] = {
178           type: ServerType.Unknown,
179           electionId: null,
180           setName: null,
181           setVersion: null
182         }
183       }
184     }
185   }
186
187   //
188   // Unknown server
189   //
190   if(!ismaster && !inList(ismaster, server, this.unknownServers)) {
191     self.set[server.name] = {
192       type: ServerType.Unknown, setVersion: null, electionId: null, setName: null
193     }
194     // Update set information about the server instance
195     self.set[server.name].type = ServerType.Unknown;
196     self.set[server.name].electionId = ismaster ? ismaster.electionId : ismaster;
197     self.set[server.name].setName = ismaster ? ismaster.setName : ismaster;
198     self.set[server.name].setVersion = ismaster ? ismaster.setVersion : ismaster;
199
200     if(self.unknownServers.indexOf(server.name) == -1) {
201       self.unknownServers.push(server.name);
202     }
203
204     // Set the topology
205     return false;
206   }
207
208   //
209   // Is this a mongos
210   //
211   if(ismaster && ismaster.msg == 'isdbgrid') {
212     return false;
213   }
214
215   // A RSOther instance
216   if((ismaster.setName && ismaster.hidden)
217     || (ismaster.setName && !ismaster.ismaster && !ismaster.secondary && !ismaster.arbiterOnly && !ismaster.passive)) {
218     self.set[server.name] = {
219       type: ServerType.RSOther, setVersion: null,
220       electionId: null, setName: ismaster.setName
221     }
222     // Set the topology
223     this.topologyType = this.primary ? TopologyType.ReplicaSetWithPrimary : TopologyType.ReplicaSetNoPrimary;
224     if(ismaster.setName) this.setName = ismaster.setName;
225     return false;
226   }
227
228   // A RSGhost instance
229   if(ismaster.isreplicaset) {
230     self.set[server.name] = {
231       type: ServerType.RSGhost, setVersion: null,
232       electionId: null, setName: null
233     }
234
235     // Set the topology
236     this.topologyType = this.primary ? TopologyType.ReplicaSetWithPrimary : TopologyType.ReplicaSetNoPrimary;
237     if(ismaster.setName) this.setName = ismaster.setName;
238
239     // Set the topology
240     return false;
241   }
242
243   //
244   // Standalone server, destroy and return
245   //
246   if(ismaster && ismaster.ismaster && !ismaster.setName) {
247     this.topologyType = this.primary ? TopologyType.ReplicaSetWithPrimary : TopologyType.Unknown;
248     this.remove(server, {force:true});
249     return false;
250   }
251
252   //
253   // Server in maintanance mode
254   //
255   if(ismaster && !ismaster.ismaster && !ismaster.secondary && !ismaster.arbiterOnly) {
256     this.remove(server, {force:true});
257     return false;
258   }
259
260   //
261   // If the .me field does not match the passed in server
262   //
263   if(ismaster.me && ismaster.me != server.name) {
264     if(this.logger.isWarn()) {
265       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));
266     }
267
268     // Set the type of topology we have
269     if(this.primary && !this.primary.equals(server)) {
270       this.topologyType = TopologyType.ReplicaSetWithPrimary;
271     } else {
272       this.topologyType = TopologyType.ReplicaSetNoPrimary;
273     }
274
275     return false;
276   }
277
278   //
279   // Primary handling
280   //
281   if(!this.primary && ismaster.ismaster && ismaster.setName) {
282     var ismasterElectionId = server.lastIsMaster().electionId;
283     if(this.setName && this.setName != ismaster.setName) {
284       this.topologyType = TopologyType.ReplicaSetNoPrimary;
285       return new MongoError(f('setName from ismaster does not match provided connection setName [%s] != [%s]', ismaster.setName, this.setName));
286     }
287
288     if(!this.maxElectionId && ismasterElectionId) {
289       this.maxElectionId = ismasterElectionId;
290     } else if(this.maxElectionId && ismasterElectionId) {
291       var result = compareObjectIds(this.maxElectionId, ismasterElectionId);
292       // Get the electionIds
293       var ismasterSetVersion = server.lastIsMaster().setVersion;
294
295       // if(result == 1 || result == 0) {
296       if(result == 1) {
297         this.topologyType = TopologyType.ReplicaSetNoPrimary;
298         return false;
299       } else if(result == 0 && ismasterSetVersion) {
300         if(ismasterSetVersion < this.maxSetVersion) {
301           this.topologyType = TopologyType.ReplicaSetNoPrimary;
302           return false;
303         }
304       }
305
306       this.maxSetVersion = ismasterSetVersion;
307       this.maxElectionId = ismasterElectionId;
308     }
309
310     self.primary = server;
311     self.set[server.name] = {
312       type: ServerType.RSPrimary,
313       setVersion: ismaster.setVersion,
314       electionId: ismaster.electionId,
315       setName: ismaster.setName
316     }
317
318     // Set the topology
319     this.topologyType = TopologyType.ReplicaSetWithPrimary;
320     if(ismaster.setName) this.setName = ismaster.setName;
321     removeFrom(server, self.unknownServers);
322     removeFrom(server, self.secondaries);
323     removeFrom(server, self.passives);
324     self.emit('joined', 'primary', server);
325     emitTopologyDescriptionChanged(self);
326     return true;
327   } else if(ismaster.ismaster && ismaster.setName) {
328     // Get the electionIds
329     var currentElectionId = self.set[self.primary.name].electionId;
330     var currentSetVersion = self.set[self.primary.name].setVersion;
331     var currentSetName = self.set[self.primary.name].setName;
332     var ismasterElectionId = server.lastIsMaster().electionId;
333     var ismasterSetVersion = server.lastIsMaster().setVersion;
334     var ismasterSetName = server.lastIsMaster().setName;
335
336     // Is it the same server instance
337     if(this.primary.equals(server)
338       && currentSetName == ismasterSetName) {
339         return false;
340     }
341
342     // If we do not have the same rs name
343     if(currentSetName && currentSetName != ismasterSetName) {
344       if(!this.primary.equals(server)) {
345         this.topologyType = TopologyType.ReplicaSetWithPrimary;
346       } else {
347         this.topologyType = TopologyType.ReplicaSetNoPrimary;
348       }
349
350       return false;
351     }
352
353     // Check if we need to replace the server
354     if(currentElectionId && ismasterElectionId) {
355       var result = compareObjectIds(currentElectionId, ismasterElectionId);
356
357       if(result == 1) {
358         return false;
359       } else if(result == 0 && (currentSetVersion > ismasterSetVersion)) {
360         return false;
361       }
362     } else if(!currentElectionId && ismasterElectionId
363       && ismasterSetVersion) {
364         if(ismasterSetVersion < this.maxSetVersion) {
365           return false;
366         }
367     }
368
369     if(!this.maxElectionId && ismasterElectionId) {
370       this.maxElectionId = ismasterElectionId;
371     } else if(this.maxElectionId && ismasterElectionId) {
372       var result = compareObjectIds(this.maxElectionId, ismasterElectionId);
373
374       if(result == 1) {
375         return false;
376       } else if(result == 0 && currentSetVersion && ismasterSetVersion) {
377         if(ismasterSetVersion < this.maxSetVersion) {
378           return false;
379         }
380       } else {
381         if(ismasterSetVersion < this.maxSetVersion) {
382           return false;
383         }
384       }
385
386       this.maxElectionId = ismasterElectionId;
387       this.maxSetVersion = ismasterSetVersion;
388     } else {
389       this.maxSetVersion = ismasterSetVersion;
390     }
391
392     // Modify the entry to unknown
393     self.set[self.primary.name] = {
394       type: ServerType.Unknown, setVersion: null,
395       electionId: null, setName: null
396     }
397
398     // Signal primary left
399     self.emit('left', 'primary', this.primary);
400     // Destroy the instance
401     self.primary.destroy();
402     // Set the new instance
403     self.primary = server;
404     // Set the set information
405     self.set[server.name] = {
406       type: ServerType.RSPrimary, setVersion: ismaster.setVersion,
407       electionId: ismaster.electionId, setName: ismaster.setName
408     }
409
410     // Set the topology
411     this.topologyType = TopologyType.ReplicaSetWithPrimary;
412     if(ismaster.setName) this.setName = ismaster.setName;
413     removeFrom(server, self.unknownServers);
414     removeFrom(server, self.secondaries);
415     removeFrom(server, self.passives);
416     self.emit('joined', 'primary', server);
417     emitTopologyDescriptionChanged(self);
418     return true;
419   }
420
421   // A possible instance
422   if(!this.primary && ismaster.primary) {
423     self.set[ismaster.primary] = {
424       type: ServerType.PossiblePrimary, setVersion: null,
425       electionId: null, setName: null
426     }
427   }
428
429   //
430   // Secondary handling
431   //
432   if(ismaster.secondary && ismaster.setName
433     && !inList(ismaster, server, this.secondaries)
434     && this.setName && this.setName == ismaster.setName) {
435     addToList(self, ServerType.RSSecondary, ismaster, server, this.secondaries);
436     // Set the topology
437     this.topologyType = this.primary ? TopologyType.ReplicaSetWithPrimary : TopologyType.ReplicaSetNoPrimary;
438     if(ismaster.setName) this.setName = ismaster.setName;
439     removeFrom(server, self.unknownServers);
440
441     // Remove primary
442     if(this.primary && this.primary.name == server.name) {
443       server.destroy();
444       this.primary = null;
445       self.emit('left', 'primary', server);
446     }
447
448     self.emit('joined', 'secondary', server);
449     emitTopologyDescriptionChanged(self);
450     return true;
451   }
452
453   //
454   // Arbiter handling
455   //
456   if(ismaster.arbiterOnly && ismaster.setName
457     && !inList(ismaster, server, this.arbiters)
458     && this.setName && this.setName == ismaster.setName) {
459     addToList(self, ServerType.RSArbiter, ismaster, server, this.arbiters);
460     // Set the topology
461     this.topologyType = this.primary ? TopologyType.ReplicaSetWithPrimary : TopologyType.ReplicaSetNoPrimary;
462     if(ismaster.setName) this.setName = ismaster.setName;
463     removeFrom(server, self.unknownServers);
464     self.emit('joined', 'arbiter', server);
465     emitTopologyDescriptionChanged(self);
466     return true;
467   }
468
469   //
470   // Passive handling
471   //
472   if(ismaster.passive && ismaster.setName
473     && !inList(ismaster, server, this.passives)
474     && this.setName && this.setName == ismaster.setName) {
475     addToList(self, ServerType.RSSecondary, ismaster, server, this.passives);
476     // Set the topology
477     this.topologyType = this.primary ? TopologyType.ReplicaSetWithPrimary : TopologyType.ReplicaSetNoPrimary;
478     if(ismaster.setName) this.setName = ismaster.setName;
479     removeFrom(server, self.unknownServers);
480
481     // Remove primary
482     if(this.primary && this.primary.name == server.name) {
483       server.destroy();
484       this.primary = null;
485       self.emit('left', 'primary', server);
486     }
487
488     self.emit('joined', 'secondary', server);
489     emitTopologyDescriptionChanged(self);
490     return true;
491   }
492
493   //
494   // Remove the primary
495   //
496   if(this.set[server.name] && this.set[server.name].type == ServerType.RSPrimary) {
497     self.emit('left', 'primary', this.primary);
498     this.primary.destroy();
499     this.primary = null;
500     this.topologyType = TopologyType.ReplicaSetNoPrimary;
501     return false;
502   }
503
504   this.topologyType = this.primary ? TopologyType.ReplicaSetWithPrimary : TopologyType.ReplicaSetNoPrimary;
505   return false;
506 }
507
508 /**
509  * Recalculate single server max staleness
510  * @method
511  */
512 ReplSetState.prototype.updateServerMaxStaleness = function(server, haInterval) {
513   // Locate the max secondary lastwrite
514   var max = 0;
515   // Go over all secondaries
516   for(var i = 0; i < this.secondaries.length; i++) {
517     max = Math.max(max, this.secondaries[i].lastWriteDate);
518   }
519
520   // Perform this servers staleness calculation
521   if(server.ismaster.maxWireVersion >= 5
522     && server.ismaster.secondary
523     && this.hasPrimary()) {
524     server.staleness = (server.lastUpdateTime - server.lastWriteDate)
525       - (this.primary.lastUpdateTime - this.primary.lastWriteDate)
526       + haInterval;
527   } else if(server.ismaster.maxWireVersion >= 5
528     && server.ismaster.secondary){
529     server.staleness = max - server.lastWriteDate + haInterval;
530   }
531 }
532
533 /**
534  * Recalculate all the stalness values for secodaries
535  * @method
536  */
537 ReplSetState.prototype.updateSecondariesMaxStaleness = function(haInterval) {
538   for(var i = 0; i < this.secondaries.length; i++) {
539     this.updateServerMaxStaleness(this.secondaries[i], haInterval);
540   }
541 }
542
543 /**
544  * Pick a server by the passed in ReadPreference
545  * @method
546  * @param {ReadPreference} readPreference The ReadPreference instance to use
547  */
548 ReplSetState.prototype.pickServer = function(readPreference) {
549   // If no read Preference set to primary by default
550   readPreference = readPreference || ReadPreference.primary;
551
552   // maxStalenessMS is not allowed with a primary read
553   if(readPreference.preference == 'primary' && readPreference.maxStalenessMS) {
554     return new MongoError('primary readPreference incompatible with maxStalenessMS');
555   }
556
557   // Check if we have any non compatible servers for maxStalenessMS
558   var allservers = this.primary ? [this.primary] : [];
559   allservers = allservers.concat(this.secondaries);
560
561   // Does any of the servers not support the right wire protocol version
562   // for maxStalenessMS when maxStalenessMS specified on readPreference. Then error out
563   if(readPreference.maxStalenessMS) {
564     for(var i = 0; i < allservers.length; i++) {
565       if(allservers[i].ismaster.maxWireVersion < 5) {
566         return new MongoError('maxStalenessMS not supported by at least one of the replicaset members');
567       }
568     }
569   }
570
571   // Do we have the nearest readPreference
572   if(readPreference.preference == 'nearest' && !readPreference.maxStalenessMS) {
573     return pickNearest(this, readPreference);
574   } else if(readPreference.preference == 'nearest' && readPreference.maxStalenessMS) {
575     return pickNearestMaxStalenessMS(this, readPreference);
576   }
577
578   // Get all the secondaries
579   var secondaries = this.secondaries;
580
581   // Check if we can satisfy and of the basic read Preferences
582   if(readPreference.equals(ReadPreference.secondary)
583     && secondaries.length == 0) {
584       return new MongoError("no secondary server available");
585     }
586
587   if(readPreference.equals(ReadPreference.secondaryPreferred)
588     && secondaries.length == 0
589     && this.primary == null) {
590       return new MongoError("no secondary or primary server available");
591     }
592
593   if(readPreference.equals(ReadPreference.primary)
594     && this.primary == null) {
595       return new MongoError("no primary server available");
596     }
597
598   // Secondary preferred or just secondaries
599   if(readPreference.equals(ReadPreference.secondaryPreferred)
600     || readPreference.equals(ReadPreference.secondary)) {
601
602     if(secondaries.length > 0 && !readPreference.maxStalenessMS) {
603       // Pick nearest of any other servers available
604       var server = pickNearest(this, readPreference);
605       // No server in the window return primary
606       if(server) {
607         return server;
608       }
609     } else if(secondaries.length > 0 && readPreference.maxStalenessMS) {
610       // Pick nearest of any other servers available
611       var server = pickNearestMaxStalenessMS(this, readPreference);
612       // No server in the window return primary
613       if(server) {
614         return server;
615       }
616     }
617
618     if(readPreference.equals(ReadPreference.secondaryPreferred)){
619       return this.primary;
620     }
621
622     return null;
623   }
624
625   // Primary preferred
626   if(readPreference.equals(ReadPreference.primaryPreferred)) {
627     var server = null;
628
629     // We prefer the primary if it's available
630     if(this.primary) {
631       return this.primary;
632     }
633
634     // Pick a secondary
635     if(secondaries.length > 0 && !readPreference.maxStalenessMS) {
636       server = pickNearest(this, readPreference);
637     } else if(secondaries.length > 0 && readPreference.maxStalenessMS) {
638       server = pickNearestMaxStalenessMS(this, readPreference);
639     }
640
641     //  Did we find a server
642     if(server) return server;
643   }
644
645   // Return the primary
646   return this.primary;
647 }
648
649 //
650 // Filter serves by tags
651 var filterByTags = function(readPreference, servers) {
652   if(readPreference.tags == null) return servers;
653   var filteredServers = [];
654   var tagsArray = Array.isArray(readPreference.tags) ? readPreference.tags : [readPreference.tags];
655
656   // Iterate over the tags
657   for(var j = 0; j < tagsArray.length; j++) {
658     var tags = tagsArray[j];
659
660     // Iterate over all the servers
661     for(var i = 0; i < servers.length; i++) {
662       var serverTag = servers[i].lastIsMaster().tags || {};
663
664       // Did we find the a matching server
665       var found = true;
666       // Check if the server is valid
667       for(var name in tags) {
668         if(serverTag[name] != tags[name]) found = false;
669       }
670
671       // Add to candidate list
672       if(found) {
673         filteredServers.push(servers[i]);
674       }
675     }
676
677     // We found servers by the highest priority
678     if(found) break;
679   }
680
681   // Returned filtered servers
682   return filteredServers;
683 }
684
685 function pickNearestMaxStalenessMS(self, readPreference) {
686   // Only get primary and secondaries as seeds
687   var seeds = {};
688   var servers = [];
689   var heartbeatFrequencyMS = self.heartbeatFrequencyMS;
690
691   // Check if the maxStalenessMS > heartbeatFrequencyMS * 2
692   if(readPreference.maxStalenessMS < (heartbeatFrequencyMS * 2)) {
693     return new MongoError('maxStalenessMS must be at least twice the haInterval');
694   }
695
696   // Add primary to list if not a secondary read preference
697   if(self.primary && readPreference.preference != 'secondary') {
698     servers.push(self.primary);
699   }
700
701   // Add all the secondaries
702   for(var i = 0; i < self.secondaries.length; i++) {
703     servers.push(self.secondaries[i]);
704   }
705
706   // Filter by tags
707   servers = filterByTags(readPreference, servers);
708
709   //
710   // Locate lowest time (picked servers are lowest time + acceptable Latency margin)
711   // var lowest = servers.length > 0 ? servers[0].lastIsMasterMS : 0;
712
713   // Filter by latency
714   servers = servers.filter(function(s) {
715     return s.staleness <= readPreference.maxStalenessMS;
716   });
717
718   // Sort by time
719   servers.sort(function(a, b) {
720     // return a.time > b.time;
721     return a.lastIsMasterMS > b.lastIsMasterMS
722   });
723
724   // No servers, default to primary
725   if(servers.length == 0) {
726     return null
727   }
728
729   // Ensure index does not overflow the number of available servers
730   self.index = self.index % servers.length;
731
732   // Get the server
733   var server = servers[self.index];
734   // Add to the index
735   self.index = self.index + 1;
736   // Return the first server of the sorted and filtered list
737   return server;
738 }
739
740 function pickNearest(self, readPreference) {
741   // Only get primary and secondaries as seeds
742   var seeds = {};
743   var servers = [];
744
745   // Add primary to list if not a secondary read preference
746   if(self.primary && readPreference.preference != 'secondary') {
747     servers.push(self.primary);
748   }
749
750   // Add all the secondaries
751   for(var i = 0; i < self.secondaries.length; i++) {
752     servers.push(self.secondaries[i]);
753   }
754
755   // Filter by tags
756   servers = filterByTags(readPreference, servers);
757
758   // Sort by time
759   servers.sort(function(a, b) {
760     // return a.time > b.time;
761     return a.lastIsMasterMS > b.lastIsMasterMS
762   });
763
764   // Locate lowest time (picked servers are lowest time + acceptable Latency margin)
765   var lowest = servers.length > 0 ? servers[0].lastIsMasterMS : 0;
766
767   // Filter by latency
768   servers = servers.filter(function(s) {
769     return s.lastIsMasterMS <= lowest + self.acceptableLatency;
770   });
771
772   // No servers, default to primary
773   if(servers.length == 0) {
774     return null
775   }
776
777   // Ensure index does not overflow the number of available servers
778   self.index = self.index % servers.length;
779   // Get the server
780   var server = servers[self.index];
781   // Add to the index
782   self.index = self.index + 1;
783   // Return the first server of the sorted and filtered list
784   return server;
785 }
786
787 function inList(ismaster, server, list) {
788   for(var i = 0; i < list.length; i++) {
789     if(list[i].name == server.name) return true;
790   }
791
792   return false;
793 }
794
795 function addToList(self, type, ismaster, server, list) {
796   // Update set information about the server instance
797   self.set[server.name].type = type;
798   self.set[server.name].electionId = ismaster ? ismaster.electionId : ismaster;
799   self.set[server.name].setName = ismaster ? ismaster.setName : ismaster;
800   self.set[server.name].setVersion = ismaster ? ismaster.setVersion : ismaster;
801   // Add to the list
802   list.push(server);
803 }
804
805 function compareObjectIds(id1, id2) {
806   var a = new Buffer(id1.toHexString(), 'hex');
807   var b = new Buffer(id2.toHexString(), 'hex');
808
809   if(a === b) {
810     return 0;
811   }
812
813   if(typeof Buffer.compare === 'function') {
814     return Buffer.compare(a, b);
815   }
816
817   var x = a.length;
818   var y = b.length;
819   var len = Math.min(x, y);
820
821   for (var i = 0; i < len; i++) {
822     if (a[i] !== b[i]) {
823       break;
824     }
825   }
826
827   if (i !== len) {
828     x = a[i];
829     y = b[i];
830   }
831
832   return x < y ? -1 : y < x ? 1 : 0;
833 }
834
835 function removeFrom(server, list) {
836   for(var i = 0; i < list.length; i++) {
837     if(list[i].equals && list[i].equals(server)) {
838       list.splice(i, 1);
839       return true;
840     } else if(typeof list[i] == 'string' && list[i] == server.name) {
841       list.splice(i, 1);
842       return true;
843     }
844   }
845
846   return false;
847 }
848
849 function emitTopologyDescriptionChanged(self) {
850   if(self.listeners('topologyDescriptionChanged').length > 0) {
851     var topology = 'Unknown';
852     var setName = self.setName;
853
854     if(self.hasPrimaryAndSecondary()) {
855       topology = 'ReplicaSetWithPrimary';
856     } else if(!self.hasPrimary() && self.hasSecondary()) {
857       topology = 'ReplicaSetNoPrimary';
858     }
859
860     // Generate description
861     var description = {
862       topologyType: topology,
863       setName: setName,
864       servers: []
865     }
866
867     // Add the primary to the list
868     if(self.hasPrimary()) {
869       var desc = self.primary.getDescription();
870       desc.type = 'RSPrimary';
871       description.servers.push(desc);
872     }
873
874     // Add all the secondaries
875     description.servers = description.servers.concat(self.secondaries.map(function(x) {
876       var description = x.getDescription();
877       description.type = 'RSSecondary';
878       return description;
879     }));
880
881     // Add all the arbiters
882     description.servers = description.servers.concat(self.arbiters.map(function(x) {
883       var description = x.getDescription();
884       description.type = 'RSArbiter';
885       return description;
886     }));
887
888     // Add all the passives
889     description.servers = description.servers.concat(self.passives.map(function(x) {
890       var description = x.getDescription();
891       description.type = 'RSSecondary';
892       return description;
893     }));
894
895     // Create the result
896     var result = {
897       topologyId: self.id,
898       previousDescription: self.replicasetDescription,
899       newDescription: description,
900       diff: diff(self.replicasetDescription, description)
901     };
902
903     // Emit the topologyDescription change
904     self.emit('topologyDescriptionChanged', result);
905
906     // Set the new description
907     self.replicasetDescription = description;
908   }
909 }
910
911 function diff(previous, current) {
912   // Difference document
913   var diff = {
914     servers: []
915   }
916
917   // Previous entry
918   if(!previous) {
919     previous = { servers: [] };
920   }
921
922   // Got through all the servers
923   for(var i = 0; i < previous.servers.length; i++) {
924     var prevServer = previous.servers[i];
925
926     // Go through all current servers
927     for(var j = 0; j < current.servers.length; j++) {
928       var currServer = current.servers[j];
929
930       // Matching server
931       if(prevServer.address === currServer.address) {
932         // We had a change in state
933         if(prevServer.type != currServer.type) {
934           diff.servers.push({
935             address: prevServer.address,
936             from: prevServer.type,
937             to: currServer.type
938           });
939         }
940       }
941     }
942   }
943
944   // Return difference
945   return diff;
946 }
947
948 module.exports = ReplSetState;