mongo 1.3.0 → 1.12.5

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 (185) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/{LICENSE.txt → LICENSE} +1 -1
  4. data/README.md +122 -271
  5. data/Rakefile +25 -209
  6. data/VERSION +1 -0
  7. data/bin/mongo_console +31 -9
  8. data/lib/mongo/bulk_write_collection_view.rb +387 -0
  9. data/lib/mongo/collection.rb +576 -269
  10. data/lib/mongo/collection_writer.rb +364 -0
  11. data/lib/mongo/connection/node.rb +249 -0
  12. data/lib/mongo/connection/pool.rb +340 -0
  13. data/lib/mongo/connection/pool_manager.rb +320 -0
  14. data/lib/mongo/connection/sharding_pool_manager.rb +67 -0
  15. data/lib/mongo/connection/socket/socket_util.rb +37 -0
  16. data/lib/mongo/connection/socket/ssl_socket.rb +95 -0
  17. data/lib/mongo/connection/socket/tcp_socket.rb +87 -0
  18. data/lib/mongo/connection/socket/unix_socket.rb +39 -0
  19. data/lib/mongo/connection/socket.rb +18 -0
  20. data/lib/mongo/connection.rb +7 -875
  21. data/lib/mongo/cursor.rb +403 -117
  22. data/lib/mongo/db.rb +444 -243
  23. data/lib/mongo/exception.rb +145 -0
  24. data/lib/mongo/functional/authentication.rb +455 -0
  25. data/lib/mongo/functional/logging.rb +85 -0
  26. data/lib/mongo/functional/read_preference.rb +183 -0
  27. data/lib/mongo/functional/scram.rb +556 -0
  28. data/lib/mongo/functional/uri_parser.rb +409 -0
  29. data/lib/mongo/functional/write_concern.rb +66 -0
  30. data/lib/mongo/functional.rb +20 -0
  31. data/lib/mongo/gridfs/grid.rb +30 -24
  32. data/lib/mongo/gridfs/grid_ext.rb +6 -10
  33. data/lib/mongo/gridfs/grid_file_system.rb +38 -20
  34. data/lib/mongo/gridfs/grid_io.rb +84 -75
  35. data/lib/mongo/gridfs.rb +18 -0
  36. data/lib/mongo/legacy.rb +140 -0
  37. data/lib/mongo/mongo_client.rb +697 -0
  38. data/lib/mongo/mongo_replica_set_client.rb +535 -0
  39. data/lib/mongo/mongo_sharded_client.rb +159 -0
  40. data/lib/mongo/networking.rb +372 -0
  41. data/lib/mongo/{util → utils}/conversions.rb +29 -8
  42. data/lib/mongo/{util → utils}/core_ext.rb +28 -18
  43. data/lib/mongo/{util → utils}/server_version.rb +4 -6
  44. data/lib/mongo/{util → utils}/support.rb +29 -31
  45. data/lib/mongo/utils/thread_local_variable_manager.rb +25 -0
  46. data/lib/mongo/utils.rb +19 -0
  47. data/lib/mongo.rb +51 -50
  48. data/mongo.gemspec +29 -32
  49. data/test/functional/authentication_test.rb +39 -0
  50. data/test/functional/bulk_api_stress_test.rb +133 -0
  51. data/test/functional/bulk_write_collection_view_test.rb +1198 -0
  52. data/test/functional/client_test.rb +627 -0
  53. data/test/functional/collection_test.rb +2175 -0
  54. data/test/functional/collection_writer_test.rb +83 -0
  55. data/test/{conversions_test.rb → functional/conversions_test.rb} +47 -3
  56. data/test/functional/cursor_fail_test.rb +57 -0
  57. data/test/functional/cursor_message_test.rb +56 -0
  58. data/test/functional/cursor_test.rb +683 -0
  59. data/test/functional/db_api_test.rb +835 -0
  60. data/test/functional/db_connection_test.rb +25 -0
  61. data/test/functional/db_test.rb +348 -0
  62. data/test/functional/grid_file_system_test.rb +285 -0
  63. data/test/{grid_io_test.rb → functional/grid_io_test.rb} +72 -11
  64. data/test/{grid_test.rb → functional/grid_test.rb} +88 -15
  65. data/test/functional/pool_test.rb +136 -0
  66. data/test/functional/safe_test.rb +98 -0
  67. data/test/functional/ssl_test.rb +29 -0
  68. data/test/functional/support_test.rb +62 -0
  69. data/test/functional/timeout_test.rb +60 -0
  70. data/test/functional/uri_test.rb +446 -0
  71. data/test/functional/write_concern_test.rb +118 -0
  72. data/test/helpers/general.rb +50 -0
  73. data/test/helpers/test_unit.rb +476 -0
  74. data/test/replica_set/authentication_test.rb +37 -0
  75. data/test/replica_set/basic_test.rb +189 -0
  76. data/test/replica_set/client_test.rb +393 -0
  77. data/test/replica_set/connection_test.rb +138 -0
  78. data/test/replica_set/count_test.rb +66 -0
  79. data/test/replica_set/cursor_test.rb +220 -0
  80. data/test/replica_set/insert_test.rb +157 -0
  81. data/test/replica_set/max_values_test.rb +151 -0
  82. data/test/replica_set/pinning_test.rb +105 -0
  83. data/test/replica_set/query_test.rb +73 -0
  84. data/test/replica_set/read_preference_test.rb +219 -0
  85. data/test/replica_set/refresh_test.rb +211 -0
  86. data/test/replica_set/replication_ack_test.rb +95 -0
  87. data/test/replica_set/ssl_test.rb +32 -0
  88. data/test/sharded_cluster/basic_test.rb +203 -0
  89. data/test/shared/authentication/basic_auth_shared.rb +260 -0
  90. data/test/shared/authentication/bulk_api_auth_shared.rb +249 -0
  91. data/test/shared/authentication/gssapi_shared.rb +176 -0
  92. data/test/shared/authentication/sasl_plain_shared.rb +96 -0
  93. data/test/shared/authentication/scram_shared.rb +92 -0
  94. data/test/shared/ssl_shared.rb +235 -0
  95. data/test/test_helper.rb +53 -94
  96. data/test/threading/basic_test.rb +120 -0
  97. data/test/tools/mongo_config.rb +708 -0
  98. data/test/tools/mongo_config_test.rb +160 -0
  99. data/test/unit/client_test.rb +381 -0
  100. data/test/unit/collection_test.rb +89 -53
  101. data/test/unit/connection_test.rb +282 -32
  102. data/test/unit/cursor_test.rb +206 -8
  103. data/test/unit/db_test.rb +55 -13
  104. data/test/unit/grid_test.rb +43 -16
  105. data/test/unit/mongo_sharded_client_test.rb +48 -0
  106. data/test/unit/node_test.rb +93 -0
  107. data/test/unit/pool_manager_test.rb +111 -0
  108. data/test/unit/read_pref_test.rb +406 -0
  109. data/test/unit/read_test.rb +159 -0
  110. data/test/unit/safe_test.rb +69 -36
  111. data/test/unit/sharding_pool_manager_test.rb +84 -0
  112. data/test/unit/write_concern_test.rb +175 -0
  113. data.tar.gz.sig +3 -0
  114. metadata +227 -216
  115. metadata.gz.sig +0 -0
  116. data/docs/CREDITS.md +0 -123
  117. data/docs/FAQ.md +0 -116
  118. data/docs/GridFS.md +0 -158
  119. data/docs/HISTORY.md +0 -244
  120. data/docs/RELEASES.md +0 -33
  121. data/docs/REPLICA_SETS.md +0 -72
  122. data/docs/TUTORIAL.md +0 -247
  123. data/docs/WRITE_CONCERN.md +0 -28
  124. data/lib/mongo/exceptions.rb +0 -71
  125. data/lib/mongo/gridfs/grid_io_fix.rb +0 -38
  126. data/lib/mongo/repl_set_connection.rb +0 -342
  127. data/lib/mongo/test.rb +0 -20
  128. data/lib/mongo/util/pool.rb +0 -177
  129. data/lib/mongo/util/uri_parser.rb +0 -185
  130. data/test/async/collection_test.rb +0 -224
  131. data/test/async/connection_test.rb +0 -24
  132. data/test/async/cursor_test.rb +0 -162
  133. data/test/async/worker_pool_test.rb +0 -99
  134. data/test/auxillary/1.4_features.rb +0 -166
  135. data/test/auxillary/authentication_test.rb +0 -68
  136. data/test/auxillary/autoreconnect_test.rb +0 -41
  137. data/test/auxillary/fork_test.rb +0 -30
  138. data/test/auxillary/repl_set_auth_test.rb +0 -58
  139. data/test/auxillary/slave_connection_test.rb +0 -36
  140. data/test/auxillary/threaded_authentication_test.rb +0 -101
  141. data/test/bson/binary_test.rb +0 -15
  142. data/test/bson/bson_test.rb +0 -649
  143. data/test/bson/byte_buffer_test.rb +0 -208
  144. data/test/bson/hash_with_indifferent_access_test.rb +0 -38
  145. data/test/bson/json_test.rb +0 -17
  146. data/test/bson/object_id_test.rb +0 -154
  147. data/test/bson/ordered_hash_test.rb +0 -204
  148. data/test/bson/timestamp_test.rb +0 -24
  149. data/test/collection_test.rb +0 -910
  150. data/test/connection_test.rb +0 -309
  151. data/test/cursor_fail_test.rb +0 -75
  152. data/test/cursor_message_test.rb +0 -43
  153. data/test/cursor_test.rb +0 -483
  154. data/test/db_api_test.rb +0 -726
  155. data/test/db_connection_test.rb +0 -15
  156. data/test/db_test.rb +0 -287
  157. data/test/grid_file_system_test.rb +0 -243
  158. data/test/load/resque/load.rb +0 -21
  159. data/test/load/resque/processor.rb +0 -26
  160. data/test/load/thin/load.rb +0 -24
  161. data/test/load/unicorn/load.rb +0 -23
  162. data/test/load/unicorn/unicorn.rb +0 -29
  163. data/test/replica_sets/connect_test.rb +0 -94
  164. data/test/replica_sets/connection_string_test.rb +0 -32
  165. data/test/replica_sets/count_test.rb +0 -35
  166. data/test/replica_sets/insert_test.rb +0 -53
  167. data/test/replica_sets/pooled_insert_test.rb +0 -55
  168. data/test/replica_sets/query_secondaries.rb +0 -96
  169. data/test/replica_sets/query_test.rb +0 -51
  170. data/test/replica_sets/replication_ack_test.rb +0 -66
  171. data/test/replica_sets/rs_test_helper.rb +0 -27
  172. data/test/safe_test.rb +0 -68
  173. data/test/support/hash_with_indifferent_access.rb +0 -186
  174. data/test/support/keys.rb +0 -45
  175. data/test/support_test.rb +0 -18
  176. data/test/threading/threading_with_large_pool_test.rb +0 -90
  177. data/test/threading_test.rb +0 -87
  178. data/test/tools/auth_repl_set_manager.rb +0 -14
  179. data/test/tools/load.rb +0 -58
  180. data/test/tools/repl_set_manager.rb +0 -266
  181. data/test/tools/sharding_manager.rb +0 -202
  182. data/test/tools/test.rb +0 -4
  183. data/test/unit/pool_test.rb +0 -9
  184. data/test/unit/repl_set_connection_test.rb +0 -59
  185. data/test/uri_test.rb +0 -91
