jmongo 1.0.3 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. data/Gemfile +8 -0
  2. data/Gemfile.lock +43 -0
  3. data/Rakefile +72 -0
  4. data/jmongo.gemspec +84 -6
  5. data/lib/jmongo.rb +6 -14
  6. data/lib/jmongo/collection.rb +196 -114
  7. data/lib/jmongo/connection.rb +39 -13
  8. data/lib/jmongo/cursor.rb +161 -63
  9. data/lib/jmongo/db.rb +119 -30
  10. data/lib/jmongo/exceptions.rb +39 -0
  11. data/lib/jmongo/mongo-2.6.5.gb1.jar +0 -0
  12. data/lib/jmongo/mongo/bson.rb +130 -0
  13. data/lib/jmongo/mongo/collection.rb +185 -0
  14. data/lib/jmongo/mongo/connection.rb +45 -0
  15. data/lib/jmongo/mongo/db.rb +31 -0
  16. data/lib/jmongo/mongo/jmongo.rb +44 -0
  17. data/lib/jmongo/mongo/mongo.rb +98 -0
  18. data/lib/jmongo/mongo/ruby_ext.rb +38 -0
  19. data/lib/jmongo/mongo/utils.rb +136 -0
  20. data/lib/jmongo/version.rb +1 -1
  21. data/test-results.txt +98 -0
  22. data/test/auxillary/1.4_features.rb +166 -0
  23. data/test/auxillary/authentication_test.rb +68 -0
  24. data/test/auxillary/autoreconnect_test.rb +41 -0
  25. data/test/auxillary/fork_test.rb +30 -0
  26. data/test/auxillary/repl_set_auth_test.rb +58 -0
  27. data/test/auxillary/slave_connection_test.rb +36 -0
  28. data/test/auxillary/threaded_authentication_test.rb +101 -0
  29. data/test/bson/binary_test.rb +15 -0
  30. data/test/bson/bson_test.rb +657 -0
  31. data/test/bson/byte_buffer_test.rb +208 -0
  32. data/test/bson/hash_with_indifferent_access_test.rb +38 -0
  33. data/test/bson/json_test.rb +17 -0
  34. data/test/bson/object_id_test.rb +138 -0
  35. data/test/bson/ordered_hash_test.rb +245 -0
  36. data/test/bson/test_helper.rb +46 -0
  37. data/test/bson/timestamp_test.rb +46 -0
  38. data/test/collection_test.rb +933 -0
  39. data/test/connection_test.rb +325 -0
  40. data/test/conversions_test.rb +121 -0
  41. data/test/cursor_fail_test.rb +75 -0
  42. data/test/cursor_message_test.rb +43 -0
  43. data/test/cursor_test.rb +547 -0
  44. data/test/data/empty_data +0 -0
  45. data/test/data/sample_data +0 -0
  46. data/test/data/sample_file.pdf +0 -0
  47. data/test/data/small_data.txt +1 -0
  48. data/test/db_api_test.rb +739 -0
  49. data/test/db_connection_test.rb +15 -0
  50. data/test/db_test.rb +325 -0
  51. data/test/grid_file_system_test.rb +260 -0
  52. data/test/grid_io_test.rb +210 -0
  53. data/test/grid_test.rb +259 -0
  54. data/test/load/thin/config.ru +6 -0
  55. data/test/load/thin/config.yml.template +6 -0
  56. data/test/load/thin/load.rb +24 -0
  57. data/test/load/unicorn/config.ru +6 -0
  58. data/test/load/unicorn/load.rb +23 -0
  59. data/test/load/unicorn/unicorn.rb.template +29 -0
  60. data/test/replica_sets/connect_test.rb +111 -0
  61. data/test/replica_sets/connection_string_test.rb +29 -0
  62. data/test/replica_sets/count_test.rb +36 -0
  63. data/test/replica_sets/insert_test.rb +54 -0
  64. data/test/replica_sets/pooled_insert_test.rb +58 -0
  65. data/test/replica_sets/query_secondaries.rb +109 -0
  66. data/test/replica_sets/query_test.rb +52 -0
  67. data/test/replica_sets/read_preference_test.rb +43 -0
  68. data/test/replica_sets/refresh_test.rb +123 -0
  69. data/test/replica_sets/replication_ack_test.rb +71 -0
  70. data/test/replica_sets/rs_test_helper.rb +27 -0
  71. data/test/safe_test.rb +68 -0
  72. data/test/support/hash_with_indifferent_access.rb +186 -0
  73. data/test/support/keys.rb +45 -0
  74. data/test/support_test.rb +19 -0
  75. data/test/test_helper.rb +111 -0
  76. data/test/threading/threading_with_large_pool_test.rb +90 -0
  77. data/test/threading_test.rb +88 -0
  78. data/test/tools/auth_repl_set_manager.rb +14 -0
  79. data/test/tools/keyfile.txt +1 -0
  80. data/test/tools/repl_set_manager.rb +377 -0
  81. data/test/unit/collection_test.rb +128 -0
  82. data/test/unit/connection_test.rb +85 -0
  83. data/test/unit/cursor_test.rb +127 -0
  84. data/test/unit/db_test.rb +96 -0
  85. data/test/unit/grid_test.rb +51 -0
  86. data/test/unit/node_test.rb +73 -0
  87. data/test/unit/pool_manager_test.rb +47 -0
  88. data/test/unit/pool_test.rb +9 -0
  89. data/test/unit/read_test.rb +101 -0
  90. data/test/unit/safe_test.rb +125 -0
  91. data/test/uri_test.rb +92 -0
  92. metadata +170 -99
  93. data/lib/jmongo/ajrb.rb +0 -189
  94. data/lib/jmongo/jmongo_jext.rb +0 -302
  95. data/lib/jmongo/mongo-2.6.3.jar +0 -0
  96. 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
@@ -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