moped 1.5.3 → 2.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of moped might be problematic. Click here for more details.

Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +42 -5
  3. data/README.md +1 -1
  4. data/lib/moped.rb +10 -13
  5. data/lib/moped/address.rb +56 -0
  6. data/lib/moped/authenticatable.rb +89 -0
  7. data/lib/moped/cluster.rb +169 -136
  8. data/lib/moped/collection.rb +53 -19
  9. data/lib/moped/connection.rb +69 -10
  10. data/lib/moped/connection/manager.rb +49 -0
  11. data/lib/moped/connection/pool.rb +198 -0
  12. data/lib/moped/connection/queue.rb +93 -0
  13. data/lib/moped/connection/reaper.rb +52 -0
  14. data/lib/moped/connection/socket.rb +4 -0
  15. data/lib/moped/connection/socket/connectable.rb +169 -0
  16. data/lib/moped/connection/socket/ssl.rb +52 -0
  17. data/lib/moped/connection/socket/tcp.rb +25 -0
  18. data/lib/moped/connection/sockets.rb +4 -0
  19. data/lib/moped/cursor.rb +3 -5
  20. data/lib/moped/database.rb +18 -24
  21. data/lib/moped/errors.rb +35 -6
  22. data/lib/moped/executable.rb +96 -0
  23. data/lib/moped/failover.rb +41 -0
  24. data/lib/moped/failover/disconnect.rb +31 -0
  25. data/lib/moped/failover/ignore.rb +29 -0
  26. data/lib/moped/failover/reconfigure.rb +34 -0
  27. data/lib/moped/failover/retry.rb +37 -0
  28. data/lib/moped/indexes.rb +4 -1
  29. data/lib/moped/instrumentable.rb +39 -0
  30. data/lib/moped/instrumentable/log.rb +43 -0
  31. data/lib/moped/instrumentable/noop.rb +31 -0
  32. data/lib/moped/loggable.rb +110 -0
  33. data/lib/moped/node.rb +316 -297
  34. data/lib/moped/operation.rb +3 -0
  35. data/lib/moped/operation/read.rb +62 -0
  36. data/lib/moped/operation/write.rb +57 -0
  37. data/lib/moped/protocol/command.rb +65 -4
  38. data/lib/moped/protocol/commands/authenticate.rb +1 -2
  39. data/lib/moped/protocol/delete.rb +16 -0
  40. data/lib/moped/protocol/get_more.rb +102 -31
  41. data/lib/moped/protocol/insert.rb +17 -0
  42. data/lib/moped/protocol/message.rb +44 -46
  43. data/lib/moped/protocol/query.rb +175 -92
  44. data/lib/moped/protocol/reply.rb +19 -8
  45. data/lib/moped/protocol/update.rb +18 -0
  46. data/lib/moped/query.rb +43 -17
  47. data/lib/moped/read_preference.rb +49 -0
  48. data/lib/moped/read_preference/nearest.rb +55 -0
  49. data/lib/moped/read_preference/primary.rb +60 -0
  50. data/lib/moped/read_preference/primary_preferred.rb +55 -0
  51. data/lib/moped/read_preference/secondary.rb +50 -0
  52. data/lib/moped/read_preference/secondary_preferred.rb +53 -0
  53. data/lib/moped/read_preference/selectable.rb +79 -0
  54. data/lib/moped/readable.rb +55 -0
  55. data/lib/moped/session.rb +122 -70
  56. data/lib/moped/{mongo_uri.rb → uri.rb} +75 -31
  57. data/lib/moped/version.rb +1 -1
  58. data/lib/moped/write_concern.rb +33 -0
  59. data/lib/moped/write_concern/propagate.rb +38 -0
  60. data/lib/moped/write_concern/unverified.rb +28 -0
  61. metadata +79 -44
  62. data/lib/moped/bson.rb +0 -45
  63. data/lib/moped/bson/binary.rb +0 -137
  64. data/lib/moped/bson/code.rb +0 -112
  65. data/lib/moped/bson/document.rb +0 -41
  66. data/lib/moped/bson/extensions.rb +0 -91
  67. data/lib/moped/bson/extensions/array.rb +0 -37
  68. data/lib/moped/bson/extensions/boolean.rb +0 -16
  69. data/lib/moped/bson/extensions/false_class.rb +0 -19
  70. data/lib/moped/bson/extensions/float.rb +0 -22
  71. data/lib/moped/bson/extensions/hash.rb +0 -39
  72. data/lib/moped/bson/extensions/integer.rb +0 -36
  73. data/lib/moped/bson/extensions/nil_class.rb +0 -19
  74. data/lib/moped/bson/extensions/object.rb +0 -11
  75. data/lib/moped/bson/extensions/regexp.rb +0 -38
  76. data/lib/moped/bson/extensions/string.rb +0 -45
  77. data/lib/moped/bson/extensions/symbol.rb +0 -33
  78. data/lib/moped/bson/extensions/time.rb +0 -23
  79. data/lib/moped/bson/extensions/true_class.rb +0 -19
  80. data/lib/moped/bson/max_key.rb +0 -51
  81. data/lib/moped/bson/min_key.rb +0 -51
  82. data/lib/moped/bson/object_id.rb +0 -301
  83. data/lib/moped/bson/timestamp.rb +0 -38
  84. data/lib/moped/bson/types.rb +0 -67
  85. data/lib/moped/logging.rb +0 -58
  86. data/lib/moped/session/context.rb +0 -115
  87. data/lib/moped/sockets/connectable.rb +0 -167
  88. data/lib/moped/sockets/ssl.rb +0 -50
  89. data/lib/moped/sockets/tcp.rb +0 -23
  90. data/lib/moped/threaded.rb +0 -69
