mongo 2.0.5 → 2.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/lib/mongo/auth/cr/conversation.rb +0 -1
  5. data/lib/mongo/client.rb +19 -5
  6. data/lib/mongo/cluster.rb +84 -16
  7. data/lib/mongo/cluster/topology.rb +3 -4
  8. data/lib/mongo/cluster/topology/replica_set.rb +75 -0
  9. data/lib/mongo/cluster/topology/sharded.rb +60 -0
  10. data/lib/mongo/cluster/topology/single.rb +51 -0
  11. data/lib/mongo/cluster/topology/unknown.rb +62 -0
  12. data/lib/mongo/error/invalid_bulk_operation.rb +2 -1
  13. data/lib/mongo/event.rb +9 -9
  14. data/lib/mongo/event/{server_added.rb → description_changed.rb} +6 -5
  15. data/lib/mongo/event/{server_removed.rb → standalone_discovered.rb} +15 -15
  16. data/lib/mongo/operation/aggregate.rb +6 -5
  17. data/lib/mongo/operation/command.rb +5 -6
  18. data/lib/mongo/operation/kill_cursors.rb +3 -2
  19. data/lib/mongo/operation/map_reduce.rb +6 -5
  20. data/lib/mongo/operation/read/collections_info.rb +5 -4
  21. data/lib/mongo/operation/read/get_more.rb +8 -7
  22. data/lib/mongo/operation/read/indexes.rb +4 -3
  23. data/lib/mongo/operation/read/list_collections.rb +5 -4
  24. data/lib/mongo/operation/read/list_indexes.rb +7 -6
  25. data/lib/mongo/operation/read/query.rb +8 -7
  26. data/lib/mongo/operation/read_preferrable.rb +6 -2
  27. data/lib/mongo/operation/write/bulk/bulk_delete.rb +13 -12
  28. data/lib/mongo/operation/write/bulk/bulk_insert.rb +10 -9
  29. data/lib/mongo/operation/write/bulk/bulk_insert/result.rb +2 -2
  30. data/lib/mongo/operation/write/bulk/bulk_update.rb +12 -11
  31. data/lib/mongo/operation/write/create_index.rb +8 -7
  32. data/lib/mongo/operation/write/create_user.rb +4 -3
  33. data/lib/mongo/operation/write/delete.rb +13 -12
  34. data/lib/mongo/operation/write/drop_index.rb +6 -5
  35. data/lib/mongo/operation/write/insert.rb +8 -7
  36. data/lib/mongo/operation/write/insert/result.rb +1 -1
  37. data/lib/mongo/operation/write/remove_user.rb +4 -3
  38. data/lib/mongo/operation/write/update.rb +10 -9
  39. data/lib/mongo/operation/write/update_user.rb +4 -3
  40. data/lib/mongo/server.rb +8 -15
  41. data/lib/mongo/server/context.rb +2 -2
  42. data/lib/mongo/server/description.rb +65 -3
  43. data/lib/mongo/server/description/features.rb +2 -1
  44. data/lib/mongo/server/description/inspector.rb +5 -5
  45. data/lib/mongo/server/description/inspector/{server_added.rb → description_changed.rb} +3 -5
  46. data/lib/mongo/server/description/inspector/{server_removed.rb → standalone_discovered.rb} +12 -15
  47. data/lib/mongo/version.rb +1 -1
  48. data/spec/mongo/auth/cr_spec.rb +1 -1
  49. data/spec/mongo/auth/ldap_spec.rb +1 -1
  50. data/spec/mongo/auth/scram_spec.rb +1 -1
  51. data/spec/mongo/auth/x509_spec.rb +1 -1
  52. data/spec/mongo/client_spec.rb +4 -0
  53. data/spec/mongo/cluster/topology/replica_set_spec.rb +254 -4
  54. data/spec/mongo/cluster/topology/sharded_spec.rb +70 -19
  55. data/spec/mongo/cluster/topology/single_spec.rb +25 -4
  56. data/spec/mongo/cluster/topology/unknown_spec.rb +167 -0
  57. data/spec/mongo/cluster/topology_spec.rb +6 -6
  58. data/spec/mongo/cluster_spec.rb +179 -1
  59. data/spec/mongo/operation/read_preferrable_spec.rb +81 -28
  60. data/spec/mongo/operation/write/bulk/bulk_delete_spec.rb +1 -0
  61. data/spec/mongo/server/connection_pool_spec.rb +10 -10
  62. data/spec/mongo/server/connection_spec.rb +1 -1
  63. data/spec/mongo/server/description/features_spec.rb +23 -3
  64. data/spec/mongo/server/description/inspector/description_changed_spec.rb +78 -0
  65. data/spec/mongo/server/description_spec.rb +238 -0
  66. data/spec/mongo/server/monitor_spec.rb +1 -1
  67. data/spec/mongo/server_discovery_and_monitoring_spec.rb +20 -4
  68. data/spec/mongo/server_selection_spec.rb +2 -2
  69. data/spec/mongo/server_spec.rb +12 -8
  70. data/spec/support/authorization.rb +8 -8
  71. data/spec/support/sdam/rs/discover_passives.yml +36 -0
  72. data/spec/support/sdam/rs/new_primary_wrong_set_name.yml +2 -2
  73. data/spec/support/sdam/rs/primary_becomes_standalone.yml +1 -1
  74. data/spec/support/sdam/rs/primary_changes_set_name.yml +2 -2
  75. data/spec/support/sdam/rs/primary_disconnect.yml +1 -1
  76. data/spec/support/sdam/rs/primary_wrong_set_name.yml +1 -1
  77. data/spec/support/sdam/rs/secondary_wrong_set_name.yml +1 -1
  78. data/spec/support/sdam/rs/secondary_wrong_set_name_with_primary.yml +2 -2
  79. data/spec/support/sdam/sharded/single_mongos.yml +33 -0
  80. data/spec/support/shared/operation.rb +12 -4
  81. data/spec/support/shared/server_selector.rb +1 -1
  82. metadata +12 -10
  83. metadata.gz.sig +0 -0
  84. data/spec/mongo/server/description/inspector/server_added_spec.rb +0 -92
  85. data/spec/mongo/server/description/inspector/server_removed_spec.rb +0 -95
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 009aca6194505e854c2e8ce72b50826c76b81942
4
- data.tar.gz: ec9fa507bafe9c3f79ce430bee77bee9fa6452c3
3
+ metadata.gz: 391a49fc68da96ed3daf59dae9e7006edd6eb7c4
4
+ data.tar.gz: c9b0c470d42785c446fa2c2ae21273bd45f487ea
5
5
  SHA512:
