mongoid 7.2.5 → 7.2.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +3 -3
  3. data/lib/config/locales/en.yml +13 -0
  4. data/lib/mongoid/association/relatable.rb +2 -0
  5. data/lib/mongoid/config/environment.rb +9 -1
  6. data/lib/mongoid/contextual/atomic.rb +7 -2
  7. data/lib/mongoid/contextual/none.rb +3 -0
  8. data/lib/mongoid/criteria/queryable/selectable.rb +2 -2
  9. data/lib/mongoid/criteria/queryable/storable.rb +4 -4
  10. data/lib/mongoid/document.rb +3 -2
  11. data/lib/mongoid/errors/empty_config_file.rb +26 -0
  12. data/lib/mongoid/errors/invalid_config_file.rb +26 -0
  13. data/lib/mongoid/errors.rb +2 -0
  14. data/lib/mongoid/persistence_context.rb +3 -1
  15. data/lib/mongoid/query_cache.rb +11 -1
  16. data/lib/mongoid/tasks/database.rb +1 -1
  17. data/lib/mongoid/version.rb +1 -1
  18. data/spec/integration/contextual/empty_spec.rb +142 -0
  19. data/spec/integration/stringified_symbol_field_spec.rb +2 -2
  20. data/spec/mongoid/association/referenced/belongs_to_query_spec.rb +20 -0
  21. data/spec/mongoid/association/referenced/has_many_models.rb +17 -0
  22. data/spec/mongoid/clients/factory_spec.rb +9 -3
  23. data/spec/mongoid/clients/options_spec.rb +11 -5
  24. data/spec/mongoid/config/environment_spec.rb +86 -8
  25. data/spec/mongoid/contextual/atomic_spec.rb +64 -25
  26. data/spec/mongoid/contextual/geo_near_spec.rb +1 -1
  27. data/spec/mongoid/document_spec.rb +21 -1
  28. data/spec/mongoid/errors/invalid_config_file_spec.rb +32 -0
  29. data/spec/mongoid/persistable/updatable_spec.rb +2 -0
  30. data/spec/mongoid/query_cache_spec.rb +24 -0
  31. data/spec/shared/lib/mrss/constraints.rb +21 -4
  32. data/spec/shared/lib/mrss/event_subscriber.rb +200 -0
  33. data/spec/shared/lib/mrss/server_version_registry.rb +17 -12
  34. data/spec/shared/share/Dockerfile.erb +5 -4
  35. data/spec/shared/shlib/server.sh +71 -21
  36. data.tar.gz.sig +0 -0
  37. metadata +544 -536
  38. metadata.gz.sig +0 -0
@@ -458,6 +458,7 @@ describe Mongoid::Persistable::Updatable do
458
458
  describe "##{method}" do
459
459
 
460
460
  context "when saving with a hash field with invalid keys" do
461
+ max_server_version '4.9'
461
462
 
462
463
  let(:person) do
463
464
  Person.create
@@ -494,6 +495,7 @@ describe Mongoid::Persistable::Updatable do
494
495
  end
495
496
 
496
497
  context "when the document has been destroyed" do
498
+ max_server_version '4.9'
497
499
 
498
500
  let(:person) do
499
501
  Person.create
@@ -24,10 +24,27 @@ describe Mongoid::QueryCache do
24
24
  SessionRegistry.instance.verify_sessions_ended!
25
25
  end
26
26
 
27
+ let(:reset_legacy_qc_warning) do
28
+ begin
29
+ Mongoid::QueryCache.remove_instance_variable('@legacy_query_cache_warned')
30
+ rescue NameError
31
+ # raised if the instance variable wasn't set
32
+ end
33
+ end
34
+
27
35
  describe '#cache' do
28
36
  context 'with driver query cache' do
29
37
  min_driver_version '2.14'
30
38
 
39
+ it 'does not log a deprecation warning' do
40
+ reset_legacy_qc_warning
41
+
42
+ expect_any_instance_of(Logger).to_not receive(:warn).with(
43
+ described_class::LEGACY_WARNING
44
+ )
45
+ described_class.cache { }
46
+ end
47
+
31
48
  context 'when query cache is not enabled' do
32
49
  before do
33
50
  Mongoid::QueryCache.enabled = false