@@ -0,0 +1,110 @@
1
+ # encoding: utf-8
2
+ module Moped
3
+
4
+ # Contains behaviour for logging.
5
+ #
6
+ # @since 1.0.0
7
+ module Loggable
8
+
9
+ # Log the provided operations.
10
+ #
11
+ # @example Log the operations.
12
+ # Loggable.log_operations("MOPED", {}, 30)
13
+ #
14
+ # @param [ String ] prefix The prefix for all operations in the log.
15
+ # @param [ Array ] ops The operations.
16
+ # @param [ String ] runtime The runtime in formatted ms.
17
+ #
18
+ # @since 2.0.0
19
+ def self.log_operations(prefix, ops, runtime)
20
+ indent = " "*prefix.length
21
+ if ops.length == 1
22
+ Moped.logger.debug([ prefix, ops.first.log_inspect, "runtime: #{runtime}" ].join(' '))
23
+ else
24
+ first, *middle, last = ops
25
+ Moped.logger.debug([ prefix, first.log_inspect ].join(' '))
26
+ middle.each { |m| Moped.logger.debug([ indent, m.log_inspect ].join(' ')) }
27
+ Moped.logger.debug([ indent, last.log_inspect, "runtime: #{runtime}" ].join(' '))
28
+ end
29
+ end
30
+
31
+ # Log the payload to debug.
32
+ #
33
+ # @example Log to debug.
34
+ # Loggable.debug("MOPED", payload "30.012ms")
35
+ #
36
+ # @param [ String ] prefix The log prefix.
37
+ # @param [ String ] payload The log operations.
38
+ # @param [ String ] runtime The runtime in formatted ms.
39
+ #
40
+ # @since 2.0.0
41
+ def self.debug(prefix, payload, runtime)
42
+ Moped.logger.debug([ prefix, payload, "runtime: #{runtime}" ].join(' '))
43
+ end
44
+
45
+ # Log the payload to warn.
46
+ #
47
+ # @example Log to warn.
48
+ # Loggable.warn("MOPED", payload "30.012ms")
49
+ #
50
+ # @param [ String ] prefix The log prefix.
51
+ # @param [ String ] payload The log operations.
52
+ # @param [ String ] runtime The runtime in formatted ms.
53
+ #
54
+ # @since 2.0.0
55
+ def self.warn(prefix, payload, runtime)
56
+ Moped.logger.warn([ prefix, payload, "runtime: #{runtime}" ].join(' '))
57
+ end
58
+
59
+ # Get the logger.
60
+ #
61
+ # @example Get the logger.
62
+ # Loggable.logger
63
+ #
64
+ # @return [ Logger ] The logger.
65
+ #
66
+ # @since 1.0.0
67
+ def logger
68
+ return @logger if defined?(@logger)
69
+ @logger = rails_logger || default_logger
70
+ end
71
+
72
+ # Get the rails logger.
73
+ #
74
+ # @example Get the rails logger.
75
+ # Loggable.rails_logger
76
+ #
77
+ # @return [ Logger ] The Rails logger.
78
+ #
79
+ # @since 1.0.0
80
+ def rails_logger
81
+ defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
82
+ end
83
+
84
+ # Get the default logger.
85
+ #
86
+ # @example Get the default logger.
87
+ # Loggable.default_logger
88
+ #
89
+ # @return [ Logger ] The default logger.
90
+ #
91
+ # @since 1.0.0
92
+ def default_logger
93
+ logger = Logger.new(STDOUT)
94
+ logger.level = Logger::INFO
95
+ logger
96
+ end
97
+
98
+ # Set the logger.
99
+ #
100
+ # @example Set the logger.
101
+ # Loggable.logger = logger
102
+ #
103
+ # @return [ Logger ] The logger.
104
+ #
105
+ # @since 1.0.0
106
+ def logger=(logger)
107
+ @logger = logger
108
+ end
109
+ end
110
+ end
data/lib/moped/node.rb CHANGED
@@ -1,28 +1,33 @@
1
+ # encoding: utf-8
2
+ require "moped/address"
3
+ require "moped/authenticatable"
4
+ require "moped/connection"
5
+ require "moped/executable"
6
+ require "moped/failover"
7
+ require "moped/instrumentable"
8
+ require "moped/operation"
9
+
1
10
  module Moped