6
- metadata.gz: ac666b6d3593018bf29f22a212bcf36a1b0dff88e9aee3e439c3f599bff14c90b8e2507355b10cc40e4426a7135313fa161d78fdc853ac4c094acfa9122d2c2c
7
- data.tar.gz: 0ff89ae3bc0661a5a39e067a0c96d7a044b291333f314dd6dbd89e8bb7c6d2f4f95ba894785d784acc8126f66896b0505900d56b39c77c5e1a4ac66f8f43f0ca
6
+ metadata.gz: b77002840a210f7bf8c98114529edd2d0a8891dc6518fcd3c3d1337b2648cf2b31b9d3564aab8ab404521232713a452986cfd6202bdaa69c06fde024305befbe
7
+ data.tar.gz: 146937702a43742e5e678845431b61b071c6b3573084dac4f7cb196f3ec601c97d78a248c39c1066a5fd7860f6195a72837210f6b8bdd84b7b6ed03328063f24
Binary file
data.tar.gz.sig CHANGED
Binary file
@@ -102,7 +102,6 @@ module Mongo
102
102
  # Conversation.new(user, "admin")
103
103
  #
104
104
  # @param [ Auth::User ] user The user to converse about.
105
- # @param [ String ] database The database to authenticate against.
106
105
  #
107
106
  # @since 2.0.0
108
107
  def initialize(user)
@@ -21,6 +21,12 @@ module Mongo
21
21
  class Client
22
22
  extend Forwardable
23
23
 
24
+ # The options that do not affect the behaviour of a cluster and its
25
+ # subcomponents.
26
+ #
27
+ # @since 2.1.0
28
+ CRUD_OPTIONS = [ :database, :read, :write ].freeze
29
+
24
30
  # @return [ Mongo::Cluster ] cluster The cluster of servers for the client.
