mongo 1.10.0-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/LICENSE +190 -0
  5. data/README.md +149 -0
  6. data/Rakefile +31 -0
  7. data/VERSION +1 -0
  8. data/bin/mongo_console +43 -0
  9. data/ext/jsasl/target/jsasl.jar +0 -0
  10. data/lib/mongo.rb +90 -0
  11. data/lib/mongo/bulk_write_collection_view.rb +380 -0
  12. data/lib/mongo/collection.rb +1164 -0
  13. data/lib/mongo/collection_writer.rb +364 -0
  14. data/lib/mongo/connection.rb +19 -0
  15. data/lib/mongo/connection/node.rb +239 -0
  16. data/lib/mongo/connection/pool.rb +347 -0
  17. data/lib/mongo/connection/pool_manager.rb +325 -0
  18. data/lib/mongo/connection/sharding_pool_manager.rb +67 -0
  19. data/lib/mongo/connection/socket.rb +18 -0
  20. data/lib/mongo/connection/socket/socket_util.rb +37 -0
  21. data/lib/mongo/connection/socket/ssl_socket.rb +95 -0
  22. data/lib/mongo/connection/socket/tcp_socket.rb +86 -0
  23. data/lib/mongo/connection/socket/unix_socket.rb +39 -0
  24. data/lib/mongo/cursor.rb +719 -0
  25. data/lib/mongo/db.rb +735 -0
  26. data/lib/mongo/exception.rb +88 -0
  27. data/lib/mongo/functional.rb +21 -0
  28. data/lib/mongo/functional/authentication.rb +318 -0
  29. data/lib/mongo/functional/logging.rb +85 -0
  30. data/lib/mongo/functional/read_preference.rb +174 -0
  31. data/lib/mongo/functional/sasl_java.rb +48 -0
  32. data/lib/mongo/functional/uri_parser.rb +374 -0
  33. data/lib/mongo/functional/write_concern.rb +66 -0
  34. data/lib/mongo/gridfs.rb +18 -0
  35. data/lib/mongo/gridfs/grid.rb +112 -0
  36. data/lib/mongo/gridfs/grid_ext.rb +53 -0
  37. data/lib/mongo/gridfs/grid_file_system.rb +163 -0
  38. data/lib/mongo/gridfs/grid_io.rb +484 -0
  39. data/lib/mongo/legacy.rb +140 -0
  40. data/lib/mongo/mongo_client.rb +702 -0
  41. data/lib/mongo/mongo_replica_set_client.rb +523 -0
  42. data/lib/mongo/mongo_sharded_client.rb +159 -0
  43. data/lib/mongo/networking.rb +370 -0
  44. data/lib/mongo/utils.rb +19 -0
  45. data/lib/mongo/utils/conversions.rb +110 -0
  46. data/lib/mongo/utils/core_ext.rb +70 -0
  47. data/lib/mongo/utils/server_version.rb +69 -0
  48. data/lib/mongo/utils/support.rb +80 -0
  49. data/lib/mongo/utils/thread_local_variable_manager.rb +25 -0
  50. data/mongo.gemspec +36 -0
  51. data/test/functional/authentication_test.rb +35 -0
  52. data/test/functional/bulk_api_stress_test.rb +133 -0
  53. data/test/functional/bulk_write_collection_view_test.rb +1129 -0
  54. data/test/functional/client_test.rb +565 -0
  55. data/test/functional/collection_test.rb +2073 -0
  56. data/test/functional/collection_writer_test.rb +83 -0
  57. data/test/functional/conversions_test.rb +163 -0
  58. data/test/functional/cursor_fail_test.rb +63 -0
  59. data/test/functional/cursor_message_test.rb +57 -0
  60. data/test/functional/cursor_test.rb +625 -0
  61. data/test/functional/db_api_test.rb +819 -0
  62. data/test/functional/db_connection_test.rb +27 -0
  63. data/test/functional/db_test.rb +344 -0
  64. data/test/functional/grid_file_system_test.rb +285 -0
  65. data/test/functional/grid_io_test.rb +252 -0
  66. data/test/functional/grid_test.rb +273 -0
  67. data/test/functional/pool_test.rb +62 -0
  68. data/test/functional/safe_test.rb +98 -0
  69. data/test/functional/ssl_test.rb +29 -0
  70. data/test/functional/support_test.rb +62 -0
  71. data/test/functional/timeout_test.rb +58 -0
  72. data/test/functional/uri_test.rb +330 -0
  73. data/test/functional/write_concern_test.rb +118 -0
  74. data/test/helpers/general.rb +50 -0
  75. data/test/helpers/test_unit.rb +317 -0
  76. data/test/replica_set/authentication_test.rb +35 -0
  77. data/test/replica_set/basic_test.rb +174 -0
  78. data/test/replica_set/client_test.rb +341 -0
  79. data/test/replica_set/complex_connect_test.rb +77 -0
  80. data/test/replica_set/connection_test.rb +138 -0
  81. data/test/replica_set/count_test.rb +64 -0
  82. data/test/replica_set/cursor_test.rb +212 -0
  83. data/test/replica_set/insert_test.rb +140 -0
  84. data/test/replica_set/max_values_test.rb +145 -0
  85. data/test/replica_set/pinning_test.rb +55 -0
  86. data/test/replica_set/query_test.rb +73 -0
  87. data/test/replica_set/read_preference_test.rb +214 -0
  88. data/test/replica_set/refresh_test.rb +175 -0
  89. data/test/replica_set/replication_ack_test.rb +94 -0
  90. data/test/replica_set/ssl_test.rb +32 -0
  91. data/test/sharded_cluster/basic_test.rb +197 -0
  92. data/test/shared/authentication/basic_auth_shared.rb +286 -0
  93. data/test/shared/authentication/bulk_api_auth_shared.rb +259 -0
  94. data/test/shared/authentication/gssapi_shared.rb +164 -0
  95. data/test/shared/authentication/sasl_plain_shared.rb +96 -0
  96. data/test/shared/ssl_shared.rb +235 -0
  97. data/test/test_helper.rb +56 -0
  98. data/test/threading/basic_test.rb +120 -0
  99. data/test/tools/mongo_config.rb +608 -0
  100. data/test/tools/mongo_config_test.rb +160 -0
  101. data/test/unit/client_test.rb +347 -0
  102. data/test/unit/collection_test.rb +166 -0
  103. data/test/unit/connection_test.rb +325 -0
  104. data/test/unit/cursor_test.rb +299 -0
  105. data/test/unit/db_test.rb +136 -0
  106. data/test/unit/grid_test.rb +76 -0
  107. data/test/unit/mongo_sharded_client_test.rb +48 -0
  108. data/test/unit/node_test.rb +93 -0
  109. data/test/unit/pool_manager_test.rb +142 -0
  110. data/test/unit/read_pref_test.rb +115 -0
  111. data/test/unit/read_test.rb +159 -0
  112. data/test/unit/safe_test.rb +158 -0
  113. data/test/unit/sharding_pool_manager_test.rb +84 -0
  114. data/test/unit/write_concern_test.rb +175 -0
  115. metadata +260 -0
  116. metadata.gz.sig +0 -0