@@ -180,6 +197,13 @@ describe Mongoid::QueryCache do
180
197
  context 'with mongoid query cache' do
181
198
  max_driver_version '2.13'
182
199
 
200
+ it 'logs a deprecation warning' do
201
+ reset_legacy_qc_warning
202
+
203
+ expect_any_instance_of(Logger).to receive(:warn).with(described_class::LEGACY_WARNING)
204
+ described_class.cache { }
205
+ end
206
+
183
207
  context 'when query cache is not enabled' do
184
208
  before do
185
209
  Mongoid::QueryCache.enabled = false
@@ -257,22 +257,39 @@ module Mrss
257
257
  end
258
258
  end
259
259
 
260
- def require_multi_shard
260
+ def require_multi_mongos
261
261
  before(:all) do
262
262
  if ClusterConfig.instance.topology == :sharded && SpecConfig.instance.addresses.length == 1
263
- skip 'Test requires a minimum of two shards if run in sharded topology'
263
+ skip 'Test requires a minimum of two mongoses if run in sharded topology'
264
+ end
265
+
266
+ if ClusterConfig.instance.topology == :load_balanced && SpecConfig.instance.single_mongos?
267
+ skip 'Test requires a minimum of two mongoses if run in load-balanced topology'
264
268
  end
265
269
  end
266
270
  end
267
271
 
268
- def require_no_multi_shard
272
+ # In sharded topology operations are distributed to the mongoses.
273
+ # When we set fail points, the fail point may be set on one mongos and
274
+ # operation may be executed on another mongos, causing failures.
275
+ # Tests that are not setting targeted fail points should utilize this
276
+ # method to restrict themselves to single mongos.
277
+ #
278
+ # In load-balanced topology, the same problem can happen when there is
279
+ # more than one mongos behind the load balancer.
280
+ def require_no_multi_mongos
269
281
  before(:all) do
270
282
  if ClusterConfig.instance.topology == :sharded && SpecConfig.instance.addresses.length > 1
271
- skip 'Test requires a single shard if run in sharded topology'
283
+ skip 'Test requires a single mongos if run in sharded topology'
284
+ end
285
+ if ClusterConfig.instance.topology == :load_balanced && !SpecConfig.instance.single_mongos?
286
+ skip 'Test requires a single mongos, as indicated by SINGLE_MONGOS=1 environment variable, if run in load-balanced topology'
272
287
  end
273
288
  end
274
289
  end
275
290
 
291
+ alias :require_no_multi_shard :require_no_multi_mongos
292
+
276
293
  def require_wired_tiger
277
294
  before(:all) do
278
295
  # Storage detection fails for serverless instances. However, it is safe to