@@ -0,0 +1,708 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Copyright (C) 2009-2013 MongoDB, Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'socket'
18
+ require 'fileutils'
19
+ require 'mongo'
20
+ require 'sfl'
21
+
22
+ $debug_level = 2
23
+ STDOUT.sync = true
24
+
25
+ def debug(level, arg)
26
+ if level <= $debug_level
27
+ file_line = caller[0][/(.*:\d+):/, 1]
28
+ calling_method = caller[0][/`([^']*)'/, 1]
29
+ puts "#{file_line}:#{calling_method}:#{arg.class == String ? arg : arg.inspect}"
30
+ end
31
+ end
32
+
33
+ #
34
+ # Design Notes
35
+ # Configuration and Cluster Management are modularized with the concept that the Cluster Manager
36
+ # can be supplied with any configuration to run.
37
+ # A configuration can be edited, modified, copied into a test file, and supplied to a cluster manager
38
+ # as a parameter.
39
+ #
40
+ module Mongo
41
+ class Config
42
+ DEFAULT_BASE_OPTS = { :host => 'localhost', :dbpath => 'data', :logpath => 'data/log' }
43
+ DEFAULT_REPLICA_SET = DEFAULT_BASE_OPTS.merge( :replicas => 3, :arbiters => 0 )
44
+ DEFAULT_SHARDED_SIMPLE = DEFAULT_BASE_OPTS.merge( :shards => 2, :configs => 1, :routers => 2 )
45
+ DEFAULT_SHARDED_REPLICA = DEFAULT_SHARDED_SIMPLE.merge( :replicas => 3, :arbiters => 0)
46
+
47
+ IGNORE_KEYS = [:host, :command, :_id]
48
+ SHARDING_OPT_KEYS = [:shards, :configs, :routers]
49
+ REPLICA_OPT_KEYS = [:replicas, :arbiters]
50
+ MONGODS_OPT_KEYS = [:mongods]
51
+ CLUSTER_OPT_KEYS = SHARDING_OPT_KEYS + REPLICA_OPT_KEYS + MONGODS_OPT_KEYS
52
+
53
+ FLAGS = [:noprealloc, :smallfiles, :logappend, :configsvr, :shardsvr, :quiet, :fastsync, :auth, :ipv6]
54
+
55
+ DEFAULT_VERIFIES = 60
56
+ BASE_PORT = 3000
57
+ @@port = BASE_PORT
58
+
59
+ def self.configdb(config)
60
+ config[:configs].collect{|c|"#{c[:host]}:#{c[:port]}"}.join(' ')
61
+ end
62
+
63
+ def self.cluster(opts = DEFAULT_SHARDED_SIMPLE)
64
+ raise "missing required option" if [:host, :dbpath].any?{|k| !opts[k]}
65
+
66
+ config = opts.reject {|k,v| CLUSTER_OPT_KEYS.include?(k)}
67
+
68
+ kinds = CLUSTER_OPT_KEYS.select{|key| opts.has_key?(key)} # order is significant
69
+
70
+ replica_count = 0
71
+
72
+ kinds.each do |kind|
73
+ config[kind] = opts.fetch(kind,1).times.collect do |i| #default to 1 of whatever
74
+ if kind == :shards && opts[:replicas]
75
+ self.cluster(opts.reject{|k,v| SHARDING_OPT_KEYS.include?(k)}.merge(:dbpath => path))
76
+ else
77
+ node = case kind
78
+ when :replicas
79
+ make_replica(opts, replica_count)
80
+ when :arbiters
81
+ make_replica(opts, replica_count)
82
+ when :configs
83
+ make_config(opts)
84
+ when :routers
85
+ make_router(config, opts)
86
+ when :shards
87
+ make_standalone_shard(kind, opts)
88
+ else
89
+ make_mongod(kind, opts)
90
+ end
91
+
92
+ replica_count += 1 if [:replicas, :arbiters].member?(kind)
93
+ node
94
+ end
95
+ end
96
+ end
97
+ config
98
+ end
99
+
100
+ def self.make_mongo(kind, opts)
101
+ dbpath = opts[:dbpath]
102
+ port = self.get_available_port
103
+ path = "#{dbpath}/#{kind}-#{port}"
104
+ logpath = "#{path}/#{kind}.log"
105
+
106
+ { :host => opts[:host],
107
+ :port => port,
108
+ :logpath => logpath,
109
+ :logappend => true }
110
+ end
111
+
112
+ def self.make_mongod(kind, opts)
113
+ params = make_mongo('mongods', opts)
114
+
115
+ mongod = ENV['MONGOD'] || 'mongod'
116
+ path = File.dirname(params[:logpath])
117
+
118
+ noprealloc = opts[:noprealloc] || true
119
+ smallfiles = opts[:smallfiles] || true
120
+ quiet = opts[:quiet] || true
121
+ fast_sync = opts[:fastsync] || false
122
+ auth = opts[:auth] || true
123
+ ipv6 = opts[:ipv6].nil? ? true : opts[:ipv6]
124
+ setParameter = opts[:setParameter] || 'enableTestCommands=1'
125
+
126
+ params.merge(:command => mongod,
127
+ :dbpath => path,
128
+ :smallfiles => smallfiles,
129
+ :noprealloc => noprealloc,
130
+ :quiet => quiet,
131
+ :fastsync => fast_sync,
132
+ :auth => auth,
133
+ :ipv6 => ipv6,
134
+ :setParameter => setParameter)
135
+ end
136
+
137
+ def self.key_file(opts)
138
+ keyFile = opts[:key_file] || '/test/fixtures/auth/keyfile'
139
+ keyFile = Dir.pwd << keyFile
140
+ system "chmod 600 #{keyFile}"
141
+ keyFile
142
+ end
143
+
144
+ # A regular mongod minus --auth and plus --keyFile.
145
+ def self.make_standalone_shard(kind, opts)
146
+ params = make_mongod(kind, opts)
147
+ params.delete(:auth)
148
+ params.merge(:keyFile => key_file(opts))
149
+ end
150
+
151
+ def self.make_replica(opts, id)
152
+ params = make_mongod('replicas', opts)
153
+
154
+ replSet = opts[:replSet] || 'ruby-driver-test'
155
+ oplogSize = opts[:oplog_size] || 5
156
+
157
+ params.merge(:_id => id,
158
+ :replSet => replSet,
159
+ :oplogSize => oplogSize,
160
+ :keyFile => key_file(opts))
161
+ end
162
+
163
+ def self.make_config(opts)
164
+ params = make_mongod('configs', opts)
165
+ params.delete(:auth)
166
+ params.merge(:configsvr => true,
167
+ :keyFile => key_file(opts))
168
+ end
169
+
170
+ def self.make_router(config, opts)
171
+ params = make_mongo('routers', opts)
172
+ mongos = ENV['MONGOS'] || 'mongos'
173
+
174
+ params.merge(
175
+ :command => mongos,
176
+ :configdb => self.configdb(config),
177
+ :keyFile => key_file(opts)
178
+ )
179
+ end
180
+
181
+ def self.port_available?(port)
182
+ ret = false
183
+ socket = Socket.new(Socket::Constants::AF_INET, Socket::Constants::SOCK_STREAM, 0)
184
+ socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1)
185
+ sockaddr = Socket.sockaddr_in(port, '0.0.0.0')
186
+ begin
187
+ socket.bind(sockaddr)
188
+ ret = true
189
+ rescue Exception
190
+ end
191
+ socket.close
192
+ ret
193
+ end
194
+
195
+ def self.get_available_port
196
+ while true
197
+ port = @@port
198
+ @@port += 1
199
+ break if port_available?(port)
200
+ end
201
+ port
202
+ end
203
+
204
+ class SysProc
205
+ attr_reader :pid, :cmd
206
+
207
+ def initialize(cmd = nil)
208
+ @pid = nil
209
+ @cmd = cmd
210
+ end
211
+
212
+ def clear_zombie
213
+ if @pid
214
+ begin
215
+ pid = Process.waitpid(@pid, Process::WNOHANG)
216
+ rescue Errno::ECHILD
217
+ # JVM might have already reaped the exit status
218
+ end
219
+ @pid = nil if pid && pid > 0
220
+ end
221
+ end
222
+
223
+ def start(verifies = 0)
224
+ clear_zombie
225
+ return @pid if running?
226
+ begin
227
+ # redirection not supported in jruby
228
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
229
+ @pid = Process.spawn(*@cmd)
230
+ else
231
+ cmd_and_opts = [@cmd, {:out => '/dev/null'}].flatten
232
+ @pid = Process.spawn(*cmd_and_opts)
233
+ end
234
+ verify(verifies) if verifies > 0
235
+ @pid
236
+ end
237
+ end
238
+
239
+ def stop
240
+ kill
241
+ wait
242
+ end
243
+
244
+ def kill(signal_no = 2)
245
+ begin
246
+ @pid && Process.kill(signal_no, @pid) && true
247
+ rescue Errno::ESRCH
248
+ false
249
+ end
250
+ # cleanup lock if unclean shutdown
251
+ begin
252
+ File.delete(File.join(@config[:dbpath], 'mongod.lock')) if @config[:dbpath]
253
+ rescue Errno::ENOENT
254
+ end
255
+ end
256
+
257
+ def wait
258
+ begin
259
+ Process.waitpid(@pid) if @pid
260
+ rescue Errno::ECHILD
261
+ # JVM might have already reaped the exit status
262
+ end
263
+ @pid = nil
264
+ end
265
+
266
+ def running?
267
+ begin
268
+ @pid && Process.kill(0, @pid) && true
269
+ rescue Errno::ESRCH
270
+ false
271
+ end
272
+ end
273
+
274
+ def verify(verifies = DEFAULT_VERIFIES)
275
+ verifies.times do |i|
276
+ return @pid if running?
277
+ sleep 1
278
+ end
279
+ nil
280
+ end
281
+ end
282
+
283
+ class Server < SysProc
284
+ attr_reader :host, :port
285
+
286
+ def initialize(cmd = nil, host = nil, port = nil)
287
+ super(cmd)
288
+ @host = host
289
+ @port = port
290
+ end
291
+
292
+ def host_port
293
+ [@host, @port].join(':')
294
+ end
295
+
296
+ def host_port_a # for old format
297
+ [@host, @port]
298
+ end
299
+ end
300
+
301
+ class DbServer < Server
302
+ attr_accessor :config
303
+
304
+ def initialize(config)
305
+ @config = config
306
+ cmd = init_config!
307
+ super(cmd, @config[:host], @config[:port])
308
+ end
309
+
310
+ def init_config!
311
+ dbpath = @config[:dbpath]
312
+ [dbpath, File.dirname(@config[:logpath])].compact.each{|dir| FileUtils.mkdir_p(dir) unless File.directory?(dir) }
313
+ command = @config[:command] || 'mongod'
314
+ params = @config.reject{|k,v| IGNORE_KEYS.include?(k)}
315
+ arguments = params.sort{|a, b| a[0].to_s <=> b[0].to_s}.collect do |arg, value| # sort block is needed for 1.8.7 which lacks Symbol#<=>
316
+ argument = '--' + arg.to_s
317
+ if FLAGS.member?(arg) && value == true
318
+ [argument]
319
+ elsif !FLAGS.member?(arg)
320
+ [argument, value.to_s]
321
+ end
322
+ end
323
+ cmd = [command, arguments].flatten.compact
324
+ end
325
+
326
+ def start(verifies = DEFAULT_VERIFIES)
327
+ super(verifies)
328
+ verify(verifies)
329
+ end
330
+
331
+ def verify(verifies = 600)
332
+ verifies.times do |i|
333
+ #puts "DbServer.verify via connection probe - port:#{@port.inspect} iteration:#{i} @pid:#{@pid.inspect} kill:#{Process.kill(0, @pid).inspect} running?:#{running?.inspect} cmd:#{cmd.inspect}"
334
+ begin
335
+ raise Mongo::ConnectionFailure unless running?
336
+ Mongo::MongoClient.new(@host, @port).close
337
+ #puts "DbServer.verified via connection - port: #{@port} iteration: #{i}"
338
+ return @pid
339
+ rescue Mongo::ConnectionFailure
340
+ sleep 1
341
+ end
342
+ end
343
+ if @config.delete(:setParameter)
344
+ @cmd = init_config!
345
+ start(verifies)
346
+ else
347
+ system "ps -fp #{@pid}; cat #{@config[:logpath]}"
348
+ raise Mongo::ConnectionFailure, "DbServer.start verify via connection probe failed - port:#{@port.inspect} @pid:#{@pid.inspect} kill:#{Process.kill(0, @pid).inspect} running?:#{running?.inspect} cmd:#{cmd.inspect}"
349
+ end
350
+ end
351
+
352
+ end
353
+
354
+ class ClusterManager
355
+ attr_reader :config
356
+ def initialize(config)
357
+ @config = config
358
+ @servers = {}
359
+ Mongo::Config::CLUSTER_OPT_KEYS.each do |key|
360
+ @servers[key] = @config[key].collect{|conf| p conf; DbServer.new(conf)} if @config[key]
361
+ end
362
+ end
363
+
364
+ def servers(key = nil)
365
+ @servers.collect{|k,v| (!key || key == k) ? v : nil}.flatten.compact
366
+ end
367
+
368
+ def ensure_authenticated(client)
369
+ begin
370
+ client[TEST_DB].authenticate(TEST_USER, TEST_USER_PWD)
371
+ rescue Mongo::MongoArgumentError => ex
372
+ # client is already authenticated
373
+ raise ex unless ex.message =~ /already authenticated/
374
+ rescue Mongo::AuthenticationError => ex
375
+ # 1) The creds are wrong
376
+ # 2) Or the user doesn't exist
377
+ roles = [ 'dbAdminAnyDatabase',
378
+ 'userAdminAnyDatabase',
379
+ 'readWriteAnyDatabase',
380
+ 'clusterAdmin' ]
381
+ begin
382
+ # Try to add the user for case (2)
383
+ client[TEST_DB].add_user(TEST_USER, TEST_USER_PWD, nil, :roles => roles)
384
+ client[TEST_DB].authenticate(TEST_USER, TEST_USER_PWD)
385
+ rescue Mongo::ConnectionFailure, Mongo::OperationFailure => ex
386
+ # Maybe not master, so try to authenticate
387
+ # 2.2 throws an OperationFailure if add_user fails
388
+ begin
389
+ client[TEST_DB].authenticate(TEST_USER, TEST_USER_PWD)
390
+ rescue => ex
391
+ # Maybe creds are wrong, nothing we can do
392
+ end
393
+ end
394
+ end
395
+ end
396
+
397
+ def command( cmd_servers, db_name, cmd, opts = {} )
398
+ ret = []
399
+ cmd = cmd.class == Array ? cmd : [ cmd ]
400
+ debug 3, "ClusterManager.command cmd:#{cmd.inspect}"
401
+ cmd_servers = cmd_servers.class == Array ? cmd_servers : [cmd_servers]
402
+ cmd_servers.each do |cmd_server|
403
+ debug 3, cmd_server.inspect
404
+ cmd_server = cmd_server.config if cmd_server.is_a?(DbServer)
405
+ client = Mongo::MongoClient.new(cmd_server[:host], cmd_server[:port])
406
+ ensure_authenticated(client)
407
+ cmd.each do |c|
408
+ debug 3, "ClusterManager.command c:#{c.inspect}"
409
+ response = client[db_name].command( c, opts )
410
+ debug 3, "ClusterManager.command response:#{response.inspect}"
411
+ raise Mongo::OperationFailure, "c:#{c.inspect} opts:#{opts.inspect} failed" unless response["ok"] == 1.0 || opts.fetch(:check_response, true) == false
412
+ ret << response
413
+ end
414
+ client.close
415
+ end
416
+ debug 3, "command ret:#{ret.inspect}"
417
+ ret.size == 1 ? ret.first : ret
418
+ end
419
+
420
+ def replica_set?
421
+ !!config[:replicas]
422
+ end
423
+
424
+ def repl_set_get_status
425
+ command( @config[:replicas], 'admin', { :replSetGetStatus => 1 }, {:check_response => false } )
426
+ end
427
+
428
+ def repl_set_get_config
429
+ host, port = primary_name.split(":")
430
+ client = Mongo::MongoClient.new(host, port)
431
+ ensure_authenticated(client)
432
+ client['local']['system.replset'].find_one
433
+ end
434
+
435
+ def repl_set_config
436
+ members = []
437
+ @config[:replicas].each{|s| members << { :_id => s[:_id], :host => "#{s[:host]}:#{s[:port]}", :tags => { :node => s[:_id].to_s } } }
438
+ @config[:arbiters].each{|s| members << { :_id => s[:_id], :host => "#{s[:host]}:#{s[:port]}", :arbiterOnly => true } }
439
+ {
440
+ :_id => @config[:replicas].first[:replSet],
441
+ :members => members
442
+ }
443
+ end
444
+
445
+ def repl_set_initiate( cfg = nil )
446
+ command( @config[:replicas].first, 'admin', { :replSetInitiate => cfg || repl_set_config } )
447
+ end
448
+
449
+ def repl_set_startup
450
+ states = nil
451
+ healthy = false
452
+
453
+ 80.times do
454
+ # enter the thunderdome...
455
+ states = repl_set_get_status.zip(repl_set_is_master)
456
+ healthy = states.all? do |status, is_master|
457
+ # check replica set status for member list
458
+ next unless status['ok'] == 1.0 && (members = status['members'])
459
+
460
+ # ensure all replica set members are in a valid state
461
+ next unless members.all? { |m| [1,2,7].include?(m['state']) }
462
+
463
+ # check for primary replica set member
464
+ next unless (primary = members.find { |m| m['state'] == 1 })
465
+
466
+ # check replica set member optimes
467
+ primary_optime = (primary['optime']['ts'] ? primary['optime']['ts'] : primary['optime']).seconds
468
+ next unless primary_optime && members.all? do |m|
469
+ m_optime = (m['optime']['ts'] ? m['optime']['ts'] : m['optime']).seconds
470
+ m['state'] == 7 || primary_optime - m_optime < 5
471
+ end
472
+
473
+ # check replica set state
474
+ case status['myState']
475
+ when 1
476
+ is_master['ismaster'] == true &&
477
+ is_master['secondary'] == false
478
+ when 2
479
+ is_master['ismaster'] == false &&
480
+ is_master['secondary'] == true
481
+ when 7
482
+ is_master['ismaster'] == false &&
483
+ is_master['secondary'] == false
484
+ end
485
+ end
486
+
487
+ return healthy if healthy
488
+ sleep(1)
489
+ end
490
+
491
+ raise Mongo::OperationFailure,
492
+ "replSet startup failed - status: #{states.inspect}"
493
+ end
494
+
495
+ def repl_set_seeds
496
+ @config[:replicas].collect{|node| "#{node[:host]}:#{node[:port]}"}
497
+ end
498
+
499
+ def repl_set_seeds_old
500
+ @config[:replicas].collect{|node| [node[:host], node[:port]]}
501
+ end
502
+
503
+ def repl_set_seeds_uri
504
+ repl_set_seeds.join(',')
505
+ end
506
+
507
+ def members_uri
508
+ members = @config[:replicas] || @config[:routers]
509
+ members.collect{|node| "#{node[:host]}:#{node[:port]}"}.join(',')
510
+ end
511
+
512
+ def repl_set_name
513
+ @config[:replicas].first[:replSet]
514
+ end
515
+
516
+ def member_names_by_state(state)
517
+ states = Array(state)
518
+ # Any status with a REMOVED node won't have the full cluster state
519
+ status = repl_set_get_status.find {|status| status['members'].find {|m| m['state'] == 'REMOVED'}.nil?}
520
+ status['members'].find_all{|member| states.index(member['state']) }.collect{|member| member['name']}
521
+ end
522
+
523
+ def primary_name
524
+ member_names_by_state(1).first
525
+ end
526
+
527
+ def secondary_names
528
+ member_names_by_state(2)
529
+ end
530
+
531
+ def replica_names
532
+ member_names_by_state([1,2])
533
+ end
534
+
535
+ def arbiter_names
536
+ member_names_by_state(7)
537
+ end
538
+
539
+ def members_by_name(names)
540
+ names.collect do |name|
541
+ member_by_name(name)
542
+ end.compact
543
+ end
544
+
545
+ def member_by_name(name)
546
+ servers.find{|server| server.host_port == name}
547
+ end
548
+
549
+ def primary
550
+ members_by_name([primary_name]).first
551
+ end
552
+
553
+ def secondaries
554
+ members_by_name(secondary_names)
555
+ end
556
+
557
+ def stop_primary
558
+ primary.stop
559
+ end
560
+
561
+ def stop_secondary
562
+ secondaries[rand(secondaries.length)].stop
563
+ end
564
+
565
+ def replicas
566
+ members_by_name(replica_names)
567
+ end
568
+
569
+ def arbiters
570
+ members_by_name(arbiter_names)
571
+ end
572
+
573
+ def config_names_by_kind(kind)
574
+ @config[kind].collect{|conf| "#{conf[:host]}:#{conf[:port]}"}
575
+ end
576
+
577
+ def shards
578
+ members_by_name(config_names_by_kind(:shards))
579
+ end
580
+
581
+ def repl_set_reconfig(new_config)
582
+ new_config['version'] = repl_set_get_config['version'] + 1
583
+ command( primary, 'admin', { :replSetReconfig => new_config } )
584
+ repl_set_startup
585
+ end
586
+
587
+ def repl_set_remove_node(state = [1,2])
588
+ names = member_names_by_state(state)
589
+ name = names[rand(names.length)]
590
+
591
+ @config[:replicas].delete_if{|node| "#{node[:host]}:#{node[:port]}" == name}
592
+ repl_set_reconfig(repl_set_config)
593
+ end
594
+
595
+ def repl_set_add_node
596
+ end
597
+
598
+ def configs
599
+ members_by_name(config_names_by_kind(:configs))
600
+ end
601
+
602
+ def routers
603
+ members_by_name(config_names_by_kind(:routers))
604
+ end
605
+
606
+ def mongos_seeds
607
+ config_names_by_kind(:routers)
608
+ end
609
+
610
+ def ismaster(servers)
611
+ command( servers, 'admin', { :ismaster => 1 } )
612
+ end
613
+
614
+ def sharded_cluster_is_master
615
+ ismaster(@config[:routers])
616
+ end
617
+
618
+ def repl_set_is_master
619
+ ismaster(@config[:replicas])
620
+ end
621
+
622
+ def addshards(shards = @config[:shards])
623
+ begin
624
+ command( @config[:routers].first, 'admin', Array(shards).collect{|s| { :addshard => "#{s[:host]}:#{s[:port]}" } } )
625
+ rescue Mongo::OperationFailure => ex
626
+ # Because we cannot run the listshards command under the localhost
627
+ # exception in > 2.7.1, we run the risk of attempting to add the same shard twice.
628
+ # Our tests may add a local db to a shard, if the cluster is still up,
629
+ # then we can ignore this.
630
+ raise ex unless ex.message =~ /host already used/
631
+ end
632
+ end
633
+
634
+ def listshards
635
+ command( @config[:routers].first, 'admin', { :listshards => 1 } )
636
+ end
637
+
638
+ def enablesharding( dbname )
639
+ command( @config[:routers].first, 'admin', { :enablesharding => dbname } )
640
+ end
641
+
642
+ def shardcollection( namespace, key, unique = false )
643
+ command( @config[:routers].first, 'admin', { :shardcollection => namespace, :key => key, :unique => unique } )
644
+ end
645
+
646
+ def mongos_discover # can also do @config[:routers] find but only want mongos for connections
647
+ (@config[:configs]).collect do |cmd_server|
648
+ client = Mongo::MongoClient.new(cmd_server[:host], cmd_server[:port])
649
+ result = client['config']['mongos'].find.to_a
650
+ client.close
651
+ result
652
+ end
653
+ end
654
+
655
+ def start
656
+ # Must start configs before mongos -- hash order not guaranteed on 1.8.X
657
+ servers(:configs).each{|server| server.start}
658
+ servers.each{|server| server.start}
659
+ # TODO - sharded replica sets - pending
660
+ if @config[:replicas]
661
+ repl_set_initiate if repl_set_get_status.first['code'] == 94 ||
662
+ (repl_set_get_status.first['startupStatus'] && repl_set_get_status.first['startupStatus'] == 3)
663
+ repl_set_startup
664
+ end
665
+ if @config[:routers]
666
+ addshards if listshards['shards'].size == 0
667
+ end
668
+ self
669
+ end
670
+ alias :restart :start
671
+
672
+ def delete_users
673
+ cmd_servers = replica_set? ? [ primary ] : routers
674
+
675
+ cmd_servers.each do |cmd_server|
676
+ next unless cmd_server
677
+ begin
678
+ client = Mongo::MongoClient.new(cmd_server.config[:host],
679
+ cmd_server.config[:port])
680
+ ensure_authenticated(client)
681
+ db = client[TEST_DB]
682
+
683
+ if client.server_version < '2.5'
684
+ db['system.users'].remove
685
+ else
686
+ db.command(:dropAllUsersFromDatabase => 1)
687
+ end
688
+ break
689
+ rescue Mongo::ConnectionFailure
690
+ end
691
+ end
692
+ end
693
+
694
+ def stop
695
+ start
696
+ delete_users
697
+ servers.each{|server| server.stop}
698
+ self
699
+ end
700
+
701
+ def clobber
702
+ FileUtils.rm_rf @config[:dbpath]
703
+ self
704
+ end
705
+ end
706
+
707
+ end
708
+ end