25
31
  attr_reader :cluster
26
32
 
@@ -203,8 +209,9 @@ module Mongo
203
209
  clone.tap do |client|
204
210
  client.options.update(new_options)
205
211
  Database.create(client)
206
- # We can't use the same cluster if authentication details have changed.
207
- if new_options[:user] || new_options[:password]
212
+ # We can't use the same cluster if some options that would affect it
213
+ # have changed.
214
+ if cluster_modifying?(new_options)
208
215
  Cluster.create(client)
209
216
  end
210
217
  end
@@ -244,9 +251,7 @@ module Mongo
244
251
  #
245
252
  # @since 2.0.5
246
253
  def list_databases
247
- use(Database::ADMIN).command(
248
- listDatabases: 1
249
- ).first['databases']
254
+ use(Database::ADMIN).command(listDatabases: 1).first['databases']
250
255
  end
251
256
 
252
257
  private
@@ -270,5 +275,14 @@ module Mongo
270
275
  @read_preference = nil
271
276
  @write_concern = nil
272
277
  end
278
+
279
+ def cluster_modifying?(new_options)
280
+ cluster_options = new_options.reject do |name|
281
+ CRUD_OPTIONS.include?(name)
282
+ end
283
+ cluster_options.any? do |name, value|
284
+ options[name] != value
285
+ end
286
+ end
273
287
  end
274
288
  end
@@ -25,9 +25,6 @@ module Mongo
25
25
  include Event::Subscriber
26
26
  include Loggable
27
27
 
28
- # @return [ Array<String> ] The provided seed addresses.
29
- attr_reader :addresses
30
-
31
28
  # @return [ Hash ] The options hash.
32
29
  attr_reader :options
33
30
 
@@ -69,10 +66,9 @@ module Mongo
69
66
  if !addresses.include?(address)
70
67
  if addition_allowed?(address)
71
68
  log_debug([ "Adding #{address.to_s} to the cluster." ])
72
- addresses.push(address)
73
- server = Server.new(address, event_listeners,
74
- options.merge(slave_ok: @slave_ok))
75
- @servers.push(server)
69
+ @update_lock.synchronize { @addresses.push(address) }
70
+ server = Server.new(address, self, event_listeners, options)
71
+ @update_lock.synchronize { @servers.push(server) }
76
72
  server
77
73
  end
78
74
  end
@@ -93,10 +89,10 @@ module Mongo
93
89
  @event_listeners = Event::Listeners.new
94
90
  @options = options.freeze
95
91
  @topology = Topology.initial(seeds, options)
96
- @slave_ok = @topology.single? unless options[:read]
92
+ @update_lock = Mutex.new
97
93
 
98
- subscribe_to(Event::SERVER_ADDED, Event::ServerAdded.new(self))
99
- subscribe_to(Event::SERVER_REMOVED, Event::ServerRemoved.new(self))
94
+ subscribe_to(Event::STANDALONE_DISCOVERED, Event::StandaloneDiscovered.new(self))
95
+ subscribe_to(Event::DESCRIPTION_CHANGED, Event::DescriptionChanged.new(self))
100
96
  subscribe_to(Event::PRIMARY_ELECTED, Event::PrimaryElected.new(self))
101
97
 
102
98
  seeds.each{ |seed| add(seed) }
@@ -138,10 +134,23 @@ module Mongo
138
134
  #
139
135
  # @since 2.0.0
140
136
  def elect_primary!(description)
141
- @topology = topology.elect_primary(description, @servers)
137
+ @topology = topology.elect_primary(description, servers_list)
142
138
  end
143
139
 
144
- # Removed the server from the cluster for the provided address, if it
140
+ # Notify the cluster that a standalone server was discovered so that the
141
+ # topology can be updated accordingly.
142
+ #
143
+ # @example Notify the cluster that a standalone server was discovered.
144
+ # cluster.standalone_discovered
145
+ #
146
+ # @return [ Topology ] The cluster topology.
147
+ #
148
+ # @since 2.0.6
149
+ def standalone_discovered
150
+ @topology = topology.standalone_discovered
151
+ end
152
+
153
+ # Remove the server from the cluster for the provided address, if it
145
154
  # exists.
146
155
  #
147
156
  # @example Remove the server from the cluster.