@@ -0,0 +1,200 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mrss
4
+ # Test event subscriber.
5
+ class EventSubscriber
6
+
7
+ # The mappings of event names to types.
8
+ MAPPINGS = {
9
+ 'topology_opening_event' => Mongo::Monitoring::Event::TopologyOpening,
10
+ 'topology_description_changed_event' => Mongo::Monitoring::Event::TopologyChanged,
11
+ 'topology_closed_event' => Mongo::Monitoring::Event::TopologyClosed,
12
+ 'server_opening_event' => Mongo::Monitoring::Event::ServerOpening,
13
+ 'server_description_changed_event' => Mongo::Monitoring::Event::ServerDescriptionChanged,
14
+ 'server_closed_event' => Mongo::Monitoring::Event::ServerClosed
15
+ }.freeze
16
+
17
+ attr_reader :all_events
18
+
19
+ attr_reader :started_events
20
+
21
+ attr_reader :succeeded_events
22
+
23
+ attr_reader :failed_events
24
+
25
+ attr_reader :published_events
26
+
27
+ # @param [ String ] name Optional name for the event subscriber.
28
+ def initialize(name: nil)
29
+ @mutex = Mutex.new
30
+ clear_events!
31
+ @name = name
32
+ end
33
+
34
+ def to_s
35
+ %Q`#<EventSubscriber:#{@name ? "\"#{@name}\"" : '%x' % object_id} \
36
+ started=#{started_events.length} \
37
+ succeeded=#{succeeded_events.length} \
38
+ failed=#{failed_events.length} \
39
+ published=#{published_events.length}>`
40
+ end
41
+
42
+ alias :inspect :to_s
43
+
44
+ # Event retrieval
45
+
46
+ def select_started_events(cls)
47
+ started_events.select do |event|
48
+ event.is_a?(cls)
49
+ end
50
+ end
51
+
52
+ def select_succeeded_events(cls)
53
+ succeeded_events.select do |event|
54
+ event.is_a?(cls)
55
+ end
56
+ end
57
+
58
+ def select_completed_events(*classes)
59
+ (succeeded_events + failed_events).select do |event|
60
+ classes.any? { |c| c === event }
61
+ end
62
+ end
63
+
64
+ def select_published_events(cls)
65
+ published_events.select do |event|
66
+ event.is_a?(cls)
67
+ end
68
+ end
69
+
70
+ # Filters command started events for the specified command name.
71
+ def command_started_events(command_name)
72
+ started_events.select do |event|
73
+ event.command[command_name]
74
+ end
75
+ end
76
+
77
+ def non_auth_command_started_events
78
+ started_events.reject do |event|
79
+ %w(authenticate getnonce saslSstart saslContinue).any? do |cmd|
80
+ event.command[cmd]
81
+ end
82
+ end
83
+ end
84
+
85
+ # Locates command stated events for the specified command name,
86
+ # asserts that there is exactly one such event, and returns it.
87
+ def single_command_started_event(command_name, include_auth: false)
88
+ events = if include_auth
89
+ started_events
90
+ else
91
+ non_auth_command_started_events
92
+ end
93
+ events.select! do |event|
94
+ event.command[command_name]
95
+ end
96
+ if events.length != 1
97
+ raise "Expected a single #{command_name} event but we have #{events.length}"
98
+ end
99
+ events.first
100
+ end
101
+
102
+
103
+ # Get the first succeeded event published for the name, and then delete it.
104
+ #
105
+ # @param [ String ] name The event name.
106
+ #
107
+ # @return [ Event ] The matching event.
108
+ def first_event(name)
109
+ cls = MAPPINGS[name]
110
+ if cls.nil?
111
+ raise ArgumentError, "Bogus event name #{name}"
112
+ end
113
+ matching = succeeded_events.find do |event|
114
+ cls === event
115
+ end
116
+ succeeded_events.delete(matching)
117
+ matching
118
+ end
119
+
120
+ # Event recording
121
+
122
+ # Cache the started event.
123
+ #
124
+ # @param [ Event ] event The event.
125
+ def started(event)
126
+ @mutex.synchronize do
127
+ started_events << event
128
+ all_events << event
129
+ end
130
+ end
131
+
132
+ # Cache the succeeded event.
133
+ #
134
+ # @param [ Event ] event The event.
135
+ def succeeded(event)
136
+ @mutex.synchronize do
137
+ succeeded_events << event
138
+ all_events << event
139
+ end
140
+ end
141
+
142
+ # Cache the failed event.
143
+ #
144
+ # @param [ Event ] event The event.
145
+ def failed(event)
146
+ @mutex.synchronize do
147
+ failed_events << event
148
+ all_events << event
149
+ end
150
+ end
151
+
152
+ def published(event)
153
+ @mutex.synchronize do
154
+ published_events << event
155
+ all_events << event
156
+ end
157
+ end
158
+
159
+ # Clear all cached events.
160
+ def clear_events!
161
+ @all_events = []
162
+ @started_events = []
163
+ @succeeded_events = []
164
+ @failed_events = []
165
+ @published_events = []
166
+ self
167
+ end
168
+ end
169
+ # Only handles succeeded events correctly.
170
+ class PhasedEventSubscriber < EventSubscriber
171
+ def initialize
172
+ super
173
+ @phase_events = {}
174
+ end
175
+
176
+ def phase_finished(phase_index)
177
+ @phase_events[phase_index] = succeeded_events
178
+ @succeeded_events = []
179
+ end
180
+
181
+ def phase_events(phase_index)
182
+ @phase_events[phase_index]
183
+ end
184
+
185
+ def event_count
186
+ @phase_events.inject(0) do |sum, event|
187
+ sum + event.length
188
+ end
189
+ end
190
+ end
191
+
192
+ class VerboseEventSubscriber < EventSubscriber
193
+ %w(started succeeded failed published).each do |meth|
194
+ define_method(meth) do |event|
195
+ puts event.summary
196
+ super(event)
197
+ end
198
+ end
199
+ end
200
+ end
@@ -48,19 +48,24 @@ module Mrss
48
48
  url = dl['archive']['url']