2
11
 
3
12
  # Represents a client to a node in a server cluster.
4
13
  #
5
- # @api private
14
+ # @since 1.0.0
6
15
  class Node
7
-
8
- # @attribute [r] address The address of the node.
9
- # @attribute [r] down_at The time the server node went down.
10
- # @attribute [r] ip_address The node's ip.
11
- # @attribute [r] peers Other peers in the replica set.
12
- # @attribute [r] port The connection port.
13
- # @attribute [r] resolved_address The host/port pair.
14
- # @attribute [r] timeout The connection timeout.
15
- # @attribute [r] options Additional options for the node (ssl).
16
- attr_reader \
17
- :address,
18
- :down_at,
19
- :ip_address,
20
- :peers,
21
- :port,
22
- :resolved_address,
23
- :timeout,
24
- :options,
25
- :refreshed_at
16
+ include Authenticatable
17
+ include Executable
18
+ include Instrumentable
19
+
20
+ # @!attribute address
21
+ # @return [ Address ] The address.
22
+ # @!attribute down_at
23
+ # @return [ Time ] The time the node was marked as down.
24
+ # @!attribute latency
25
+ # @return [ Integer ] The latency in milliseconds.
26
+ # @!attribute options
27
+ # @return [ Hash ] The node options.
28
+ # @!attribute refreshed_at
29
+ # @return [ Time ] The last time the node did a refresh.
30
+ attr_reader :address, :down_at, :latency, :options, :refreshed_at
26
31
 
27
32
  # Is this node equal to another?
28
33
  #
@@ -35,29 +40,21 @@ module Moped
35
40
  #
36
41
  # @since 1.0.0
37
42
  def ==(other)
38
- resolved_address == other.resolved_address
43
+ return false unless other.is_a?(Node)
44
+ address.resolved == other.address.resolved
39
45
  end
40
46
  alias :eql? :==
41
47
 
42
- # Apply the authentication details to this node.
43
- #
44
- # @example Apply authentication.
45
- # node.apply_auth([ :db, "user", "pass" ])
48
+ # Is the node an arbiter?
46
49
  #
47
- # @param [ Array<String> ] credentials The db, username, and password.
50
+ # @example Is the node an arbiter?
51
+ # node.arbiter?
48
52
  #
49
- # @return [ Node ] The authenticated node.
53
+ # @return [ true, false ] If the node is an arbiter.
50
54
  #
51
55
  # @since 1.0.0
52
- def apply_auth(credentials)
53
- unless auth == credentials
54
- logouts = auth.keys - credentials.keys
55
- logouts.each { |database| logout(database) }
56
- credentials.each do |database, (username, password)|
57
- login(database, username, password) unless auth[database] == [username, password]
58
- end
59
- end
60
- self
56
+ def arbiter?
57
+ !!@arbiter
61
58
  end
62
59
 
63
60
  # Is the cluster auto-discovering new nodes in the cluster?
@@ -87,30 +84,65 @@ module Moped
87
84
  #
88
85
  # @since 1.0.0
89
86
  def command(database, cmd, options = {})
90
- operation = Protocol::Command.new(database, cmd, options)
91
-
92
- process(operation) do |reply|
93
- result = reply.documents.first
94
- if reply.command_failure?
95
- if reply.unauthorized? && auth.has_key?(database)
96
- login(database, *auth[database])
97
- result = command(database, cmd, options)
98
- else
99
- raise Errors::OperationFailure.new(operation, result)
100
- end
101
- end
102
- result
87
+ read(Protocol::Command.new(database, cmd, options))
88
+ end
89
+
90
+ # Connect the node on the underlying connection.
91
+ #
92
+ # @example Connect the node.
93
+ # node.connect
94
+ #
95
+ # @raise [ Errors::ConnectionFailure ] If connection failed.
96
+ #
97
+ # @return [ true ] If the connection suceeded.
98
+ #
99
+ # @since 2.0.0
100
+ def connect
101
+ start = Time.now
102
+ connection{ |conn| conn.connect }
103
+ @latency = Time.now - start
104
+ @down_at = nil
105
+ true
106
+ end
107
+
108
+ # Is the node currently connected?
109
+ #
110
+ # @example Is the node connected?
111
+ # node.connected?
112
+ #
113
+ # @return [ true, false ] If the node is connected or not.
114
+ #
115
+ # @since 2.0.0
116
+ def connected?
117
+ connection{ |conn| conn.connected? }
118
+ end
119
+
120
+ # Get the underlying connection for the node.
121
+ #
122
+ # @example Get the node's connection.
123
+ # node.connection
124
+ #
125
+ # @return [ Connection ] The connection.
126
+ #
127
+ # @since 2.0.0
128
+ def connection
129
+ pool.with_connection do |conn|
130
+ yield(conn)
103
131
  end
