mongo 2.19.3 → 2.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/Rakefile +27 -154
  4. data/lib/mongo/cluster/topology/base.rb +16 -0
  5. data/lib/mongo/cluster.rb +27 -1
  6. data/lib/mongo/collection/view/iterable.rb +1 -0
  7. data/lib/mongo/collection.rb +4 -2
  8. data/lib/mongo/error/transactions_not_supported.rb +34 -0
  9. data/lib/mongo/error.rb +1 -0
  10. data/lib/mongo/grid/fs_bucket.rb +6 -0
  11. data/lib/mongo/monitoring/event/secure.rb +1 -1
  12. data/lib/mongo/operation/shared/executable.rb +43 -27
  13. data/lib/mongo/operation/shared/response_handling.rb +23 -25
  14. data/lib/mongo/retryable/read_worker.rb +7 -6
  15. data/lib/mongo/retryable/write_worker.rb +7 -4
  16. data/lib/mongo/retryable.rb +2 -2
  17. data/lib/mongo/server/app_metadata/environment.rb +64 -9
  18. data/lib/mongo/server/app_metadata.rb +5 -4
  19. data/lib/mongo/server/description/features.rb +1 -0
  20. data/lib/mongo/server_selector/base.rb +32 -6
  21. data/lib/mongo/session/server_session/dirtyable.rb +52 -0
  22. data/lib/mongo/session/server_session.rb +3 -0
  23. data/lib/mongo/session/session_pool.rb +12 -18
  24. data/lib/mongo/session.rb +32 -0
  25. data/lib/mongo/uri.rb +0 -4
  26. data/lib/mongo/version.rb +1 -1
  27. data/mongo.gemspec +1 -7
  28. data/spec/atlas/atlas_connectivity_spec.rb +4 -4
  29. data/spec/faas/ruby-sam-app/Gemfile +9 -0
  30. data/spec/faas/ruby-sam-app/mongodb/Gemfile +4 -0
  31. data/spec/faas/ruby-sam-app/mongodb/app.rb +149 -0
  32. data/spec/faas/ruby-sam-app/template.yaml +48 -0
  33. data/spec/integration/client_side_encryption/corpus_spec.rb +10 -2
  34. data/spec/integration/retryable_reads_errors_spec.rb +161 -8
  35. data/spec/integration/retryable_writes_errors_spec.rb +156 -0
  36. data/spec/mongo/cluster_spec.rb +36 -0
  37. data/spec/mongo/collection/view/aggregation_spec.rb +6 -1
  38. data/spec/mongo/collection/view/explainable_spec.rb +2 -0
  39. data/spec/mongo/collection_crud_spec.rb +1 -1
  40. data/spec/mongo/operation/insert_spec.rb +1 -1
  41. data/spec/mongo/retryable/write_worker_spec.rb +39 -0
  42. data/spec/mongo/server/app_metadata/environment_spec.rb +135 -0
  43. data/spec/mongo/server/app_metadata_spec.rb +12 -2
  44. data/spec/mongo/server/connection_spec.rb +4 -0
  45. data/spec/mongo/session/session_pool_spec.rb +1 -16
  46. data/spec/mongo/session_transaction_spec.rb +15 -0
  47. data/spec/mongo/uri_spec.rb +0 -9
  48. data/spec/runners/crud/test.rb +0 -8
  49. data/spec/runners/crud.rb +1 -1
  50. data/spec/runners/transactions/test.rb +12 -3
  51. data/spec/runners/unified/assertions.rb +16 -3
  52. data/spec/runners/unified/crud_operations.rb +12 -0
  53. data/spec/runners/unified/support_operations.rb +3 -5
  54. data/spec/runners/unified/test.rb +8 -1
  55. data/spec/shared/lib/mrss/docker_runner.rb +3 -0
  56. data/spec/shared/share/Dockerfile.erb +20 -69
  57. data/spec/shared/shlib/server.sh +1 -0
  58. data/spec/shared/shlib/set_env.sh +5 -28
  59. data/spec/spec_tests/data/client_side_encryption/explain.yml +2 -2
  60. data/spec/spec_tests/data/connection_string/invalid-uris.yml +0 -10
  61. data/spec/spec_tests/data/connection_string/valid-options.yml +13 -0
  62. data/spec/spec_tests/data/crud_unified/find-test-all-options.yml +348 -0
  63. data/spec/spec_tests/data/index_management/createSearchIndex.yml +5 -3
  64. data/spec/spec_tests/data/index_management/createSearchIndexes.yml +7 -4
  65. data/spec/spec_tests/data/index_management/dropSearchIndex.yml +2 -1
  66. data/spec/spec_tests/data/index_management/listSearchIndexes.yml +13 -7
  67. data/spec/spec_tests/data/index_management/updateSearchIndex.yml +2 -1
  68. data/spec/spec_tests/data/retryable_writes/unified/bulkWrite-serverErrors.yml +3 -6
  69. data/spec/spec_tests/data/retryable_writes/unified/insertOne-serverErrors.yml +3 -6
  70. data/spec/spec_tests/data/run_command_unified/runCommand.yml +319 -0
  71. data/spec/spec_tests/data/sessions_unified/driver-sessions-dirty-session-errors.yml +351 -0
  72. data/spec/spec_tests/data/unified/valid-pass/poc-crud.yml +1 -1
  73. data/spec/spec_tests/data/unified/valid-pass/poc-retryable-writes.yml +7 -7
  74. data/spec/spec_tests/data/unified/valid-pass/poc-sessions.yml +3 -4
  75. data/spec/spec_tests/data/unified/valid-pass/poc-transactions-convenient-api.yml +1 -1
  76. data/spec/spec_tests/data/unified/valid-pass/poc-transactions-mongos-pin-auto.yml +1 -1
  77. data/spec/spec_tests/data/unified/valid-pass/poc-transactions.yml +3 -3
  78. data/spec/spec_tests/run_command_unified_spec.rb +13 -0
  79. data/spec/spec_tests/sdam_unified_spec.rb +2 -0
  80. data/spec/support/constraints.rb +6 -0
  81. data/spec/support/ocsp +1 -1
  82. data/spec/support/recording_logger.rb +27 -0
  83. data.tar.gz.sig +0 -0
  84. metadata +1272 -1253
  85. metadata.gz.sig +0 -0
  86. data/spec/spec_tests/data/cmap/pool-clear-interrupt-immediately.yml +0 -49