@@ -0,0 +1,347 @@
1
+ # Copyright (C) 2009-2013 MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Mongo
16
+ class Pool
17
+ PING_ATTEMPTS = 6
18
+ MAX_PING_TIME = 1_000_000
19
+ PRUNE_INTERVAL = 10_000
20
+
21
+ attr_accessor :host,
22
+ :port,
23
+ :address,
24
+ :size,
25
+ :timeout,
26
+ :checked_out,
27
+ :client,
28
+ :node
29
+
30
+ # Create a new pool of connections.
31
+ def initialize(client, host, port, opts={})
32
+ @client = client
33
+
34
+ @host, @port = host, port
35
+
36
+ # A Mongo::Node object.
37
+ @node = opts[:node]
38
+
39
+ # The string address
40
+ @address = "#{@host}:#{@port}"
41
+
42
+ # Pool size and timeout.
43
+ @size = opts.fetch(:size, 20)
44
+ @timeout = opts.fetch(:timeout, 30)
45
+
46
+ # Mutex for synchronizing pool access
47
+ @connection_mutex = Mutex.new
48
+
49
+ # Mutex for synchronizing pings
50
+ @ping_mutex = Mutex.new
51
+
52
+ # Condition variable for signal and wait
53
+ @queue = ConditionVariable.new
54
+
55
+ @sockets = []
56
+ @checked_out = []
57
+ @ping_time = nil
58
+ @last_ping = nil
59
+ @closed = false
60
+ @thread_ids_to_sockets = {}
61
+ @checkout_counter = 0
62
+ end
63
+
64
+ # Close this pool.
65
+ #
66
+ # @option opts [Boolean]:soft (false) If true,
67
+ # close only those sockets that are not checked out.
68
+ def close(opts={})
69
+ @connection_mutex.synchronize do
70
+ if opts[:soft] && !@checked_out.empty?
71
+ @closing = true
72
+ close_sockets(@sockets - @checked_out)
73
+ else
74
+ close_sockets(@sockets)
75
+ @closed = true
76
+ end
77
+ @node.close if @node
78
+ end
79
+ true
80
+ end
81
+
82
+ def tags
83
+ @node.tags
84
+ end
85
+
86
+ def healthy?
87
+ close if @sockets.all?(&:closed?)
88
+ !closed? && node.healthy?
89
+ end
90
+
91
+ def closed?
92
+ @closed
93
+ end
94
+
95
+ def up?
96
+ !@closed
97
+ end
98
+
99
+ def inspect
100
+ "#<Mongo::Pool:0x#{self.object_id.to_s(16)} @host=#{@host} @port=#{port} " +
101
+ "@ping_time=#{@ping_time} #{@checked_out.size}/#{@size} sockets available " +
102
+ "up=#{!closed?}>"
103
+ end
104
+
105
+ def host_string
106
+ "#{@host}:#{@port}"
107
+ end
108
+
109
+ def host_port
110
+ [@host, @port]
111
+ end
112
+
113
+ # Refresh ping time only if we haven't
114
+ # checked within the last five minutes.
115
+ def ping_time
116
+ @ping_mutex.synchronize do
117
+ if !@last_ping || (Time.now - @last_ping) > 300
118
+ @ping_time = refresh_ping_time
119
+ @last_ping = Time.now
120
+ end
121
+ end
122
+ @ping_time
123
+ end
124
+
125
+ # Return the time it takes on average
126
+ # to do a round-trip against this node.
127
+ def refresh_ping_time
128
+ trials = []
129
+ PING_ATTEMPTS.times do
130
+ t1 = Time.now
131
+ if !self.ping
132
+ return MAX_PING_TIME
133
+ end
134
+ trials << (Time.now - t1) * 1000
135
+ end
136
+
137
+ trials.sort!
138
+
139
+ # Delete shortest and longest times
140
+ trials.delete_at(trials.length-1)
141
+ trials.delete_at(0)
142
+
143
+ total = 0.0
144
+ trials.each { |t| total += t }
145
+
146
+ (total / trials.length).ceil
147
+ end
148
+
149
+ def ping
150
+ begin
151
+ return self.client['admin'].command({:ping => 1}, :socket => @node.socket, :timeout => MAX_PING_TIME)
152
+ rescue ConnectionFailure, OperationFailure, SocketError, SystemCallError, IOError
153
+ return false
154
+ end
155
+ end
156
+
157
+ # Return a socket to the pool.
158
+ def checkin(socket)
159
+ @connection_mutex.synchronize do
160
+ if @checked_out.delete(socket)
161
+ @queue.broadcast
162
+ else
163
+ return false
164
+ end
165
+ end
166
+ true
167
+ end
168
+
169
+ # Adds a new socket to the pool and checks it out.
170
+ #
171
+ # This method is called exclusively from #checkout;
172
+ # therefore, it runs within a mutex.
173
+ def checkout_new_socket
174
+ begin
175
+ socket = @client.socket_class.new(@host, @port, @client.op_timeout,
176
+ @client.connect_timeout,
177
+ @client.socket_opts)
178
+ socket.pool = self
179
+ rescue => ex
180
+ socket.close if socket
181
+ @node.close if @node
182
+ raise ConnectionFailure, "Failed to connect to host #{@host} and port #{@port}: #{ex}"
183
+ end
184
+
185
+ # If any saved authentications exist, we want to apply those
186
+ # when creating new sockets and process logouts.
187
+ check_auths(socket)
188
+
189
+ @sockets << socket
190
+ @checked_out << socket
191
+ @thread_ids_to_sockets[Thread.current.object_id] = socket
192
+ socket
193
+ end
194
+
195
+ # If a user calls DB#authenticate, and several sockets exist,
196
+ # then we need a way to apply the authentication on each socket.
197
+ # So we store the apply_authentication method, and this will be
198
+ # applied right before the next use of each socket.
199
+ #
200
+ # @deprecated This method has been replaced by Pool#check_auths (private)
201
+ # and it isn't necessary to ever invoke this method directly.
202
+ def authenticate_existing
203
+ @connection_mutex.synchronize do
204
+ @sockets.each do |socket|
205
+ check_auths(socket)
206
+ end
207
+ end
208
+ end
209
+
210
+ # Store the logout op for each existing socket to be applied before
211
+ # the next use of each socket.
212
+ #
213
+ # @deprecated This method has been replaced by Pool#check_auths (private)
214
+ # and it isn't necessary to ever invoke this method directly.
215
+ def logout_existing(database)
216
+ @connection_mutex.synchronize do
217
+ @sockets.each do |socket|
218
+ check_auths(socket)
219
+ end
220
+ end
221
+ end
222
+
223
+ # Checks out the first available socket from the pool.
224
+ #
225
+ # If the pid has changed, remove the socket and check out
226
+ # new one.
227
+ #
228
+ # This method is called exclusively from #checkout;
229
+ # therefore, it runs within a mutex.
230
+ def checkout_existing_socket(socket=nil)
231
+ if !socket
232
+ available = @sockets - @checked_out
233
+ socket = available[rand(available.length)]
234
+ end
235
+
236
+ if socket.pid != Process.pid
237
+ @sockets.delete(socket)
238
+ if socket
239
+ socket.close unless socket.closed?
240
+ end
241
+ checkout_new_socket
242
+ else
243
+ @checked_out << socket
244
+ @thread_ids_to_sockets[Thread.current.object_id] = socket
245
+ socket
246
+ end
247
+ end
248
+
249
+ def prune_threads
250
+ live_threads = Thread.list.map(&:object_id)
251
+ @thread_ids_to_sockets.reject! do |key, value|
252
+ !live_threads.include?(key)
253
+ end
254
+ end
255
+
256
+ def check_prune
257
+ if @checkout_counter > PRUNE_INTERVAL
258
+ @checkout_counter = 0
259
+ prune_threads
260
+ else
261
+ @checkout_counter += 1
262
+ end
263
+ end
264
+
265
+ # Check out an existing socket or create a new socket if the maximum
266
+ # pool size has not been exceeded. Otherwise, wait for the next
267
+ # available socket.
268
+ def checkout
269
+ @client.connect if !@client.connected?
270
+ start_time = Time.now
271
+ loop do
272
+ if (Time.now - start_time) > @timeout
273
+ raise ConnectionTimeoutError, "could not obtain connection within " +
274
+ "#{@timeout} seconds. The max pool size is currently #{@size}; " +
275
+ "consider increasing the pool size or timeout."
276
+ end
277
+
278
+ @connection_mutex.synchronize do
279
+ check_prune
280
+ socket = nil
281
+ if socket_for_thread = @thread_ids_to_sockets[Thread.current.object_id]
282
+ if !@checked_out.include?(socket_for_thread)
283
+ socket = checkout_existing_socket(socket_for_thread)
284
+ end
285
+ else
286
+ if @sockets.size < @size
287
+ socket = checkout_new_socket
288
+ elsif @checked_out.size < @sockets.size
289
+ socket = checkout_existing_socket
290
+ end
291
+ end
292
+
293
+ if socket
294
+ check_auths(socket)
295
+
296
+ if socket.closed?
297
+ @checked_out.delete(socket)
298
+ @sockets.delete(socket)
299
+ @thread_ids_to_sockets.delete(Thread.current.object_id)
300
+ socket = checkout_new_socket
301
+ end
302
+
303
+ return socket
304
+ else
305
+ # Otherwise, wait
306
+ @queue.wait(@connection_mutex)
307
+ end
308
+ end
309
+ end
310
+ end
311
+
312
+ private
313
+
314
+ # Helper method to handle keeping track of auths/logouts for sockets.
315
+ #
316
+ # @param socket [Socket] The socket instance to be checked.
317
+ #
318
+ # @return [Socket] The authenticated socket instance.
319
+ def check_auths(socket)
320
+ # find and handle logouts
321
+ (socket.auths - @client.auths).each do |auth|
322
+ @client.issue_logout(auth[:source], :socket => socket)
323
+ socket.auths.delete(auth)
324
+ end
325
+
326
+ # find and handle new auths
327
+ (@client.auths - socket.auths).each do |auth|
328
+ @client.issue_authentication(auth, :socket => socket)
329
+ socket.auths.add(auth)
330
+ end
331
+
332
+ socket
333
+ end
334
+
335
+ def close_sockets(sockets)
336
+ sockets.each do |socket|
337
+ @sockets.delete(socket)
338
+ begin
339
+ socket.close unless socket.closed?
340
+ rescue IOError => ex
341
+ warn "IOError when attempting to close socket connected to #{@host}:#{@port}: #{ex.inspect}"
342
+ end
343
+ end
344
+ end
345
+
346
+ end
347
+ end
@@ -0,0 +1,325 @@
1
+ # Copyright (C) 2009-2013 MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Mongo
16
+ class PoolManager
17
+ include ThreadLocalVariableManager
18
+
19
+ attr_reader :client,
20
+ :primary,
21
+ :primary_pool,
22
+ :seeds,
23
+ :max_bson_size,
24
+ :max_message_size,
25
+ :max_wire_version,
26
+ :min_wire_version
27
+
28
+ # Create a new set of connection pools.
29
+ #
30
+ # The pool manager will by default use the original seed list passed
31
+ # to the connection objects, accessible via connection.seeds. In addition,
32
+ # the user may pass an additional list of seeds nodes discovered in real
33
+ # time. The union of these lists will be used when attempting to connect,
34
+ # with the newly-discovered nodes being used first.
35
+ def initialize(client, seeds=[])
36
+ @client = client
37
+ @seeds = seeds
38
+
39
+ @pools = Set.new
40
+ @primary = nil
41
+ @primary_pool = nil
42
+ @secondaries = Set.new
43
+ @secondary_pools = []
44
+ @hosts = Set.new
45
+ @members = Set.new
46
+ @refresh_required = false
47
+ @max_bson_size = DEFAULT_MAX_BSON_SIZE
48
+ @max_message_size = @max_bson_size * MESSAGE_SIZE_FACTOR
49
+ @max_wire_version = 0
50
+ @min_wire_version = 0
51
+ @connect_mutex = Mutex.new
52
+ thread_local[:locks][:connecting_manager] = false
53
+ end
54
+
55
+ def inspect
56
+ "<Mongo::PoolManager:0x#{self.object_id.to_s(16)} @seeds=#{@seeds}>"
57
+ end
58
+
59
+ def connect
60
+ @connect_mutex.synchronize do
61
+ begin
62
+ thread_local[:locks][:connecting_manager] = true
63
+ @refresh_required = false
64
+ disconnect_old_members
65
+ connect_to_members
66
+ initialize_pools(@members)
67
+ update_max_sizes
68
+ @seeds = discovered_seeds
69
+ ensure
70
+ thread_local[:locks][:connecting_manager] = false
71
+ end
72
+ end
73
+ end
74
+
75
+ def refresh!(additional_seeds)
76
+ @seeds |= additional_seeds
77
+ connect
78
+ end
79
+
80
+ # We're healthy if all members are pingable and if the view
81
+ # of the replica set returned by isMaster is equivalent
82
+ # to our view. If any of these isn't the case,
83
+ # set @refresh_required to true, and return.
84
+ def check_connection_health
85
+ return if thread_local[:locks][:connecting_manager]
86
+ members = copy_members
87
+ begin
88
+ seed = get_valid_seed_node
89
+ rescue ConnectionFailure
90
+ @refresh_required = true
91
+ return
92
+ end
93
+
94
+ unless current_config = seed.config
95
+ @refresh_required = true
96
+ seed.close
97
+ return
98
+ end
99
+
100
+ if current_config['hosts'].length != members.length
101
+ @refresh_required = true
102
+ seed.close
103
+ return
104
+ end
105
+
106
+ current_config['hosts'].each do |host|
107
+ member = members.detect do |m|
108
+ m.address == host
109
+ end
110
+
111
+ if member && validate_existing_member(current_config, member)
112
+ next
113
+ else
114
+ @refresh_required = true
115
+ seed.close
116
+ return
117
+ end
118
+ end
119
+
120
+ seed.close
121
+ end
122
+
123
+ # The replica set connection should initiate a full refresh.
124
+ def refresh_required?
125
+ @refresh_required
126
+ end
127
+
128
+ def closed?
129
+ pools.all? { |pool| pool.closed? }
130
+ end
131
+
132
+ def close(opts={})
133
+ begin
134
+ pools.each { |pool| pool.close(opts) }
135
+ rescue ConnectionFailure
136
+ end
137
+ end
138
+
139
+ def read
140
+ read_pool.host_port
141
+ end
142
+
143
+ def hosts
144
+ @connect_mutex.synchronize do
145
+ @hosts.nil? ? nil : @hosts.clone
146
+ end
147
+ end
148
+
149
+ def pools
150
+ @connect_mutex.synchronize do
151
+ @pools.nil? ? nil : @pools.clone
152
+ end
153
+ end
154
+
155
+ def secondaries
156
+ @connect_mutex.synchronize do
157
+ @secondaries.nil? ? nil : @secondaries.clone
158
+ end
159
+ end
160
+
161
+ def secondary_pools
162
+ @connect_mutex.synchronize do
163
+ @secondary_pools.nil? ? nil : @secondary_pools.clone
164
+ end
165
+ end
166
+
167
+ def arbiters
168
+ @connect_mutex.synchronize do
169
+ @arbiters.nil? ? nil : @arbiters.clone
170
+ end
171
+ end
172
+
173
+ def state_snapshot
174
+ @connect_mutex.synchronize do
175
+ { :pools => @pools.nil? ? nil : @pools.clone,
176
+ :secondaries => @secondaries.nil? ? nil : @secondaries.clone,
177
+ :secondary_pools => @secondary_pools.nil? ? nil : @secondary_pools.clone,
178
+ :hosts => @hosts.nil? ? nil : @hosts.clone,
179
+ :arbiters => @arbiters.nil? ? nil : @arbiters.clone
180
+ }
181
+ end
182
+ end
183
+
184
+ private
185
+
186
+ def update_max_sizes
187
+ unless @members.size == 0
188
+ @max_bson_size = @members.map(&:max_bson_size).min
189
+ @max_message_size = @members.map(&:max_message_size).min
190
+ @max_wire_version = @members.map(&:max_wire_version).min
191
+ @min_wire_version = @members.map(&:min_wire_version).max
192
+ end
193
+ end
194
+
195
+ def validate_existing_member(current_config, member)
196
+ if current_config['ismaster'] && member.last_state != :primary
197
+ return false
198
+ elsif member.last_state != :other
199
+ return false
200
+ end
201
+ return true
202
+ end
203
+
204
+ # For any existing members, close and remove any that are unhealthy or already closed.
205
+ def disconnect_old_members
206
+ @pools.reject! {|pool| !pool.healthy? }
207
+ @members.reject! {|node| !node.healthy? }
208
+ end
209
+
210
+ # Connect to each member of the replica set
211
+ # as reported by the given seed node.
212
+ def connect_to_members
213
+ seed = get_valid_seed_node
214
+ seed.node_list.each do |host|
215
+ if existing = @members.detect {|node| node =~ host }
216
+ if existing.healthy?
217
+ # Refresh this node's configuration
218
+ existing.set_config
219
+ # If we are unhealthy after refreshing our config, drop from the set.
220
+ if !existing.healthy?
221
+ @members.delete(existing)
222
+ else
223
+ next
224
+ end
225
+ else
226
+ existing.close
227
+ @members.delete(existing)
228
+ end
229
+ end
230
+
231
+ node = Mongo::Node.new(self.client, host)
232
+ node.connect
233
+ @members << node if node.healthy?
234
+ end
235
+ seed.close
236
+
237
+ if @members.empty?
238
+ raise ConnectionFailure, "Failed to connect to any given member."
239
+ end
240
+ end
241
+
242
+ # Initialize the connection pools for the primary and secondary nodes.
243
+ def initialize_pools(members)
244
+ @primary_pool = nil
245
+ @primary = nil
246
+ @secondaries.clear
247
+ @secondary_pools.clear
248
+ @hosts.clear
249
+
250
+ members.each do |member|
251
+ member.last_state = nil
252
+ @hosts << member.host_string
253
+ if member.primary?
254
+ assign_primary(member)
255
+ elsif member.secondary?
256
+ # member could be not primary but secondary still is false
257
+ assign_secondary(member)
258
+ end
259
+ end
260
+
261
+ @arbiters = members.first.arbiters
262
+ end
263
+
264
+ def assign_primary(member)
265
+ member.last_state = :primary
266
+ @primary = member.host_port
267
+ if existing = @pools.detect {|pool| pool.node == member }
268
+ @primary_pool = existing
269
+ else
270
+ @primary_pool = Pool.new(self.client, member.host, member.port,
271
+ :size => self.client.pool_size,
272
+ :timeout => self.client.pool_timeout,
273
+ :node => member
274
+ )
275
+ @pools << @primary_pool
276
+ end
277
+ end
278
+
279
+ def assign_secondary(member)
280
+ member.last_state = :secondary
281
+ @secondaries << member.host_port
282
+ if existing = @pools.detect {|pool| pool.node == member }
283
+ @secondary_pools << existing
284
+ else
285
+ pool = Pool.new(self.client, member.host, member.port,
286
+ :size => self.client.pool_size,
287
+ :timeout => self.client.pool_timeout,
288
+ :node => member
289
+ )
290
+ @secondary_pools << pool
291
+ @pools << pool
292
+ end
293
+ end
294
+
295
+ # Iterate through the list of provided seed
296
+ # nodes until we've gotten a response from the
297
+ # replica set we're trying to connect to.
298
+ #
299
+ # If we don't get a response, raise an exception.
300
+ def get_valid_seed_node
301
+ @seeds.each do |seed|
302
+ node = Mongo::Node.new(self.client, seed)
303
+ node.connect
304
+ return node if node.healthy?
305
+ end
306
+
307
+ raise ConnectionFailure, "Cannot connect to a replica set using seeds " +
308
+ "#{@seeds.map {|s| "#{s[0]}:#{s[1]}" }.join(', ')}"
309
+ end
310
+
311
+ def discovered_seeds
312
+ @members.map(&:host_port)
313
+ end
314
+
315
+ def copy_members
316
+ members = Set.new
317
+ @connect_mutex.synchronize do
318
+ @members.map do |m|
319
+ members << m.dup
320
+ end
321
+ end
322
+ members
323
+ end
324
+ end
325
+ end