mongo 1.9.2 → 1.10.0.rc0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/LICENSE +1 -1
- data/README.md +94 -334
- data/Rakefile +6 -4
- data/VERSION +1 -1
- data/bin/mongo_console +13 -6
- data/lib/mongo.rb +22 -27
- data/lib/mongo/bulk_write_collection_view.rb +352 -0
- data/lib/mongo/collection.rb +128 -188
- data/lib/mongo/collection_writer.rb +348 -0
- data/lib/mongo/connection.rb +19 -0
- data/lib/mongo/{util → connection}/node.rb +15 -1
- data/lib/mongo/{util → connection}/pool.rb +34 -19
- data/lib/mongo/{util → connection}/pool_manager.rb +8 -2
- data/lib/mongo/{util → connection}/sharding_pool_manager.rb +1 -1
- data/lib/mongo/connection/socket.rb +18 -0
- data/lib/mongo/{util → connection/socket}/socket_util.rb +5 -2
- data/lib/mongo/{util → connection/socket}/ssl_socket.rb +3 -4
- data/lib/mongo/{util → connection/socket}/tcp_socket.rb +25 -15
- data/lib/mongo/{util → connection/socket}/unix_socket.rb +6 -4
- data/lib/mongo/cursor.rb +113 -47
- data/lib/mongo/db.rb +203 -131
- data/lib/mongo/{exceptions.rb → exception.rb} +7 -1
- data/lib/mongo/functional.rb +19 -0
- data/lib/mongo/functional/authentication.rb +303 -0
- data/lib/mongo/{util → functional}/logging.rb +1 -1
- data/lib/mongo/{util → functional}/read_preference.rb +49 -1
- data/lib/mongo/{util → functional}/uri_parser.rb +81 -69
- data/lib/mongo/{util → functional}/write_concern.rb +2 -1
- data/{test/unit/pool_test.rb → lib/mongo/gridfs.rb} +5 -10
- data/lib/mongo/gridfs/grid.rb +1 -3
- data/lib/mongo/gridfs/grid_ext.rb +1 -1
- data/lib/mongo/gridfs/grid_file_system.rb +1 -1
- data/lib/mongo/gridfs/grid_io.rb +1 -1
- data/lib/mongo/legacy.rb +63 -8
- data/lib/mongo/mongo_client.rb +128 -154
- data/lib/mongo/mongo_replica_set_client.rb +17 -11
- data/lib/mongo/mongo_sharded_client.rb +2 -1
- data/lib/mongo/networking.rb +19 -10
- data/lib/mongo/utils.rb +19 -0
- data/lib/mongo/{util → utils}/conversions.rb +1 -1
- data/lib/mongo/{util → utils}/core_ext.rb +1 -1
- data/lib/mongo/{util → utils}/server_version.rb +1 -1
- data/lib/mongo/{util → utils}/support.rb +10 -57
- data/lib/mongo/{util → utils}/thread_local_variable_manager.rb +1 -1
- data/test/functional/authentication_test.rb +8 -21
- data/test/functional/bulk_write_collection_view_test.rb +782 -0
- data/test/functional/{connection_test.rb → client_test.rb} +153 -78
- data/test/functional/collection_test.rb +343 -97
- data/test/functional/collection_writer_test.rb +83 -0
- data/test/functional/conversions_test.rb +1 -3
- data/test/functional/cursor_fail_test.rb +3 -3
- data/test/functional/cursor_message_test.rb +3 -3
- data/test/functional/cursor_test.rb +38 -3
- data/test/functional/db_api_test.rb +5 -5
- data/test/functional/db_connection_test.rb +2 -2
- data/test/functional/db_test.rb +35 -11
- data/test/functional/grid_file_system_test.rb +2 -2
- data/test/functional/grid_io_test.rb +2 -2
- data/test/functional/grid_test.rb +2 -2
- data/test/functional/pool_test.rb +2 -3
- data/test/functional/safe_test.rb +5 -5
- data/test/functional/ssl_test.rb +22 -102
- data/test/functional/support_test.rb +1 -1
- data/test/functional/timeout_test.rb +6 -22
- data/test/functional/uri_test.rb +113 -12
- data/test/functional/write_concern_test.rb +6 -6
- data/test/helpers/general.rb +50 -0
- data/test/helpers/test_unit.rb +309 -0
- data/test/replica_set/authentication_test.rb +8 -23
- data/test/replica_set/basic_test.rb +41 -14
- data/test/replica_set/client_test.rb +179 -117
- data/test/replica_set/complex_connect_test.rb +6 -7
- data/test/replica_set/connection_test.rb +46 -38
- data/test/replica_set/count_test.rb +2 -2
- data/test/replica_set/cursor_test.rb +8 -8
- data/test/replica_set/insert_test.rb +64 -2
- data/test/replica_set/max_values_test.rb +59 -10
- data/test/replica_set/pinning_test.rb +2 -2
- data/test/replica_set/query_test.rb +2 -2
- data/test/replica_set/read_preference_test.rb +6 -6
- data/test/replica_set/refresh_test.rb +7 -7
- data/test/replica_set/replication_ack_test.rb +5 -5
- data/test/replica_set/ssl_test.rb +24 -106
- data/test/sharded_cluster/basic_test.rb +43 -15
- data/test/shared/authentication/basic_auth_shared.rb +215 -0
- data/test/shared/authentication/sasl_plain_shared.rb +96 -0
- data/test/shared/ssl_shared.rb +173 -0
- data/test/test_helper.rb +31 -199
- data/test/threading/basic_test.rb +29 -3
- data/test/tools/mongo_config.rb +45 -20
- data/test/tools/mongo_config_test.rb +1 -1
- data/test/unit/client_test.rb +136 -57
- data/test/unit/collection_test.rb +31 -55
- data/test/unit/connection_test.rb +135 -72
- data/test/unit/cursor_test.rb +2 -2
- data/test/unit/db_test.rb +19 -15
- data/test/unit/grid_test.rb +2 -2
- data/test/unit/mongo_sharded_client_test.rb +17 -15
- data/test/unit/node_test.rb +2 -2
- data/test/unit/pool_manager_test.rb +7 -5
- data/test/unit/read_pref_test.rb +82 -2
- data/test/unit/read_test.rb +14 -14
- data/test/unit/safe_test.rb +9 -9
- data/test/unit/sharding_pool_manager_test.rb +11 -5
- data/test/unit/write_concern_test.rb +9 -9
- metadata +71 -56
- metadata.gz.sig +0 -0
- data/test/functional/threading_test.rb +0 -109
- data/test/shared/authentication.rb +0 -121
- data/test/unit/util_test.rb +0 -69
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (C) 2013
|
1
|
+
# Copyright (C) 2009-2013 MongoDB, Inc.
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
@@ -65,6 +65,9 @@ module Mongo
|
|
65
65
|
# Raised when a database operation fails.
|
66
66
|
class OperationFailure < MongoDBError; end
|
67
67
|
|
68
|
+
# Raised when a database operation exceeds maximum specified time.
|
69
|
+
class ExecutionTimeout < OperationFailure; end
|
70
|
+
|
68
71
|
# Raised when a socket read operation times out.
|
69
72
|
class OperationTimeout < SocketError; end
|
70
73
|
|
@@ -76,4 +79,7 @@ module Mongo
|
|
76
79
|
|
77
80
|
# Raised when the client supplies an invalid value to sort by.
|
78
81
|
class InvalidSortValueError < MongoRubyError; end
|
82
|
+
|
83
|
+
# Raised for bulk write errors.
|
84
|
+
class BulkWriteError < OperationFailure; end
|
79
85
|
end
|
@@ -0,0 +1,19 @@
|
|
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 'mongo/functional/authentication'
|
16
|
+
require 'mongo/functional/logging'
|
17
|
+
require 'mongo/functional/read_preference'
|
18
|
+
require 'mongo/functional/write_concern'
|
19
|
+
require 'mongo/functional/uri_parser'
|
@@ -0,0 +1,303 @@
|
|
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 'digest/md5'
|
16
|
+
|
17
|
+
module Mongo
|
18
|
+
module Authentication
|
19
|
+
|
20
|
+
DEFAULT_MECHANISM = 'MONGODB-CR'
|
21
|
+
MECHANISMS = ['GSSAPI', 'MONGODB-CR', 'MONGODB-X509', 'PLAIN']
|
22
|
+
|
23
|
+
# authentication module methods
|
24
|
+
class << self
|
25
|
+
# Helper to validate an authentication mechanism and optionally
|
26
|
+
# raise an error if invalid.
|
27
|
+
#
|
28
|
+
# @param mechanism [String] [description]
|
29
|
+
# @param raise_error [Boolean] [description]
|
30
|
+
#
|
31
|
+
# @raise [ArgumentError] if raise_error and not a valid auth mechanism.
|
32
|
+
# @return [Boolean] returns the validation result.
|
33
|
+
def validate_mechanism(mechanism, raise_error=false)
|
34
|
+
return true if MECHANISMS.include?(mechanism.upcase)
|
35
|
+
if raise_error
|
36
|
+
raise ArgumentError,
|
37
|
+
"Invalid authentication mechanism provided. Must be one of " +
|
38
|
+
"#{Mongo::Authentication::MECHANISMS.join(', ')}."
|
39
|
+
end
|
40
|
+
false
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
# Helper to validate and normalize credential sets.
|
45
|
+
#
|
46
|
+
# @param auth [Hash] A hash containing the credential set.
|
47
|
+
#
|
48
|
+
# @raise [MongoArgumentError] if the credential set is invalid.
|
49
|
+
# @return [Hash] The validated credential set.
|
50
|
+
def validate_credentials(auth)
|
51
|
+
# set the default auth mechanism if not defined
|
52
|
+
auth[:mechanism] ||= DEFAULT_MECHANISM
|
53
|
+
|
54
|
+
# set the default auth source if not defined
|
55
|
+
auth[:source] = auth[:source] || auth[:db_name] || 'admin'
|
56
|
+
|
57
|
+
if (auth[:mechanism] == 'MONGODB-CR' || auth[:mechanism] == 'PLAIN') && !auth[:password]
|
58
|
+
raise MongoArgumentError,
|
59
|
+
"When using the authentication mechanism #{auth[:mechanism]} " +
|
60
|
+
"both username and password are required."
|
61
|
+
end
|
62
|
+
auth
|
63
|
+
end
|
64
|
+
|
65
|
+
# Generate an MD5 for authentication.
|
66
|
+
#
|
67
|
+
# @param username [String] The username.
|
68
|
+
# @param password [String] The user's password.
|
69
|
+
# @param nonce [String] The nonce value.
|
70
|
+
#
|
71
|
+
# @return [String] MD5 key for db authentication.
|
72
|
+
def auth_key(username, password, nonce)
|
73
|
+
Digest::MD5.hexdigest("#{nonce}#{username}#{hash_password(username, password)}")
|
74
|
+
end
|
75
|
+
|
76
|
+
# Return a hashed password for auth.
|
77
|
+
#
|
78
|
+
# @param username [String] The username.
|
79
|
+
# @param password [String] The users's password.
|
80
|
+
#
|
81
|
+
# @return [String] The hashed password value.
|
82
|
+
def hash_password(username, password)
|
83
|
+
Digest::MD5.hexdigest("#{username}:mongo:#{password}")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Saves a cache of authentication credentials to the current
|
88
|
+
# client instance. This method is called automatically by DB#authenticate.
|
89
|
+
#
|
90
|
+
# @param db_name [String] The current database name.
|
91
|
+
# @param username [String] The current username.
|
92
|
+
# @param password [String] (nil) The users's password (not required for
|
93
|
+
# all authentication mechanisms).
|
94
|
+
# @param source [String] (nil) The authentication source database
|
95
|
+
# (if different than the current database).
|
96
|
+
# @param mechanism [String] (nil) The authentication mechanism being used
|
97
|
+
# (default: 'MONGODB-CR').
|
98
|
+
#
|
99
|
+
# @raise [MongoArgumentError] Raised if the database has already been used
|
100
|
+
# for authentication. A log out is required before additional auths can
|
101
|
+
# be issued against a given database.
|
102
|
+
# @raise [AuthenticationError] Raised if authentication fails.
|
103
|
+
# @return [Hash] a hash representing the authentication just added.
|
104
|
+
def add_auth(db_name, username, password=nil, source=nil, mechanism=nil)
|
105
|
+
auth = Authentication.validate_credentials({
|
106
|
+
:db_name => db_name,
|
107
|
+
:username => username,
|
108
|
+
:password => password,
|
109
|
+
:source => source,
|
110
|
+
:mechanism => mechanism
|
111
|
+
})
|
112
|
+
|
113
|
+
if @auths.any? {|a| a[:source] == auth[:source]}
|
114
|
+
raise MongoArgumentError,
|
115
|
+
"Another user has already authenticated to the database " +
|
116
|
+
"'#{auth[:source]}' and multiple authentications are not " +
|
117
|
+
"permitted. Please logout first."
|
118
|
+
end
|
119
|
+
|
120
|
+
begin
|
121
|
+
socket = self.checkout_reader(:mode => :primary_preferred)
|
122
|
+
self.issue_authentication(auth, :socket => socket)
|
123
|
+
ensure
|
124
|
+
socket.checkin if socket
|
125
|
+
end
|
126
|
+
|
127
|
+
@auths << auth
|
128
|
+
auth
|
129
|
+
end
|
130
|
+
|
131
|
+
# Remove a saved authentication for this connection.
|
132
|
+
#
|
133
|
+
# @param db_name [String] The database name.
|
134
|
+
#
|
135
|
+
# @return [Boolean] The result of the operation.
|
136
|
+
def remove_auth(db_name)
|
137
|
+
return false unless @auths
|
138
|
+
@auths.reject! { |a| a[:source] == db_name } ? true : false
|
139
|
+
end
|
140
|
+
|
141
|
+
# Remove all authentication information stored in this connection.
|
142
|
+
#
|
143
|
+
# @return [Boolean] result of the operation.
|
144
|
+
def clear_auths
|
145
|
+
@auths = Set.new
|
146
|
+
true
|
147
|
+
end
|
148
|
+
|
149
|
+
# Method to handle and issue logout commands.
|
150
|
+
#
|
151
|
+
# @note This method should not be called directly. Use DB#logout.
|
152
|
+
#
|
153
|
+
# @param db_name [String] The database name.
|
154
|
+
# @param opts [Hash] Hash of optional settings and configuration values.
|
155
|
+
#
|
156
|
+
# @option opts [Socket] socket (nil) Optional socket instance to use.
|
157
|
+
#
|
158
|
+
# @raise [MongoDBError] Raised if the logout operation fails.
|
159
|
+
# @return [Boolean] The result of the logout operation.
|
160
|
+
def issue_logout(db_name, opts={})
|
161
|
+
doc = db(db_name).command({:logout => 1}, :socket => opts[:socket])
|
162
|
+
unless Support.ok?(doc)
|
163
|
+
raise MongoDBError, "Error logging out on DB #{db_name}."
|
164
|
+
end
|
165
|
+
true # somewhat pointless, but here to preserve the existing API
|
166
|
+
end
|
167
|
+
|
168
|
+
# Method to handle and issue authentication commands.
|
169
|
+
#
|
170
|
+
# @note This method should not be called directly. Use DB#authenticate.
|
171
|
+
#
|
172
|
+
# @param auth [Hash] The authentication credentials to be used.
|
173
|
+
# @param opts [Hash] Hash of optional settings and configuration values.
|
174
|
+
#
|
175
|
+
# @option opts [Socket] socket (nil) Optional socket instance to use.
|
176
|
+
#
|
177
|
+
# @raise [AuthenticationError] Raised if the authentication fails.
|
178
|
+
# @return [Boolean] Result of the authentication operation.
|
179
|
+
def issue_authentication(auth, opts={})
|
180
|
+
result = case auth[:mechanism]
|
181
|
+
when 'MONGODB-CR'
|
182
|
+
issue_cr(auth, opts)
|
183
|
+
when 'MONGODB-X509'
|
184
|
+
issue_x509(auth, opts)
|
185
|
+
when 'PLAIN'
|
186
|
+
issue_plain(auth, opts)
|
187
|
+
when 'GSSAPI'
|
188
|
+
issue_gssapi(auth, opts)
|
189
|
+
end
|
190
|
+
|
191
|
+
unless Support.ok?(result)
|
192
|
+
raise AuthenticationError,
|
193
|
+
"Failed to authenticate user '#{auth[:username]}' " +
|
194
|
+
"on db '#{auth[:source]}'."
|
195
|
+
end
|
196
|
+
|
197
|
+
true
|
198
|
+
end
|
199
|
+
|
200
|
+
private
|
201
|
+
|
202
|
+
# Handles issuing authentication commands for the MONGODB-CR auth mechanism.
|
203
|
+
#
|
204
|
+
# @param auth [Hash] The authentication credentials to be used.
|
205
|
+
# @param opts [Hash] Hash of optional settings and configuration values.
|
206
|
+
#
|
207
|
+
# @option opts [Socket] socket (nil) Optional socket instance to use.
|
208
|
+
#
|
209
|
+
# @return [Boolean] Result of the authentication operation.
|
210
|
+
#
|
211
|
+
# @private
|
212
|
+
def issue_cr(auth, opts={})
|
213
|
+
database = db(auth[:source])
|
214
|
+
nonce = get_nonce(database, opts)
|
215
|
+
|
216
|
+
# build auth command document
|
217
|
+
cmd = BSON::OrderedHash.new
|
218
|
+
cmd['authenticate'] = 1
|
219
|
+
cmd['user'] = auth[:username]
|
220
|
+
cmd['nonce'] = nonce
|
221
|
+
cmd['key'] = Authentication.auth_key(auth[:username],
|
222
|
+
auth[:password],
|
223
|
+
nonce)
|
224
|
+
|
225
|
+
database.command(cmd, :check_response => false,
|
226
|
+
:socket => opts[:socket])
|
227
|
+
end
|
228
|
+
|
229
|
+
# Handles issuing authentication commands for the MONGODB-X509 auth mechanism.
|
230
|
+
#
|
231
|
+
# @param auth [Hash] The authentication credentials to be used.
|
232
|
+
# @param opts [Hash] Hash of optional settings and configuration values.
|
233
|
+
#
|
234
|
+
# @private
|
235
|
+
def issue_x509(auth, opts={})
|
236
|
+
database = db('$external')
|
237
|
+
|
238
|
+
cmd = BSON::OrderedHash.new
|
239
|
+
cmd[:authenticate] = 1
|
240
|
+
cmd[:mechanism] = auth[:mechanism]
|
241
|
+
cmd[:user] = auth[:username]
|
242
|
+
|
243
|
+
database.command(cmd, :check_response => false,
|
244
|
+
:socket => opts[:socket])
|
245
|
+
end
|
246
|
+
|
247
|
+
# Handles issuing authentication commands for the PLAIN auth mechanism.
|
248
|
+
#
|
249
|
+
# @param auth [Hash] The authentication credentials to be used.
|
250
|
+
# @param opts [Hash] Hash of optional settings and configuration values.
|
251
|
+
#
|
252
|
+
# @option opts [Socket] socket (nil) Optional socket instance to use.
|
253
|
+
#
|
254
|
+
# @return [Boolean] Result of the authentication operation.
|
255
|
+
#
|
256
|
+
# @private
|
257
|
+
def issue_plain(auth, opts={})
|
258
|
+
database = db(auth[:source])
|
259
|
+
payload = "\x00#{auth[:username]}\x00#{auth[:password]}"
|
260
|
+
|
261
|
+
cmd = BSON::OrderedHash.new
|
262
|
+
cmd[:saslStart] = 1
|
263
|
+
cmd[:mechanism] = auth[:mechanism]
|
264
|
+
cmd[:payload] = BSON::Binary.new(payload)
|
265
|
+
cmd[:autoAuthorize] = 1
|
266
|
+
|
267
|
+
database.command(cmd, :check_response => false,
|
268
|
+
:socket => opts[:socket])
|
269
|
+
end
|
270
|
+
|
271
|
+
# Handles issuing authentication commands for the GSSAPI auth mechanism.
|
272
|
+
#
|
273
|
+
# @param auth [Hash] The authentication credentials to be used.
|
274
|
+
# @param opts [Hash] Hash of optional settings and configuration values.
|
275
|
+
#
|
276
|
+
# @private
|
277
|
+
def issue_gssapi(auth, opts={})
|
278
|
+
raise NotImplementedError,
|
279
|
+
"The #{auth[:mechanism]} authentication mechanism is not yet supported."
|
280
|
+
end
|
281
|
+
|
282
|
+
# Helper to fetch a nonce value from a given database instance.
|
283
|
+
#
|
284
|
+
# @param db [Mongo::DB] The DB instance to use for issue the nonce command.
|
285
|
+
# @param opts [Hash] Hash of optional settings and configuration values.
|
286
|
+
#
|
287
|
+
# @option opts [Socket] socket (nil) Optional socket instance to use.
|
288
|
+
#
|
289
|
+
# @raise [MongoDBError] Raised if there is an error executing the command.
|
290
|
+
# @return [String] Returns the nonce value.
|
291
|
+
#
|
292
|
+
# @private
|
293
|
+
def get_nonce(database, opts={})
|
294
|
+
doc = database.command({:getnonce => 1}, :check_response => false,
|
295
|
+
:socket => opts[:socket])
|
296
|
+
unless Support.ok?(doc)
|
297
|
+
raise MongoDBError, "Error retrieving nonce: #{doc}"
|
298
|
+
end
|
299
|
+
doc['nonce']
|
300
|
+
end
|
301
|
+
|
302
|
+
end
|
303
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (C) 2013
|
1
|
+
# Copyright (C) 2009-2013 MongoDB, Inc.
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
@@ -30,6 +30,24 @@ module Mongo
|
|
30
30
|
:nearest => 'nearest'
|
31
31
|
}
|
32
32
|
|
33
|
+
# Commands that may be sent to replica-set secondaries, depending on
|
34
|
+
# read preference and tags. All other commands are always run on the primary.
|
35
|
+
SECONDARY_OK_COMMANDS = [
|
36
|
+
'group',
|
37
|
+
'aggregate',
|
38
|
+
'collstats',
|
39
|
+
'dbstats',
|
40
|
+
'count',
|
41
|
+
'distinct',
|
42
|
+
'geonear',
|
43
|
+
'geosearch',
|
44
|
+
'geowalk',
|
45
|
+
'mapreduce',
|
46
|
+
'replsetgetstatus',
|
47
|
+
'ismaster',
|
48
|
+
'parallelcollectionscan'
|
49
|
+
]
|
50
|
+
|
33
51
|
def self.mongos(mode, tag_sets)
|
34
52
|
if mode != :secondary_preferred || !tag_sets.empty?
|
35
53
|
mongos_read_preference = BSON::OrderedHash[:mode => MONGOS_MODES[mode]]
|
@@ -47,6 +65,36 @@ module Mongo
|
|
47
65
|
end
|
48
66
|
end
|
49
67
|
|
68
|
+
# Returns true if it's ok to run the command on a secondary
|
69
|
+
def self.secondary_ok?(selector)
|
70
|
+
command = selector.keys.first.to_s.downcase
|
71
|
+
|
72
|
+
if command == 'mapreduce'
|
73
|
+
out = selector.select { |k, v| k.to_s.downcase == 'out' }.first.last
|
74
|
+
# the server only looks at the first key in the out object
|
75
|
+
return out.respond_to?(:keys) && out.keys.first.to_s.downcase == 'inline'
|
76
|
+
elsif command == 'aggregate'
|
77
|
+
return selector['pipeline'].none? { |op| op.key?('$out') || op.key?(:$out) }
|
78
|
+
end
|
79
|
+
SECONDARY_OK_COMMANDS.member?(command)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns true if the command should be rerouted to the primary.
|
83
|
+
def self.reroute_cmd_primary?(read_pref, selector)
|
84
|
+
return false if read_pref == :primary
|
85
|
+
!secondary_ok?(selector)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Given a command and read preference, possibly reroute to primary.
|
89
|
+
def self.cmd_read_pref(read_pref, selector)
|
90
|
+
ReadPreference::validate(read_pref)
|
91
|
+
if reroute_cmd_primary?(read_pref, selector)
|
92
|
+
warn "Database command '#{selector.keys.first}' rerouted to primary node"
|
93
|
+
read_pref = :primary
|
94
|
+
end
|
95
|
+
read_pref
|
96
|
+
end
|
97
|
+
|
50
98
|
def read_preference
|
51
99
|
{
|
52
100
|
:mode => @read,
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (C) 2013
|
1
|
+
# Copyright (C) 2009-2013 MongoDB, Inc.
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
@@ -18,11 +18,9 @@ require 'uri'
|
|
18
18
|
module Mongo
|
19
19
|
class URIParser
|
20
20
|
|
21
|
-
|
22
|
-
PASS_REGEX = /([^@,]+)/
|
23
|
-
AUTH_REGEX = /(#{USER_REGEX}:#{PASS_REGEX}@)?/
|
21
|
+
AUTH_REGEX = /((.+)@)?/
|
24
22
|
|
25
|
-
HOST_REGEX = /([-.\w]+)/
|
23
|
+
HOST_REGEX = /([-.\w]+)|(\[[^\]]+\])/
|
26
24
|
PORT_REGEX = /(?::(\w+))?/
|
27
25
|
NODE_REGEX = /((#{HOST_REGEX}#{PORT_REGEX},?)+)/
|
28
26
|
|
@@ -34,14 +32,16 @@ module Mongo
|
|
34
32
|
SPEC_ATTRS = [:nodes, :auths]
|
35
33
|
|
36
34
|
READ_PREFERENCES = {
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
35
|
+
'primary' => :primary,
|
36
|
+
'primarypreferred' => :primary_preferred,
|
37
|
+
'secondary' => :secondary,
|
38
|
+
'secondarypreferred' => :secondary_preferred,
|
39
|
+
'nearest' => :nearest
|
42
40
|
}
|
43
41
|
|
44
42
|
OPT_ATTRS = [
|
43
|
+
:authmechanism,
|
44
|
+
:authsource,
|
45
45
|
:connect,
|
46
46
|
:connecttimeoutms,
|
47
47
|
:fsync,
|
@@ -59,6 +59,8 @@ module Mongo
|
|
59
59
|
]
|
60
60
|
|
61
61
|
OPT_VALID = {
|
62
|
+
:authmechanism => lambda { |arg| Mongo::Authentication.validate_mechanism(arg) },
|
63
|
+
:authsource => lambda { |arg| arg.length > 0 },
|
62
64
|
:connect => lambda { |arg| [ 'direct', 'replicaset', 'true', 'false', true, false ].include?(arg) },
|
63
65
|
:connecttimeoutms => lambda { |arg| arg =~ /^\d+$/ },
|
64
66
|
:fsync => lambda { |arg| ['true', 'false'].include?(arg) },
|
@@ -76,6 +78,8 @@ module Mongo
|
|
76
78
|
}
|
77
79
|
|
78
80
|
OPT_ERR = {
|
81
|
+
:authmechanism => "must be one of #{Mongo::Authentication::MECHANISMS.join(', ')}",
|
82
|
+
:authsource => "must be a string containing the name of the database being used for authentication",
|
79
83
|
:connect => "must be 'direct', 'replicaset', 'true', or 'false'",
|
80
84
|
:connecttimeoutms => "must be an integer specifying milliseconds",
|
81
85
|
:fsync => "must be 'true' or 'false'",
|
@@ -85,7 +89,7 @@ module Mongo
|
|
85
89
|
:replicaset => "must be a string containing the name of the replica set to connect to",
|
86
90
|
:safe => "must be 'true' or 'false'",
|
87
91
|
:slaveok => "must be 'true' or 'false'",
|
88
|
-
:
|
92
|
+
:settimeoutms => "must be an integer specifying milliseconds",
|
89
93
|
:ssl => "must be 'true' or 'false'",
|
90
94
|
:w => "must be an integer indicating number of nodes to replicate to or a string " +
|
91
95
|
"specifying that replication is required to the majority or nodes with a " +
|
@@ -95,6 +99,8 @@ module Mongo
|
|
95
99
|
}
|
96
100
|
|
97
101
|
OPT_CONV = {
|
102
|
+
:authmechanism => lambda { |arg| arg.upcase },
|
103
|
+
:authsource => lambda { |arg| arg },
|
98
104
|
:connect => lambda { |arg| arg == 'false' ? false : arg }, # convert 'false' to FalseClass
|
99
105
|
:connecttimeoutms => lambda { |arg| arg.to_f / 1000 }, # stored as seconds
|
100
106
|
:fsync => lambda { |arg| arg == 'true' ? true : false },
|
@@ -112,8 +118,11 @@ module Mongo
|
|
112
118
|
}
|
113
119
|
|
114
120
|
attr_reader :auths,
|
121
|
+
:authmechanism,
|
122
|
+
:authsource,
|
115
123
|
:connect,
|
116
124
|
:connecttimeoutms,
|
125
|
+
:db_name,
|
117
126
|
:fsync,
|
118
127
|
:journal,
|
119
128
|
:nodes,
|
@@ -134,9 +143,8 @@ module Mongo
|
|
134
143
|
# @note Passwords can contain any character except for ','
|
135
144
|
#
|
136
145
|
# @param [String] uri The MongoDB URI string.
|
137
|
-
# @param [Hash,nil] extra_opts Extra options. Will override anything
|
138
|
-
#
|
139
|
-
# @core connections
|
146
|
+
# @param [Hash,nil] extra_opts Extra options. Will override anything
|
147
|
+
# already specified in the URI.
|
140
148
|
def initialize(uri)
|
141
149
|
if uri.start_with?('mongodb://')
|
142
150
|
uri = uri[10..-1]
|
@@ -145,8 +153,8 @@ module Mongo
|
|
145
153
|
end
|
146
154
|
|
147
155
|
hosts, opts = uri.split('?')
|
148
|
-
parse_hosts(hosts)
|
149
156
|
parse_options(opts)
|
157
|
+
parse_hosts(hosts)
|
150
158
|
validate_connect
|
151
159
|
end
|
152
160
|
|
@@ -155,7 +163,7 @@ module Mongo
|
|
155
163
|
# @note Don't confuse this with attribute getter method #connect.
|
156
164
|
#
|
157
165
|
# @return [MongoClient,MongoReplicaSetClient]
|
158
|
-
def connection(extra_opts, legacy = false, sharded = false)
|
166
|
+
def connection(extra_opts={}, legacy = false, sharded = false)
|
159
167
|
opts = connection_options.merge!(extra_opts)
|
160
168
|
if(legacy)
|
161
169
|
if replicaset?
|
@@ -218,26 +226,15 @@ module Mongo
|
|
218
226
|
end
|
219
227
|
opts[:wtimeout] = @wtimeoutms
|
220
228
|
|
221
|
-
opts[:w]
|
222
|
-
opts[:w]
|
223
|
-
opts[:j]
|
229
|
+
opts[:w] = 1 if @safe
|
230
|
+
opts[:w] = @w if @w
|
231
|
+
opts[:j] = @journal
|
224
232
|
opts[:fsync] = @fsync
|
225
233
|
|
226
|
-
if @connecttimeoutms
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
if @sockettimeoutms
|
231
|
-
opts[:op_timeout] = @sockettimeoutms
|
232
|
-
end
|
233
|
-
|
234
|
-
if @pool_size
|
235
|
-
opts[:pool_size] = @pool_size
|
236
|
-
end
|
237
|
-
|
238
|
-
if @readpreference
|
239
|
-
opts[:read] = @readpreference
|
240
|
-
end
|
234
|
+
opts[:connect_timeout] = @connecttimeoutms if @connecttimeoutms
|
235
|
+
opts[:op_timeout] = @sockettimeoutms if @sockettimeoutms
|
236
|
+
opts[:pool_size] = @pool_size if @pool_size
|
237
|
+
opts[:read] = @readpreference if @readpreference
|
241
238
|
|
242
239
|
if @slaveok && !@readpreference
|
243
240
|
unless replicaset?
|
@@ -247,14 +244,13 @@ module Mongo
|
|
247
244
|
end
|
248
245
|
end
|
249
246
|
|
250
|
-
opts[:ssl] = @ssl
|
251
|
-
opts[:auths] = auths
|
252
|
-
|
253
247
|
if replicaset.is_a?(String)
|
254
248
|
opts[:name] = replicaset
|
255
249
|
end
|
256
250
|
|
257
|
-
opts[:
|
251
|
+
opts[:db_name] = @db_name if @db_name
|
252
|
+
opts[:auths] = @auths if @auths
|
253
|
+
opts[:ssl] = @ssl
|
258
254
|
opts[:connect] = connect?
|
259
255
|
|
260
256
|
opts
|
@@ -266,47 +262,63 @@ module Mongo
|
|
266
262
|
|
267
263
|
private
|
268
264
|
|
269
|
-
def parse_hosts(
|
265
|
+
def parse_hosts(uri_without_protocol)
|
270
266
|
@nodes = []
|
271
|
-
@auths =
|
267
|
+
@auths = Set.new
|
272
268
|
|
273
|
-
matches = MONGODB_URI_MATCHER.match(
|
274
|
-
|
275
|
-
|
276
|
-
raise MongoArgumentError, "MongoDB URI must match this spec: #{MONGODB_URI_SPEC}"
|
269
|
+
unless matches = MONGODB_URI_MATCHER.match(uri_without_protocol)
|
270
|
+
raise MongoArgumentError,
|
271
|
+
"MongoDB URI must match this spec: #{MONGODB_URI_SPEC}"
|
277
272
|
end
|
278
273
|
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
@db = matches[8]
|
283
|
-
|
284
|
-
hosturis.each do |hosturi|
|
285
|
-
# If port is present, use it, otherwise use default port
|
286
|
-
host, port = hosturi.split(':') + [MongoClient::DEFAULT_PORT]
|
274
|
+
user_info = matches[2].split(':') if matches[2]
|
275
|
+
host_info = matches[3].split(',')
|
276
|
+
@db_name = matches[8]
|
287
277
|
|
288
|
-
|
289
|
-
|
278
|
+
host_info.each do |host|
|
279
|
+
if host[0,1] == '['
|
280
|
+
host, port = host.split(']:') << MongoClient::DEFAULT_PORT
|
281
|
+
host = host.end_with?(']') ? host[1...-1] : host[1..-1]
|
282
|
+
else
|
283
|
+
host, port = host.split(':') << MongoClient::DEFAULT_PORT
|
290
284
|
end
|
291
|
-
|
292
|
-
|
293
|
-
|
285
|
+
unless port.to_s =~ /^\d+$/
|
286
|
+
raise MongoArgumentError,
|
287
|
+
"Invalid port #{port}; port must be specified as digits."
|
288
|
+
end
|
289
|
+
@nodes << [host, port.to_i]
|
294
290
|
end
|
295
291
|
|
296
292
|
if @nodes.empty?
|
297
|
-
raise MongoArgumentError,
|
293
|
+
raise MongoArgumentError,
|
294
|
+
"No nodes specified. Please ensure that you've provided at " +
|
295
|
+
"least one node."
|
296
|
+
end
|
297
|
+
|
298
|
+
# no user info to parse, exit here
|
299
|
+
return unless user_info
|
300
|
+
|
301
|
+
# check for url encoding for username and password
|
302
|
+
username, password = user_info
|
303
|
+
if user_info.size > 2 ||
|
304
|
+
(username && username.include?('@')) ||
|
305
|
+
(password && password.include?('@'))
|
306
|
+
|
307
|
+
raise MongoArgumentError,
|
308
|
+
"The characters ':' and '@' in a username or password " +
|
309
|
+
"must be escaped (RFC 2396)."
|
298
310
|
end
|
299
311
|
|
300
|
-
if
|
301
|
-
|
302
|
-
|
303
|
-
:
|
304
|
-
:
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
312
|
+
# if username exists, proceed adding to auth set
|
313
|
+
unless username.nil? || username.empty?
|
314
|
+
auth = Authentication.validate_credentials({
|
315
|
+
:db_name => @db_name,
|
316
|
+
:username => URI.unescape(username),
|
317
|
+
:password => password ? URI.unescape(password) : nil,
|
318
|
+
:source => @authsource,
|
319
|
+
:mechanism => @authmechanism
|
320
|
+
})
|
321
|
+
@auths << auth
|
310
322
|
end
|
311
323
|
end
|
312
324
|
|
@@ -321,7 +333,7 @@ module Mongo
|
|
321
333
|
return if string_opts.empty?
|
322
334
|
|
323
335
|
if string_opts.include?(';') and string_opts.include?('&')
|
324
|
-
raise MongoArgumentError,
|
336
|
+
raise MongoArgumentError, 'must not mix URL separators ; and &'
|
325
337
|
end
|
326
338
|
|
327
339
|
opts = CGI.parse(string_opts).inject({}) do |memo, (key, value)|
|