@@ -18,9 +18,12 @@ module Mongo
18
18
  class Server
19
19
  class AppMetadata
20
20
  # Implements the logic from the handshake spec, for deducing and
21
- # reporting the current FaaS environment in which the program is
21
+ # reporting the current environment in which the program is
22
22
  # executing.
23
23
  #
24
+ # This includes FaaS environment checks, as well as checks for the
25
+ # presence of a container (Docker) and/or orchestrator (Kubernetes).
26
+ #
24
27
  # @api private
25
28
  class Environment
26
29
  # Error class for reporting that too many discriminators were found
@@ -39,6 +42,10 @@ module Mongo
39
42
  # Error class for reporting that the value for a field is too long.
40
43
  class ValueTooLong < Mongo::Error; end
41
44
 
45
+ # The name and location of the .dockerenv file that will signal the
46
+ # presence of Docker.
47
+ DOCKERENV_PATH = '/.dockerenv'
48
+
42
49
  # This value is not explicitly specified in the spec, only implied to be
43
50
  # less than 512.
44
51
  MAXIMUM_VALUE_LENGTH = 500
@@ -102,9 +109,11 @@ module Mongo
102
109
  # if the environment contains invalid or contradictory state, it will
103
110
  # be initialized with {{name}} set to {{nil}}.
104
111
  def initialize
112
+ @fields = {}
105
113
  @error = nil
106
114
  @name = detect_environment
107
- populate_fields
115
+ populate_faas_fields
116
+ detect_container
108
117
  rescue TooManyEnvironments => e
