jmongo 1.0.3 → 1.1.0
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.
- data/Gemfile +8 -0
- data/Gemfile.lock +43 -0
- data/Rakefile +72 -0
- data/jmongo.gemspec +84 -6
- data/lib/jmongo.rb +6 -14
- data/lib/jmongo/collection.rb +196 -114
- data/lib/jmongo/connection.rb +39 -13
- data/lib/jmongo/cursor.rb +161 -63
- data/lib/jmongo/db.rb +119 -30
- data/lib/jmongo/exceptions.rb +39 -0
- data/lib/jmongo/mongo-2.6.5.gb1.jar +0 -0
- data/lib/jmongo/mongo/bson.rb +130 -0
- data/lib/jmongo/mongo/collection.rb +185 -0
- data/lib/jmongo/mongo/connection.rb +45 -0
- data/lib/jmongo/mongo/db.rb +31 -0
- data/lib/jmongo/mongo/jmongo.rb +44 -0
- data/lib/jmongo/mongo/mongo.rb +98 -0
- data/lib/jmongo/mongo/ruby_ext.rb +38 -0
- data/lib/jmongo/mongo/utils.rb +136 -0
- data/lib/jmongo/version.rb +1 -1
- data/test-results.txt +98 -0
- data/test/auxillary/1.4_features.rb +166 -0
- data/test/auxillary/authentication_test.rb +68 -0
- data/test/auxillary/autoreconnect_test.rb +41 -0
- data/test/auxillary/fork_test.rb +30 -0
- data/test/auxillary/repl_set_auth_test.rb +58 -0
- data/test/auxillary/slave_connection_test.rb +36 -0
- data/test/auxillary/threaded_authentication_test.rb +101 -0
- data/test/bson/binary_test.rb +15 -0
- data/test/bson/bson_test.rb +657 -0
- data/test/bson/byte_buffer_test.rb +208 -0
- data/test/bson/hash_with_indifferent_access_test.rb +38 -0
- data/test/bson/json_test.rb +17 -0
- data/test/bson/object_id_test.rb +138 -0
- data/test/bson/ordered_hash_test.rb +245 -0
- data/test/bson/test_helper.rb +46 -0
- data/test/bson/timestamp_test.rb +46 -0
- data/test/collection_test.rb +933 -0
- data/test/connection_test.rb +325 -0
- data/test/conversions_test.rb +121 -0
- data/test/cursor_fail_test.rb +75 -0
- data/test/cursor_message_test.rb +43 -0
- data/test/cursor_test.rb +547 -0
- data/test/data/empty_data +0 -0
- data/test/data/sample_data +0 -0
- data/test/data/sample_file.pdf +0 -0
- data/test/data/small_data.txt +1 -0
- data/test/db_api_test.rb +739 -0
- data/test/db_connection_test.rb +15 -0
- data/test/db_test.rb +325 -0
- data/test/grid_file_system_test.rb +260 -0
- data/test/grid_io_test.rb +210 -0
- data/test/grid_test.rb +259 -0
- data/test/load/thin/config.ru +6 -0
- data/test/load/thin/config.yml.template +6 -0
- data/test/load/thin/load.rb +24 -0
- data/test/load/unicorn/config.ru +6 -0
- data/test/load/unicorn/load.rb +23 -0
- data/test/load/unicorn/unicorn.rb.template +29 -0
- data/test/replica_sets/connect_test.rb +111 -0
- data/test/replica_sets/connection_string_test.rb +29 -0
- data/test/replica_sets/count_test.rb +36 -0
- data/test/replica_sets/insert_test.rb +54 -0
- data/test/replica_sets/pooled_insert_test.rb +58 -0
- data/test/replica_sets/query_secondaries.rb +109 -0
- data/test/replica_sets/query_test.rb +52 -0
- data/test/replica_sets/read_preference_test.rb +43 -0
- data/test/replica_sets/refresh_test.rb +123 -0
- data/test/replica_sets/replication_ack_test.rb +71 -0
- data/test/replica_sets/rs_test_helper.rb +27 -0
- data/test/safe_test.rb +68 -0
- data/test/support/hash_with_indifferent_access.rb +186 -0
- data/test/support/keys.rb +45 -0
- data/test/support_test.rb +19 -0
- data/test/test_helper.rb +111 -0
- data/test/threading/threading_with_large_pool_test.rb +90 -0
- data/test/threading_test.rb +88 -0
- data/test/tools/auth_repl_set_manager.rb +14 -0
- data/test/tools/keyfile.txt +1 -0
- data/test/tools/repl_set_manager.rb +377 -0
- data/test/unit/collection_test.rb +128 -0
- data/test/unit/connection_test.rb +85 -0
- data/test/unit/cursor_test.rb +127 -0
- data/test/unit/db_test.rb +96 -0
- data/test/unit/grid_test.rb +51 -0
- data/test/unit/node_test.rb +73 -0
- data/test/unit/pool_manager_test.rb +47 -0
- data/test/unit/pool_test.rb +9 -0
- data/test/unit/read_test.rb +101 -0
- data/test/unit/safe_test.rb +125 -0
- data/test/uri_test.rb +92 -0
- metadata +170 -99
- data/lib/jmongo/ajrb.rb +0 -189
- data/lib/jmongo/jmongo_jext.rb +0 -302
- data/lib/jmongo/mongo-2.6.3.jar +0 -0
- data/lib/jmongo/utils.rb +0 -61
@@ -0,0 +1,45 @@
|
|
1
|
+
class Hash
|
2
|
+
# Return a new hash with all keys converted to strings.
|
3
|
+
def stringify_keys
|
4
|
+
dup.stringify_keys!
|
5
|
+
end
|
6
|
+
|
7
|
+
# Destructively convert all keys to strings.
|
8
|
+
def stringify_keys!
|
9
|
+
keys.each do |key|
|
10
|
+
self[key.to_s] = delete(key)
|
11
|
+
end
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
# Return a new hash with all keys converted to symbols, as long as
|
16
|
+
# they respond to +to_sym+.
|
17
|
+
def symbolize_keys
|
18
|
+
dup.symbolize_keys!
|
19
|
+
end
|
20
|
+
|
21
|
+
# Destructively convert all keys to symbols, as long as they respond
|
22
|
+
# to +to_sym+.
|
23
|
+
def symbolize_keys!
|
24
|
+
keys.each do |key|
|
25
|
+
self[(key.to_sym rescue key) || key] = delete(key)
|
26
|
+
end
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
alias_method :to_options, :symbolize_keys
|
31
|
+
#alias_method :to_options!, :symbolize_keys!
|
32
|
+
|
33
|
+
# Validate all keys in a hash match *valid keys, raising ArgumentError on a mismatch.
|
34
|
+
# Note that keys are NOT treated indifferently, meaning if you use strings for keys but assert symbols
|
35
|
+
# as keys, this will fail.
|
36
|
+
#
|
37
|
+
# ==== Examples
|
38
|
+
# { :name => "Rob", :years => "28" }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key(s): years"
|
39
|
+
# { :name => "Rob", :age => "28" }.assert_valid_keys("name", "age") # => raises "ArgumentError: Unknown key(s): name, age"
|
40
|
+
# { :name => "Rob", :age => "28" }.assert_valid_keys(:name, :age) # => passes, raises nothing
|
41
|
+
def assert_valid_keys(*valid_keys)
|
42
|
+
unknown_keys = keys - [valid_keys].flatten
|
43
|
+
raise(ArgumentError, "Unknown key(s): #{unknown_keys.join(", ")}") unless unknown_keys.empty?
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
__END__
|
2
|
+
require './test/test_helper'
|
3
|
+
|
4
|
+
class SupportTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def test_command_response_succeeds
|
7
|
+
assert Support.ok?('ok' => 1)
|
8
|
+
assert Support.ok?('ok' => 1.0)
|
9
|
+
assert Support.ok?('ok' => true)
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_command_response_fails
|
13
|
+
assert !Support.ok?('ok' => 0)
|
14
|
+
assert !Support.ok?('ok' => 0.0)
|
15
|
+
assert !Support.ok?('ok' => 0.0)
|
16
|
+
assert !Support.ok?('ok' => 'str')
|
17
|
+
assert !Support.ok?('ok' => false)
|
18
|
+
end
|
19
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
require 'rubygems' if RUBY_VERSION < '1.9.0' && ENV['C_EXT']
|
3
|
+
require 'jmongo'
|
4
|
+
#require 'test/unit'
|
5
|
+
require 'awesome_print'
|
6
|
+
|
7
|
+
require 'minitest/autorun'
|
8
|
+
|
9
|
+
def silently
|
10
|
+
warn_level = $VERBOSE
|
11
|
+
$VERBOSE = nil
|
12
|
+
result = yield
|
13
|
+
$VERBOSE = warn_level
|
14
|
+
result
|
15
|
+
end
|
16
|
+
|
17
|
+
def apr(obj, prefix = '=====')
|
18
|
+
puts prefix
|
19
|
+
ap obj
|
20
|
+
puts '====='
|
21
|
+
end
|
22
|
+
|
23
|
+
begin
|
24
|
+
require 'rubygems' if RUBY_VERSION < "1.9.0" && !ENV['C_EXT']
|
25
|
+
silently { require 'shoulda' }
|
26
|
+
silently { require 'mocha' }
|
27
|
+
rescue LoadError
|
28
|
+
puts <<MSG
|
29
|
+
|
30
|
+
This test suite requires shoulda and mocha.
|
31
|
+
You can install them as follows:
|
32
|
+
gem install shoulda
|
33
|
+
gem install mocha
|
34
|
+
|
35
|
+
MSG
|
36
|
+
|
37
|
+
exit
|
38
|
+
end
|
39
|
+
|
40
|
+
require 'bson_ext/cbson' if !(RUBY_PLATFORM =~ /java/) && ENV['C_EXT']
|
41
|
+
|
42
|
+
unless defined? MONGO_TEST_DB
|
43
|
+
MONGO_TEST_DB = 'ruby-test-db'
|
44
|
+
end
|
45
|
+
|
46
|
+
unless defined? TEST_PORT
|
47
|
+
TEST_PORT = ENV['MONGO_RUBY_DRIVER_PORT'] ? ENV['MONGO_RUBY_DRIVER_PORT'].to_i : Mongo::Connection::DEFAULT_PORT
|
48
|
+
end
|
49
|
+
|
50
|
+
unless defined? TEST_HOST
|
51
|
+
TEST_HOST = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
|
52
|
+
end
|
53
|
+
|
54
|
+
class MiniTest::Unit::TestCase
|
55
|
+
include Mongo
|
56
|
+
include BSON
|
57
|
+
|
58
|
+
def self.standard_connection(options={})
|
59
|
+
Connection.new(TEST_HOST, TEST_PORT, options)
|
60
|
+
end
|
61
|
+
|
62
|
+
def standard_connection(options={})
|
63
|
+
self.class.standard_connection(options)
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.host_port
|
67
|
+
"#{mongo_host}:#{mongo_port}"
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.mongo_host
|
71
|
+
TEST_HOST
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.mongo_port
|
75
|
+
TEST_PORT
|
76
|
+
end
|
77
|
+
|
78
|
+
def host_port
|
79
|
+
self.class.host_port
|
80
|
+
end
|
81
|
+
|
82
|
+
def mongo_host
|
83
|
+
self.class.mongo_host
|
84
|
+
end
|
85
|
+
|
86
|
+
def mongo_port
|
87
|
+
self.class.mongo_port
|
88
|
+
end
|
89
|
+
|
90
|
+
def new_mock_socket(host='localhost', port=27017)
|
91
|
+
socket = Object.new
|
92
|
+
socket.stubs(:setsockopt).with(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
93
|
+
socket.stubs(:close)
|
94
|
+
socket
|
95
|
+
end
|
96
|
+
|
97
|
+
def new_mock_db
|
98
|
+
db = Object.new
|
99
|
+
end
|
100
|
+
|
101
|
+
def assert_raise_error(klass, message)
|
102
|
+
begin
|
103
|
+
yield
|
104
|
+
rescue => e
|
105
|
+
assert_equal klass, e.class
|
106
|
+
assert e.message.include?(message), "#{e.message} does not include #{message}."
|
107
|
+
else
|
108
|
+
flunk "Expected assertion #{klass} but none was raised."
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require './test/test_helper'
|
2
|
+
|
3
|
+
# Essentialy the same as test_threading.rb but with an expanded pool for
|
4
|
+
# testing multiple connections.
|
5
|
+
class TestThreadingLargePool < Test::Unit::TestCase
|
6
|
+
|
7
|
+
include Mongo
|
8
|
+
|
9
|
+
@@db = standard_connection(:pool_size => 50, :timeout => 60).db(MONGO_TEST_DB)
|
10
|
+
@@coll = @@db.collection('thread-test-collection')
|
11
|
+
|
12
|
+
def set_up_safe_data
|
13
|
+
@@db.drop_collection('duplicate')
|
14
|
+
@@db.drop_collection('unique')
|
15
|
+
@duplicate = @@db.collection('duplicate')
|
16
|
+
@unique = @@db.collection('unique')
|
17
|
+
|
18
|
+
@duplicate.insert("test" => "insert")
|
19
|
+
@duplicate.insert("test" => "update")
|
20
|
+
@unique.insert("test" => "insert")
|
21
|
+
@unique.insert("test" => "update")
|
22
|
+
@unique.create_index("test", :unique => true)
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_safe_update
|
26
|
+
set_up_safe_data
|
27
|
+
threads = []
|
28
|
+
300.times do |i|
|
29
|
+
threads[i] = Thread.new do
|
30
|
+
if i % 2 == 0
|
31
|
+
assert_raise Mongo::OperationFailure do
|
32
|
+
@unique.update({"test" => "insert"}, {"$set" => {"test" => "update"}}, :safe => true)
|
33
|
+
end
|
34
|
+
else
|
35
|
+
@duplicate.update({"test" => "insert"}, {"$set" => {"test" => "update"}}, :safe => true)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
300.times do |i|
|
41
|
+
threads[i].join
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_safe_insert
|
46
|
+
set_up_safe_data
|
47
|
+
threads = []
|
48
|
+
300.times do |i|
|
49
|
+
threads[i] = Thread.new do
|
50
|
+
if i % 2 == 0
|
51
|
+
assert_raise Mongo::OperationFailure do
|
52
|
+
@unique.insert({"test" => "insert"}, :safe => true)
|
53
|
+
end
|
54
|
+
else
|
55
|
+
@duplicate.insert({"test" => "insert"}, :safe => true)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
300.times do |i|
|
61
|
+
threads[i].join
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_threading
|
66
|
+
@@coll.drop
|
67
|
+
@@coll = @@db.collection('thread-test-collection')
|
68
|
+
|
69
|
+
1000.times do |i|
|
70
|
+
@@coll.insert("x" => i)
|
71
|
+
end
|
72
|
+
|
73
|
+
threads = []
|
74
|
+
|
75
|
+
10.times do |i|
|
76
|
+
threads[i] = Thread.new do
|
77
|
+
sum = 0
|
78
|
+
@@coll.find().each do |document|
|
79
|
+
sum += document["x"]
|
80
|
+
end
|
81
|
+
assert_equal 499500, sum
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
10.times do |i|
|
86
|
+
threads[i].join
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
__END__
|
2
|
+
require './test/test_helper'
|
3
|
+
|
4
|
+
class TestThreading < Test::Unit::TestCase
|
5
|
+
|
6
|
+
include Mongo
|
7
|
+
|
8
|
+
@@db = standard_connection(:pool_size => 1, :timeout => 30).db(MONGO_TEST_DB)
|
9
|
+
@@coll = @@db.collection('thread-test-collection')
|
10
|
+
|
11
|
+
def set_up_safe_data
|
12
|
+
@@db.drop_collection('duplicate')
|
13
|
+
@@db.drop_collection('unique')
|
14
|
+
@duplicate = @@db.collection('duplicate')
|
15
|
+
@unique = @@db.collection('unique')
|
16
|
+
|
17
|
+
@duplicate.insert("test" => "insert")
|
18
|
+
@duplicate.insert("test" => "update")
|
19
|
+
@unique.insert("test" => "insert")
|
20
|
+
@unique.insert("test" => "update")
|
21
|
+
@unique.create_index("test", :unique => true)
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_safe_update
|
25
|
+
set_up_safe_data
|
26
|
+
threads = []
|
27
|
+
100.times do |i|
|
28
|
+
threads[i] = Thread.new do
|
29
|
+
if i % 2 == 0
|
30
|
+
assert_raise Mongo::OperationFailure do
|
31
|
+
@unique.update({"test" => "insert"}, {"$set" => {"test" => "update"}}, :safe => true)
|
32
|
+
end
|
33
|
+
else
|
34
|
+
@duplicate.update({"test" => "insert"}, {"$set" => {"test" => "update"}}, :safe => true)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
100.times do |i|
|
40
|
+
threads[i].join
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_safe_insert
|
45
|
+
set_up_safe_data
|
46
|
+
threads = []
|
47
|
+
100.times do |i|
|
48
|
+
threads[i] = Thread.new do
|
49
|
+
if i % 2 == 0
|
50
|
+
assert_raise Mongo::OperationFailure do
|
51
|
+
@unique.insert({"test" => "insert"}, :safe => true)
|
52
|
+
end
|
53
|
+
else
|
54
|
+
@duplicate.insert({"test" => "insert"}, :safe => true)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
100.times do |i|
|
60
|
+
threads[i].join
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_threading
|
65
|
+
@@coll.drop
|
66
|
+
@@coll = @@db.collection('thread-test-collection')
|
67
|
+
|
68
|
+
1000.times do |i|
|
69
|
+
@@coll.insert("x" => i)
|
70
|
+
end
|
71
|
+
|
72
|
+
threads = []
|
73
|
+
|
74
|
+
10.times do |i|
|
75
|
+
threads[i] = Thread.new do
|
76
|
+
sum = 0
|
77
|
+
@@coll.find().each do |document|
|
78
|
+
sum += document["x"]
|
79
|
+
end
|
80
|
+
assert_equal 499500, sum
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
10.times do |i|
|
85
|
+
threads[i].join
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require File.join((File.expand_path(File.dirname(__FILE__))), 'repl_set_manager')
|
2
|
+
|
3
|
+
class AuthReplSetManager < ReplSetManager
|
4
|
+
def initialize(opts={})
|
5
|
+
super(opts)
|
6
|
+
|
7
|
+
@key_path = opts[:key_path] || File.join(File.expand_path(File.dirname(__FILE__)), "keyfile.txt")
|
8
|
+
system("chmod 600 #{@key_path}")
|
9
|
+
end
|
10
|
+
|
11
|
+
def start_cmd(n)
|
12
|
+
super + " --keyFile #{@key_path}"
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
THIS IS A SECRET KEYFILE FOR REPLICA SETS BWAHAHAHAH
|
@@ -0,0 +1,377 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require 'thread'
|
4
|
+
|
5
|
+
STDOUT.sync = true
|
6
|
+
|
7
|
+
unless defined? Mongo
|
8
|
+
require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'mongo')
|
9
|
+
end
|
10
|
+
|
11
|
+
class ReplSetManager
|
12
|
+
|
13
|
+
attr_accessor :host, :start_port, :ports, :name, :mongods, :tags, :version
|
14
|
+
|
15
|
+
def initialize(opts={})
|
16
|
+
@start_port = opts[:start_port] || 30000
|
17
|
+
@ports = []
|
18
|
+
@name = opts[:name] || 'replica-set-foo'
|
19
|
+
@host = opts[:host] || 'localhost'
|
20
|
+
@retries = opts[:retries] || 30
|
21
|
+
@config = {"_id" => @name, "members" => []}
|
22
|
+
@durable = opts.fetch(:durable, false)
|
23
|
+
@path = File.join(File.expand_path(File.dirname(__FILE__)), "data")
|
24
|
+
@oplog_size = opts.fetch(:oplog_size, 32)
|
25
|
+
@tags = [{"dc" => "ny", "rack" => "a", "db" => "main"},
|
26
|
+
{"dc" => "ny", "rack" => "b", "db" => "main"},
|
27
|
+
{"dc" => "sf", "rack" => "a", "db" => "main"}]
|
28
|
+
|
29
|
+
@arbiter_count = opts[:arbiter_count] || 2
|
30
|
+
@secondary_count = opts[:secondary_count] || 2
|
31
|
+
@passive_count = opts[:passive_count] || 0
|
32
|
+
@primary_count = 1
|
33
|
+
|
34
|
+
@count = @primary_count + @passive_count + @arbiter_count + @secondary_count
|
35
|
+
if @count > 7
|
36
|
+
raise StandardError, "Cannot create a replica set with #{node_count} nodes. 7 is the max."
|
37
|
+
end
|
38
|
+
|
39
|
+
@mongods = {}
|
40
|
+
version_string = `mongod --version`
|
41
|
+
version_string =~ /(\d\.\d\.\d)/
|
42
|
+
@version = $1.split(".").map {|d| d.to_i }
|
43
|
+
end
|
44
|
+
|
45
|
+
def start_set
|
46
|
+
begin
|
47
|
+
con = Mongo::Connection.new(@host, @start_port)
|
48
|
+
rescue Mongo::ConnectionFailure
|
49
|
+
end
|
50
|
+
|
51
|
+
if con && ensure_up(1, con)
|
52
|
+
should_start = false
|
53
|
+
puts "** Replica set already started."
|
54
|
+
else
|
55
|
+
should_start = true
|
56
|
+
system("killall mongod")
|
57
|
+
puts "** Starting a replica set with #{@count} nodes"
|
58
|
+
end
|
59
|
+
|
60
|
+
n = 0
|
61
|
+
(@primary_count + @secondary_count).times do
|
62
|
+
init_node(n, should_start) do |attrs|
|
63
|
+
if @version[0] >= 2
|
64
|
+
attrs['tags'] = @tags[n % @tags.size]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
n += 1
|
68
|
+
end
|
69
|
+
|
70
|
+
@passive_count.times do
|
71
|
+
init_node(n, should_start) do |attrs|
|
72
|
+
attrs['priority'] = 0
|
73
|
+
end
|
74
|
+
n += 1
|
75
|
+
end
|
76
|
+
|
77
|
+
@arbiter_count.times do
|
78
|
+
init_node(n, should_start) do |attrs|
|
79
|
+
attrs['arbiterOnly'] = true
|
80
|
+
end
|
81
|
+
n += 1
|
82
|
+
end
|
83
|
+
|
84
|
+
if con && ensure_up(1, con)
|
85
|
+
@mongods.each do |k, v|
|
86
|
+
v['up'] = true
|
87
|
+
v['pid'] = File.open(File.join(v['db_path'], 'mongod.lock')).read.strip
|
88
|
+
end
|
89
|
+
else
|
90
|
+
initiate
|
91
|
+
ensure_up
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def cleanup_set
|
96
|
+
system("killall mongod")
|
97
|
+
@count.times do |n|
|
98
|
+
system("rm -rf #{@mongods[n]['db_path']}")
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def init_node(n, should_start=true)
|
103
|
+
@mongods[n] ||= {}
|
104
|
+
port = @start_port + n
|
105
|
+
@ports << port
|
106
|
+
@mongods[n]['port'] = port
|
107
|
+
@mongods[n]['db_path'] = get_path("rs-#{port}")
|
108
|
+
@mongods[n]['log_path'] = get_path("log-#{port}")
|
109
|
+
@mongods[n]['start'] = start_cmd(n)
|
110
|
+
|
111
|
+
if should_start
|
112
|
+
system("rm -rf #{@mongods[n]['db_path']}")
|
113
|
+
system("mkdir -p #{@mongods[n]['db_path']}")
|
114
|
+
start(n)
|
115
|
+
end
|
116
|
+
|
117
|
+
member = {'_id' => n, 'host' => "#{@host}:#{@mongods[n]['port']}"}
|
118
|
+
|
119
|
+
if block_given?
|
120
|
+
custom_attrs = {}
|
121
|
+
yield custom_attrs
|
122
|
+
member.merge!(custom_attrs)
|
123
|
+
@mongods[n].merge!(custom_attrs)
|
124
|
+
end
|
125
|
+
|
126
|
+
@config['members'] << member
|
127
|
+
end
|
128
|
+
|
129
|
+
def journal_switch
|
130
|
+
if @version[0] >= 2
|
131
|
+
if @durable
|
132
|
+
"--journal"
|
133
|
+
else
|
134
|
+
"--nojournal"
|
135
|
+
end
|
136
|
+
elsif @durable
|
137
|
+
"--journal"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def start_cmd(n)
|
142
|
+
@mongods[n]['start'] = "mongod --replSet #{@name} --logpath '#{@mongods[n]['log_path']}' " +
|
143
|
+
"--oplogSize #{@oplog_size} #{journal_switch} --dbpath #{@mongods[n]['db_path']} --port #{@mongods[n]['port']} --fork"
|
144
|
+
@mongods[n]['start'] += " --dur" if @durable
|
145
|
+
@mongods[n]['start']
|
146
|
+
end
|
147
|
+
|
148
|
+
def remove_secondary_node
|
149
|
+
primary = get_node_with_state(1)
|
150
|
+
con = get_connection(primary)
|
151
|
+
config = con['local']['system.replset'].find_one
|
152
|
+
secondary = get_node_with_state(2)
|
153
|
+
host_port = "#{@host}:#{@mongods[secondary]['port']}"
|
154
|
+
kill(secondary)
|
155
|
+
@mongods.delete(secondary)
|
156
|
+
@config['members'].reject! {|m| m['host'] == host_port}
|
157
|
+
@config['version'] = config['version'] + 1
|
158
|
+
|
159
|
+
begin
|
160
|
+
con['admin'].command({'replSetReconfig' => @config})
|
161
|
+
rescue Mongo::ConnectionFailure
|
162
|
+
end
|
163
|
+
|
164
|
+
con.close
|
165
|
+
|
166
|
+
return secondary
|
167
|
+
end
|
168
|
+
|
169
|
+
def add_node(n=nil)
|
170
|
+
primary = get_node_with_state(1)
|
171
|
+
con = get_connection(primary)
|
172
|
+
init_node(n || @mongods.length)
|
173
|
+
|
174
|
+
config = con['local']['system.replset'].find_one
|
175
|
+
@config['version'] = config['version'] + 1
|
176
|
+
|
177
|
+
# We expect a connection failure on reconfigure here.
|
178
|
+
begin
|
179
|
+
con['admin'].command({'replSetReconfig' => @config})
|
180
|
+
rescue Mongo::ConnectionFailure
|
181
|
+
end
|
182
|
+
|
183
|
+
con.close
|
184
|
+
ensure_up
|
185
|
+
end
|
186
|
+
|
187
|
+
def kill(node, signal=2)
|
188
|
+
pid = @mongods[node]['pid']
|
189
|
+
puts "** Killing node with pid #{pid} at port #{@mongods[node]['port']}"
|
190
|
+
system("kill -#{signal} #{@mongods[node]['pid']}")
|
191
|
+
@mongods[node]['up'] = false
|
192
|
+
sleep(1)
|
193
|
+
end
|
194
|
+
|
195
|
+
def kill_primary(signal=2)
|
196
|
+
node = get_node_with_state(1)
|
197
|
+
kill(node, signal)
|
198
|
+
return node
|
199
|
+
end
|
200
|
+
|
201
|
+
# Note that we have to rescue a connection failure
|
202
|
+
# when we run the StepDown command because that
|
203
|
+
# command will close the connection.
|
204
|
+
def step_down_primary
|
205
|
+
primary = get_node_with_state(1)
|
206
|
+
con = get_connection(primary)
|
207
|
+
begin
|
208
|
+
con['admin'].command({'replSetStepDown' => 90})
|
209
|
+
rescue Mongo::ConnectionFailure
|
210
|
+
end
|
211
|
+
con.close
|
212
|
+
end
|
213
|
+
|
214
|
+
def kill_secondary
|
215
|
+
node = get_node_with_state(2)
|
216
|
+
kill(node)
|
217
|
+
return node
|
218
|
+
end
|
219
|
+
|
220
|
+
def kill_all_secondaries
|
221
|
+
nodes = get_all_nodes_with_state(2)
|
222
|
+
if nodes
|
223
|
+
nodes.each do |n|
|
224
|
+
kill(n)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def restart_killed_nodes
|
230
|
+
nodes = @mongods.keys.select do |key|
|
231
|
+
@mongods[key]['up'] == false
|
232
|
+
end
|
233
|
+
|
234
|
+
nodes.each do |node|
|
235
|
+
start(node)
|
236
|
+
end
|
237
|
+
|
238
|
+
ensure_up
|
239
|
+
end
|
240
|
+
|
241
|
+
def get_node_from_port(port)
|
242
|
+
@mongods.keys.detect { |key| @mongods[key]['port'] == port }
|
243
|
+
end
|
244
|
+
|
245
|
+
def start(node)
|
246
|
+
system(@mongods[node]['start'])
|
247
|
+
@mongods[node]['up'] = true
|
248
|
+
sleep(0.5)
|
249
|
+
@mongods[node]['pid'] = File.open(File.join(@mongods[node]['db_path'], 'mongod.lock')).read.strip
|
250
|
+
end
|
251
|
+
alias :restart :start
|
252
|
+
|
253
|
+
def ensure_up(n=nil, connection=nil)
|
254
|
+
print "** Ensuring members are up..."
|
255
|
+
|
256
|
+
attempt(n) do
|
257
|
+
con = connection || get_connection
|
258
|
+
status = con['admin'].command({'replSetGetStatus' => 1})
|
259
|
+
print "."
|
260
|
+
if status['members'].all? { |m| m['health'] == 1 &&
|
261
|
+
[1, 2, 7].include?(m['state']) } &&
|
262
|
+
status['members'].any? { |m| m['state'] == 1 }
|
263
|
+
print "all members up!\n\n"
|
264
|
+
con.close
|
265
|
+
return status
|
266
|
+
else
|
267
|
+
con.close
|
268
|
+
raise Mongo::OperationFailure
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
return false
|
273
|
+
end
|
274
|
+
|
275
|
+
def primary
|
276
|
+
nodes = get_all_host_pairs_with_state(1)
|
277
|
+
nodes.empty? ? nil : nodes[0]
|
278
|
+
end
|
279
|
+
|
280
|
+
def secondaries
|
281
|
+
get_all_host_pairs_with_state(2)
|
282
|
+
end
|
283
|
+
|
284
|
+
def arbiters
|
285
|
+
get_all_host_pairs_with_state(7)
|
286
|
+
end
|
287
|
+
|
288
|
+
# String used for adding a shard via mongos
|
289
|
+
# using the addshard command.
|
290
|
+
def shard_string
|
291
|
+
str = "#{@name}/"
|
292
|
+
str << @mongods.map do |k, mongod|
|
293
|
+
"#{@host}:#{mongod['port']}"
|
294
|
+
end.join(',')
|
295
|
+
str
|
296
|
+
end
|
297
|
+
|
298
|
+
private
|
299
|
+
|
300
|
+
def initiate
|
301
|
+
con = get_connection
|
302
|
+
|
303
|
+
attempt do
|
304
|
+
con['admin'].command({'replSetInitiate' => @config})
|
305
|
+
end
|
306
|
+
|
307
|
+
con.close
|
308
|
+
end
|
309
|
+
|
310
|
+
def get_all_nodes_with_state(state)
|
311
|
+
status = ensure_up
|
312
|
+
nodes = status['members'].select {|m| m['state'] == state}
|
313
|
+
nodes = nodes.map do |node|
|
314
|
+
host_port = node['name'].split(':')
|
315
|
+
port = host_port[1] ? host_port[1].to_i : 27017
|
316
|
+
@mongods.keys.detect {|key| @mongods[key]['port'] == port}
|
317
|
+
end
|
318
|
+
|
319
|
+
nodes == [] ? false : nodes
|
320
|
+
end
|
321
|
+
|
322
|
+
def get_node_with_state(state)
|
323
|
+
status = ensure_up
|
324
|
+
node = status['members'].detect {|m| m['state'] == state}
|
325
|
+
if node
|
326
|
+
host_port = node['name'].split(':')
|
327
|
+
port = host_port[1] ? host_port[1].to_i : 27017
|
328
|
+
key = @mongods.keys.detect {|n| @mongods[n]['port'] == port}
|
329
|
+
return key
|
330
|
+
else
|
331
|
+
return false
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
def get_all_host_pairs_with_state(state)
|
336
|
+
status = ensure_up
|
337
|
+
nodes = status['members'].select {|m| m['state'] == state}
|
338
|
+
nodes.map do |node|
|
339
|
+
host_port = node['name'].split(':')
|
340
|
+
port = host_port[1] ? host_port[1].to_i : 27017
|
341
|
+
[host, port]
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
def get_connection(node=nil)
|
346
|
+
con = attempt do
|
347
|
+
if !node
|
348
|
+
node = @mongods.keys.detect {|key| !@mongods[key]['arbiterOnly'] && @mongods[key]['up'] }
|
349
|
+
end
|
350
|
+
con = Mongo::Connection.new(@host, @mongods[node]['port'], :slave_ok => true)
|
351
|
+
end
|
352
|
+
|
353
|
+
return con
|
354
|
+
end
|
355
|
+
|
356
|
+
def get_path(name)
|
357
|
+
File.join(@path, name)
|
358
|
+
end
|
359
|
+
|
360
|
+
def attempt(retries=nil)
|
361
|
+
raise "No block given!" unless block_given?
|
362
|
+
count = 0
|
363
|
+
|
364
|
+
while count < (retries || @retries) do
|
365
|
+
begin
|
366
|
+
return yield
|
367
|
+
rescue Mongo::OperationFailure, Mongo::ConnectionFailure => ex
|
368
|
+
sleep(2)
|
369
|
+
count += 1
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
puts "NO MORE ATTEMPTS"
|
374
|
+
raise ex
|
375
|
+
end
|
376
|
+
|
377
|
+
end
|