4fb1d4d7fa877301b1c862b2f4c3da586b2044d0
[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   ReadPreference = require('./read_preference'),
8   MongoError = require('../error');
9
10 var TopologyType = {
11   'Single': 'Single', 'ReplicaSetNoPrimary': 'ReplicaSetNoPrimary',
12   'ReplicaSetWithPrimary': 'ReplicaSetWithPrimary', 'Sharded': 'Sharded',
13   'Unknown': 'Unknown'
14 };
15
16 var ServerType = {
17   'Standalone': 'Standalone', 'Mongos': 'Mongos', 'PossiblePrimary': 'PossiblePrimary',
18   'RSPrimary': 'RSPrimary', 'RSSecondary': 'RSSecondary', 'RSArbiter': 'RSArbiter',
19   'RSOther': 'RSOther', 'RSGhost': 'RSGhost', 'Unknown': 'Unknown'
20 };
21
22 var ReplSetState = function(options) {
23   options = options || {};
24   // Add event listener
25   EventEmitter.call(this);
26   // Topology state
27   this.topologyType = TopologyType.ReplicaSetNoPrimary;
28   this.setName = options.setName;
29
30   // Server set
31   this.set = {};
32
33   // Unpacked options
34   this.id = options.id;
35   this.setName = options.setName;
36
37   // Replicaset logger
38   this.logger = options.logger || Logger('ReplSet', options);
39
40   // Server selection index
41   this.index = 0;
42   // Acceptable latency
43   this.acceptableLatency = options.acceptableLatency || 15;
44
45   // heartbeatFrequencyMS
46   this.heartbeatFrequencyMS = options.heartbeatFrequencyMS || 10000;
47
48   // Server side
49   this.primary = null;
50   this.secondaries = [];
51   this.arbiters = [];
52   this.passives = [];
53   this.ghosts = [];
54   // Current unknown hosts
55   this.unknownServers = [];
56   // In set status
57   this.set = {};
58   // Status
59   this.maxElectionId = null;
60   this.maxSetVersion = 0;
61   // Description of the Replicaset
62   this.replicasetDescription = {
63     "topologyType": "Unknown", "servers": []
64   };
65 }
66
67 inherits(ReplSetState, EventEmitter);
68
69 ReplSetState.prototype.hasPrimaryAndSecondary = function() {
70   return this.primary != null && this.secondaries.length > 0;
71 }
72
73 ReplSetState.prototype.hasPrimary = function() {
74   return this.primary != null;
75 }
76
77 ReplSetState.prototype.hasSecondary = function() {
78   return this.secondaries.length > 0;
79 }
80
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);
87   return servers;
88 }
89
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 = [];
99   this.arbiters = [];
100   this.passives = [];
101   this.ghosts = [];
102   this.unknownServers = [];
103   this.set = {};
104 }
105
106 ReplSetState.prototype.remove = function(server, options) {
107   options = options || {};
108
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);
114
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()) {
118       return;
119     }
120   }
121
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;
128   }
129
130   // Remove type
131   var removeType = null;
132
133   // Remove from any lists
134   if(this.primary && this.primary.equals(server)) {
135     this.primary = null;
136     this.topologyType = TopologyType.ReplicaSetNoPrimary;
137     removeType = 'primary';
138   }
139
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);
146
147   // Do we have a removeType
148   if(removeType) {
149     this.emit('left', removeType, server);
150   }
151 }
152
153 ReplSetState.prototype.update = function(server) {
154   var self = this;
155   // Get the current ismaster
156   var ismaster = server.lastIsMaster();
157
158   //
159   // Add any hosts
160   //
161   if(ismaster) {
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 : []);
166
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]);
173       }
174
175       if(!this.set[hosts[i].toLowerCase()]) {
176         this.set[hosts[i].toLowerCase()] = {
177           type: ServerType.Unknown,
178           electionId: null,
179           setName: null,
180           setVersion: null
181         }
182       }
183     }
184   }
185
186   //
187   // Unknown server
188   //
189   if(!ismaster && !inList(ismaster, server, this.unknownServers)) {
190     self.set[server.name.toLowerCase()] = {
191       type: ServerType.Unknown, setVersion: null, electionId: null, setName: null
192     }
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;
198
199     if(self.unknownServers.indexOf(server.name) == -1) {
200       self.unknownServers.push(server.name);
201     }
202
203     // Set the topology
204     return false;
205   }
206
207   //
208   // Is this a mongos
209   //
210   if(ismaster && ismaster.msg == 'isdbgrid') {
211     return false;
212   }
213
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
220     }
221     // Set the topology
222     this.topologyType = this.primary ? TopologyType.ReplicaSetWithPrimary : TopologyType.ReplicaSetNoPrimary;
223     if(ismaster.setName) this.setName = ismaster.setName;
224     return false;
225   }
226
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
232     }
233
234     // Set the topology
235     this.topologyType = this.primary ? TopologyType.ReplicaSetWithPrimary : TopologyType.ReplicaSetNoPrimary;
236     if(ismaster.setName) this.setName = ismaster.setName;
237
238     // Set the topology
239     return false;
240   }
241
242   //
243   // Standalone server, destroy and return
244   //
245   if(ismaster && ismaster.ismaster && !ismaster.setName) {
246     this.topologyType = this.primary ? TopologyType.ReplicaSetWithPrimary : TopologyType.Unknown;
247     this.remove(server, {force:true});
248     return false;
249   }
250
251   //
252   // Server in maintanance mode
253   //
254   if(ismaster && !ismaster.ismaster && !ismaster.secondary && !ismaster.arbiterOnly) {
255     this.remove(server, {force:true});
256     return false;
257   }
258
259   //
260   // If the .me field does not match the passed in server
261   //
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));
265     }
266
267     // Delete from the set
268     delete this.set[server.name.toLowerCase()];
269
270     // Set the type of topology we have
271     if(this.primary && !this.primary.equals(server)) {
272       this.topologyType = TopologyType.ReplicaSetWithPrimary;
273     } else {
274       this.topologyType = TopologyType.ReplicaSetNoPrimary;
275     }
276
277     //
278     // We have a potential primary
279     //
280     if(!this.primary && ismaster.primary) {
281       this.set[ismaster.primary.toLowerCase()] = {
282         type: ServerType.PossiblePrimary,
283         setName: null,
284         electionId: null,
285         setVersion: null,
286       }
287     }
288
289     return false;
290   }
291
292   //
293   // Primary handling
294   //
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));
300     }
301
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;
308
309       // if(result == 1 || result == 0) {
310       if(result == 1) {
311         this.topologyType = TopologyType.ReplicaSetNoPrimary;
312         return false;
313       } else if(result == 0 && ismasterSetVersion) {
314         if(ismasterSetVersion < this.maxSetVersion) {
315           this.topologyType = TopologyType.ReplicaSetNoPrimary;
316           return false;
317         }
318       }
319
320       this.maxSetVersion = ismasterSetVersion;
321       this.maxElectionId = ismasterElectionId;
322     }
323
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());
327
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
336       }
337
338       // Set the topology
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);
345     } else {
346       this.topologyType = TopologyType.ReplicaSetNoPrimary;
347     }
348
349     emitTopologyDescriptionChanged(self);
350     return true;
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;
359
360     // Is it the same server instance
361     if(this.primary.equals(server)
362       && currentSetName == ismasterSetName) {
363         return false;
364     }
365
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;
370       } else {
371         this.topologyType = TopologyType.ReplicaSetNoPrimary;
372       }
373
374       return false;
375     }
376
377     // Check if we need to replace the server
378     if(currentElectionId && ismasterElectionId) {
379       result = compareObjectIds(currentElectionId, ismasterElectionId);
380
381       if(result == 1) {
382         return false;
383       } else if(result == 0 && (currentSetVersion > ismasterSetVersion)) {
384         return false;
385       }
386     } else if(!currentElectionId && ismasterElectionId
387       && ismasterSetVersion) {
388         if(ismasterSetVersion < this.maxSetVersion) {
389           return false;
390         }
391     }
392
393     if(!this.maxElectionId && ismasterElectionId) {
394       this.maxElectionId = ismasterElectionId;
395     } else if(this.maxElectionId && ismasterElectionId) {
396       result = compareObjectIds(this.maxElectionId, ismasterElectionId);
397
398       if(result == 1) {
399         return false;
400       } else if(result == 0 && currentSetVersion && ismasterSetVersion) {
401         if(ismasterSetVersion < this.maxSetVersion) {
402           return false;
403         }
404       } else {
405         if(ismasterSetVersion < this.maxSetVersion) {
406           return false;
407         }
408       }
409
410       this.maxElectionId = ismasterElectionId;
411       this.maxSetVersion = ismasterSetVersion;
412     } else {
413       this.maxSetVersion = ismasterSetVersion;
414     }
415
416     // Modify the entry to unknown
417     self.set[self.primary.name.toLowerCase()] = {
418       type: ServerType.Unknown, setVersion: null,
419       electionId: null, setName: null
420     }
421
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
432     }
433
434     // Set the topology
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);
442     return true;
443   }
444
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
450     }
451   }
452
453   //
454   // Secondary handling
455   //
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);
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
465     // Remove primary
466     if(this.primary && this.primary.name == server.name) {
467       server.destroy();
468       this.primary = null;
469       self.emit('left', 'primary', server);
470     }
471
472     self.emit('joined', 'secondary', server);
473     emitTopologyDescriptionChanged(self);
474     return true;
475   }
476
477   //
478   // Arbiter handling
479   //
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);
484     // Set the topology
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);
490     return true;
491   }
492
493   //
494   // Passive handling
495   //
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);
500     // Set the topology
501     this.topologyType = this.primary ? TopologyType.ReplicaSetWithPrimary : TopologyType.ReplicaSetNoPrimary;
502     if(ismaster.setName) this.setName = ismaster.setName;
503     removeFrom(server, self.unknownServers);
504
505     // Remove primary
506     if(this.primary && this.primary.name == server.name) {
507       server.destroy();
508       this.primary = null;
509       self.emit('left', 'primary', server);
510     }
511
512     self.emit('joined', 'secondary', server);
513     emitTopologyDescriptionChanged(self);
514     return true;
515   }
516
517   //
518   // Remove the primary
519   //
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();
523     this.primary = null;
524     this.topologyType = TopologyType.ReplicaSetNoPrimary;
525     return false;
526   }
527
528   this.topologyType = this.primary ? TopologyType.ReplicaSetWithPrimary : TopologyType.ReplicaSetNoPrimary;
529   return false;
530 }
531
532 /**
533  * Recalculate single server max staleness
534  * @method
535  */
536 ReplSetState.prototype.updateServerMaxStaleness = function(server, haInterval) {
537   // Locate the max secondary lastwrite
538   var max = 0;
539   // Go over all secondaries
540   for(var i = 0; i < this.secondaries.length; i++) {
541     max = Math.max(max, this.secondaries[i].lastWriteDate);
542   }
543
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)
550       + haInterval;
551   } else if(server.ismaster.maxWireVersion >= 5
552     && server.ismaster.secondary){
553     server.staleness = max - server.lastWriteDate + haInterval;
554   }
555 }
556
557 /**
558  * Recalculate all the stalness values for secodaries
559  * @method
560  */
561 ReplSetState.prototype.updateSecondariesMaxStaleness = function(haInterval) {
562   for(var i = 0; i < this.secondaries.length; i++) {
563     this.updateServerMaxStaleness(this.secondaries[i], haInterval);
564   }
565 }
566
567 /**
568  * Pick a server by the passed in ReadPreference
569  * @method
570  * @param {ReadPreference} readPreference The ReadPreference instance to use
571  */
572 ReplSetState.prototype.pickServer = function(readPreference) {
573   // If no read Preference set to primary by default
574   readPreference = readPreference || ReadPreference.primary;
575
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');
579   }
580
581   // Check if we have any non compatible servers for maxStalenessSeconds
582   var allservers = this.primary ? [this.primary] : [];
583   allservers = allservers.concat(this.secondaries);
584
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');
591       }
592     }
593   }
594
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);
600   }
601
602   // Get all the secondaries
603   var secondaries = this.secondaries;
604
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");
609     }
610
611   if(readPreference.equals(ReadPreference.secondaryPreferred)
612     && secondaries.length == 0
613     && this.primary == null) {
614       return new MongoError("no secondary or primary server available");
615     }
616
617   if(readPreference.equals(ReadPreference.primary)
618     && this.primary == null) {
619       return new MongoError("no primary server available");
620     }
621
622   // Secondary preferred or just secondaries
623   if(readPreference.equals(ReadPreference.secondaryPreferred)
624     || readPreference.equals(ReadPreference.secondary)) {
625
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
630       if(server) {
631         return server;
632       }
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
637       if(server) {
638         return server;
639       }
640     }
641
642     if(readPreference.equals(ReadPreference.secondaryPreferred)){
643       return this.primary;
644     }
645
646     return null;
647   }
648
649   // Primary preferred
650   if(readPreference.equals(ReadPreference.primaryPreferred)) {
651     server = null;
652
653     // We prefer the primary if it's available
654     if(this.primary) {
655       return this.primary;
656     }
657
658     // Pick a secondary
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);
663     }
664
665     //  Did we find a server
666     if(server) return server;
667   }
668
669   // Return the primary
670   return this.primary;
671 }
672
673 //
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];
679
680   // Iterate over the tags
681   for(var j = 0; j < tagsArray.length; j++) {
682     var tags = tagsArray[j];
683
684     // Iterate over all the servers
685     for(var i = 0; i < servers.length; i++) {
686       var serverTag = servers[i].lastIsMaster().tags || {};
687
688       // Did we find the a matching server
689       var found = true;
690       // Check if the server is valid
691       for(var name in tags) {
692         if(serverTag[name] != tags[name]) {
693           found = false;
694         }
695       }
696
697       // Add to candidate list
698       if(found) {
699         filteredServers.push(servers[i]);
700       }
701     }
702   }
703
704   // Returned filtered servers
705   return filteredServers;
706 }
707
708 function pickNearestMaxStalenessSeconds(self, readPreference) {
709   // Only get primary and secondaries as seeds
710   var servers = [];
711   var heartbeatFrequencyMS = self.heartbeatFrequencyMS;
712
713   // Get the maxStalenessMS
714   var maxStalenessMS = readPreference.maxStalenessSeconds * 1000;
715
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');
719   }
720
721   // Add primary to list if not a secondary read preference
722   if(self.primary && readPreference.preference != 'secondary') {
723     servers.push(self.primary);
724   }
725
726   // Add all the secondaries
727   for(var i = 0; i < self.secondaries.length; i++) {
728     servers.push(self.secondaries[i]);
729   }
730
731   // Filter by tags
732   servers = filterByTags(readPreference, servers);
733
734   //
735   // Locate lowest time (picked servers are lowest time + acceptable Latency margin)
736   // var lowest = servers.length > 0 ? servers[0].lastIsMasterMS : 0;
737
738   // Filter by latency
739   servers = servers.filter(function(s) {
740     return s.staleness <= maxStalenessMS;
741   });
742
743   // Sort by time
744   servers.sort(function(a, b) {
745     // return a.time > b.time;
746     return a.lastIsMasterMS > b.lastIsMasterMS
747   });
748
749   // No servers, default to primary
750   if(servers.length == 0) {
751     return null
752   }
753
754   // Ensure index does not overflow the number of available servers
755   self.index = self.index % servers.length;
756
757   // Get the server
758   var server = servers[self.index];
759   // Add to the index
760   self.index = self.index + 1;
761   // Return the first server of the sorted and filtered list
762   return server;
763 }
764
765 function pickNearest(self, readPreference) {
766   // Only get primary and secondaries as seeds
767   var servers = [];
768
769   // Add primary to list if not a secondary read preference
770   if(self.primary && readPreference.preference != 'secondary') {
771     servers.push(self.primary);
772   }
773
774   // Add all the secondaries
775   for(var i = 0; i < self.secondaries.length; i++) {
776     servers.push(self.secondaries[i]);
777   }
778
779   // Filter by tags
780   servers = filterByTags(readPreference, servers);
781
782   // Sort by time
783   servers.sort(function(a, b) {
784     // return a.time > b.time;
785     return a.lastIsMasterMS > b.lastIsMasterMS
786   });
787
788   // Locate lowest time (picked servers are lowest time + acceptable Latency margin)
789   var lowest = servers.length > 0 ? servers[0].lastIsMasterMS : 0;
790
791   // Filter by latency
792   servers = servers.filter(function(s) {
793     return s.lastIsMasterMS <= lowest + self.acceptableLatency;
794   });
795
796   // No servers, default to primary
797   if(servers.length == 0) {
798     return null
799   }
800
801   // Ensure index does not overflow the number of available servers
802   self.index = self.index % servers.length;
803   // Get the server
804   var server = servers[self.index];
805   // Add to the index
806   self.index = self.index + 1;
807   // Return the first server of the sorted and filtered list
808   return server;
809 }
810
811 function inList(ismaster, server, list) {
812   for(var i = 0; i < list.length; i++) {
813     if(list[i].name == server.name) return true;
814   }
815
816   return false;
817 }
818
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;
825   // Add to the list
826   list.push(server);
827 }
828
829 function compareObjectIds(id1, id2) {
830   var a = new Buffer(id1.toHexString(), 'hex');
831   var b = new Buffer(id2.toHexString(), 'hex');
832
833   if(a === b) {
834     return 0;
835   }
836
837   if(typeof Buffer.compare === 'function') {
838     return Buffer.compare(a, b);
839   }
840
841   var x = a.length;
842   var y = b.length;
843   var len = Math.min(x, y);
844
845   for (var i = 0; i < len; i++) {
846     if (a[i] !== b[i]) {
847       break;
848     }
849   }
850
851   if (i !== len) {
852     x = a[i];
853     y = b[i];
854   }
855
856   return x < y ? -1 : y < x ? 1 : 0;
857 }
858
859 function removeFrom(server, list) {
860   for(var i = 0; i < list.length; i++) {
861     if(list[i].equals && list[i].equals(server)) {
862       list.splice(i, 1);
863       return true;
864     } else if(typeof list[i] == 'string' && list[i] == server.name) {
865       list.splice(i, 1);
866       return true;
867     }
868   }
869
870   return false;
871 }
872
873 function emitTopologyDescriptionChanged(self) {
874   if(self.listeners('topologyDescriptionChanged').length > 0) {
875     var topology = 'Unknown';
876     var setName = self.setName;
877
878     if(self.hasPrimaryAndSecondary()) {
879       topology = 'ReplicaSetWithPrimary';
880     } else if(!self.hasPrimary() && self.hasSecondary()) {
881       topology = 'ReplicaSetNoPrimary';
882     }
883
884     // Generate description
885     var description = {
886       topologyType: topology,
887       setName: setName,
888       servers: []
889     }
890
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);
896     }
897
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';
902       return description;
903     }));
904
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';
909       return description;
910     }));
911
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';
916       return description;
917     }));
918
919     // Create the result
920     var result = {
921       topologyId: self.id,
922       previousDescription: self.replicasetDescription,
923       newDescription: description,
924       diff: diff(self.replicasetDescription, description)
925     };
926
927     // Emit the topologyDescription change
928     self.emit('topologyDescriptionChanged', result);
929
930     // Set the new description
931     self.replicasetDescription = description;
932   }
933 }
934
935 function diff(previous, current) {
936   // Difference document
937   var diff = {
938     servers: []
939   }
940
941   // Previous entry
942   if(!previous) {
943     previous = { servers: [] };
944   }
945
946   // Got through all the servers
947   for(var i = 0; i < previous.servers.length; i++) {
948     var prevServer = previous.servers[i];
949
950     // Go through all current servers
951     for(var j = 0; j < current.servers.length; j++) {
952       var currServer = current.servers[j];
953
954       // Matching server
955       if(prevServer.address === currServer.address) {
956         // We had a change in state
957         if(prevServer.type != currServer.type) {
958           diff.servers.push({
959             address: prevServer.address,
960             from: prevServer.type,
961             to: currServer.type
962           });
963         }
964       }
965     }
966   }
967
968   // Return difference
969   return diff;
970 }
971
972 module.exports = ReplSetState;