@@ -153,9 +162,10 @@ module Mongo
153
162
  def remove(host)
154
163
  log_debug([ "#{host} being removed from the cluster." ])
155
164
  address = Address.new(host)
156
- removed_servers = @servers.reject!{ |server| server.address == address }
165
+ removed_servers = @servers.select { |s| s.address == address }
166
+ @update_lock.synchronize { @servers = @servers - removed_servers }
157
167
  removed_servers.each{ |server| server.disconnect! } if removed_servers
158
- addresses.reject!{ |addr| addr == address }
168
+ @update_lock.synchronize { @addresses.reject! { |addr| addr == address } }
159
169
  end
160
170
 
161
171
  # Force a scan of all known servers in the cluster.
@@ -170,7 +180,7 @@ module Mongo
170
180
  #
171
181
  # @since 2.0.0
172
182
  def scan!
173
- @servers.each{ |server| server.scan! } and true
183
+ servers_list.each{ |server| server.scan! } and true
174
184
  end
175
185
 
176
186
  # Get a list of server candidates from the cluster that can have operations
@@ -183,7 +193,37 @@ module Mongo
183
193
  #
184
194
  # @since 2.0.0
185
195
  def servers
186
- topology.servers(@servers.compact).compact
196
+ topology.servers(servers_list.compact).compact
197
+ end
198
+
199
+ # Add hosts in a description to the cluster.
200
+ #
201
+ # @example Add hosts in a description to the cluster.
202
+ # cluster.add_hosts(description)
203
+ #
204
+ # @param [ Mongo::Server::Description ] description The description.
205
+ #
206
+ # @since 2.0.6
207
+ def add_hosts(description)
208
+ if topology.add_hosts?(description, servers_list)
209
+ description.servers.each { |s| add(s) }
210
+ end
211
+ end
212
+
213
+ # Remove hosts in a description from the cluster.
214
+ #
215
+ # @example Remove hosts in a description from the cluster.
216
+ # cluster.remove_hosts(description)
217
+ #
218
+ # @param [ Mongo::Server::Description ] description The description.
219
+ #
220
+ # @since 2.0.6
221
+ def remove_hosts(description)
222
+ if topology.remove_hosts?(description)
223
+ servers_list.each do |s|
224
+ remove(s.address.to_s) if topology.remove_server?(description, s)
225
+ end
226
+ end
187
227
  end
188
228
 
189
229
  # Create a cluster for the provided client, for use when we don't want the
@@ -204,6 +244,18 @@ module Mongo
204
244
  client.instance_variable_set(:@cluster, cluster)
205
245
  end
206
246
 
247
+ # The addresses in the cluster.
248
+ #
249
+ # @example Get the addresses in the cluster.
250
+ # cluster.addresses
251
+ #
252
+ # @return [ Array<Mongo::Address> ] The addresses.
253
+ #
254
+ # @since 2.0.6
255
+ def addresses
256
+ addresses_list
257
+ end
258
+
207
259
  private
208
260
 
209
261
  def direct_connection?(address)
@@ -213,5 +265,21 @@ module Mongo
213
265
  def addition_allowed?(address)
214
266
  !@topology.single? || direct_connection?(address)
215
267
  end
268
+
269
+ def servers_list
270
+ @update_lock.synchronize do
271
+ @servers.reduce([]) do |servers, server|
272
+ servers << server
273
+ end
274
+ end
275
+ end
276
+
277
+ def addresses_list
278
+ @update_lock.synchronize do
279
+ @addresses.reduce([]) do |addresses, address|
280
+ addresses << address
281
+ end
282
+ end
283
+ end
216
284
  end
217
285
  end
@@ -48,12 +48,11 @@ module Mongo
48
48
  # @since 2.0.0
49
49
  def initial(seeds, options)
50
50
  if options.has_key?(:connect)
51
- return OPTIONS.fetch(options[:connect]).new(options, seeds)
52
- end
53
- if options.has_key?(:replica_set)
51
+ OPTIONS.fetch(options[:connect]).new(options, seeds)
52
+ elsif options.has_key?(:replica_set)
54
53
  ReplicaSet.new(options, seeds)
55
54
  else
