mongo 1.2.0 → 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +15 -3
- data/docs/FAQ.md +4 -0
- data/docs/HISTORY.md +11 -4
- data/lib/mongo.rb +1 -1
- data/lib/mongo/collection.rb +28 -25
- data/lib/mongo/connection.rb +54 -12
- data/lib/mongo/cursor.rb +17 -11
- data/lib/mongo/db.rb +31 -11
- data/lib/mongo/repl_set_connection.rb +17 -2
- data/lib/mongo/util/pool.rb +50 -6
- data/test/auxillary/repl_set_auth_test.rb +58 -0
- data/test/auxillary/threaded_authentication_test.rb +101 -0
- data/test/bson/bson_test.rb +43 -0
- data/test/connection_test.rb +1 -1
- data/test/db_api_test.rb +0 -37
- data/test/load/thin/load.rb +24 -0
- data/test/load/unicorn/load.rb +23 -0
- data/test/load/unicorn/unicorn.rb +29 -0
- data/test/tools/auth_repl_set_manager.rb +14 -0
- data/test/tools/load.rb +58 -0
- data/test/tools/repl_set_manager.rb +34 -9
- data/test/tools/sharding_manager.rb +202 -0
- data/test/tools/test.rb +3 -12
- data/test/unit/collection_test.rb +19 -22
- data/test/unit/connection_test.rb +0 -1
- data/test/unit/db_test.rb +1 -0
- metadata +23 -11
@@ -125,6 +125,7 @@ module Mongo
|
|
125
125
|
end
|
126
126
|
end
|
127
127
|
end
|
128
|
+
alias :reconnect :connect
|
128
129
|
|
129
130
|
def connecting?
|
130
131
|
@nodes_to_try.length > 0
|
@@ -167,6 +168,20 @@ module Mongo
|
|
167
168
|
@read_secondary || @slave_ok
|
168
169
|
end
|
169
170
|
|
171
|
+
def authenticate_pools
|
172
|
+
super
|
173
|
+
@secondary_pools.each do |pool|
|
174
|
+
pool.authenticate_existing
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def logout_pools(db)
|
179
|
+
super
|
180
|
+
@secondary_pools.each do |pool|
|
181
|
+
pool.logout_existing(db)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
170
185
|
private
|
171
186
|
|
172
187
|
def check_is_master(node)
|
@@ -175,7 +190,7 @@ module Mongo
|
|
175
190
|
socket = TCPSocket.new(host, port)
|
176
191
|
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
177
192
|
|
178
|
-
config = self['admin'].command({:ismaster => 1}, :
|
193
|
+
config = self['admin'].command({:ismaster => 1}, :socket => socket)
|
179
194
|
|
180
195
|
check_set_name(config, socket)
|
181
196
|
rescue OperationFailure, SocketError, SystemCallError, IOError => ex
|
@@ -218,7 +233,7 @@ module Mongo
|
|
218
233
|
def check_set_name(config, socket)
|
219
234
|
if @replica_set
|
220
235
|
config = self['admin'].command({:replSetGetStatus => 1},
|
221
|
-
:
|
236
|
+
:socket => socket, :check_response => false)
|
222
237
|
|
223
238
|
if !Mongo::Support.ok?(config)
|
224
239
|
raise ReplicaSetConnectionError, config['errmsg']
|
data/lib/mongo/util/pool.rb
CHANGED
@@ -37,6 +37,9 @@ module Mongo
|
|
37
37
|
# Condition variable for signal and wait
|
38
38
|
@queue = ConditionVariable.new
|
39
39
|
|
40
|
+
# Operations to perform on a socket
|
41
|
+
@socket_ops = Hash.new { |h, k| h[k] = [] }
|
42
|
+
|
40
43
|
@sockets = []
|
41
44
|
@checked_out = []
|
42
45
|
end
|
@@ -75,11 +78,42 @@ module Mongo
|
|
75
78
|
rescue => ex
|
76
79
|
raise ConnectionFailure, "Failed to connect socket: #{ex}"
|
77
80
|
end
|
81
|
+
|
82
|
+
# If any saved authentications exist, we want to apply those
|
83
|
+
# when creating new sockets.
|
84
|
+
@connection.apply_saved_authentication(:socket => socket)
|
85
|
+
|
78
86
|
@sockets << socket
|
79
87
|
@checked_out << socket
|
80
88
|
socket
|
81
89
|
end
|
82
90
|
|
91
|
+
# If a user calls DB#authenticate, and several sockets exist,
|
92
|
+
# then we need a way to apply the authentication on each socket.
|
93
|
+
# So we store the apply_authentication method, and this will be
|
94
|
+
# applied right before the next use of each socket.
|
95
|
+
def authenticate_existing
|
96
|
+
@connection_mutex.synchronize do
|
97
|
+
@sockets.each do |socket|
|
98
|
+
@socket_ops[socket] << Proc.new do
|
99
|
+
@connection.apply_saved_authentication(:socket => socket)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Store the logout op for each existing socket to be applied before
|
106
|
+
# the next use of each socket.
|
107
|
+
def logout_existing(db)
|
108
|
+
@connection_mutex.synchronize do
|
109
|
+
@sockets.each do |socket|
|
110
|
+
@socket_ops[socket] << Proc.new do
|
111
|
+
@connection.db(db).issue_logout(:socket => socket)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
83
117
|
# Checks out the first available socket from the pool.
|
84
118
|
#
|
85
119
|
# This method is called exclusively from #checkout;
|
@@ -110,14 +144,24 @@ module Mongo
|
|
110
144
|
checkout_new_socket
|
111
145
|
end
|
112
146
|
|
113
|
-
|
147
|
+
if socket
|
148
|
+
|
149
|
+
# This call all procs, in order, scoped to existing sockets.
|
150
|
+
# At the moment, we use this to lazily authenticate and
|
151
|
+
# logout existing socket connections.
|
152
|
+
@socket_ops[socket].reject! do |op|
|
153
|
+
op.call
|
154
|
+
end
|
114
155
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
156
|
+
return socket
|
157
|
+
else
|
158
|
+
# Otherwise, wait
|
159
|
+
if @logger
|
160
|
+
@logger.warn "MONGODB Waiting for available connection; " +
|
161
|
+
"#{@checked_out.size} of #{@size} connections checked out."
|
162
|
+
end
|
163
|
+
@queue.wait(@connection_mutex)
|
119
164
|
end
|
120
|
-
@queue.wait(@connection_mutex)
|
121
165
|
end
|
122
166
|
end
|
123
167
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
require './test/test_helper'
|
3
|
+
require './test/tools/auth_repl_set_manager'
|
4
|
+
|
5
|
+
class AuthTest < Test::Unit::TestCase
|
6
|
+
include Mongo
|
7
|
+
|
8
|
+
def setup
|
9
|
+
@manager = AuthReplSetManager.new(:start_port => 40000)
|
10
|
+
@manager.start_set
|
11
|
+
end
|
12
|
+
|
13
|
+
def teardown
|
14
|
+
@manager.cleanup_set
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_repl_set_auth
|
18
|
+
@conn = ReplSetConnection.new([@manager.host, @manager.ports[0]], [@manager.host, @manager.ports[1]],
|
19
|
+
[@manager.host, @manager.ports[2]], :name => @manager.name)
|
20
|
+
|
21
|
+
# Add an admin user
|
22
|
+
@conn['admin'].add_user("me", "secret")
|
23
|
+
|
24
|
+
# Ensure that insert fails
|
25
|
+
assert_raise_error Mongo::OperationFailure, "unauthorized" do
|
26
|
+
@conn['foo']['stuff'].insert({:a => 2}, :safe => {:w => 3})
|
27
|
+
end
|
28
|
+
|
29
|
+
# Then authenticate
|
30
|
+
assert @conn['admin'].authenticate("me", "secret")
|
31
|
+
|
32
|
+
# Insert should succeed now
|
33
|
+
assert @conn['foo']['stuff'].insert({:a => 2}, :safe => {:w => 3})
|
34
|
+
|
35
|
+
# So should a query
|
36
|
+
assert @conn['foo']['stuff'].find_one
|
37
|
+
|
38
|
+
# But not when we logout
|
39
|
+
@conn['admin'].logout
|
40
|
+
|
41
|
+
assert_raise_error Mongo::OperationFailure, "unauthorized" do
|
42
|
+
@conn['foo']['stuff'].find_one
|
43
|
+
end
|
44
|
+
|
45
|
+
# Same should apply to a random secondary
|
46
|
+
@slave1 = Connection.new(@conn.secondary_pools[0].host,
|
47
|
+
@conn.secondary_pools[0].port, :slave_ok => true)
|
48
|
+
|
49
|
+
# Find should fail
|
50
|
+
assert_raise_error Mongo::OperationFailure, "unauthorized" do
|
51
|
+
@slave1['foo']['stuff'].find_one
|
52
|
+
end
|
53
|
+
|
54
|
+
# But not when authenticated
|
55
|
+
@slave1['admin'].authenticate("me", "secret")
|
56
|
+
assert @slave1['foo']['stuff'].find_one
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
require 'mongo'
|
3
|
+
require 'thread'
|
4
|
+
require 'test/unit'
|
5
|
+
require './test/test_helper'
|
6
|
+
|
7
|
+
# NOTE: This test requires bouncing the server.
|
8
|
+
# It also requires that a user exists on the admin database.
|
9
|
+
class AuthenticationTest < Test::Unit::TestCase
|
10
|
+
include Mongo
|
11
|
+
|
12
|
+
def setup
|
13
|
+
@conn = standard_connection(:pool_size => 10)
|
14
|
+
@db1 = @conn.db('mongo-ruby-test-auth1')
|
15
|
+
@db2 = @conn.db('mongo-ruby-test-auth2')
|
16
|
+
@admin = @conn.db('admin')
|
17
|
+
end
|
18
|
+
|
19
|
+
def teardown
|
20
|
+
@db1.authenticate('user1', 'secret')
|
21
|
+
@db2.authenticate('user2', 'secret')
|
22
|
+
@conn.drop_database('mongo-ruby-test-auth1')
|
23
|
+
@conn.drop_database('mongo-ruby-test-auth2')
|
24
|
+
end
|
25
|
+
|
26
|
+
def threaded_exec
|
27
|
+
threads = []
|
28
|
+
|
29
|
+
100.times do
|
30
|
+
threads << Thread.new do
|
31
|
+
yield
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
100.times do |n|
|
36
|
+
threads[n].join
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_authenticate
|
41
|
+
@admin.authenticate('bob', 'secret')
|
42
|
+
@db1.add_user('user1', 'secret')
|
43
|
+
@db2.add_user('user2', 'secret')
|
44
|
+
@admin.logout
|
45
|
+
|
46
|
+
threaded_exec do
|
47
|
+
assert_raise Mongo::OperationFailure do
|
48
|
+
@db1['stuff'].insert({:a => 2}, :safe => true)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
threaded_exec do
|
53
|
+
assert_raise Mongo::OperationFailure do
|
54
|
+
@db2['stuff'].insert({:a => 2}, :safe => true)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
@db1.authenticate('user1', 'secret')
|
59
|
+
@db2.authenticate('user2', 'secret')
|
60
|
+
|
61
|
+
threaded_exec do
|
62
|
+
assert @db1['stuff'].insert({:a => 2}, :safe => true)
|
63
|
+
end
|
64
|
+
|
65
|
+
threaded_exec do
|
66
|
+
assert @db2['stuff'].insert({:a => 2}, :safe => true)
|
67
|
+
end
|
68
|
+
|
69
|
+
puts "Please bounce the server."
|
70
|
+
gets
|
71
|
+
|
72
|
+
# Here we reconnect.
|
73
|
+
begin
|
74
|
+
@db1['stuff'].find.to_a
|
75
|
+
rescue Mongo::ConnectionFailure
|
76
|
+
end
|
77
|
+
|
78
|
+
threaded_exec do
|
79
|
+
assert @db1['stuff'].insert({:a => 2}, :safe => true)
|
80
|
+
end
|
81
|
+
|
82
|
+
threaded_exec do
|
83
|
+
assert @db2['stuff'].insert({:a => 2}, :safe => true)
|
84
|
+
end
|
85
|
+
|
86
|
+
@db1.logout
|
87
|
+
threaded_exec do
|
88
|
+
assert_raise Mongo::OperationFailure do
|
89
|
+
@db1['stuff'].insert({:a => 2}, :safe => true)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
@db2.logout
|
94
|
+
threaded_exec do
|
95
|
+
assert_raise Mongo::OperationFailure do
|
96
|
+
assert @db2['stuff'].insert({:a => 2}, :safe => true)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
data/test/bson/bson_test.rb
CHANGED
@@ -562,4 +562,47 @@ class BSONTest < Test::Unit::TestCase
|
|
562
562
|
end
|
563
563
|
end
|
564
564
|
|
565
|
+
def test_invalid_key_names
|
566
|
+
assert @encoder.serialize({"hello" => "world"}, true)
|
567
|
+
assert @encoder.serialize({"hello" => {"hello" => "world"}}, true)
|
568
|
+
|
569
|
+
assert @encoder.serialize({"he$llo" => "world"}, true)
|
570
|
+
assert @encoder.serialize({"hello" => {"hell$o" => "world"}}, true)
|
571
|
+
|
572
|
+
assert_raise BSON::InvalidDocument do
|
573
|
+
@encoder.serialize({"he\0llo" => "world"}, true)
|
574
|
+
end
|
575
|
+
|
576
|
+
assert_raise_error BSON::InvalidKeyName, "$hello" do
|
577
|
+
@encoder.serialize({"$hello" => "world"}, true)
|
578
|
+
end
|
579
|
+
|
580
|
+
assert_raise BSON::InvalidKeyName do
|
581
|
+
@encoder.serialize({"hello" => {"$hello" => "world"}}, true)
|
582
|
+
end
|
583
|
+
|
584
|
+
assert_raise_error BSON::InvalidKeyName, ".hello" do
|
585
|
+
@encoder.serialize({".hello" => "world"}, true)
|
586
|
+
end
|
587
|
+
|
588
|
+
assert_raise BSON::InvalidKeyName do
|
589
|
+
@encoder.serialize({"hello" => {".hello" => "world"}}, true)
|
590
|
+
end
|
591
|
+
|
592
|
+
assert_raise BSON::InvalidKeyName do
|
593
|
+
@encoder.serialize({"hello." => "world"}, true)
|
594
|
+
end
|
595
|
+
|
596
|
+
assert_raise BSON::InvalidKeyName do
|
597
|
+
@encoder.serialize({"hello" => {"hello." => "world"}}, true)
|
598
|
+
end
|
599
|
+
|
600
|
+
assert_raise BSON::InvalidKeyName do
|
601
|
+
@encoder.serialize({"hel.lo" => "world"}, true)
|
602
|
+
end
|
603
|
+
|
604
|
+
assert_raise BSON::InvalidKeyName do
|
605
|
+
@encoder.serialize({"hello" => {"hel.lo" => "world"}}, true)
|
606
|
+
end
|
607
|
+
end
|
565
608
|
end
|
data/test/connection_test.rb
CHANGED
@@ -167,7 +167,7 @@ class TestConnection < Test::Unit::TestCase
|
|
167
167
|
|
168
168
|
def test_max_bson_size_value
|
169
169
|
conn = standard_connection
|
170
|
-
if conn.server_version > "1.
|
170
|
+
if conn.server_version > "1.7.2"
|
171
171
|
assert_equal conn['admin'].command({:ismaster => 1})['maxBsonObjectSize'], conn.max_bson_size
|
172
172
|
end
|
173
173
|
|
data/test/db_api_test.rb
CHANGED
@@ -621,43 +621,6 @@ class DBAPITest < Test::Unit::TestCase
|
|
621
621
|
assert_equal("mike", @@coll.find_one()["hello"])
|
622
622
|
end
|
623
623
|
|
624
|
-
def test_invalid_key_names
|
625
|
-
@@coll.remove
|
626
|
-
|
627
|
-
@@coll.insert({"hello" => "world"})
|
628
|
-
@@coll.insert({"hello" => {"hello" => "world"}})
|
629
|
-
|
630
|
-
assert_raise BSON::InvalidKeyName do
|
631
|
-
@@coll.insert({"$hello" => "world"})
|
632
|
-
end
|
633
|
-
|
634
|
-
assert_raise BSON::InvalidKeyName do
|
635
|
-
@@coll.insert({"hello" => {"$hello" => "world"}})
|
636
|
-
end
|
637
|
-
|
638
|
-
@@coll.insert({"he$llo" => "world"})
|
639
|
-
@@coll.insert({"hello" => {"hell$o" => "world"}})
|
640
|
-
|
641
|
-
assert_raise BSON::InvalidKeyName do
|
642
|
-
@@coll.insert({".hello" => "world"})
|
643
|
-
end
|
644
|
-
assert_raise BSON::InvalidKeyName do
|
645
|
-
@@coll.insert({"hello" => {".hello" => "world"}})
|
646
|
-
end
|
647
|
-
assert_raise BSON::InvalidKeyName do
|
648
|
-
@@coll.insert({"hello." => "world"})
|
649
|
-
end
|
650
|
-
assert_raise BSON::InvalidKeyName do
|
651
|
-
@@coll.insert({"hello" => {"hello." => "world"}})
|
652
|
-
end
|
653
|
-
assert_raise BSON::InvalidKeyName do
|
654
|
-
@@coll.insert({"hel.lo" => "world"})
|
655
|
-
end
|
656
|
-
assert_raise BSON::InvalidKeyName do
|
657
|
-
@@coll.insert({"hello" => {"hel.lo" => "world"}})
|
658
|
-
end
|
659
|
-
end
|
660
|
-
|
661
624
|
def test_collection_names
|
662
625
|
assert_raise TypeError do
|
663
626
|
@@db.collection(5)
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', '..', '..', 'lib', 'mongo')
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
$con = Mongo::Connection.new
|
5
|
+
$db = $con['foo']
|
6
|
+
|
7
|
+
class Load < Sinatra::Base
|
8
|
+
|
9
|
+
configure do
|
10
|
+
LOGGER = Logger.new("sinatra.log")
|
11
|
+
enable :logging, :dump_errors
|
12
|
+
set :raise_errors, true
|
13
|
+
end
|
14
|
+
|
15
|
+
get '/' do
|
16
|
+
3.times do |n|
|
17
|
+
if (v=$db.eval("1 + #{n}")) != 1 + n
|
18
|
+
STDERR << "#{1 + n} expected but got #{v}"
|
19
|
+
raise StandardError, "#{1 + n} expected but got #{v}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'mongo')
|
2
|
+
|
3
|
+
$con = Mongo::Connection.new
|
4
|
+
$db = $con['foo']
|
5
|
+
|
6
|
+
class Load < Sinatra::Base
|
7
|
+
|
8
|
+
configure do
|
9
|
+
LOGGER = Logger.new("sinatra.log")
|
10
|
+
enable :logging, :dump_errors
|
11
|
+
set :raise_errors, true
|
12
|
+
end
|
13
|
+
|
14
|
+
get '/' do
|
15
|
+
3.times do |n|
|
16
|
+
if (v=$db.eval("1 + #{n}")) != 1 + n
|
17
|
+
STDERR << "#{1 + n} expected but got #{v}"
|
18
|
+
raise StandardError, "#{1 + n} expected but got #{v}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# set path to app that will be used to configure unicorn,
|
2
|
+
# # note the trailing slash in this example
|
3
|
+
@dir = "/home/kyle/work/10gen/ruby-driver/test/load/"
|
4
|
+
|
5
|
+
worker_processes 10
|
6
|
+
working_directory @dir
|
7
|
+
|
8
|
+
preload_app true
|
9
|
+
|
10
|
+
timeout 30
|
11
|
+
|
12
|
+
# Specify path to socket unicorn listens to,
|
13
|
+
# we will use this in our nginx.conf later
|
14
|
+
listen "#{@dir}tmp/sockets/unicorn.sock", :backlog => 64
|
15
|
+
|
16
|
+
# Set process id path
|
17
|
+
pid "#{@dir}tmp/pids/unicorn.pid"
|
18
|
+
|
19
|
+
# # Set log file paths
|
20
|
+
stderr_path "#{@dir}log/unicorn.stderr.log"
|
21
|
+
stdout_path "#{@dir}log/unicorn.stdout.log"
|
22
|
+
|
23
|
+
# NOTE: You need this when using forking web servers!
|
24
|
+
after_fork do |server, worker|
|
25
|
+
$con.close if $con
|
26
|
+
$con = Mongo::Connection.new
|
27
|
+
$db = $con['foo']
|
28
|
+
STDERR << "FORKED #{server} #{worker}"
|
29
|
+
end
|