104
132
  end
105
133
 
106
134
  # Force the node to disconnect from the server.
107
135
  #
108
- # @return [ nil ] nil.
136
+ # @example Disconnect the node.
137
+ # node.disconnect
138
+ #
139
+ # @return [ true ] If the disconnection succeeded.
109
140
  #
110
141
  # @since 1.2.0
111
142
  def disconnect
112
- auth.clear
113
- connection.disconnect
143
+ credentials.clear
144
+ connection{ |conn| conn.disconnect }
145
+ true
114
146
  end
115
147
 
116
148
  # Is the node down?
@@ -125,6 +157,20 @@ module Moped
125
157
  @down_at
126
158
  end
127
159
 
160
+ # Mark the node as down.
161
+ #
162
+ # @example Mark the node as down.
163
+ # node.down!
164
+ #
165
+ # @return [ nil ] Nothing.
166
+ #
167
+ # @since 2.0.0
168
+ def down!
169
+ @down_at = Time.new
170
+ @latency = nil
171
+ disconnect if connected?
172
+ end
173
+
128
174
  # Yields the block if a connection can be established, retrying when a
129
175
  # connection error is raised.
130
176
  #
@@ -138,47 +184,16 @@ module Moped
138
184
  # @return [ nil ] nil.
139
185
  #
140
186
  # @since 1.0.0
141
- def ensure_connected
142
- # Don't run the reconnection login if we're already inside an
143
- # +ensure_connected+ block.
144
- return yield if Threaded.executing?(:connection)
145
- Threaded.begin(:connection)
146
- retry_on_failure = true
147
-
148
- begin
149
- connect unless connected?
150
- yield
151
- rescue Errors::PotentialReconfiguration => e
152
- if e.reconfiguring_replica_set?
153
- raise Errors::ReplicaSetReconfigured.new(e.command, e.details)
154
- elsif e.connection_failure?
155
- raise Errors::ConnectionFailure.new(e.inspect)
156
- end
157
- raise
158
- rescue Errors::DoNotDisconnect
159
- # These exceptions are "expected" in the normal course of events, and
160
- # don't necessitate disconnecting.
161
- raise
162
- rescue Errors::ConnectionFailure
163
- disconnect
164
- if retry_on_failure
165
- # Maybe there was a hiccup -- try reconnecting one more time
166
- retry_on_failure = false
167
- retry
168
- else
169
- # Nope, we failed to connect twice. Flag the node as down and re-raise
170
- # the exception.
171
- down!
172
- raise
187
+ def ensure_connected(&block)
188
+ return yield if executing?(:connection)
189
+ execute(:connection) do
190
+ begin
191
+ connect unless connected?
192
+ yield(self)
193
+ rescue Exception => e
194
+ Failover.get(e).execute(e, self, &block)
173
195
  end
174
- rescue
175
- # Looks like we got an unexpected error, so we'll clean up the connection
176
- # and re-raise the exception.
177
- disconnect
178
- raise $!.extend(Errors::SocketError)
179
196
  end
180
- ensure
181
- Threaded.end(:connection)
182
197
  end
183
198
 
184
199
  # Set a flag on the node for the duration of provided block so that an
@@ -186,17 +201,16 @@ module Moped
186
201
  #
187
202
  # @example Ensure this node is primary.
188
203
  # node.ensure_primary do
189
- # #...
204
+ # node.command(ismaster: 1)
190
205
  # end
191
206
  #
192
207
  # @return [ nil ] nil.
193
208
  #
194
209
  # @since 1.0.0
195
210
  def ensure_primary
196
- Threaded.begin(:ensure_primary)
197
- yield
198
- ensure
199
- Threaded.end(:ensure_primary)
211
+ execute(:ensure_primary) do
212
+ yield(self)
213
+ end
200
214
  end
201
215
 
202
216
  # Execute a get more operation on the node.
@@ -214,9 +228,7 @@ module Moped
214
228
  #
215
229
  # @since 1.0.0
216
230
  def get_more(database, collection, cursor_id, limit)