49
49
  end
50
50
  rescue MissingDownloadUrl
51
- if %w(4.7 4.7.0).include?(desired_version)
52
- # 4.7.0 has no advertised downloads but it is downloadable and
53
- # we do need it. Dirty hack below.
54
- registry = self.class.new('4.4.3', arch)
55
- registry.download_url.sub('4.4.3', '4.7.0').tap do |url|
56
- # Sanity check - ensure the URL we hacked up is a valid one
57
- io = uri_open(url)
58
- begin
59
- io.read(1)
60
- ensure
61
- io.close
62
- end
51
+ if %w(2.6 3.0).include?(desired_version) && arch == 'ubuntu1604'
52
+ # 2.6 and 3.0 are only available for ubuntu1204 and ubuntu1404.
53
+ # Those ubuntus have ancient Pythons that don't work due to not
54
+ # implementing recent TLS protocols.
55
+ # Because of this we test on ubuntu1604 which has a newer Python.
56
+ # But we still need to retrieve ubuntu1404-targeting builds.
57
+ url = self.class.new('3.2', arch).download_url
58
+ unless url.include?('3.2.')
59
+ raise 'URL not in expected format'
63
60
  end
61
+ url = case desired_version
62
+ when '2.6'
63
+ url.sub(/\b3\.2\.\d+/, '2.6.12')
64
+ when '3.0'
65
+ url.sub(/\b3\.2\.\d+/, '3.0.15')
66
+ else
67
+ raise NotImplementedError
68
+ end.sub('ubuntu1604', 'ubuntu1404')
64
69
  else
65
70
  raise
66
71
  end
@@ -55,7 +55,7 @@ FROM <%= base_image %>
55
55
  # Ruby runtime dependencies: libyaml-0-2
56
56
  # Compiling ruby libraries: gcc make
57
57
  # Compiling pyhton packages: python2.7-dev
58
- # JRuby: openjdk-8-jre-headless
58
+ # JRuby: openjdk-8-jdk-headless
59
59
  # Server dependencies: libsnmp30 libcurl3/libcurl4
60
60
  # Determining OS we are running on: lsb-release
61
61
  # Load balancer testing: haproxy
@@ -68,6 +68,7 @@ FROM <%= base_image %>
68
68
  # nokogiri: zlib1g-dev
69
69
  # Mongoid testing: tzdata shared-mime-info
70
70
  # Mongoid application testing: nodejs (8.x or newer)
71
+ # Test suite: procps for ps (to kill JRubies)
71
72
  #
72
73
  # We currently use Python 2-compatible version of mtools, which
73
74
  # is installable via pip (which uses Python 2). All of the MongoDB
@@ -75,7 +76,7 @@ FROM <%= base_image %>
75
76
  # therefore install python-pip in all configurations here.
76
77
 