56
- seeds.size > 1 ? Unknown.new(options, seeds) : Single.new(options, seeds)
55
+ Unknown.new(options, seeds)
57
56
  end
58
57
  end
59
58
  end
@@ -125,6 +125,54 @@ module Mongo
125
125
  end
126
126
  end
127
127
 
128
+ # Whether a server description's hosts may be added to the cluster.
129
+ #
130
+ # @example Check if a description's hosts may be added to the cluster.
131
+ # topology.add_hosts?(description, servers)
132
+ #
133
+ # @param [ Mongo::Server::Description ] description The description.
134
+ # @param [ Array<Mongo::Server> ] servers The cluster servers.
135
+ #
136
+ # @return [ true, false ] Whether a description's hosts may be added.
137
+ #
138
+ # @since 2.0.6
139
+ def add_hosts?(description, servers)
140
+ !!(member_of_this_set?(description) && !has_primary?(servers))
141
+ end
142
+
143
+ # Whether a description can be used to remove hosts from the cluster.
144
+ #
145
+ # @example Check if a description can be used to remove hosts from the cluster.
146
+ # topology.remove_hosts?(description)
147
+ #
148
+ # @param [ Mongo::Server::Description ] description The description.
149
+ #
150
+ # @return [ true, false ] Whether hosts may be removed from the cluster.
151
+ #
152
+ # @since 2.0.6
153
+ def remove_hosts?(description)
154
+ !description.config.empty? &&
155
+ (description.primary? ||
156
+ description.hosts.empty? ||
157
+ !member_of_this_set?(description))
158
+ end
159
+
160
+ # Whether a specific server in the cluster can be removed, given a description.
161
+ #
162
+ # @example Check if a specific server can be removed from the cluster.
163
+ # topology.remove_server?(description, server)
164
+ #
165
+ # @param [ Mongo::Server::Description ] description The description.
166
+ # @param [ Mongo::Serve ] server The server in question.
167
+ #
168
+ # @return [ true, false ] Whether the server can be removed from the cluster.
169
+ #
170
+ # @since 2.0.6
171
+ def remove_server?(description, server)
172
+ remove_self?(description, server) ||
173
+ (member_of_this_set?(description) && !description.lists_server?(server))
174
+ end
175
+
128
176
  # A replica set topology is not sharded.
129
177
  #
130
178
  # @example Is the topology sharded?
@@ -154,6 +202,33 @@ module Mongo
154
202
  #
155
203
  # @since 2.0.0
156
204
  def unknown?; false; end
205
+
206
+ # Notify the topology that a standalone was discovered.
207
+ #
208
+ # @example Notify the topology that a standalone was discovered.
209
+ # topology.standalone_discovered
210
+ #
211
+ # @return [ Topology::ReplicaSet ] Always returns self.
212
+ #
213
+ # @since 2.0.6
214
+ def standalone_discovered; self; end
215
+
216
+ private
217
+
218
+ def has_primary?(servers)
219
+ servers.find { |s| s.primary? }
220
+ end
221
+
222
+ def member_of_this_set?(description)
223
+ description.replica_set_member? &&
224
+ description.replica_set_name == replica_set_name
225
+ end
226
+
227
+ def remove_self?(description, server)
228
+ !member_of_this_set?(description) &&
229
+ description.is_server?(server) &&
230
+ !description.ghost?
231
+ end
157
232
  end
158
233
  end
159
234
  end
@@ -97,6 +97,50 @@ module Mongo
97
97
  servers.select{ |server| server.mongos? }
98
98
  end
99
99
 