217
- reply = process(Protocol::GetMore.new(database, collection, cursor_id, limit))
218
- raise Moped::Errors::CursorNotFound.new("GET MORE", cursor_id) if reply.cursor_not_found?
219
- reply
231
+ read(Protocol::GetMore.new(database, collection, cursor_id, limit))
220
232
  end
221
233
 
222
234
  # Get the hash identifier for the node.
@@ -228,7 +240,7 @@ module Moped
228
240
  #
229
241
  # @since 1.0.0
230
242
  def hash
231
- resolved_address.hash
243
+ address.resolved.hash
232
244
  end
233
245
 
234
246
  # Creat the new node.
@@ -241,14 +253,15 @@ module Moped
241
253
  #
242
254
  # @since 1.0.0
243
255
  def initialize(address, options = {})
244
- @address = address
256
+ @address = Address.new(address)
245
257
  @options = options
246
- @timeout = options[:timeout] || 5
247
258
  @down_at = nil
248
259
  @refreshed_at = nil
260
+ @latency = nil
249
261
  @primary = nil
250
262
  @secondary = nil
251
- resolve_address
263
+ @instrumenter = options[:instrumenter] || Instrumentable::Log
264
+ @address.resolve(self)
252
265
  end
253
266
 
254
267
  # Insert documents into the database.
@@ -263,8 +276,8 @@ module Moped
263
276
  # @return [ Message ] The result of the operation.
264
277
  #
265
278
  # @since 1.0.0
266
- def insert(database, collection, documents, options = {})
267
- process(Protocol::Insert.new(database, collection, documents, options))
279
+ def insert(database, collection, documents, concern, options = {})
280
+ write(Protocol::Insert.new(database, collection, documents, options), concern)
268
281
  end
269
282
 
270
283
  # Kill all provided cursors on the node.
@@ -281,6 +294,20 @@ module Moped
281
294
  process(Protocol::KillCursors.new(cursor_ids))
282
295
  end
283
296
 
297
+ # Can we send messages to this node in normal cirucmstances? This is true
298
+ # only if the node is a primary or secondary node - arbiters or passives
299
+ # cannot be sent anything.
300
+ #
301
+ # @example Is the node messagable?
302
+ # node.messagable?
303
+ #
304
+ # @return [ true, false ] If messages can be sent to the node.
305
+ #
306
+ # @since 2.0.0
307
+ def messagable?
308
+ primary? || secondary?
309
+ end
310
+
284
311
  # Does the node need to be refreshed?
285
312
  #
286
313
  # @example Does the node require refreshing?
@@ -295,6 +322,31 @@ module Moped
295
322
  !refreshed_at || refreshed_at < time
296
323
  end
297
324
 
325
+ # Is the node passive?
326
+ #
327
+ # @example Is the node passive?
328
+ # node.passive?
329
+ #
330
+ # @return [ true, false ] If the node is passive.
331
+ #
332
+ # @since 1.0.0
333
+ def passive?
334
+ !!@passive
335
+ end
336
+
337
+ # Get all the other nodes in the replica set according to the server
338
+ # information.
339
+ #
340
+ # @example Get the node's peers.
341
+ # node.peers
342
+ #
343
+ # @return [ Array<Node> ] The peers.
344
+ #
345
+ # @since 2.0.0
346
+ def peers
347
+ @peers ||= []
348
+ end
349
+
298
350
  # Execute a pipeline of commands, for example a safe mode persist.
299
351
  #
300
352
  # @example Execute a pipeline.
@@ -305,14 +357,12 @@ module Moped
305
357
  # @return [ nil ] nil.
306
358
  #
307
359
  # @since 1.0.0
360
+ # @todo: Remove with piggbacked gle.
308
361
  def pipeline
309
- Threaded.begin(:pipeline)
310
- begin
311
- yield
312
- ensure
313
- Threaded.end(:pipeline)
362
+ execute(:pipeline) do
363
+ yield(self)
314
364
  end
315
- flush unless Threaded.executing?(:pipeline)
365
+ flush unless executing?(:pipeline)
316
366
  end
317
367
 
318
368
  # Is the node the replica set primary?
@@ -324,31 +374,29 @@ module Moped
324
374
  #
325
375
  # @since 1.0.0
326
376
  def primary?
327
- @primary
377
+ !!@primary
328
378
  end
329
379
 
330
- # Is the node an arbiter?
380
+ # Processes the provided operation on this node, and will execute the
381
+ # callback when the operation is sent to the database.
331
382
  #
332
- # @example Is the node an arbiter?
333
- # node.arbiter?
383
+ # @example Process a read operation.
384
+ # node.process(query) do |reply|
385
+ # return reply.documents
386
+ # end
334
387
  #