109
118
  self.error = "too many environments detected: #{e.message}"
110
119
  rescue MissingVariable => e
@@ -115,6 +124,23 @@ module Mongo
115
124
  self.error = "value for #{e.message} is too long"
116
125
  end
117
126
 
127
+ # Queries the detected container information.
128
+ #
129
+ # @return [ Hash | nil ] the detected container information, or
130
+ # nil if no container was detected.
131
+ def container
132
+ fields[:container]
133
+ end
134
+
135
+ # Queries whether any environment information was able to be
136
+ # detected.
137
+ #
138
+ # @return [ true | false ] if any environment information was
139
+ # detected.
140
+ def present?
141
+ @name || fields.any?
142
+ end
143
+
118
144
  # Queries whether the current environment is a valid FaaS environment.
119
145
  #
120
146
  # @return [ true | false ] whether the environment is a FaaS
@@ -159,14 +185,11 @@ module Mongo
159
185
  @name == 'vercel'
160
186
  end
161
187
 
162
- # Compiles the detected environment information into a Hash. It will
163
- # always include a {{name}} key, but may include other keys as well,
164
- # depending on the detected FaaS environment. (See the handshake
165
- # spec for details.)
188
+ # Compiles the detected environment information into a Hash.
166
189
  #
167
190
  # @return [ Hash ] the detected environment information.
168
191
  def to_h
169
- fields.merge(name: name)
192
+ name ? fields.merge(name: name) : fields
170
193
  end
171
194
 
172
195
  private
@@ -192,6 +215,38 @@ module Mongo
192
215
  names.first
193
216
  end
194
217
 
218
+ # Looks for the presence of a container. Currently can detect
219
+ # Docker (by the existence of a .dockerenv file in the root
220
+ # directory) and Kubernetes (by the existence of the KUBERNETES_SERVICE_HOST
221
+ # environment variable).
222
+ def detect_container
223
+ runtime = docker_present? && 'docker'
224
+ orchestrator = kubernetes_present? && 'kubernetes'
225
+
226
+ return unless runtime || orchestrator
227
+
228
+ fields[:container] = {}
229
+ fields[:container][:runtime] = runtime if runtime
230
+ fields[:container][:orchestrator] = orchestrator if orchestrator
231
+ end
232
+
233
+ # Checks for the existence of a .dockerenv in the root directory.
234
+ def docker_present?
235
+ File.exist?(dockerenv_path)
236
+ end
237
+
238
+ # Implementing this as a method so that it can be mocked in tests, to
239
+ # test the presence or absence of Docker.
240
+ def dockerenv_path
241
+ DOCKERENV_PATH
242
+ end
243
+
244
+ # Checks for the presence of a non-empty KUBERNETES_SERVICE_HOST
245
+ # environment variable.
246
+ def kubernetes_present?
247
+ !ENV['KUBERNETES_SERVICE_HOST'].to_s.empty?
248
+ end
249
+
195
250
  # Determines whether the named environment variable exists, and (if
196
251
  # a pattern has been declared for that descriminator) whether the
197
252
  # pattern matches the value of the variable.
@@ -212,10 +267,10 @@ module Mongo
212
267
  # Extracts environment information from the current environment
213
268
  # variables, based on the detected FaaS environment. Populates the
214
269
  # {{@fields}} instance variable.
215
- def populate_fields
270
+ def populate_faas_fields
216
271
  return unless name
217
272
 
218
- @fields = FIELDS[name].each_with_object({}) do |(var, defn), fields|
273
+ FIELDS[name].each_with_object(@fields) do |(var, defn), fields|
219
274
  fields[defn[:field]] = extract_field(var, defn)
220
275
  end
221
276
  end
@@ -187,13 +187,14 @@ module Mongo
187
187
  }
188
188
  end
189
189
 
190
- # Returns the environment doc describing the current FaaS environment.
190
+ # Returns the environment doc describing the current execution
191
+ # environment.
191
192
  #