100
+ # Whether a server description's hosts may be added to the cluster.
101
+ #
102
+ # @example Check if a description's hosts may be added to the cluster.
103
+ # topology.add_hosts?(description, servers)
104
+ #
105
+ # @param [ Mongo::Server::Description ] description The description.
106
+ # @param [ Array<Mongo::Server> ] servers The cluster servers.
107
+ #
108
+ # @return [ false ] A description's hosts are never added to a
109
+ # sharded cluster.
110
+ #
111
+ # @since 2.0.6
112
+ def add_hosts?(description, servers); false; end
113
+
114
+ # Whether a description can be used to remove hosts from the cluster.
115
+ #
116
+ # @example Check if a description can be used to remove hosts from
117
+ # the cluster.
118
+ # topology.remove_hosts?(description)
119
+ #
120
+ # @param [ Mongo::Server::Description ] description The description.
121
+ #
122
+ # @return [ true ] A description can always be used to remove hosts
123
+ # from a sharded cluster.
124
+ #
125
+ # @since 2.0.6
126
+ def remove_hosts?(description); true; end
127
+
128
+ # Whether a specific server in the cluster can be removed, given a description.
129
+ #
130
+ # @example Check if a specific server can be removed from the cluster.
131
+ # topology.remove_server?(description, server)
132
+ #
133
+ # @param [ Mongo::Server::Description ] description The description.
134
+ # @param [ Mongo::Serve ] server The server in question.
135
+ #
136
+ # @return [ true, false ] Whether the server can be removed from the cluster.
137
+ #
138
+ # @since 2.0.6
139
+ def remove_server?(description, server)
140
+ remove_self?(description, server) ||
141
+ !(server.mongos? || server.unknown?)
142
+ end
143
+
100
144
  # A sharded topology is sharded.
101
145
  #
102
146
  # @example Is the topology sharded?
@@ -126,6 +170,22 @@ module Mongo
126
170
  #
127
171
  # @since 2.0.0
128
172
  def unknown?; false; end
173
+
174
+ # Notify the topology that a standalone was discovered.
175
+ #
176
+ # @example Notify the topology that a standalone was discovered.
177
+ # topology.standalone_discovered
178
+ #
179
+ # @return [ Topology::Sharded ] Always returns self.
180
+ #
181
+ # @since 2.0.6
182
+ def standalone_discovered; self; end
183
+
184
+ private
185
+
186
+ def remove_self?(description, server)
187
+ description.is_server?(server) && !description.mongos?
188
+ end
129
189
  end
130
190
  end
131
191
  end
@@ -101,6 +101,47 @@ module Mongo
101
101
  [ servers.detect { |server| !server.unknown? } ]
102
102
  end
103
103
 
104
+ # Whether a server description's hosts may be added to the cluster.
105
+ #
106
+ # @example Check if a description's hosts may be added to the cluster.
107
+ # topology.add_hosts?(description, servers)
108
+ #
109
+ # @param [ Mongo::Server::Description ] description The description.
110
+ # @param [ Array<Mongo::Server> ] servers The cluster servers.
111
+ #
112
+ # @return [ false ] A description's hosts are never added to a
113
+ # cluster of Single topology.
114
+ #
115
+ # @since 2.0.6
116
+ def add_hosts?(description, servers); false; end
117
+
118
+ # Whether a description can be used to remove hosts from the cluster.
119
+ #
120
+ # @example Check if a description can be used to remove hosts from
121
+ # the cluster.
122
+ # topology.remove_hosts?(description)
123
+ #
124
+ # @param [ Mongo::Server::Description ] description The description.
125
+ #
126
+ # @return [ true ] A description can never be used to remove hosts
127
+ # from a cluster of Single topology.
128
+ #
129
+ # @since 2.0.6
130
+ def remove_hosts?(description); false; end
131
+
132
+ # Whether a specific server in the cluster can be removed, given a description.
133
+ #
134
+ # @example Check if a specific server can be removed from the cluster.
135
+ # topology.remove_server?(description, server)
136
+ #
137
+ # @param [ Mongo::Server::Description ] description The description.
138
+ # @param [ Mongo::Serve ] server The server in question.
139
+ #
140
+ # @return [ false ] A server is never removed from a cluster of Single topology.
141
+ #
142
+ # @since 2.0.6
143
+ def remove_server?(description, server); false; end
144
+
104
145
  # A single topology is not sharded.
105
146
  #
106
147
  # @example Is the topology sharded?
@@ -130,6 +171,16 @@ module Mongo
130
171
  #
131
172
  # @since 2.0.0
132
173
  def unknown?; false; end
174
+
175
+ # Notify the topology that a standalone was discovered.
176
+ #
177
+ # @example Notify the topology that a standalone was discovered.
178
+ # topology.standalone_discovered
179
+ #
180
+ # @return [ Topology::Single ] Always returns self.
181
+ #
182
+ # @since 2.0.6
183
+ def standalone_discovered; self; end
133
184
  end
134
185
  end
135
186
  end