335
- # @return [ true, false ] If the node is an arbiter.
388
+ # @param [ Message ] operation The database operation.
389
+ # @param [ Proc ] callback The callback to run on operation completion.
336
390
  #
337
- # @since 1.0.0
338
- def arbiter?
339
- @arbiter
340
- end
341
-
342
- # Is the node passive?
343
- #
344
- # @example Is the node passive?
345
- # node.passive?
346
- #
347
- # @return [ true, false ] If the node is passive.
391
+ # @return [ Object ] The result of the callback.
348
392
  #
349
393
  # @since 1.0.0
350
- def passive?
351
- @passive
394
+ def process(operation, &callback)
395
+ if executing?(:pipeline)
396
+ queue.push([ operation, callback ])
397
+ else
398
+ flush([[ operation, callback ]])
399
+ end
352
400
  end
353
401
 
354
402
  # Execute a query on the node.
@@ -367,25 +415,7 @@ module Moped
367
415
  #
368
416
  # @since 1.0.0
369
417
  def query(database, collection, selector, options = {})
370
- operation = Protocol::Query.new(database, collection, selector, options)
371
-
372
- process(operation) do |reply|
373
- if reply.query_failed?
374
- if reply.unauthorized? && auth.has_key?(database)
375
- # If we got here, most likely this is the case of Moped
376
- # authenticating successfully against the node originally, but the
377
- # node has been reset or gone down and come back up. The most
378
- # common case here is a rs.stepDown() which will reinitialize the
379
- # connection. In this case we need to requthenticate and try again,
380
- # otherwise we'll just raise the error to the user.
381
- login(database, *auth[database])
382
- reply = query(database, collection, selector, options)
383
- else
384
- raise Errors::QueryFailure.new(operation, reply.documents.first)
385
- end
386
- end
387
- reply
388
- end
418
+ read(Protocol::Query.new(database, collection, selector, options))
389
419
  end
390
420
 
391
421
  # Refresh information about the node, such as it's status in the replica
@@ -403,27 +433,18 @@ module Moped
403
433
  #
404
434
  # @since 1.0.0
405
435
  def refresh
406
- if resolve_address
436
+ if address.resolve(self)
407
437
  begin
408
438
  @refreshed_at = Time.now
409
- info = command("admin", ismaster: 1)
410
- primary = true if info["ismaster"]
411
- secondary = true if info["secondary"]
412
- generate_peers(info)
413
-
414
- @primary, @secondary = primary, secondary
415
- @arbiter = info["arbiterOnly"]
416
- @passive = info["passive"]
417
-
418
- if !primary && Threaded.executing?(:ensure_primary)
439
+ configure(command("admin", ismaster: 1))
440
+ if !primary? && executing?(:ensure_primary)
419
441
  raise Errors::ReplicaSetReconfigured.new("#{inspect} is no longer the primary node.", {})
420
- elsif !primary && !secondary
442
+ elsif !messagable?
421
443
  # not primary or secondary so mark it as down, since it's probably
422
444
  # a recovering node withing the replica set
423
445
  down!
424
446
  end
425
447
  rescue Timeout::Error
426
- @peers = []
427
448
  down!
428
449
  end
429
450
  end
@@ -442,8 +463,8 @@ module Moped
442
463
  # @return [ Message ] The result of the operation.
443
464
  #
444
465
  # @since 1.0.0
445
- def remove(database, collection, selector, options = {})
446
- process(Protocol::Delete.new(database, collection, selector, options))
466
+ def remove(database, collection, selector, concern, options = {})
467
+ write(Protocol::Delete.new(database, collection, selector, options), concern)
447
468
  end
448
469
 
449
470
  # Is the node a replica set secondary?
@@ -458,6 +479,18 @@ module Moped
458
479
  @secondary
459
480
  end
460
481
 
482
+ # Get the timeout, in seconds, for this node.
483
+ #
484
+ # @example Get the timeout in seconds.
485
+ # node.timeout
486
+ #
487
+ # @return [ Integer ] The configured timeout or the default of 5.
488
+ #
489
+ # @since 1.0.0
490
+ def timeout
491
+ @timeout ||= (options[:timeout] || 5)
492
+ end
493
+
461
494
  # Execute an update command for the provided selector.
462
495
  #
463
496
  # @example Update documents.
@@ -472,8 +505,8 @@ module Moped
472
505
  # @return [ Message ] The result of the operation.
473
506
  #
474
507
  # @since 1.0.0
475
- def update(database, collection, selector, change, options = {})
476
- process(Protocol::Update.new(database, collection, selector, change, options))
508
+ def update(database, collection, selector, change, concern, options = {})
509
+ write(Protocol::Update.new(database, collection, selector, change, options), concern)
477
510
  end
478
511
 
479
512
  # Get the node as a nice formatted string.