77
78
  <% packages = %w(
78
- lsb-release bzip2 curl zsh
79
+ procps lsb-release bzip2 curl zsh
79
80
  git make gcc libyaml-0-2 libgmp-dev zlib1g-dev libsnappy-dev
80
81
  krb5-user krb5-kdc krb5-admin-server libsasl2-dev libsasl2-modules-gssapi-mit
81
82
  haproxy
@@ -103,11 +104,11 @@ FROM <%= base_image %>
103
104
  <% end %>
104
105
 
105
106
  <% if distro =~ /debian10/ %>
106
- <% packages << 'openjdk-11-jre-headless' %>
107
+ <% packages << 'openjdk-11-jdk-headless' %>
107
108
  <% elsif distro =~ /ubuntu1404/ %>
108
109
  # Ubuntu 14.04 only has openjdk 7, this is too old to be useful
109
110
  <% else %>
110
- <% packages << 'openjdk-8-jre-headless' %>
111
+ <% packages << 'openjdk-8-jdk-headless' %>
111
112
  <% end %>
112
113
 
113
114
  # ubuntu1404, ubuntu1604: libcurl3
@@ -10,21 +10,21 @@ set_fcv() {
10
10
 
11
11
  add_uri_option() {
12
12
  opt=$1
13
-
13
+
14
14
  if ! echo $MONGODB_URI |sed -e s,//,, |grep -q /; then
15
15
  MONGODB_URI="$MONGODB_URI/"
16
16
  fi
17
-
17
+
18
18
  if ! echo $MONGODB_URI |grep -q '?'; then
19
19
  MONGODB_URI="$MONGODB_URI?"
20
20
  fi
21
-
21
+
22
22
  MONGODB_URI="$MONGODB_URI&$opt"
23
23
  }
24
24
 
25
25
  prepare_server() {
26
26
  arch=$1
27
-
27
+
28
28
  if test -n "$USE_OPT_MONGODB"; then
29
29
  export BINDIR=/opt/mongodb/bin
30
30
  export PATH=$BINDIR:$PATH
@@ -39,7 +39,7 @@ prepare_server() {
39
39
  else
40
40
  download_version="$MONGODB_VERSION"
41
41
  fi
42
-
42
+
43
43
  url=`$(dirname $0)/get-mongodb-download-url $download_version $arch`
44
44
 
45
45
  prepare_server_from_url $url
@@ -80,7 +80,7 @@ install_mlaunch_pip() {
80
80
  # mlaunch is preinstalled in the docker image, do not install it here
81
81
  return
82
82
  fi
83
-
83
+
84
84
  python -V || true
85
85
  python3 -V || true
86
86
  pythonpath="$MONGO_ORCHESTRATION_HOME"/python
@@ -96,20 +96,20 @@ install_mlaunch_git() {
96
96
  python3 -V || true
97
97
  which pip || true
98
98
  which pip3 || true
99
-
99
+
100
100
  if false; then
101
101
  if ! virtualenv --version; then
102
102
  python3 `which pip3` install --user virtualenv
103
103
  export PATH=$HOME/.local/bin:$PATH
104
104
  virtualenv --version
105
105
  fi
106
-
106
+
107
107
  venvpath="$MONGO_ORCHESTRATION_HOME"/venv
108
108
  virtualenv -p python3 $venvpath
109
109
  . $venvpath/bin/activate
110
-
110
+
111
111
  pip3 install psutil pymongo
112
-
112
+
113
113
  git clone $repo mlaunch
114
114
  cd mlaunch
115
115
  git checkout origin/$branch
@@ -118,13 +118,13 @@ install_mlaunch_git() {
118
118
  else
119
119
  pip install --user 'virtualenv==13'
120
120
  export PATH=$HOME/.local/bin:$PATH
121
-
121
+
122
122
  venvpath="$MONGO_ORCHESTRATION_HOME"/venv
123
123
  virtualenv $venvpath
124
124
  . $venvpath/bin/activate
125
-
125
+
126
126
  pip install psutil pymongo
127
-
127
+
128
128
  git clone $repo mlaunch
129
129
  (cd mlaunch &&
130
130
  git checkout origin/$branch &&
@@ -133,26 +133,32 @@ install_mlaunch_git() {
133
133
  fi
134
134
  }
135
135
 
136
+ # This function sets followong global variables:
137
+ # server_cert_path
138
+ # server_ca_path
139
+ # server_client_cert_path
140
+ #
141
+ # These variables are used later to connect to processes via mongo client.
136
142
  calculate_server_args() {
137
143
  local mongo_version=`echo $MONGODB_VERSION |tr -d .`
138
-
144
+
139
145
  if test -z "$mongo_version"; then
140
146
  echo "$MONGODB_VERSION must be set and not contain only dots" 1>&2
141
147
  exit 3
142
148
  fi
143
-
149
+
144
150
  if test $mongo_version = latest; then
145
151
  mongo_version=49
146
152
  fi
147
153
 
148
154
  local args="--setParameter enableTestCommands=1"
149
-
155
+
150
156
  if test $mongo_version -ge 50; then
151
157
  args="$args --setParameter acceptApiVersion2=1"
152
158
  elif test $mongo_version -ge 47; then
153
159
  args="$args --setParameter acceptAPIVersion2=1"
154
160
  fi
155
-
161
+
156
162
  # diagnosticDataCollectionEnabled is a mongod-only parameter on server 3.2,
157
163
  # and mlaunch does not support specifying mongod-only parameters:
158
164
  # https://github.com/rueckstiess/mtools/issues/696
@@ -181,7 +187,7 @@ calculate_server_args() {
181
187
  haproxy_config=$MRSS_ROOT/share/haproxy-1.conf
182
188
  else
183
189
  args="$args --mongos 2"
184
- haproxy_config=$MRSS_ROOT/share/haproxy-1.conf
190
+ haproxy_config=$MRSS_ROOT/share/haproxy-2.conf
185
191
  fi
186
192
  uri_options="$uri_options&loadBalanced=true"
187
193
  else
@@ -209,7 +215,6 @@ calculate_server_args() {
209
215
  fi
210
216
  fi
211
217
 
212
- local server_cert_path server_ca_path server_client_cert_path
213
218
  if test "$SSL" = ssl || test -n "$OCSP_ALGORITHM"; then
214
219
  if test -n "$OCSP_ALGORITHM"; then
215
220
  if test "$OCSP_MUST_STAPLE" = 1; then
@@ -288,7 +293,7 @@ calculate_server_args() {
288
293
  ocsp_args="$ocsp_args --fault $OCSP_STATUS"
289
294
  fi
290
295
  fi
291
-
296
+
292
297
  OCSP_ARGS="$ocsp_args"
293
298
  SERVER_ARGS="$args"
294
299
  URI_OPTIONS="$uri_options"
@@ -306,12 +311,57 @@ launch_server() {
306
311
  local dbdir="$1"
307
312
  python -m mtools.mlaunch.mlaunch --dir "$dbdir" --binarypath "$BINDIR" $SERVER_ARGS
308
313
 
314
+ if test "$TOPOLOGY" = sharded-cluster && test $MONGODB_VERSION = 3.6; then
315
+ # On 3.6 server the sessions collection is not immediately available,
316
+ # so we run the refreshLogicalSessionCacheNow command on the config server
317
+ # and again on each mongos in order for the mongoses
318
+ # to correctly report logicalSessionTimeoutMinutes.
319
+ mongos_regex="\s*mongos\s+([0-9]+)\s+running\s+[0-9]+"
320
+ config_server_regex="\s*config\sserver\s+([0-9]+)\s+running\s+[0-9]+"
321
+ config_server=""
322
+ mongoses=()
323
+ if test "$AUTH" = auth
324
+ then
325
+ base_url="mongodb://bob:pwd123@localhost"
326
+ else
327
+ base_url="mongodb://localhost"
328
+ fi
329
+ if test "$SSL" = "ssl"
330
+ then
331
+ mongo_command="${BINDIR}/mongo --ssl --sslPEMKeyFile $server_cert_path --sslCAFile $server_ca_path"
332
+ else
333
+ mongo_command="${BINDIR}/mongo"
334
+ fi
335
+
336
+ while read -r line
337
+ do
338
+ if [[ $line =~ $config_server_regex ]]
339
+ then
340
+ port="${BASH_REMATCH[1]}"
341
+ config_server="${base_url}:${port}"
342
+ fi
343
+ if [[ $line =~ $mongos_regex ]]
344
+ then
345
+ port="${BASH_REMATCH[1]}"
346
+ mongoses+=("${base_url}:${port}")
347
+ fi
348
+ done < <(python -m mtools.mlaunch.mlaunch list --dir "$dbdir" --binarypath "$BINDIR")
349
+
350
+ if [ -n "$config_server" ]; then
351
+ ${mongo_command} "$config_server" --eval 'db.adminCommand("refreshLogicalSessionCacheNow")'
352
+ for mongos in ${mongoses[*]}
353
+ do
354
+ ${mongo_command} "$mongos" --eval 'db.adminCommand("refreshLogicalSessionCacheNow")'
355
+ done
356
+ fi
357
+ fi
358
+
309
359
  if test "$TOPOLOGY" = load-balanced; then
310
360
  if test -z "$haproxy_config"; then
311
361
  echo haproxy_config should have been set 1>&2
312
362
  exit 3
313
363
  fi
314
-
364
+
315
365
  haproxy -D -f $haproxy_config -p $mongodb_dir/haproxy.pid
316
366
  fi
317
367
  }