192
- # @return [ Hash | nil ] the environment doc (or nil if not in a FaaS
193
- # environment).
193
+ # @return [ Hash | nil ] the environment doc (or nil if no relevant
194
+ # environment info was detected)
194
195
  def env_doc
195
196
  env = Environment.new
196
- env.faas? ? env.to_h : nil
197
+ env.present? ? env.to_h : nil
197
198
  end
198
199
 
199
200
  def type
@@ -48,6 +48,7 @@ module Mongo
48
48
  # provided by the client during findAndModify operations, requiring the
49
49
  # driver to raise client-side errors when those options are provided.
50
50
  find_and_modify_option_validation: 8,
51
+ sharded_transactions: 8,
51
52
  transactions: 7,
52
53
  scram_sha_256: 7,
53
54
  array_filters: 6,
@@ -164,6 +164,10 @@ module Mongo
164
164
  # for mongos pinning. Added in version 2.10.0.
165
165
  # @param [ true | false ] write_aggregation Whether we need a server that
166
166
  # supports writing aggregations (e.g. with $merge/$out) on secondaries.
167
+ # @param [ Array<Server> ] deprioritized A list of servers that should
168
+ # be selected from only if no other servers are available. This is
169
+ # used to avoid selecting the same server twice in a row when
170
+ # retrying a command.
167
171
  #
168
172
  # @return [ Mongo::Server ] A server matching the server preference.
169
173
  #
@@ -174,8 +178,8 @@ module Mongo
174
178
  # lint mode is enabled.
175
179
  #
176
180
  # @since 2.0.0
177
- def select_server(cluster, ping = nil, session = nil, write_aggregation: false)
178
- select_server_impl(cluster, ping, session, write_aggregation).tap do |server|
181
+ def select_server(cluster, ping = nil, session = nil, write_aggregation: false, deprioritized: [])
182
+ select_server_impl(cluster, ping, session, write_aggregation, deprioritized).tap do |server|
179
183
  if Lint.enabled? && !server.pool.ready?
180
184
  raise Error::LintError, 'Server selector returning a server with a pool which is not ready'
181
185
  end
@@ -183,7 +187,7 @@ module Mongo
183
187
  end
184
188
 
185
189
  # Parameters and return values are the same as for select_server.
186
- private def select_server_impl(cluster, ping, session, write_aggregation)
190
+ private def select_server_impl(cluster, ping, session, write_aggregation, deprioritized)
187
191
  if cluster.topology.is_a?(Cluster::Topology::LoadBalanced)
188
192
  return cluster.servers.first
189
193
  end
@@ -266,7 +270,7 @@ module Mongo
266
270
  end
267
271
  end
268
272
 
269
- server = try_select_server(cluster, write_aggregation: write_aggregation)
273
+ server = try_select_server(cluster, write_aggregation: write_aggregation, deprioritized: deprioritized)
270
274
 
271
275
  if server
272
276
  unless cluster.topology.compatible?
@@ -321,11 +325,15 @@ module Mongo
321
325
  # an eligible server.
322
326
  # @param [ true | false ] write_aggregation Whether we need a server that
323
327
  # supports writing aggregations (e.g. with $merge/$out) on secondaries.
328
+ # @param [ Array<Server> ] deprioritized A list of servers that should
329
+ # be selected from only if no other servers are available. This is
330
+ # used to avoid selecting the same server twice in a row when
331
+ # retrying a command.
324
332
  #
325
333
  # @return [ Server | nil ] A suitable server, if one exists.
326
334
  #
327
335
  # @api private
328
- def try_select_server(cluster, write_aggregation: false)
336
+ def try_select_server(cluster, write_aggregation: false, deprioritized: [])
329
337
  servers = if write_aggregation && cluster.replica_set?
330
338
  # 1. Check if ALL servers in cluster support secondary writes.
331
339
  is_write_supported = cluster.servers.reduce(true) do |res, server|
