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.
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