@@ -485,117 +518,68 @@ module Moped
485
518
  #
486
519
  # @since 1.0.0
487
520
  def inspect
488
- "<#{self.class.name} resolved_address=#{@resolved_address.inspect}>"
521
+ "<#{self.class.name} resolved_address=#{address.resolved.inspect}>"
489
522
  end
490
523
 
491
524
  private
492
525
 
493
- def auth
494
- @auth ||= {}
495
- end
496
-
497
- def login(database, username, password)
498
- getnonce = Protocol::Command.new(database, getnonce: 1)
499
- connection.write [getnonce]
500
- result = connection.read.documents.first
501
- raise Errors::OperationFailure.new(getnonce, result) unless result["ok"] == 1
502
- authenticate = Protocol::Commands::Authenticate.new(database, username, password, result["nonce"])
503
- connection.write [authenticate]
504
- result = connection.read.documents.first
505
-
506
- unless result["ok"] == 1
507
- # See if we had connectivity issues so we can retry
508
- e = Errors::PotentialReconfiguration.new(authenticate, result)
509
- if e.reconfiguring_replica_set?
510
- raise Errors::ReplicaSetReconfigured.new(e.command, e.details)
511
- elsif e.connection_failure?
512
- raise Errors::ConnectionFailure.new(e.inspect)
513
- end
514
-
515
- raise Errors::AuthenticationFailure.new(authenticate, result)
516
- end
517
- auth[database] = [username, password]
518
- end
519
-
520
- def logout(database)
521
- command = Protocol::Command.new(database, logout: 1)
522
- connection.write [command]
523
- result = connection.read.documents.first
524
- raise Errors::OperationFailure.new(command, result) unless result["ok"] == 1
525
-
526
- auth.delete(database)
527
- end
528
-
529
- private
530
-
531
- def generate_peers(info)
532
- peers = []
533
- if auto_discovering?
534
- peers.push(info["primary"]) if info["primary"]
535
- peers.concat(info["hosts"]) if info["hosts"]
536
- peers.concat(info["passives"]) if info["passives"]
537
- peers.concat(info["arbiters"]) if info["arbiters"]
538
- end
539
- @peers = peers.map{ |peer| discover(peer) }.uniq
540
- end
541
-
542
- def discover(peer)
543
- Node.new(peer, options).tap do |node|
544
- node.send(:auth).merge!(auth)
545
- end
546
- end
547
-
548
- def initialize_copy(_)
549
- @connection = nil
550
- end
551
-
552
- def connection
553
- @connection ||= Connection.new(ip_address, port, timeout, options)
554
- end
555
-
556
- def connected?
557
- connection.connected?
558
- end
559
-
560
- # Mark the node as down.
526
+ # Configure the node based on the return from the ismaster command.
561
527
  #
562
- # Returns nothing.
563
- def down!
564
- @down_at = Time.new
565
-
566
- disconnect
567
- end
568
-
569
- # Connect to the node.
528
+ # @api private
570
529
  #
571
- # Returns nothing.
572
- # Raises Moped::ConnectionError if the connection times out.
573
- # Raises Moped::ConnectionError if the server is unavailable.
574
- def connect
575
- connection.connect
576
- @down_at = nil
530
+ # @example Configure the node.
531
+ # node.configure(ismaster)
532
+ #
533
+ # @param [ Hash ] settings The result of the ismaster command.
534
+ #
535
+ # @since 2.0.0
536
+ def configure(settings)
537
+ @arbiter = settings["arbiterOnly"]
538
+ @passive = settings["passive"]
539
+ @primary = settings["ismaster"]
540
+ @secondary = settings["secondary"]
541
+ discover(settings["hosts"]) if auto_discovering?
577
542
  end
578
543
 
579
- def process(operation, &callback)
580
- if Threaded.executing? :pipeline
581
- queue.push [operation, callback]
582
- else
583
- flush([[operation, callback]])
544
+ # Discover the additional nodes.
545
+ #
546
+ # @api private
547
+ #
548
+ # @example Discover the additional nodes.
549
+ # node.discover([ "127.0.0.1:27019" ])
550
+ #
551
+ # @param [ Array<String> ] nodes The new nodes.
552
+ #
553
+ # @since 2.0.0
554
+ def discover(*nodes)
555
+ nodes.flatten.compact.each do |peer|
556
+ node = Node.new(peer, options)
557
+ node.credentials.merge!(credentials)
558
+ peers.push(node)
584
559
  end
585
560
  end
586
561
 
587
- def queue
588
- Threaded.stack(:pipelined_operations)
589
- end
590
-
562
+ # Flush the node operations to the database.
563
+ #
564
+ # @api private
565
+ #
566
+ # @example Flush the operations.
567
+ # node.flush([ command ])
568
+ #
569
+ # @param [ Array<Message> ] ops The operations to flush.
570
+ #
571
+ # @return [ Object ] The result of the operations.
572
+ #
573
+ # @since 2.0.0
591
574
  def flush(ops = queue)