@@ -347,7 +355,7 @@ module Mongo
347
355
  # by the selector (e.g. for secondary preferred, the first
348
356
  # server may be a secondary and the second server may be primary)
349
357
  # and we should take the first server here respecting the order
350
- server = servers.first
358
+ server = suitable_server(servers, deprioritized)
351
359
 
352
360
  if server
353
361
  if Lint.enabled?
@@ -418,6 +426,24 @@ module Mongo
418
426
 
419
427
  private
420
428
 
429
+ # Returns a server from the list of servers that is suitable for
430
+ # executing the operation.
431
+ #
432
+ # @param [ Array<Server> ] servers The candidate servers.
433
+ # @param [ Array<Server> ] deprioritized A list of servers that should
434
+ # be selected from only if no other servers are available.
435
+ #
436
+ # @return [ Server | nil ] The suitable server or nil if no suitable
437
+ # server is available.
438
+ def suitable_server(servers, deprioritized)
439
+ preferred = servers - deprioritized
440
+ if preferred.empty?
441
+ servers.first
442
+ else
443
+ preferred.first
444
+ end
445
+ end
446
+
421
447
  # Convert this server preference definition into a format appropriate
422
448
  # for sending to a MongoDB server (i.e., as a command field).
423
449
  #
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (C) 2024 MongoDB Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ module Mongo
18
+ class Session
19
+ class ServerSession
20
+ # Functionality for manipulating and querying a session's
21
+ # "dirty" state, per the last paragraph at
22
+ # https://github.com/mongodb/specifications/blob/master/source/sessions/driver-sessions.rst#server-session-pool
23
+ #
24
+ # If a driver has a server session pool and a network error is
25
+ # encountered when executing any command with a ClientSession, the
26
+ # driver MUST mark the associated ServerSession as dirty. Dirty server
27
+ # sessions are discarded when returned to the server session pool. It is
28
+ # valid for a dirty session to be used for subsequent commands (e.g. an
29
+ # implicit retry attempt, a later command in a bulk write, or a later
30
+ # operation on an explicit session), however, it MUST remain dirty for
31
+ # the remainder of its lifetime regardless if later commands succeed.
32
+ #
33
+ # @api private
34
+ module Dirtyable
35
+ # Query whether the server session has been marked dirty or not.
36
+ #
37
+ # @return [ true | false ] the server session's dirty state
38
+ def dirty?
39
+ @dirty
40
+ end
41
+
42
+ # Mark the server session as dirty (the default) or clean.
43
+ #
44
+ # @param [ true | false ] mark whether the mark the server session
45
+ # dirty or not.
46
+ def dirty!(mark = true)
47
+ @dirty = mark
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -15,6 +15,8 @@
15
15
  # See the License for the specific language governing permissions and
16
16
  # limitations under the License.
17
17
 
18
+ require 'mongo/session/server_session/dirtyable'
19
+
18
20
  module Mongo
19
21
 
20
22
  class Session
@@ -25,6 +27,7 @@ module Mongo
25
27
  #
26
28
  # @since 2.5.0
27
29
  class ServerSession
30
+ include Dirtyable
28
31
 
29
32
  # Regex for removing dashes from the UUID string.
30
33
  #
@@ -25,21 +25,6 @@ module Mongo
25
25
  #
26
26
  # @since 2.5.0
27
27
  class SessionPool
28
-
29
- # Create a SessionPool.
30
- #
31
- # @example
32
- # SessionPool.create(cluster)
33
- #
34
- # @param [ Mongo::Cluster ] cluster The cluster that will be associated with this
35
- # session pool.
36
- #
37
- # @since 2.5.0
38
- def self.create(cluster)
39
- pool = new(cluster)
40
- cluster.instance_variable_set(:@session_pool, pool)
41
- end
42
-
43
28
  # Initialize a SessionPool.
44
29
  #
45
30
  # @example
@@ -105,9 +90,7 @@ module Mongo
105
90
 
106
91
  @mutex.synchronize do
107
92
  prune!
