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,56 @@
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
+ # NOTE: on ruby <1.9 you need to run individual tests with 'bundle exec'
16
+
17
+ unless RUBY_VERSION < '1.9' || ENV.key?('JENKINS_CI')
18
+ require 'simplecov'
19
+ require 'coveralls'
20
+
21
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
22
+ SimpleCov::Formatter::HTMLFormatter,
23
+ Coveralls::SimpleCov::Formatter
24
+ ]
25
+
26
+ SimpleCov.start do
27
+ add_group 'Driver', 'lib/mongo'
28
+ add_group 'BSON', 'lib/bson'
29
+
30
+ add_filter 'tasks'
31
+ add_filter 'test'
32
+ add_filter 'bin'
33
+ end
34
+ end
35
+
36
+ # required for at_exit, at_start hooks
37
+ require 'test-unit'
38
+
39
+ require 'test/unit'
40
+ require 'shoulda'
41
+ require 'mocha/setup'
42
+
43
+ # cluster manager
44
+ require 'tools/mongo_config'
45
+
46
+ # test helpers
47
+ require 'helpers/general'
48
+ require 'helpers/test_unit'
49
+
50
+ # optional development and debug utilities
51
+ begin
52
+ require 'pry-rescue'
53
+ require 'pry-nav'
54
+ rescue LoadError
55
+ # failed to load, skipping pry
56
+ end
@@ -0,0 +1,120 @@
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
+ require 'test_helper'
16
+
17
+ class ThreadingTest < Test::Unit::TestCase
18
+
19
+ include Mongo
20
+
21
+ def setup
22
+ @client = standard_connection(:pool_size => 10, :pool_timeout => 30)
23
+ @db = @client.db(TEST_DB)
24
+ @coll = @db.collection('thread-test-collection')
25
+ @coll.drop
26
+
27
+ collections = ['duplicate', 'unique']
28
+
29
+ collections.each do |coll_name|
30
+ coll = @db.collection(coll_name)
31
+ coll.drop
32
+ coll.insert("test" => "insert")
33
+ coll.insert("test" => "update")
34
+ instance_variable_set("@#{coll_name}", coll)
35
+ end
36
+
37
+ @unique.create_index("test", :unique => true)
38
+ end
39
+
40
+ def test_safe_update
41
+ threads = []
42
+ 300.times do |i|
43
+ threads << Thread.new do
44
+ if i % 2 == 0
45
+ assert_raise Mongo::OperationFailure do
46
+ @unique.update({"test" => "insert"}, {"$set" => {"test" => "update"}})
47
+ end
48
+ else
49
+ @duplicate.update({"test" => "insert"}, {"$set" => {"test" => "update"}})
50
+ @duplicate.update({"test" => "update"}, {"$set" => {"test" => "insert"}})
51
+ end
52
+ end
53
+ end
54
+
55
+ threads.each {|thread| thread.join}
56
+ end
57
+
58
+ def test_safe_insert
59
+ threads = []
60
+ 300.times do |i|
61
+ threads << Thread.new do
62
+ if i % 2 == 0
63
+ assert_raise Mongo::OperationFailure do
64
+ @unique.insert({"test" => "insert"})
65
+ end
66
+ else
67
+ @duplicate.insert({"test" => "insert"})
68
+ end
69
+ end
70
+ end
71
+
72
+ threads.each {|thread| thread.join}
73
+ end
74
+
75
+ def test_concurrent_find
76
+ n_threads = 50
77
+
78
+ 1000.times do |i|
79
+ @coll.insert({ "x" => "a" })
80
+ end
81
+
82
+ threads = []
83
+ n_threads.times do |i|
84
+ threads << Thread.new do
85
+ sum = 0
86
+ @coll.find.to_a.size
87
+ end
88
+ end
89
+
90
+ thread_values = threads.map(&:value)
91
+ assert thread_values.all?{|v| v == 1000}
92
+ assert_equal thread_values.size, n_threads
93
+ end
94
+
95
+ def test_threading
96
+ @coll.drop
97
+ @coll = @db.collection('thread-test-collection')
98
+
99
+ docs = []
100
+ 1000.times {|i| docs << {:x => i}}
101
+ @coll.insert(docs)
102
+
103
+ threads = []
104
+
105
+ 10.times do |i|
106
+ threads[i] = Thread.new do
107
+ sum = 0
108
+ @coll.find().each do |document|
109
+ sum += document["x"]
110
+ end
111
+ assert_equal 499500, sum
112
+ end
113
+ end
114
+
115
+ 10.times do |i|
116
+ threads[i].join
117
+ end
118
+ end
119
+
120
+ end
@@ -0,0 +1,608 @@
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
+ else
87
+ make_mongod(kind, opts)
88
+ end
89
+
90
+ replica_count += 1 if [:replicas, :arbiters].member?(kind)
91
+ node
92
+ end
93
+ end
94
+ end
95
+ config
96
+ end
97
+
98
+ def self.make_mongo(kind, opts)
99
+ dbpath = opts[:dbpath]
100
+ port = self.get_available_port
101
+ path = "#{dbpath}/#{kind}-#{port}"
102
+ logpath = "#{path}/#{kind}.log"
103
+
104
+ { :host => opts[:host],
105
+ :port => port,
106
+ :logpath => logpath,
107
+ :logappend => true }
108
+ end
109
+
110
+ def self.make_mongod(kind, opts)
111
+ params = make_mongo('mongods', opts)
112
+
113
+ mongod = ENV['MONGOD'] || 'mongod'
114
+ path = File.dirname(params[:logpath])
115
+
116
+ noprealloc = opts[:noprealloc] || true
117
+ smallfiles = opts[:smallfiles] || true
118
+ quiet = opts[:quiet] || true
119
+ fast_sync = opts[:fastsync] || false
120
+ auth = opts[:auth] || true
121
+ ipv6 = opts[:ipv6].nil? ? true : opts[:ipv6]
122
+
123
+ params.merge(:command => mongod,
124
+ :dbpath => path,
125
+ :smallfiles => smallfiles,
126
+ :noprealloc => noprealloc,
127
+ :quiet => quiet,
128
+ :fastsync => fast_sync,
129
+ :auth => auth,
130
+ :ipv6 => ipv6)
131
+ end
132
+
133
+ def self.make_replica(opts, id)
134
+ params = make_mongod('replicas', opts)
135
+
136
+ replSet = opts[:replSet] || 'ruby-driver-test'
137
+ oplogSize = opts[:oplog_size] || 5
138
+ keyFile = opts[:key_file] || '/test/fixtures/auth/keyfile'
139
+
140
+ keyFile = Dir.pwd << keyFile
141
+ system "chmod 600 #{keyFile}"
142
+
143
+ params.merge(:_id => id,
144
+ :replSet => replSet,
145
+ :oplogSize => oplogSize,
146
+ :keyFile => keyFile)
147
+ end
148
+
149
+ def self.make_config(opts)
150
+ params = make_mongod('configs', opts)
151
+ params.merge(:configsvr => nil)
152
+ end
153
+
154
+ def self.make_router(config, opts)
155
+ params = make_mongo('routers', opts)
156
+ mongos = ENV['MONGOS'] || 'mongos'
157
+
158
+ params.merge(
159
+ :command => mongos,
160
+ :configdb => self.configdb(config)
161
+ )
162
+ end
163
+
164
+ def self.port_available?(port)
165
+ ret = false
166
+ socket = Socket.new(Socket::Constants::AF_INET, Socket::Constants::SOCK_STREAM, 0)
167
+ socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1)
168
+ sockaddr = Socket.sockaddr_in(port, '0.0.0.0')
169
+ begin
170
+ socket.bind(sockaddr)
171
+ ret = true
172
+ rescue Exception
173
+ end
174
+ socket.close
175
+ ret
176
+ end
177
+
178
+ def self.get_available_port
179
+ while true
180
+ port = @@port
181
+ @@port += 1
182
+ break if port_available?(port)
183
+ end
184
+ port
185
+ end
186
+
187
+ class SysProc
188
+ attr_reader :pid, :cmd
189
+
190
+ def initialize(cmd = nil)
191
+ @pid = nil
192
+ @cmd = cmd
193
+ end
194
+
195
+ def clear_zombie
196
+ if @pid
197
+ begin
198
+ pid = Process.waitpid(@pid, Process::WNOHANG)
199
+ rescue Errno::ECHILD
200
+ # JVM might have already reaped the exit status
201
+ end
202
+ @pid = nil if pid && pid > 0
203
+ end
204
+ end
205
+
206
+ def start(verifies = 0)
207
+ clear_zombie
208
+ return @pid if running?
209
+ begin
210
+ # redirection not supported in jruby
211
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
212
+ @pid = Process.spawn(*@cmd)
213
+ else
214
+ cmd_and_opts = [@cmd, {:out => '/dev/null'}].flatten
215
+ @pid = Process.spawn(*cmd_and_opts)
216
+ end
217
+ verify(verifies) if verifies > 0
218
+ @pid
219
+ end
220
+ end
221
+
222
+ def stop
223
+ kill
224
+ wait
225
+ end
226
+
227
+ def kill(signal_no = 2)
228
+ begin
229
+ @pid && Process.kill(signal_no, @pid) && true
230
+ rescue Errno::ESRCH
231
+ false
232
+ end
233
+ # cleanup lock if unclean shutdown
234
+ begin
235
+ File.delete(File.join(@config[:dbpath], 'mongod.lock')) if @config[:dbpath]
236
+ rescue Errno::ENOENT
237
+ end
238
+ end
239
+
240
+ def wait
241
+ begin
242
+ Process.waitpid(@pid) if @pid
243
+ rescue Errno::ECHILD
244
+ # JVM might have already reaped the exit status
245
+ end
246
+ @pid = nil
247
+ end
248
+
249
+ def running?
250
+ begin
251
+ @pid && Process.kill(0, @pid) && true
252
+ rescue Errno::ESRCH
253
+ false
254
+ end
255
+ end
256
+
257
+ def verify(verifies = DEFAULT_VERIFIES)
258
+ verifies.times do |i|
259
+ return @pid if running?
260
+ sleep 1
261
+ end
262
+ nil
263
+ end
264
+ end
265
+
266
+ class Server < SysProc
267
+ attr_reader :host, :port
268
+
269
+ def initialize(cmd = nil, host = nil, port = nil)
270
+ super(cmd)
271
+ @host = host
272
+ @port = port
273
+ end
274
+
275
+ def host_port
276
+ [@host, @port].join(':')
277
+ end
278
+
279
+ def host_port_a # for old format
280
+ [@host, @port]
281
+ end
282
+ end
283
+
284
+ class DbServer < Server
285
+ attr_accessor :config
286
+
287
+ def initialize(config)
288
+ @config = config
289
+ dbpath = @config[:dbpath]
290
+ [dbpath, File.dirname(@config[:logpath])].compact.each{|dir| FileUtils.mkdir_p(dir) unless File.directory?(dir) }
291
+ command = @config[:command] || 'mongod'
292
+ params = @config.reject{|k,v| IGNORE_KEYS.include?(k)}
293
+ 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#<=>
294
+ argument = '--' + arg.to_s
295
+ if FLAGS.member?(arg) && value == true
296
+ [argument]
297
+ elsif !FLAGS.member?(arg)
298
+ [argument, value.to_s]
299
+ end
300
+ end
301
+ cmd = [command, arguments].flatten.compact
302
+ super(cmd, @config[:host], @config[:port])
303
+ end
304
+
305
+ def start(verifies = DEFAULT_VERIFIES)
306
+ super(verifies)
307
+ verify(verifies)
308
+ end
309
+
310
+ def verify(verifies = 600)
311
+ verifies.times do |i|
312
+ #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}"
313
+ begin
314
+ raise Mongo::ConnectionFailure unless running?
315
+ Mongo::MongoClient.new(@host, @port).close
316
+ #puts "DbServer.verified via connection - port: #{@port} iteration: #{i}"
317
+ return @pid
318
+ rescue Mongo::ConnectionFailure
319
+ sleep 1
320
+ end
321
+ end
322
+ system "ps -fp #{@pid}; cat #{@config[:logpath]}"
323
+ 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}"
324
+ end
325
+
326
+ end
327
+
328
+ class ClusterManager
329
+ attr_reader :config
330
+ def initialize(config)
331
+ @config = config
332
+ @servers = {}
333
+ Mongo::Config::CLUSTER_OPT_KEYS.each do |key|
334
+ @servers[key] = @config[key].collect{|conf| DbServer.new(conf)} if @config[key]
335
+ end
336
+ end
337
+
338
+ def servers(key = nil)
339
+ @servers.collect{|k,v| (!key || key == k) ? v : nil}.flatten.compact
340
+ end
341
+
342
+ def command( cmd_servers, db_name, cmd, opts = {} )
343
+ ret = []
344
+ cmd = cmd.class == Array ? cmd : [ cmd ]
345
+ debug 3, "ClusterManager.command cmd:#{cmd.inspect}"
346
+ cmd_servers = cmd_servers.class == Array ? cmd_servers : [cmd_servers]
347
+ cmd_servers.each do |cmd_server|
348
+ debug 3, cmd_server.inspect
349
+ cmd_server = cmd_server.config if cmd_server.is_a?(DbServer)
350
+ client = Mongo::MongoClient.new(cmd_server[:host], cmd_server[:port])
351
+ cmd.each do |c|
352
+ debug 3, "ClusterManager.command c:#{c.inspect}"
353
+ response = client[db_name].command( c, opts )
354
+ debug 3, "ClusterManager.command response:#{response.inspect}"
355
+ raise Mongo::OperationFailure, "c:#{c.inspect} opts:#{opts.inspect} failed" unless response["ok"] == 1.0 || opts.fetch(:check_response, true) == false
356
+ ret << response
357
+ end
358
+ client.close
359
+ end
360
+ debug 3, "command ret:#{ret.inspect}"
361
+ ret.size == 1 ? ret.first : ret
362
+ end
363
+
364
+ def repl_set_get_status
365
+ command( @config[:replicas], 'admin', { :replSetGetStatus => 1 }, {:check_response => false } )
366
+ end
367
+
368
+ def repl_set_get_config
369
+ host, port = primary_name.split(":")
370
+ client = Mongo::MongoClient.new(host, port)
371
+ client['local']['system.replset'].find_one
372
+ end
373
+
374
+ def repl_set_config
375
+ members = []
376
+ @config[:replicas].each{|s| members << { :_id => s[:_id], :host => "#{s[:host]}:#{s[:port]}", :tags => { :node => s[:_id].to_s } } }
377
+ @config[:arbiters].each{|s| members << { :_id => s[:_id], :host => "#{s[:host]}:#{s[:port]}", :arbiterOnly => true } }
378
+ {
379
+ :_id => @config[:replicas].first[:replSet],
380
+ :members => members
381
+ }
382
+ end
383
+
384
+ def repl_set_initiate( cfg = nil )
385
+ command( @config[:replicas].first, 'admin', { :replSetInitiate => cfg || repl_set_config } )
386
+ end
387
+
388
+ def repl_set_startup
389
+ states = nil
390
+ healthy = false
391
+
392
+ 60.times do
393
+ # enter the thunderdome...
394
+ states = repl_set_get_status.zip(repl_set_is_master)
395
+ healthy = states.all? do |status, is_master|
396
+ # check replica set status for member list
397
+ next unless status['ok'] == 1.0 && (members = status['members'])
398
+
399
+ # ensure all replica set members are in a valid state
400
+ next unless members.all? { |m| [1,2,7].include?(m['state']) }
401
+
402
+ # check for primary replica set member
403
+ next unless (primary = members.find { |m| m['state'] == 1 })
404
+
405
+ # check replica set member optimes
406
+ primary_optime = primary['optime'].seconds
407
+ next unless primary_optime && members.all? do |m|
408
+ m['state'] == 7 || primary_optime - m['optime'].seconds < 5
409
+ end
410
+
411
+ # check replica set state
412
+ case status['myState']
413
+ when 1
414
+ is_master['ismaster'] == true &&
415
+ is_master['secondary'] == false
416
+ when 2
417
+ is_master['ismaster'] == false &&
418
+ is_master['secondary'] == true
419
+ when 7
420
+ is_master['ismaster'] == false &&
421
+ is_master['secondary'] == false
422
+ end
423
+ end
424
+
425
+ return healthy if healthy
426
+ sleep(1)
427
+ end
428
+
429
+ raise Mongo::OperationFailure,
430
+ "replSet startup failed - status: #{states.inspect}"
431
+ end
432
+
433
+ def repl_set_seeds
434
+ @config[:replicas].collect{|node| "#{node[:host]}:#{node[:port]}"}
435
+ end
436
+
437
+ def repl_set_seeds_old
438
+ @config[:replicas].collect{|node| [node[:host], node[:port]]}
439
+ end
440
+
441
+ def repl_set_seeds_uri
442
+ repl_set_seeds.join(',')
443
+ end
444
+
445
+ def repl_set_name
446
+ @config[:replicas].first[:replSet]
447
+ end
448
+
449
+ def member_names_by_state(state)
450
+ states = Array(state)
451
+ # Any status with a REMOVED node won't have the full cluster state
452
+ status = repl_set_get_status.find {|status| status['members'].find {|m| m['state'] == 'REMOVED'}.nil?}
453
+ status['members'].find_all{|member| states.index(member['state']) }.collect{|member| member['name']}
454
+ end
455
+
456
+ def primary_name
457
+ member_names_by_state(1).first
458
+ end
459
+
460
+ def secondary_names
461
+ member_names_by_state(2)
462
+ end
463
+
464
+ def replica_names
465
+ member_names_by_state([1,2])
466
+ end
467
+
468
+ def arbiter_names
469
+ member_names_by_state(7)
470
+ end
471
+
472
+ def members_by_name(names)
473
+ names.collect do |name|
474
+ member_by_name(name)
475
+ end.compact
476
+ end
477
+
478
+ def member_by_name(name)
479
+ servers.find{|server| server.host_port == name}
480
+ end
481
+
482
+ def primary
483
+ members_by_name([primary_name]).first
484
+ end
485
+
486
+ def secondaries
487
+ members_by_name(secondary_names)
488
+ end
489
+
490
+ def stop_primary
491
+ primary.stop
492
+ end
493
+
494
+ def stop_secondary
495
+ secondaries[rand(secondaries.length)].stop
496
+ end
497
+
498
+ def replicas
499
+ members_by_name(replica_names)
500
+ end
501
+
502
+ def arbiters
503
+ members_by_name(arbiter_names)
504
+ end
505
+
506
+ def config_names_by_kind(kind)
507
+ @config[kind].collect{|conf| "#{conf[:host]}:#{conf[:port]}"}
508
+ end
509
+
510
+ def shards
511
+ members_by_name(config_names_by_kind(:shards))
512
+ end
513
+
514
+ def repl_set_reconfig(new_config)
515
+ new_config['version'] = repl_set_get_config['version'] + 1
516
+ command( primary, 'admin', { :replSetReconfig => new_config } )
517
+ repl_set_startup
518
+ end
519
+
520
+ def repl_set_remove_node(state = [1,2])
521
+ names = member_names_by_state(state)
522
+ name = names[rand(names.length)]
523
+
524
+ @config[:replicas].delete_if{|node| "#{node[:host]}:#{node[:port]}" == name}
525
+ repl_set_reconfig(repl_set_config)
526
+ end
527
+
528
+ def repl_set_add_node
529
+ end
530
+
531
+ def configs
532
+ members_by_name(config_names_by_kind(:configs))
533
+ end
534
+
535
+ def routers
536
+ members_by_name(config_names_by_kind(:routers))
537
+ end
538
+
539
+ def mongos_seeds
540
+ config_names_by_kind(:routers)
541
+ end
542
+
543
+ def ismaster(servers)
544
+ command( servers, 'admin', { :ismaster => 1 } )
545
+ end
546
+
547
+ def sharded_cluster_is_master
548
+ ismaster(@config[:routers])
549
+ end
550
+
551
+ def repl_set_is_master
552
+ ismaster(@config[:replicas])
553
+ end
554
+
555
+ def addshards(shards = @config[:shards])
556
+ command( @config[:routers].first, 'admin', Array(shards).collect{|s| { :addshard => "#{s[:host]}:#{s[:port]}" } } )
557
+ end
558
+
559
+ def listshards
560
+ command( @config[:routers].first, 'admin', { :listshards => 1 } )
561
+ end
562
+
563
+ def enablesharding( dbname )
564
+ command( @config[:routers].first, 'admin', { :enablesharding => dbname } )
565
+ end
566
+
567
+ def shardcollection( namespace, key, unique = false )
568
+ command( @config[:routers].first, 'admin', { :shardcollection => namespace, :key => key, :unique => unique } )
569
+ end
570
+
571
+ def mongos_discover # can also do @config[:routers] find but only want mongos for connections
572
+ (@config[:configs]).collect do |cmd_server|
573
+ client = Mongo::MongoClient.new(cmd_server[:host], cmd_server[:port])
574
+ result = client['config']['mongos'].find.to_a
575
+ client.close
576
+ result
577
+ end
578
+ end
579
+
580
+ def start
581
+ # Must start configs before mongos -- hash order not guaranteed on 1.8.X
582
+ servers(:configs).each{|server| server.start}
583
+ servers.each{|server| server.start}
584
+ # TODO - sharded replica sets - pending
585
+ if @config[:replicas]
586
+ repl_set_initiate if repl_set_get_status.first['startupStatus'] == 3
587
+ repl_set_startup
588
+ end
589
+ if @config[:routers]
590
+ addshards if listshards['shards'].size == 0
591
+ end
592
+ self
593
+ end
594
+ alias :restart :start
595
+
596
+ def stop
597
+ servers.each{|server| server.stop}
598
+ self
599
+ end
600
+
601
+ def clobber
602
+ FileUtils.rm_rf @config[:dbpath]
603
+ self
604
+ end
605
+ end
606
+
607
+ end
608
+ end