592
575
  operations, callbacks = ops.transpose
593
-
594
576
  logging(operations) do
595
577
  ensure_connected do
596
- connection.write operations
597
- replies = connection.receive_replies(operations)
598
-
578
+ replies = nil
579
+ connection do |conn|
580
+ conn.write(operations)
581
+ replies = conn.receive_replies(operations)
582
+ end
599
583
  replies.zip(callbacks).map do |reply, callback|
600
584
  callback ? callback[reply] : reply
601
585
  end.last
@@ -605,50 +589,85 @@ module Moped
605
589
  ops.clear
606
590
  end
607
591
 
592
+ # Yield the block with logging.
593
+ #
594
+ # @api private
595
+ #
596
+ # @example Yield with logging.
597
+ # logging(operations) do
598
+ # node.command(ismaster: 1)
599
+ # end
600
+ #
601
+ # @param [ Array<Message> ] operations The operations.
602
+ #
603
+ # @return [ Object ] The result of the yield.
604
+ #
605
+ # @since 2.0.0
608
606
  def logging(operations)
609
- instrument_start = (logger = Moped.logger) && logger.debug? && Time.new
610
- yield
611
- ensure
612
- log_operations(logger, operations, 1000 * (Time.new.to_f - instrument_start.to_f)) if instrument_start
607
+ instrument(TOPIC, prefix: " MOPED: #{address.resolved}", ops: operations) do
608
+ yield if block_given?
609
+ end
613
610
  end
614
611
 
615
- def log_operations(logger, ops, duration_ms)
616
- prefix = " MOPED: #{resolved_address} "
617
- indent = " "*prefix.length
618
- runtime = (" (%.4fms)" % duration_ms)
619
-
620
- if ops.length == 1
621
- logger.debug prefix + ops.first.log_inspect + runtime
622
- else
623
- first, *middle, last = ops
612
+ # Get the connection pool for the node.
613
+ #
614
+ # @api private
615
+ #
616
+ # @example Get the connection pool.
617
+ # node.pool
618
+ #
619
+ # @return [ Connection::Pool ] The connection pool.
620
+ #
621
+ # @since 2.0.0
622
+ def pool
623
+ @pool ||= Connection::Manager.pool(self)
624
+ end
624
625
 
625
- logger.debug prefix + first.log_inspect
626
- middle.each { |m| logger.debug indent + m.log_inspect }
627
- logger.debug indent + last.log_inspect + runtime
628
- end
626
+ # Execute a read operation.
627
+ #
628
+ # @api private
629
+ #
630
+ # @example Execute a read operation.
631
+ # node.read(operation)
632
+ #
633
+ # @param [ Message ] operation The read operation.
634
+ #
635
+ # @return [ Object ] The result of the read.
636
+ #
637
+ # @since 2.0.0
638
+ def read(operation)
639
+ Operation::Read.new(operation).execute(self)
629
640
  end
630
641
 
631
- def resolve_address
632
- unless ip_address
633
- begin
634
- parse_address and true
635
- rescue SocketError
636
- if logger = Moped.logger
637
- logger.warn " MOPED: Could not resolve IP address for #{address}"
638
- end
639
- @down_at = Time.new
640
- false
641
- end
642
- else
643
- true
644
- end
642
+ # Execute a write operation.
643
+ #
644
+ # @api private
645
+ #
646
+ # @example Execute a write operation.
647
+ # node.write(operation, concern)
648
+ #
649
+ # @param [ Message ] operation The write operation.
650
+ # @param [ WriteConcern ] concern The write concern.
651
+ #
652
+ # @return [ Object ] The result of the write.
653
+ #
654
+ # @since 2.0.0
655
+ def write(operation, concern)
656
+ Operation::Write.new(operation, concern).execute(self)
645
657
  end
646
658
 
647
- def parse_address
648
- host, port = address.split(":")
649
- @port = (port || 27017).to_i
650
- @ip_address = ::Socket.getaddrinfo(host, nil, ::Socket::AF_INET, ::Socket::SOCK_STREAM).first[3]
651
- @resolved_address = "#{@ip_address}:#{@port}"
659
+ # Get the queue of operations.
660
+ #
661
+ # @api private
662
+ #
663
+ # @example Get the operation queue.
664
+ # node.queue
665
+ #
666
+ # @return [ Array<Message> ] The queue of operations.
667
+ #
668
+ # @since 2.0.0
669
+ def queue
670
+ stack(:pipelined_operations)
652
671
  end
653
672
  end
654
673
  end