108
- unless about_to_expire?(session)
109
- @queue.unshift(session)
110
- end
93
+ @queue.unshift(session) if return_to_queue?(session)
111
94
  end
112
95
  end
113
96
 
@@ -136,6 +119,17 @@ module Mongo
136
119
 
137
120
  private
138
121
 
122
+ # Query whether the given session is okay to return to the
123
+ # pool's queue.
124
+ #
125
+ # @param [ Session::ServerSession ] session the session to query
126
+ #
127
+ # @return [ true | false ] whether to return the session to the
128
+ # queue.
129
+ def return_to_queue?(session)
130
+ !session.dirty? && !about_to_expire?(session)
131
+ end
132
+
139
133
  def about_to_expire?(session)
140
134
  if session.nil?
141
135
  raise ArgumentError, 'session cannot be nil'
data/lib/mongo/session.rb CHANGED
@@ -123,6 +123,23 @@ module Mongo
123
123
  # @since 2.5.0
124
124
  attr_reader :operation_time
125
125
 
126
+ # Sets the dirty state to the given value for the underlying server
127
+ # session. If there is no server session, this does nothing.
128
+ #
129
+ # @param [ true | false ] mark whether to mark the server session as
130
+ # dirty, or not.
131
+ def dirty!(mark = true)
132
+ @server_session&.dirty!(mark)
133
+ end
134
+
135
+ # @return [ true | false | nil ] whether the underlying server session is
136
+ # dirty. If no server session exists for this session, returns nil.
137
+ #
138
+ # @api private
139
+ def dirty?
140
+ @server_session&.dirty?
141
+ end
142
+
126
143
  # @return [ Hash ] The options for the transaction currently being executed
127
144
  # on this session.
128
145
  #
@@ -538,6 +555,8 @@ module Mongo
538
555
  #
539
556
  # @since 2.6.0
540
557
  def start_transaction(options = nil)
558
+ check_transactions_supported!
559
+
541
560
  if options
542
561
  Lint.validate_read_concern_option(options[:read_concern])
543
562
 
@@ -1185,5 +1204,18 @@ module Mongo
1185
1204
  raise Mongo::Error::InvalidSession.new(MISMATCHED_CLUSTER_ERROR_MSG)
1186
1205
  end
1187
1206
  end
1207
+
1208
+ def check_transactions_supported!
1209
+ raise Mongo::Error::TransactionsNotSupported, "standalone topology" if cluster.single?
1210
+
1211
+ cluster.next_primary.with_connection do |conn|
1212
+ if cluster.replica_set? && !conn.features.transactions_enabled?
1213
+ raise Mongo::Error::TransactionsNotSupported, "server version is < 4.0"
1214
+ end
1215
+ if cluster.sharded? && !conn.features.sharded_transactions_enabled?
1216
+ raise Mongo::Error::TransactionsNotSupported, "sharded transactions require server version >= 4.2"
1217
+ end
1218
+ end
1219
+ end
1188
1220
  end
1189
1221
  end
data/lib/mongo/uri.rb CHANGED
@@ -377,10 +377,6 @@ module Mongo
377
377
  raise_invalid_error!("Options contain an unescaped question mark (?), or the database name contains a question mark and was not escaped")
378
378
  end
379
379
 
380
- if options && !hosts_and_db.index('/')
381
- raise_invalid_error!("MongoDB URI must have a slash (/) after the hosts if options are given")
382
- end
383
-
384
380
  hosts, db = hosts_and_db.split('/', 2)
385
381
  if db && db.index('/')
386
382
  raise_invalid_error!("Database name contains an unescaped slash (/): #{db}")
data/lib/mongo/version.rb CHANGED
@@ -20,5 +20,5 @@ module Mongo
20
20
  # The current version of the driver.
21
21
  #
22
22
  # @since 2.0.0
23
- VERSION = '2.19.3'.freeze
23
+ VERSION = '2.20.0'.freeze
24
24
  end
