mongo 1.3.0 → 1.12.5
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.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data/{LICENSE.txt → LICENSE} +1 -1
- data/README.md +122 -271
- data/Rakefile +25 -209
- data/VERSION +1 -0
- data/bin/mongo_console +31 -9
- data/lib/mongo/bulk_write_collection_view.rb +387 -0
- data/lib/mongo/collection.rb +576 -269
- data/lib/mongo/collection_writer.rb +364 -0
- data/lib/mongo/connection/node.rb +249 -0
- data/lib/mongo/connection/pool.rb +340 -0
- data/lib/mongo/connection/pool_manager.rb +320 -0
- data/lib/mongo/connection/sharding_pool_manager.rb +67 -0
- data/lib/mongo/connection/socket/socket_util.rb +37 -0
- data/lib/mongo/connection/socket/ssl_socket.rb +95 -0
- data/lib/mongo/connection/socket/tcp_socket.rb +87 -0
- data/lib/mongo/connection/socket/unix_socket.rb +39 -0
- data/lib/mongo/connection/socket.rb +18 -0
- data/lib/mongo/connection.rb +7 -875
- data/lib/mongo/cursor.rb +403 -117
- data/lib/mongo/db.rb +444 -243
- data/lib/mongo/exception.rb +145 -0
- data/lib/mongo/functional/authentication.rb +455 -0
- data/lib/mongo/functional/logging.rb +85 -0
- data/lib/mongo/functional/read_preference.rb +183 -0
- data/lib/mongo/functional/scram.rb +556 -0
- data/lib/mongo/functional/uri_parser.rb +409 -0
- data/lib/mongo/functional/write_concern.rb +66 -0
- data/lib/mongo/functional.rb +20 -0
- data/lib/mongo/gridfs/grid.rb +30 -24
- data/lib/mongo/gridfs/grid_ext.rb +6 -10
- data/lib/mongo/gridfs/grid_file_system.rb +38 -20
- data/lib/mongo/gridfs/grid_io.rb +84 -75
- data/lib/mongo/gridfs.rb +18 -0
- data/lib/mongo/legacy.rb +140 -0
- data/lib/mongo/mongo_client.rb +697 -0
- data/lib/mongo/mongo_replica_set_client.rb +535 -0
- data/lib/mongo/mongo_sharded_client.rb +159 -0
- data/lib/mongo/networking.rb +372 -0
- data/lib/mongo/{util → utils}/conversions.rb +29 -8
- data/lib/mongo/{util → utils}/core_ext.rb +28 -18
- data/lib/mongo/{util → utils}/server_version.rb +4 -6
- data/lib/mongo/{util → utils}/support.rb +29 -31
- data/lib/mongo/utils/thread_local_variable_manager.rb +25 -0
- data/lib/mongo/utils.rb +19 -0
- data/lib/mongo.rb +51 -50
- data/mongo.gemspec +29 -32
- data/test/functional/authentication_test.rb +39 -0
- data/test/functional/bulk_api_stress_test.rb +133 -0
- data/test/functional/bulk_write_collection_view_test.rb +1198 -0
- data/test/functional/client_test.rb +627 -0
- data/test/functional/collection_test.rb +2175 -0
- data/test/functional/collection_writer_test.rb +83 -0
- data/test/{conversions_test.rb → functional/conversions_test.rb} +47 -3
- data/test/functional/cursor_fail_test.rb +57 -0
- data/test/functional/cursor_message_test.rb +56 -0
- data/test/functional/cursor_test.rb +683 -0
- data/test/functional/db_api_test.rb +835 -0
- data/test/functional/db_connection_test.rb +25 -0
- data/test/functional/db_test.rb +348 -0
- data/test/functional/grid_file_system_test.rb +285 -0
- data/test/{grid_io_test.rb → functional/grid_io_test.rb} +72 -11
- data/test/{grid_test.rb → functional/grid_test.rb} +88 -15
- data/test/functional/pool_test.rb +136 -0
- data/test/functional/safe_test.rb +98 -0
- data/test/functional/ssl_test.rb +29 -0
- data/test/functional/support_test.rb +62 -0
- data/test/functional/timeout_test.rb +60 -0
- data/test/functional/uri_test.rb +446 -0
- data/test/functional/write_concern_test.rb +118 -0
- data/test/helpers/general.rb +50 -0
- data/test/helpers/test_unit.rb +476 -0
- data/test/replica_set/authentication_test.rb +37 -0
- data/test/replica_set/basic_test.rb +189 -0
- data/test/replica_set/client_test.rb +393 -0
- data/test/replica_set/connection_test.rb +138 -0
- data/test/replica_set/count_test.rb +66 -0
- data/test/replica_set/cursor_test.rb +220 -0
- data/test/replica_set/insert_test.rb +157 -0
- data/test/replica_set/max_values_test.rb +151 -0
- data/test/replica_set/pinning_test.rb +105 -0
- data/test/replica_set/query_test.rb +73 -0
- data/test/replica_set/read_preference_test.rb +219 -0
- data/test/replica_set/refresh_test.rb +211 -0
- data/test/replica_set/replication_ack_test.rb +95 -0
- data/test/replica_set/ssl_test.rb +32 -0
- data/test/sharded_cluster/basic_test.rb +203 -0
- data/test/shared/authentication/basic_auth_shared.rb +260 -0
- data/test/shared/authentication/bulk_api_auth_shared.rb +249 -0
- data/test/shared/authentication/gssapi_shared.rb +176 -0
- data/test/shared/authentication/sasl_plain_shared.rb +96 -0
- data/test/shared/authentication/scram_shared.rb +92 -0
- data/test/shared/ssl_shared.rb +235 -0
- data/test/test_helper.rb +53 -94
- data/test/threading/basic_test.rb +120 -0
- data/test/tools/mongo_config.rb +708 -0
- data/test/tools/mongo_config_test.rb +160 -0
- data/test/unit/client_test.rb +381 -0
- data/test/unit/collection_test.rb +89 -53
- data/test/unit/connection_test.rb +282 -32
- data/test/unit/cursor_test.rb +206 -8
- data/test/unit/db_test.rb +55 -13
- data/test/unit/grid_test.rb +43 -16
- data/test/unit/mongo_sharded_client_test.rb +48 -0
- data/test/unit/node_test.rb +93 -0
- data/test/unit/pool_manager_test.rb +111 -0
- data/test/unit/read_pref_test.rb +406 -0
- data/test/unit/read_test.rb +159 -0
- data/test/unit/safe_test.rb +69 -36
- data/test/unit/sharding_pool_manager_test.rb +84 -0
- data/test/unit/write_concern_test.rb +175 -0
- data.tar.gz.sig +3 -0
- metadata +227 -216
- metadata.gz.sig +0 -0
- data/docs/CREDITS.md +0 -123
- data/docs/FAQ.md +0 -116
- data/docs/GridFS.md +0 -158
- data/docs/HISTORY.md +0 -244
- data/docs/RELEASES.md +0 -33
- data/docs/REPLICA_SETS.md +0 -72
- data/docs/TUTORIAL.md +0 -247
- data/docs/WRITE_CONCERN.md +0 -28
- data/lib/mongo/exceptions.rb +0 -71
- data/lib/mongo/gridfs/grid_io_fix.rb +0 -38
- data/lib/mongo/repl_set_connection.rb +0 -342
- data/lib/mongo/test.rb +0 -20
- data/lib/mongo/util/pool.rb +0 -177
- data/lib/mongo/util/uri_parser.rb +0 -185
- data/test/async/collection_test.rb +0 -224
- data/test/async/connection_test.rb +0 -24
- data/test/async/cursor_test.rb +0 -162
- data/test/async/worker_pool_test.rb +0 -99
- data/test/auxillary/1.4_features.rb +0 -166
- data/test/auxillary/authentication_test.rb +0 -68
- data/test/auxillary/autoreconnect_test.rb +0 -41
- data/test/auxillary/fork_test.rb +0 -30
- data/test/auxillary/repl_set_auth_test.rb +0 -58
- data/test/auxillary/slave_connection_test.rb +0 -36
- data/test/auxillary/threaded_authentication_test.rb +0 -101
- data/test/bson/binary_test.rb +0 -15
- data/test/bson/bson_test.rb +0 -649
- data/test/bson/byte_buffer_test.rb +0 -208
- data/test/bson/hash_with_indifferent_access_test.rb +0 -38
- data/test/bson/json_test.rb +0 -17
- data/test/bson/object_id_test.rb +0 -154
- data/test/bson/ordered_hash_test.rb +0 -204
- data/test/bson/timestamp_test.rb +0 -24
- data/test/collection_test.rb +0 -910
- data/test/connection_test.rb +0 -309
- data/test/cursor_fail_test.rb +0 -75
- data/test/cursor_message_test.rb +0 -43
- data/test/cursor_test.rb +0 -483
- data/test/db_api_test.rb +0 -726
- data/test/db_connection_test.rb +0 -15
- data/test/db_test.rb +0 -287
- data/test/grid_file_system_test.rb +0 -243
- data/test/load/resque/load.rb +0 -21
- data/test/load/resque/processor.rb +0 -26
- data/test/load/thin/load.rb +0 -24
- data/test/load/unicorn/load.rb +0 -23
- data/test/load/unicorn/unicorn.rb +0 -29
- data/test/replica_sets/connect_test.rb +0 -94
- data/test/replica_sets/connection_string_test.rb +0 -32
- data/test/replica_sets/count_test.rb +0 -35
- data/test/replica_sets/insert_test.rb +0 -53
- data/test/replica_sets/pooled_insert_test.rb +0 -55
- data/test/replica_sets/query_secondaries.rb +0 -96
- data/test/replica_sets/query_test.rb +0 -51
- data/test/replica_sets/replication_ack_test.rb +0 -66
- data/test/replica_sets/rs_test_helper.rb +0 -27
- data/test/safe_test.rb +0 -68
- data/test/support/hash_with_indifferent_access.rb +0 -186
- data/test/support/keys.rb +0 -45
- data/test/support_test.rb +0 -18
- data/test/threading/threading_with_large_pool_test.rb +0 -90
- data/test/threading_test.rb +0 -87
- data/test/tools/auth_repl_set_manager.rb +0 -14
- data/test/tools/load.rb +0 -58
- data/test/tools/repl_set_manager.rb +0 -266
- data/test/tools/sharding_manager.rb +0 -202
- data/test/tools/test.rb +0 -4
- data/test/unit/pool_test.rb +0 -9
- data/test/unit/repl_set_connection_test.rb +0 -59
- data/test/uri_test.rb +0 -91
@@ -0,0 +1,111 @@
|
|
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 'test_helper'
|
16
|
+
include Mongo
|
17
|
+
|
18
|
+
class PoolManagerUnitTest < Test::Unit::TestCase
|
19
|
+
|
20
|
+
context "Initialization: " do
|
21
|
+
|
22
|
+
setup do
|
23
|
+
TCPSocket.stubs(:new).returns(new_mock_socket)
|
24
|
+
@db = new_mock_db
|
25
|
+
|
26
|
+
@client = stub("MongoClient")
|
27
|
+
@client.stubs(:connect_timeout).returns(5)
|
28
|
+
@client.stubs(:op_timeout).returns(5)
|
29
|
+
@client.stubs(:pool_size).returns(2)
|
30
|
+
@client.stubs(:pool_timeout).returns(100)
|
31
|
+
@client.stubs(:seeds).returns(['localhost:30000'])
|
32
|
+
@client.stubs(:socket_class).returns(TCPSocket)
|
33
|
+
@client.stubs(:mongos?).returns(false)
|
34
|
+
@client.stubs(:[]).returns(@db)
|
35
|
+
@client.stubs(:socket_opts)
|
36
|
+
|
37
|
+
@client.stubs(:replica_set_name).returns(nil)
|
38
|
+
@client.stubs(:log)
|
39
|
+
@arbiters = ['localhost:27020']
|
40
|
+
@hosts = [
|
41
|
+
'localhost:27017',
|
42
|
+
'localhost:27018',
|
43
|
+
'localhost:27019',
|
44
|
+
'localhost:27020'
|
45
|
+
]
|
46
|
+
|
47
|
+
@ismaster = {
|
48
|
+
'hosts' => @hosts,
|
49
|
+
'arbiters' => @arbiters,
|
50
|
+
'maxBsonObjectSize' => 1024,
|
51
|
+
'maxMessageSizeBytes' => 1024 * 2.5,
|
52
|
+
'maxWireVersion' => 1,
|
53
|
+
'minWireVersion' => 0
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
should "populate pools correctly" do
|
58
|
+
|
59
|
+
@db.stubs(:command).returns(
|
60
|
+
# First call to get a socket.
|
61
|
+
@ismaster.merge({'ismaster' => true}),
|
62
|
+
|
63
|
+
# Subsequent calls to configure pools.
|
64
|
+
@ismaster.merge({'ismaster' => true}),
|
65
|
+
@ismaster.merge({'secondary' => true, 'maxBsonObjectSize' => 500}),
|
66
|
+
@ismaster.merge({'secondary' => true, 'maxMessageSizeBytes' => 700}),
|
67
|
+
@ismaster.merge({'arbiterOnly' => true})
|
68
|
+
)
|
69
|
+
|
70
|
+
seeds = [['localhost', 27017]]
|
71
|
+
manager = Mongo::PoolManager.new(@client, seeds)
|
72
|
+
@client.stubs(:local_manager).returns(manager)
|
73
|
+
manager.connect
|
74
|
+
|
75
|
+
assert_equal ['localhost', 27017], manager.primary
|
76
|
+
assert_equal 27017, manager.primary_pool.port
|
77
|
+
assert_equal 2, manager.secondaries.length
|
78
|
+
assert_equal [27018, 27019], manager.secondary_pools.map(&:port).sort
|
79
|
+
assert_equal [['localhost', 27020]], manager.arbiters
|
80
|
+
assert_equal 500, manager.max_bson_size
|
81
|
+
assert_equal 700, manager.max_message_size
|
82
|
+
end
|
83
|
+
|
84
|
+
should "populate pools with single unqueryable seed" do
|
85
|
+
|
86
|
+
@db.stubs(:command).returns(
|
87
|
+
# First call to recovering node
|
88
|
+
@ismaster.merge({'ismaster' => false, 'secondary' => false}),
|
89
|
+
|
90
|
+
# Subsequent calls to configure pools.
|
91
|
+
@ismaster.merge({'ismaster' => false, 'secondary' => false}),
|
92
|
+
@ismaster.merge({'ismaster' => true}),
|
93
|
+
@ismaster.merge({'secondary' => true}),
|
94
|
+
@ismaster.merge({'arbiterOnly' => true})
|
95
|
+
)
|
96
|
+
|
97
|
+
seeds = [['localhost', 27017]]
|
98
|
+
manager = PoolManager.new(@client, seeds)
|
99
|
+
@client.stubs(:local_manager).returns(manager)
|
100
|
+
manager.connect
|
101
|
+
|
102
|
+
assert_equal ['localhost', 27018], manager.primary
|
103
|
+
assert_equal 27018, manager.primary_pool.port
|
104
|
+
assert_equal 1, manager.secondaries.length
|
105
|
+
assert_equal 27019, manager.secondary_pools[0].port
|
106
|
+
assert_equal [['localhost', 27020]], manager.arbiters
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
@@ -0,0 +1,406 @@
|
|
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 'test_helper'
|
16
|
+
|
17
|
+
class ReadPreferenceUnitTest < Test::Unit::TestCase
|
18
|
+
|
19
|
+
include ReadPreference
|
20
|
+
|
21
|
+
def setup
|
22
|
+
mock_pool = mock()
|
23
|
+
mock_pool.stubs(:ping_time).returns(Pool::MAX_PING_TIME)
|
24
|
+
|
25
|
+
stubs(:primary_pool).returns(mock_pool)
|
26
|
+
stubs(:secondary_pools).returns([mock_pool])
|
27
|
+
stubs(:pools).returns([mock_pool])
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_select_pool
|
31
|
+
ReadPreference::READ_PREFERENCES.map do |rp|
|
32
|
+
assert select_pool({:mode => rp, :tags => [], :latency => 15})
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_sok_mapreduce_out_string_returns_false
|
37
|
+
command = BSON::OrderedHash['mapreduce', 'test-collection',
|
38
|
+
'out', 'new-test-collection']
|
39
|
+
assert_equal false, ReadPreference::secondary_ok?(command)
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_sok_mapreduce_replace_collection_returns_false
|
43
|
+
command = BSON::OrderedHash['mapreduce', 'test-collection',
|
44
|
+
'out', BSON::OrderedHash['replace', 'new-test-collection']]
|
45
|
+
assert_equal false, ReadPreference::secondary_ok?(command)
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_sok_mapreduce_inline_collection_returns_false
|
49
|
+
command = BSON::OrderedHash['mapreduce', 'test-collection',
|
50
|
+
'out', 'inline']
|
51
|
+
assert_equal false, ReadPreference::secondary_ok?(command)
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_sok_inline_symbol_mapreduce_returns_true
|
55
|
+
command = BSON::OrderedHash['mapreduce', 'test-collection',
|
56
|
+
'out', BSON::OrderedHash[:inline, 'true']]
|
57
|
+
assert_equal true, ReadPreference::secondary_ok?(command)
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_sok_inline_string_mapreduce_returns_true
|
61
|
+
command = BSON::OrderedHash['mapreduce', 'test-collection',
|
62
|
+
'out', BSON::OrderedHash['inline', 'true']]
|
63
|
+
assert_equal true, ReadPreference::secondary_ok?(command)
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_sok_count_true
|
67
|
+
command = BSON::OrderedHash['count', 'test-collection',
|
68
|
+
'query', BSON::OrderedHash['a', 'b']]
|
69
|
+
assert_equal true, ReadPreference::secondary_ok?(command)
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_sok_server_status_returns_false
|
73
|
+
command = BSON::OrderedHash['serverStatus', 1]
|
74
|
+
assert_equal false, ReadPreference::secondary_ok?(command)
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_sok_text_returns_true
|
78
|
+
command = BSON::OrderedHash['text', BSON::OrderedHash['search', 'coffee']]
|
79
|
+
assert_equal true, ReadPreference::secondary_ok?(command)
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_cmd_reroute_with_secondary
|
83
|
+
ReadPreference::expects(:warn).with(regexp_matches(/rerouted to primary/))
|
84
|
+
command = BSON::OrderedHash['mapreduce', 'test-collection',
|
85
|
+
'out', 'new-test-collection']
|
86
|
+
assert_equal :primary, ReadPreference::cmd_read_pref(:secondary, command)
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_find_and_modify_reroute_with_secondary
|
90
|
+
ReadPreference::expects(:warn).with(regexp_matches(/rerouted to primary/))
|
91
|
+
command = BSON::OrderedHash['findAndModify', 'test-collection',
|
92
|
+
'query', {}]
|
93
|
+
assert_equal :primary, ReadPreference::cmd_read_pref(:secondary, command)
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_cmd_no_reroute_with_secondary
|
97
|
+
command = BSON::OrderedHash['mapreduce', 'test-collection',
|
98
|
+
'out', BSON::OrderedHash['inline', 'true']]
|
99
|
+
assert_equal :secondary, ReadPreference::cmd_read_pref(:secondary, command)
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_cmd_no_reroute_with_primary
|
103
|
+
command = BSON::OrderedHash['mapreduce', 'test-collection',
|
104
|
+
'out', 'new-test-collection']
|
105
|
+
assert_equal :primary, ReadPreference::cmd_read_pref(:primary, command)
|
106
|
+
end
|
107
|
+
|
108
|
+
def test_cmd_no_reroute_with_primary_secondary_ok
|
109
|
+
command = BSON::OrderedHash['mapreduce', 'test-collection',
|
110
|
+
'out', BSON::OrderedHash['inline', 'true']]
|
111
|
+
assert_equal :primary, ReadPreference::cmd_read_pref(:primary, command)
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_parallel_scan_secondary_ok
|
115
|
+
command = BSON::OrderedHash['parallelCollectionScan', 'test-collection',
|
116
|
+
'numCursors', 3]
|
117
|
+
assert_equal true, ReadPreference::secondary_ok?(command)
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_primary_with_tags_raises_error
|
121
|
+
# Confirm that an error is raised if you provide tags and read pref is primary
|
122
|
+
client = MongoReplicaSetClient.new(["#{TEST_HOST}:#{TEST_PORT}"], :connect => false)
|
123
|
+
client.stubs(:primary_pool).returns(mock_pool)
|
124
|
+
read_pref_tags = {'dc' => 'nyc'}
|
125
|
+
read_pref = client.read_preference.merge(:mode => :primary,
|
126
|
+
:tags => [read_pref_tags],
|
127
|
+
:latency => 6)
|
128
|
+
assert_raise Mongo::MongoArgumentError do
|
129
|
+
client.select_pool(read_pref)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def test_secondary_pref
|
134
|
+
# Confirm that a primary is not selected
|
135
|
+
primary_pool = mock('pool')
|
136
|
+
|
137
|
+
secondary_nyc = mock_pool({'dc' => 'nyc'}, 5)
|
138
|
+
secondary_chi = mock_pool({'dc' => 'chicago'}, 5)
|
139
|
+
secondary_pools = [secondary_nyc, secondary_chi]
|
140
|
+
|
141
|
+
client = MongoReplicaSetClient.new(["#{TEST_HOST}:#{TEST_PORT}"], :connect => false)
|
142
|
+
client.stubs(:primary_pool).returns(primary_pool)
|
143
|
+
client.stubs(:secondary_pools).returns(secondary_pools)
|
144
|
+
|
145
|
+
read_pref = client.read_preference.merge(:mode => :secondary)
|
146
|
+
assert_not_equal Hash.new, client.select_pool(read_pref).tags
|
147
|
+
end
|
148
|
+
|
149
|
+
def test_secondary_tags_pref
|
150
|
+
# Confirm that a secondary with matching tags is selected
|
151
|
+
secondary_nyc = mock_pool({'dc' => 'nyc'}, 5)
|
152
|
+
secondary_chi = mock_pool({'dc' => 'chicago'}, 5)
|
153
|
+
secondary_pools = [secondary_nyc, secondary_chi]
|
154
|
+
|
155
|
+
client = MongoReplicaSetClient.new(["#{TEST_HOST}:#{TEST_PORT}"], :connect => false)
|
156
|
+
client.stubs(:primary_pool).returns(mock_pool)
|
157
|
+
client.stubs(:secondary_pools).returns(secondary_pools)
|
158
|
+
|
159
|
+
read_pref_tags = {'dc' => 'nyc'}
|
160
|
+
read_pref = client.read_preference.merge(:mode => :secondary,
|
161
|
+
:tags => [read_pref_tags])
|
162
|
+
assert_equal read_pref_tags, client.select_pool(read_pref).tags
|
163
|
+
end
|
164
|
+
|
165
|
+
def test_secondary_tags_with_latency
|
166
|
+
# Confirm that between more than 1 secondary matching tags, only the one within
|
167
|
+
# max acceptable latency is selected
|
168
|
+
secondary_nyc = mock_pool({'dc' => 'nyc'}, 5)
|
169
|
+
secondary_nyc2 = mock_pool({'dc' => 'nyc'}, 25)
|
170
|
+
secondary_pools = [secondary_nyc, secondary_nyc2]
|
171
|
+
|
172
|
+
client = MongoReplicaSetClient.new(["#{TEST_HOST}:#{TEST_PORT}"], :connect => false)
|
173
|
+
client.stubs(:secondary_pools).returns(secondary_pools)
|
174
|
+
|
175
|
+
read_pref_tags = {'dc' => 'nyc'}
|
176
|
+
read_pref = client.read_preference.merge(:mode => :secondary,
|
177
|
+
:tags => [read_pref_tags])
|
178
|
+
assert_equal 5, client.select_pool(read_pref).ping_time
|
179
|
+
end
|
180
|
+
|
181
|
+
def test_secondary_latency_pref
|
182
|
+
# Confirm that only the latency of pools matching tags is considered
|
183
|
+
secondary_nyc = mock_pool({'dc' => 'nyc'}, 10)
|
184
|
+
secondary_chi = mock_pool({'dc' => 'chicago'}, 5)
|
185
|
+
secondary_pools = [secondary_nyc, secondary_chi]
|
186
|
+
|
187
|
+
client = MongoReplicaSetClient.new(["#{TEST_HOST}:#{TEST_PORT}"], :connect => false)
|
188
|
+
client.stubs(:primary_pool).returns(mock_pool)
|
189
|
+
client.stubs(:secondary_pools).returns(secondary_pools)
|
190
|
+
|
191
|
+
read_pref_tags = {'dc' => 'nyc'}
|
192
|
+
read_pref = client.read_preference.merge(:mode => :secondary,
|
193
|
+
:tags => [read_pref_tags],
|
194
|
+
:latency => 3)
|
195
|
+
assert_equal read_pref_tags, client.select_pool(read_pref).tags
|
196
|
+
end
|
197
|
+
|
198
|
+
def test_primary_preferred_primary_available
|
199
|
+
# Confirm that the primary is always selected if it's available
|
200
|
+
secondary_nyc = mock_pool({'dc' => 'nyc'}, 5)
|
201
|
+
secondary_chi = mock_pool({'dc' => 'chicago'}, 10)
|
202
|
+
secondary_pools = [secondary_nyc, secondary_chi]
|
203
|
+
primary_pool = mock_pool
|
204
|
+
|
205
|
+
client = MongoReplicaSetClient.new(["#{TEST_HOST}:#{TEST_PORT}"], :connect => false)
|
206
|
+
client.stubs(:secondary_pools).returns(secondary_pools)
|
207
|
+
client.stubs(:primary_pool).returns(primary_pool)
|
208
|
+
|
209
|
+
read_pref_tags = {'dc' => 'chicago'}
|
210
|
+
read_pref = client.read_preference.merge(:mode => :primary_preferred,
|
211
|
+
:tags => [read_pref_tags],
|
212
|
+
:latency => 6)
|
213
|
+
assert_equal primary_pool, client.select_pool(read_pref)
|
214
|
+
end
|
215
|
+
|
216
|
+
def test_primary_preferred_primary_not_available
|
217
|
+
# Confirm that a secondary with matching tags is selected if primary is
|
218
|
+
# not available
|
219
|
+
secondary_nyc = mock_pool({'dc' => 'nyc'}, 5)
|
220
|
+
secondary_chi = mock_pool({'dc' => 'chicago'}, 10)
|
221
|
+
secondary_pools = [secondary_nyc, secondary_chi]
|
222
|
+
|
223
|
+
client = MongoReplicaSetClient.new(["#{TEST_HOST}:#{TEST_PORT}"], :connect => false)
|
224
|
+
client.stubs(:secondary_pools).returns(secondary_pools)
|
225
|
+
|
226
|
+
read_pref_tags = {'dc' => 'chicago'}
|
227
|
+
read_pref = client.read_preference.merge(:mode => :primary_preferred,
|
228
|
+
:tags => [read_pref_tags],
|
229
|
+
:latency => 6)
|
230
|
+
assert_equal read_pref_tags, client.select_pool(read_pref).tags
|
231
|
+
end
|
232
|
+
|
233
|
+
def test_primary_preferred_primary_not_available_and_no_matching_tags
|
234
|
+
# Confirm that tags are taken into account if primary is not available and
|
235
|
+
# secondaries are considered for selection.
|
236
|
+
secondary_nyc = mock_pool({'dc' => 'nyc'}, 5)
|
237
|
+
secondary_chi = mock_pool({'dc' => 'chicago'}, 10)
|
238
|
+
secondary_pools = [secondary_nyc, secondary_chi]
|
239
|
+
|
240
|
+
client = MongoReplicaSetClient.new(["#{TEST_HOST}:#{TEST_PORT}"], :connect => false)
|
241
|
+
client.stubs(:secondary_pools).returns(secondary_pools)
|
242
|
+
|
243
|
+
read_pref_tags = {'dc' => 'other_city'}
|
244
|
+
read_pref = client.read_preference.merge(:mode => :primary_preferred,
|
245
|
+
:tags => [read_pref_tags],
|
246
|
+
:latency => 6)
|
247
|
+
assert_equal nil, client.select_pool(read_pref)
|
248
|
+
end
|
249
|
+
|
250
|
+
def test_secondary_preferred_with_tags
|
251
|
+
# Confirm that tags are taken into account
|
252
|
+
secondary_nyc = mock_pool({'dc' => 'nyc'}, 5)
|
253
|
+
secondary_chi = mock_pool({'dc' => 'chicago'}, 10)
|
254
|
+
secondary_pools = [secondary_nyc, secondary_chi]
|
255
|
+
|
256
|
+
client = MongoReplicaSetClient.new(["#{TEST_HOST}:#{TEST_PORT}"], :connect => false)
|
257
|
+
client.stubs(:secondary_pools).returns(secondary_pools)
|
258
|
+
|
259
|
+
read_pref_tags = {'dc' => 'chicago'}
|
260
|
+
read_pref = client.read_preference.merge(:mode => :secondary_preferred,
|
261
|
+
:tags => [read_pref_tags],
|
262
|
+
:latency => 6)
|
263
|
+
assert_equal read_pref_tags, client.select_pool(read_pref).tags
|
264
|
+
end
|
265
|
+
|
266
|
+
def test_secondary_preferred_with_no_matching_tags
|
267
|
+
# Confirm that the primary is selected if no secondaries with matching tags
|
268
|
+
# are found
|
269
|
+
secondary_nyc = mock_pool({'dc' => 'nyc'}, 5)
|
270
|
+
secondary_chi = mock_pool({'dc' => 'chicago'}, 10)
|
271
|
+
secondary_pools = [secondary_nyc, secondary_chi]
|
272
|
+
|
273
|
+
client = MongoReplicaSetClient.new(["#{TEST_HOST}:#{TEST_PORT}"], :connect => false)
|
274
|
+
client.stubs(:secondary_pools).returns(secondary_pools)
|
275
|
+
client.stubs(:primary_pool).returns(mock_pool)
|
276
|
+
|
277
|
+
read_pref_tags = {'dc' => 'other_city'}
|
278
|
+
read_pref = client.read_preference.merge(:mode => :secondary_preferred,
|
279
|
+
:tags => [read_pref_tags],
|
280
|
+
:latency => 6)
|
281
|
+
assert_equal Hash.new, client.select_pool(read_pref).tags
|
282
|
+
end
|
283
|
+
|
284
|
+
def test_nearest_with_tags
|
285
|
+
# Confirm that tags are taken into account when selecting nearest
|
286
|
+
secondary_nyc = mock_pool({'dc' => 'nyc'}, 5)
|
287
|
+
secondary_chi = mock_pool({'dc' => 'chicago'}, 10)
|
288
|
+
primary_pool = mock_pool
|
289
|
+
pools = [secondary_nyc, secondary_chi, primary_pool]
|
290
|
+
|
291
|
+
client = MongoReplicaSetClient.new(["#{TEST_HOST}:#{TEST_PORT}"], :connect => false)
|
292
|
+
client.stubs(:pools).returns(pools)
|
293
|
+
|
294
|
+
read_pref_tags = {'dc' => 'nyc'}
|
295
|
+
read_pref = client.read_preference.merge(:mode => :nearest,
|
296
|
+
:tags => [read_pref_tags],
|
297
|
+
:latency => 3)
|
298
|
+
assert_equal read_pref_tags, client.select_pool(read_pref).tags
|
299
|
+
end
|
300
|
+
|
301
|
+
def test_nearest
|
302
|
+
# Confirm that the nearest is selected when tags aren't specified
|
303
|
+
secondary_nyc = mock_pool({'dc' => 'nyc'}, 5)
|
304
|
+
secondary_chi = mock_pool({'dc' => 'chicago'}, 10)
|
305
|
+
primary_pool = mock_pool({}, 1)
|
306
|
+
pools = [secondary_nyc, secondary_chi, primary_pool]
|
307
|
+
|
308
|
+
client = MongoReplicaSetClient.new(["#{TEST_HOST}:#{TEST_PORT}"], :connect => false)
|
309
|
+
client.stubs(:pools).returns(pools)
|
310
|
+
|
311
|
+
read_pref = client.read_preference.merge(:mode => :nearest,
|
312
|
+
:latency => 3)
|
313
|
+
assert_equal Hash.new, client.select_pool(read_pref).tags
|
314
|
+
end
|
315
|
+
|
316
|
+
def test_nearest_primary_matching
|
317
|
+
# Confirm that a primary matching tags is included in nearest candidates
|
318
|
+
secondary_nyc = mock_pool({'dc' => 'nyc'}, 5)
|
319
|
+
secondary_chi = mock_pool({'dc' => 'chicago'}, 10)
|
320
|
+
primary_pool = mock_pool({'dc' => 'boston'}, 1)
|
321
|
+
secondary_pools = [secondary_nyc, secondary_chi]
|
322
|
+
|
323
|
+
client = MongoReplicaSetClient.new(["#{TEST_HOST}:#{TEST_PORT}"], :connect => false)
|
324
|
+
client.stubs(:secondary_pools).returns(secondary_pools)
|
325
|
+
client.stubs(:primary_pool).returns(primary_pool)
|
326
|
+
client.stubs(:pools).returns(secondary_pools << primary_pool)
|
327
|
+
|
328
|
+
read_pref_tags = {'dc' => 'boston'}
|
329
|
+
read_pref = client.read_preference.merge(:mode => :nearest,
|
330
|
+
:tags => [read_pref_tags])
|
331
|
+
assert_equal primary_pool, client.select_pool(read_pref)
|
332
|
+
end
|
333
|
+
|
334
|
+
def test_nearest_primary_not_matching
|
335
|
+
# Confirm that a primary not matching tags is not included in nearest candidates
|
336
|
+
secondary_nyc = mock_pool({'dc' => 'nyc'}, 25)
|
337
|
+
secondary_chi = mock_pool({'dc' => 'chicago'}, 25)
|
338
|
+
primary_pool = mock_pool({'dc' => 'boston'}, 1)
|
339
|
+
secondary_pools = [secondary_nyc, secondary_chi]
|
340
|
+
|
341
|
+
client = MongoReplicaSetClient.new(["#{TEST_HOST}:#{TEST_PORT}"], :connect => false)
|
342
|
+
client.stubs(:secondary_pools).returns(secondary_pools)
|
343
|
+
client.stubs(:primary_pool).returns(mock_pool)
|
344
|
+
client.stubs(:pools).returns(secondary_pools << primary_pool)
|
345
|
+
|
346
|
+
read_pref_tags = {'dc' => 'SF'}
|
347
|
+
read_pref = client.read_preference.merge(:mode => :nearest,
|
348
|
+
:tags => [read_pref_tags])
|
349
|
+
assert_equal nil, client.select_pool(read_pref)
|
350
|
+
end
|
351
|
+
|
352
|
+
def test_nearest_primary_not_matching_excluded_from_latency_calculations
|
353
|
+
# Confirm that a primary not matching tags is not included in the latency calculations
|
354
|
+
secondary1 = mock_pool({'dc' => 'nyc'}, 10)
|
355
|
+
secondary2 = mock_pool({'dc' => 'nyc'}, 10)
|
356
|
+
primary_pool = mock_pool({'dc' => 'boston'}, 1)
|
357
|
+
secondary_pools = [secondary1, secondary2]
|
358
|
+
|
359
|
+
client = MongoReplicaSetClient.new(["#{TEST_HOST}:#{TEST_PORT}"], :connect => false)
|
360
|
+
client.stubs(:secondary_pools).returns(secondary_pools)
|
361
|
+
client.stubs(:primary_pool).returns(mock_pool)
|
362
|
+
client.stubs(:pools).returns(secondary_pools << primary_pool)
|
363
|
+
|
364
|
+
read_pref_tags = {'dc' => 'nyc'}
|
365
|
+
read_pref = client.read_preference.merge(:mode => :nearest,
|
366
|
+
:tags => [read_pref_tags],
|
367
|
+
:latency => 5)
|
368
|
+
assert_equal 'nyc', client.select_pool(read_pref).tags['dc']
|
369
|
+
end
|
370
|
+
|
371
|
+
def test_nearest_matching_tags_but_not_available
|
372
|
+
# Confirm that even if a server matches a tag, it's not selected if it's down
|
373
|
+
secondary_nyc = mock_pool({'dc' => 'nyc'}, 5)
|
374
|
+
secondary_chi = mock_pool({'dc' => 'chicago'}, 10)
|
375
|
+
primary_pool = mock_pool({'dc' => 'chicago'}, nil)
|
376
|
+
secondary_pools = [secondary_nyc, secondary_chi]
|
377
|
+
|
378
|
+
client = MongoReplicaSetClient.new(["#{TEST_HOST}:#{TEST_PORT}"], :connect => false)
|
379
|
+
client.stubs(:secondary_pools).returns(secondary_pools)
|
380
|
+
client.stubs(:primary_pool).returns(primary_pool)
|
381
|
+
client.stubs(:pools).returns(secondary_pools << primary_pool)
|
382
|
+
|
383
|
+
tags = [{'dc' => 'nyc'}, {'dc' => 'chicago'}, {}]
|
384
|
+
read_pref = client.read_preference.merge(:mode => :nearest,
|
385
|
+
:tags => tags)
|
386
|
+
assert_equal secondary_nyc, client.select_pool(read_pref)
|
387
|
+
end
|
388
|
+
|
389
|
+
def test_nearest_multiple_tags
|
390
|
+
# Confirm that with multiple tags in the read preference, servers are still selected
|
391
|
+
secondary_nyc = mock_pool({}, 5)
|
392
|
+
secondary_chi = mock_pool({'dc' => 'chicago'}, 10)
|
393
|
+
primary_pool = mock_pool({}, 1)
|
394
|
+
secondary_pools = [secondary_nyc, secondary_chi]
|
395
|
+
|
396
|
+
client = MongoReplicaSetClient.new(["#{TEST_HOST}:#{TEST_PORT}"], :connect => false)
|
397
|
+
client.stubs(:secondary_pools).returns(secondary_pools)
|
398
|
+
client.stubs(:primary_pool).returns(mock_pool)
|
399
|
+
client.stubs(:pools).returns(secondary_pools << primary_pool)
|
400
|
+
|
401
|
+
tags = [{'dc' => 'nyc'}, {'dc' => 'chicago'}, {}]
|
402
|
+
read_pref = client.read_preference.merge(:mode => :nearest,
|
403
|
+
:tags => tags)
|
404
|
+
assert_equal secondary_chi, client.select_pool(read_pref)
|
405
|
+
end
|
406
|
+
end
|
@@ -0,0 +1,159 @@
|
|
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 'test_helper'
|
16
|
+
|
17
|
+
class ReadUnitTest < Test::Unit::TestCase
|
18
|
+
|
19
|
+
context "Read mode on standard connection: " do
|
20
|
+
setup do
|
21
|
+
@read = :secondary
|
22
|
+
@client = MongoClient.new('localhost', 27017, :read => @read, :connect => false)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
context "Read preferences on replica set connection: " do
|
28
|
+
setup do
|
29
|
+
@read = :secondary_preferred
|
30
|
+
@acceptable_latency = 100
|
31
|
+
@tags = {"dc" => "Tyler", "rack" => "Brock"}
|
32
|
+
@bad_tags = {"wow" => "cool"}
|
33
|
+
@client = MongoReplicaSetClient.new(
|
34
|
+
['localhost:27017'],
|
35
|
+
:read => @read,
|
36
|
+
:tag_sets => @tags,
|
37
|
+
:secondary_acceptable_latency_ms => @acceptable_latency,
|
38
|
+
:connect => false
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
should "store read preference on MongoClient" do
|
43
|
+
assert_equal @read, @client.read
|
44
|
+
assert_equal @tags, @client.tag_sets
|
45
|
+
assert_equal @acceptable_latency, @client.acceptable_latency
|
46
|
+
end
|
47
|
+
|
48
|
+
should "propogate to DB" do
|
49
|
+
db = @client[TEST_DB]
|
50
|
+
assert_equal @read, db.read
|
51
|
+
assert_equal @tags, db.tag_sets
|
52
|
+
assert_equal @acceptable_latency, db.acceptable_latency
|
53
|
+
|
54
|
+
db = @client.db(TEST_DB)
|
55
|
+
assert_equal @read, db.read
|
56
|
+
assert_equal @tags, db.tag_sets
|
57
|
+
assert_equal @acceptable_latency, db.acceptable_latency
|
58
|
+
|
59
|
+
db = DB.new(TEST_DB, @client)
|
60
|
+
assert_equal @read, db.read
|
61
|
+
assert_equal @tags, db.tag_sets
|
62
|
+
assert_equal @acceptable_latency, db.acceptable_latency
|
63
|
+
end
|
64
|
+
|
65
|
+
should "allow db override" do
|
66
|
+
db = DB.new(TEST_DB, @client, :read => :primary, :tag_sets => @bad_tags, :acceptable_latency => 25)
|
67
|
+
assert_equal :primary, db.read
|
68
|
+
assert_equal @bad_tags, db.tag_sets
|
69
|
+
assert_equal 25, db.acceptable_latency
|
70
|
+
|
71
|
+
db = @client.db(TEST_DB, :read => :primary, :tag_sets => @bad_tags, :acceptable_latency => 25)
|
72
|
+
assert_equal :primary, db.read
|
73
|
+
assert_equal @bad_tags, db.tag_sets
|
74
|
+
assert_equal 25, db.acceptable_latency
|
75
|
+
end
|
76
|
+
|
77
|
+
context "on DB: " do
|
78
|
+
setup do
|
79
|
+
@db = @client[TEST_DB]
|
80
|
+
end
|
81
|
+
|
82
|
+
should "propogate to collection" do
|
83
|
+
col = @db.collection('read-unit-test')
|
84
|
+
assert_equal @read, col.read
|
85
|
+
assert_equal @tags, col.tag_sets
|
86
|
+
assert_equal @acceptable_latency, col.acceptable_latency
|
87
|
+
|
88
|
+
col = @db['read-unit-test']
|
89
|
+
assert_equal @read, col.read
|
90
|
+
assert_equal @tags, col.tag_sets
|
91
|
+
assert_equal @acceptable_latency, col.acceptable_latency
|
92
|
+
|
93
|
+
col = Collection.new('read-unit-test', @db)
|
94
|
+
assert_equal @read, col.read
|
95
|
+
assert_equal @tags, col.tag_sets
|
96
|
+
assert_equal @acceptable_latency, col.acceptable_latency
|
97
|
+
end
|
98
|
+
|
99
|
+
should "allow override on collection" do
|
100
|
+
col = @db.collection('read-unit-test', :read => :primary, :tag_sets => @bad_tags, :acceptable_latency => 25)
|
101
|
+
assert_equal :primary, col.read
|
102
|
+
assert_equal @bad_tags, col.tag_sets
|
103
|
+
assert_equal 25, col.acceptable_latency
|
104
|
+
|
105
|
+
col = Collection.new('read-unit-test', @db, :read => :primary, :tag_sets => @bad_tags, :acceptable_latency => 25)
|
106
|
+
assert_equal :primary, col.read
|
107
|
+
assert_equal @bad_tags, col.tag_sets
|
108
|
+
assert_equal 25, col.acceptable_latency
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context "on read mode ops" do
|
113
|
+
setup do
|
114
|
+
@col = @client[TEST_DB]['read-unit-test']
|
115
|
+
@mock_socket = new_mock_socket
|
116
|
+
end
|
117
|
+
|
118
|
+
should "use default value on query" do
|
119
|
+
@cursor = @col.find({:a => 1})
|
120
|
+
sock = new_mock_socket
|
121
|
+
read_pool = stub(:checkin => true)
|
122
|
+
@client.stubs(:read_pool).returns(read_pool)
|
123
|
+
local_manager = PoolManager.new(@client, @client.seeds)
|
124
|
+
@client.stubs(:local_manager).returns(local_manager)
|
125
|
+
primary_pool = stub(:checkin => true)
|
126
|
+
sock.stubs(:pool).returns(primary_pool)
|
127
|
+
@client.stubs(:primary_pool).returns(primary_pool)
|
128
|
+
@client.expects(:checkout_reader).returns(sock)
|
129
|
+
@client.expects(:receive_message).with do |o, m, l, s, c, r|
|
130
|
+
r == nil
|
131
|
+
end.returns([[], 0, 0])
|
132
|
+
|
133
|
+
@cursor.next
|
134
|
+
end
|
135
|
+
|
136
|
+
should "allow override default value on query" do
|
137
|
+
@cursor = @col.find({:a => 1}, :read => :primary)
|
138
|
+
sock = new_mock_socket
|
139
|
+
local_manager = PoolManager.new(@client, @client.seeds)
|
140
|
+
@client.stubs(:local_manager).returns(local_manager)
|
141
|
+
primary_pool = stub(:checkin => true)
|
142
|
+
sock.stubs(:pool).returns(primary_pool)
|
143
|
+
@client.stubs(:primary_pool).returns(primary_pool)
|
144
|
+
@client.expects(:checkout_reader).returns(sock)
|
145
|
+
@client.expects(:receive_message).with do |o, m, l, s, c, r|
|
146
|
+
r == nil
|
147
|
+
end.returns([[], 0, 0])
|
148
|
+
|
149
|
+
@cursor.next
|
150
|
+
end
|
151
|
+
|
152
|
+
should "allow override alternate value on query" do
|
153
|
+
assert_raise MongoArgumentError do
|
154
|
+
@col.find_one({:a => 1}, :read => {:dc => "ny"})
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|