data/mongo.gemspec CHANGED
@@ -41,11 +41,5 @@ Gem::Specification.new do |s|
41
41
 
42
42
  s.required_ruby_version = ">= 2.5"
43
43
 
44
- # For testing driver against bson master we need to depend on bson < 6.0.0
45
- # but in release version we want to depend on bson < 5.0.0.
46
- if %w(1 yes true).include?(ENV['MONGO_RUBY_DRIVER_BSON_MASTER'])
47
- s.add_dependency 'bson', '>=4.13.0', '<6.0.0'
48
- else
49
- s.add_dependency 'bson', '>=4.14.1', '<5.0.0'
50
- end
44
+ s.add_dependency 'bson', '>=4.14.1', '<6.0.0'
51
45
  end
@@ -11,13 +11,13 @@ describe 'Atlas connectivity' do
11
11
 
12
12
  describe 'connection to Atlas' do
13
13
  it 'runs ismaster successfully' do
14
- result = client.database.command(:ismaster => 1)
15
- expect(result.documents.first['ismaster']).to be true
14
+ expect { client.database.command(:hello => 1) }
15
+ .not_to raise_error
16
16
  end
17
17
 
18
18
  it 'runs findOne successfully' do
19
- result = client.use(:test)['test'].find.to_a
20
- expect(result).to be_a(Array)
19
+ expect { client.use(:test)['test'].find.to_a }
20
+ .not_to raise_error
21
21
  end
22
22
  end
23
23
  end
@@ -0,0 +1,9 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "httparty"
4
+ gem "mongo"
5
+
6
+ group :test do
7
+ gem "test-unit"
8
+ gem "mocha"
9
+ end
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "httparty"
4
+ gem "mongo"
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mongo'
4
+ require 'json'
5
+
6
+ class StatsAggregator
7
+
8
+ def initialize
9
+ @open_connections = 0
10
+ @heartbeats_count = 0
11
+ @total_heartbeat_time = 0
12
+ @commands_count = 0
13
+ @total_command_time = 0
14
+ end
15
+
16
+ def add_command(duration)
17
+ @commands_count += 1
18
+ @total_command_time += duration
19
+ end
20
+
21
+ def add_heartbeat(duration)
22
+ @heartbeats_count += 1
23
+ @total_heartbeat_time += duration
24
+ end
25
+
26
+ def add_connection
27
+ @open_connections += 1
28
+ end
29
+
30
+ def remove_connection
31
+ @open_connections -= 1
32
+ end
33
+
34
+ def average_heartbeat_time
35
+ if @heartbeats_count == 0
36
+ 0
37
+ else
38
+ @total_heartbeat_time / @heartbeats_count
39
+ end
40
+ end
41
+
42
+ def average_command_time
43
+ if @commands_count == 0
44
+ 0
45
+ else
46
+ @total_command_time / @commands_count
47
+ end
48
+ end
49
+
50
+ def reset
51
+ @open_connections = 0
52
+ @heartbeats_count = 0
53
+ @total_heartbeat_time = 0
54
+ @commands_count = 0
55
+ @total_command_time = 0
56
+ end
57
+
58
+ def result
59
+ {
60
+ average_heartbeat_time: average_heartbeat_time,
61
+ average_command_time: average_command_time,
62
+ heartbeats_count: @heartbeats_count,
63
+ open_connections: @open_connections,
64
+ }
65
+ end
66
+ end
67
+
68
+ class CommandMonitor
69
+
70
+ def initialize(stats_aggregator)
71
+ @stats_aggregator = stats_aggregator
72
+ end
73
+
74
+ def started(event); end
75
+
76
+ def failed(event)
77
+ @stats_aggregator.add_command(event.duration)
78
+ end
79
+
80
+ def succeeded(event)
81
+ @stats_aggregator.add_command(event.duration)
82
+ end
83
+ end
84
+
85
+ class HeartbeatMonitor
86
+
87
+ def initialize(stats_aggregator)
88
+ @stats_aggregator = stats_aggregator
89
+ end
90
+
91
+ def started(event); end
92
+
93
+ def succeeded(event)
94
+ @stats_aggregator.add_heartbeat(event.duration)
95
+ end
96
+
97
+ def failed(event)
98
+ @stats_aggregator.add_heartbeat(event.duration)
99
+ end
100
+ end
101
+
102
+ class PoolMonitor
103
+
104
+ def initialize(stats_aggregator)
105
+ @stats_aggregator = stats_aggregator
106
+ end
107
+
108
+ def published(event)
109
+ case event
110
+ when Mongo::Monitoring::Event::Cmap::ConnectionCreated
111
+ @stats_aggregator.add_connection
112
+ when Mongo::Monitoring::Event::Cmap::ConnectionClosed
113
+ @stats_aggregator.remove_connection
114
+ end
115
+ end
116
+ end
117
+
118
+ $stats_aggregator = StatsAggregator.new
119
+
120
+ command_monitor = CommandMonitor.new($stats_aggregator)
121
+ heartbeat_monitor = HeartbeatMonitor.new($stats_aggregator)
122
+ pool_monitor = PoolMonitor.new($stats_aggregator)
123
+
124
+ sdam_proc = proc do |client|
125
+ client.subscribe(Mongo::Monitoring::COMMAND, command_monitor)
126
+ client.subscribe(Mongo::Monitoring::SERVER_HEARTBEAT, heartbeat_monitor)
127
+ client.subscribe(Mongo::Monitoring::CONNECTION_POOL, pool_monitor)
128
+ end
129
+
130
+ puts 'Connecting'
131
+ $client = Mongo::Client.new(ENV['MONGODB_URI'], sdam_proc: sdam_proc)
132
+ # Populate the connection pool
133
+ $client.use('lambda_test').database.list_collections
134
+ puts 'Connected'
135
+
136
+ def lambda_handler(event:, context:)
137
+ db = $client.use('lambda_test')
138
+ collection = db[:test_collection]
139
+ result = collection.insert_one({ name: 'test' })
140
+ collection.delete_one({ _id: result.inserted_id })
141
+ response = $stats_aggregator.result.to_json
142
+ $stats_aggregator.reset
143
+ puts "Response: #{response}"
144
+
145
+ {
146
+ statusCode: 200,
147
+ body: response
148
+ }
149
+ end
@@ -0,0 +1,48 @@
1
+ AWSTemplateFormatVersion: '2010-09-09'
2
+ Transform: AWS::Serverless-2016-10-31
3
+ Description: >
4
+ Sample SAM Template for ruby-sam-app
5
+
6
+ # More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
7
+ Globals:
8
+ Function:
9
+ Timeout: 30
10
+ MemorySize: 128
11
+
12
+ Parameters:
13
+ MongoDbUri:
14
+ Type: String
15
+ Description: The MongoDB connection string.
16
+
17
+ Resources:
18
+ MongoDBFunction:
19
+ Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
20
+ Properties:
21
+ CodeUri: mongodb/
22
+ Environment:
23
+ Variables:
24
+ MONGODB_URI: !Ref MongoDbUri
25
+ Handler: app.lambda_handler
26
+ Runtime: ruby3.2
27
+ Architectures:
28
+ - x86_64
29
+ Events:
30
+ MongoDB:
31
+ Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
32
+ Properties:
33
+ Path: /mongodb
34
+ Method: get
35
+
36
+ Outputs:
37
+ # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
38
+ # Find out more about other implicit resources you can reference within SAM
39
+ # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
40
+ MongoDBApi:
41
+ Description: "API Gateway endpoint URL for Prod stage for MongoDB function"
42
+ Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/mongodb/"
43
+ MongoDBFunction:
44
+ Description: "MongoDB Lambda Function ARN"
45
+ Value: !GetAtt MongoDBFunction.Arn
46
+ MongoDBFunctionIamRole:
47
+ Description: "Implicit IAM Role created for MongoDB function"
48
+ Value: !GetAtt MongoDBFunctionRole.Arn