bson 4.13.0 → 4.14.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -15,6 +15,11 @@ module Mrss
15
15
  @single_server
16
16
  end
17
17
 
18
+ def sharded_ish?
19
+ determine_cluster_config
20
+ @topology == :sharded || @topology == :load_balanced
21
+ end
22
+
18
23
  def replica_set_name
19
24
  determine_cluster_config
20
25
  @replica_set_name
@@ -48,7 +53,7 @@ module Mrss
48
53
  raise "Deployment server version not known - check that connection to deployment succeeded"
49
54
  end
50
55
 
51
- if server_version >= '3.4' && topology != :sharded
56
+ if server_version >= '3.4' && !sharded_ish?
52
57
  fcv
53
58
  else
54
59
  if short_server_version == '4.1'
@@ -115,7 +120,7 @@ module Mrss
115
120
  :mmapv1
116
121
  else
117
122
  client = ClientRegistry.instance.global_client('root_authorized')
118
- if topology == :sharded
123
+ if sharded_ish?
119
124
  shards = client.use(:admin).command(listShards: 1).first
120
125
  if shards['shards'].empty?
121
126
  raise 'Shards are empty'
@@ -204,9 +209,14 @@ module Mrss
204
209
  @server_version = build_info['version']
205
210
  @enterprise = build_info['modules'] && build_info['modules'].include?('enterprise')
206
211
 
207
- @server_parameters = client.use(:admin).command(getParameter: '*').first
212
+ @server_parameters = begin
213
+ client.use(:admin).command(getParameter: '*').first
214
+ rescue => e
215
+ STDERR.puts("WARNING: Failed to obtain server parameters: #{e.class}: #{e.message}")
216
+ {}
217
+ end
208
218
 
209
- if @topology != :sharded && short_server_version >= '3.4'
219
+ if !sharded_ish? && short_server_version >= '3.4'
210
220
  rv = @server_parameters['featureCompatibilityVersion']
211
221
  @fcv = rv['version'] || rv
212
222
  end
@@ -52,7 +52,7 @@ module Mrss
52
52
  end
53
53
 
54
54
  def require_topology(*topologies)
55
- invalid_topologies = topologies - [:single, :replica_set, :sharded]
55
+ invalid_topologies = topologies - [:single, :replica_set, :sharded, :load_balanced]
56
56
 
57
57
  unless invalid_topologies.empty?
58
58
  raise ArgumentError, "Invalid topologies requested: #{invalid_topologies.join(', ')}"
@@ -82,7 +82,7 @@ module Mrss
82
82
  unless ClusterConfig.instance.server_version >= '4.0'
83
83
  skip 'Transactions tests in a replica set topology require server 4.0+'
84
84
  end
85
- when :sharded
85
+ when :sharded, :load_balanced
86
86
  unless ClusterConfig.instance.server_version >= '4.2'
87
87
  skip 'Transactions tests in a sharded cluster topology require server 4.2+'
88
88
  end
@@ -113,6 +113,14 @@ module Mrss
113
113
  end
114
114
  end
115
115
 
116
+ def require_retry_writes
117
+ before(:all) do
118
+ unless SpecConfig.instance.retry_writes?
119
+ skip "Retry writes is disabled"
120
+ end
121
+ end
122
+ end
123
+
116
124
  def require_no_retry_writes
117
125
  before(:all) do
118
126
  if SpecConfig.instance.retry_writes?
@@ -215,7 +223,8 @@ module Mrss
215
223
 
216
224
  def require_no_auth
217
225
  before(:all) do
218
- if (ENV['AUTH'] && ENV['AUTH'] != 'noauth') || SpecConfig.instance.user || ClusterConfig.instance.auth_enabled?
226
+ auth = ENV.fetch('AUTH', '')
227
+ if (!auth.empty? && auth != 'noauth') || SpecConfig.instance.user || ClusterConfig.instance.auth_enabled?
219
228
  skip "Auth not allowed"
220
229
  end
221
230
  end
@@ -241,31 +250,52 @@ module Mrss
241
250
  # (mongos 4.0+ overrides the write concern)
242
251
  def require_set_write_concern
243
252
  before(:all) do
244
- if ClusterConfig.instance.topology == :sharded && ClusterConfig.instance.short_server_version >= '4.0'
253
+ if %i(sharded load_balanced).include?(ClusterConfig.instance.topology) &&
254
+ ClusterConfig.instance.short_server_version >= '4.0'
255
+ then
245
256
  skip "mongos 4.0+ overrides write concern"
246
257
  end
247
258
  end
248
259
  end
249
260
 
250
- def require_multi_shard
261
+ def require_multi_mongos
251
262
  before(:all) do
252
263
  if ClusterConfig.instance.topology == :sharded && SpecConfig.instance.addresses.length == 1
253
- skip 'Test requires a minimum of two shards if run in sharded topology'
264
+ skip 'Test requires a minimum of two mongoses if run in sharded topology'
265
+ end
266
+
267
+ if ClusterConfig.instance.topology == :load_balanced && SpecConfig.instance.single_mongos?
268
+ skip 'Test requires a minimum of two mongoses if run in load-balanced topology'
254
269
  end
255
270
  end
256
271
  end
257
272
 
258
- def require_no_multi_shard
273
+ # In sharded topology operations are distributed to the mongoses.
274
+ # When we set fail points, the fail point may be set on one mongos and
275
+ # operation may be executed on another mongos, causing failures.
276
+ # Tests that are not setting targeted fail points should utilize this
277
+ # method to restrict themselves to single mongos.
278
+ #
279
+ # In load-balanced topology, the same problem can happen when there is
280
+ # more than one mongos behind the load balancer.
281
+ def require_no_multi_mongos
259
282
  before(:all) do
260
283
  if ClusterConfig.instance.topology == :sharded && SpecConfig.instance.addresses.length > 1
261
- skip 'Test requires a single shard if run in sharded topology'
284
+ skip 'Test requires a single mongos if run in sharded topology'
285
+ end
286
+ if ClusterConfig.instance.topology == :load_balanced && !SpecConfig.instance.single_mongos?
287
+ skip 'Test requires a single mongos, as indicated by SINGLE_MONGOS=1 environment variable, if run in load-balanced topology'
262
288
  end
263
289
  end
264
290
  end
265
291
 
292
+ alias :require_no_multi_shard :require_no_multi_mongos
293
+
266
294
  def require_wired_tiger
267
295
  before(:all) do
268
- if ClusterConfig.instance.storage_engine != :wired_tiger
296
+ # Storage detection fails for serverless instances. However, it is safe to
297
+ # assume that a serverless instance uses WiredTiger Storage Engine.
298
+ if !SpecConfig.instance.serverless? && ClusterConfig.instance.storage_engine != :wired_tiger
269
299
  skip 'Test requires WiredTiger storage engine'
270
300
  end
271
301
  end
@@ -274,7 +304,9 @@ module Mrss
274
304
  def require_wired_tiger_on_36
275
305
  before(:all) do
276
306
  if ClusterConfig.instance.short_server_version >= '3.6'
277
- if ClusterConfig.instance.storage_engine != :wired_tiger
307
+ # Storage detection fails for serverless instances. However, it is safe to
308
+ # assume that a serverless instance uses WiredTiger Storage Engine.
309
+ if !SpecConfig.instance.serverless? && ClusterConfig.instance.storage_engine != :wired_tiger
278
310
  skip 'Test requires WiredTiger storage engine on 3.6+ servers'
279
311
  end
280
312
  end
@@ -283,7 +315,7 @@ module Mrss
283
315
 
284
316
  def require_mmapv1
285
317
  before(:all) do
286
- if ClusterConfig.instance.storage_engine != :mmapv1
318
+ if SpecConfig.instance.serverless? || ClusterConfig.instance.storage_engine != :mmapv1
287
319
  skip 'Test requires MMAPv1 storage engine'
288
320
  end
289
321
  end
@@ -342,5 +374,13 @@ module Mrss
342
374
  end
343
375
  end
344
376
  end
377
+
378
+ def require_unix_socket
379
+ before(:all) do
380
+ if ENV['TOPOLOGY'] == 'load-balanced'
381
+ skip 'Load balancer does not listen on Unix sockets'
382
+ end
383
+ end
384
+ end
345
385
  end
346
386
  end
@@ -111,7 +111,7 @@ module Mrss
111
111
  '.'])
112
112
  end
113
113
 
114
- BASE_TEST_COMMAND = %w(docker run -i --tmpfs /tmpfs:exec).freeze
114
+ BASE_TEST_COMMAND = %w(docker run --rm -i --tmpfs /tmpfs:exec).freeze
115
115
 
116
116
  def run_tests
117
117
  run_command(BASE_TEST_COMMAND + tty_arg + extra_env + [image_tag] +
@@ -173,15 +173,17 @@ module Mrss
173
173
  end
174
174
 
175
175
  def distro
176
- @options[:distro] || 'ubuntu1604'
176
+ @options[:distro] || 'ubuntu1804'
177
177
  end
178
178
 
179
179
  BASE_IMAGES = {
180
180
  'debian81' => 'debian:jessie',
181
181
  'debian92' => 'debian:stretch',
182
+ 'debian10' => 'debian:buster',
182
183
  'ubuntu1404' => 'ubuntu:trusty',
183
184
  'ubuntu1604' => 'ubuntu:xenial',
184
185
  'ubuntu1804' => 'ubuntu:bionic',
186
+ 'ubuntu2004' => 'ubuntu:focal',
185
187
  'rhel62' => 'centos:6',
186
188
  'rhel70' => 'centos:7',
187
189
  }.freeze
@@ -198,6 +200,10 @@ module Mrss
198
200
  ruby == 'ruby-head'
199
201
  end
200
202
 
203
+ def system_ruby?
204
+ %w(1 true yes).include?(@env['SYSTEM_RUBY']&.downcase)
205
+ end
206
+
201
207
  def server_version
202
208
  @env['MONGODB_VERSION']
203
209
  end
@@ -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
@@ -25,18 +25,28 @@ module Mrss
25
25
  end
26
26
 
27
27
  def initialize(root: nil, classifiers:, priority_order:,
28
- spec_root: nil, rspec_json_path: nil, rspec_all_json_path: nil
28
+ spec_root: nil, rspec_json_path: nil, rspec_all_json_path: nil,
29
+ randomize: false
29
30
  )
30
31
  @spec_root = spec_root || File.join(root, 'spec')
31
32
  @classifiers = classifiers
32
33
  @priority_order = priority_order
33
34
  @rspec_json_path = rspec_json_path || File.join(root, 'tmp/rspec.json')
34
35
  @rspec_all_json_path = rspec_all_json_path || File.join(root, 'tmp/rspec-all.json')
36
+ @randomize = !!randomize
35
37
  end
36
38
 
37
39
  attr_reader :spec_root, :classifiers, :priority_order
38
40
  attr_reader :rspec_json_path, :rspec_all_json_path
39
41
 
42
+ def randomize?
43
+ @randomize
44
+ end
45
+
46
+ def seed
47
+ @seed ||= (rand * 100_000).to_i
48
+ end
49
+
40
50
  def buckets
41
51
  @buckets ||= {}.tap do |buckets|
42
52
  Find.find(spec_root) do |path|
@@ -81,10 +91,20 @@ module Mrss
81
91
  end
82
92
 
83
93
  def run
94
+ run_buckets(*buckets.keys)
95
+ end
96
+
97
+ def run_buckets(*buckets)
84
98
  FileUtils.rm_f(rspec_all_json_path)
85
99
 
100
+ buckets.each do |bucket|
101
+ if bucket && !self.buckets[bucket]
102
+ raise "Unknown bucket #{bucket}"
103
+ end
104
+ end
105
+ buckets = Hash[self.buckets.select { |k, v| buckets.include?(k) }]
106
+
86
107
  failed = []
87
- buckets = self.buckets.dup
88
108
 
89
109
  priority_order.each do |category|
90
110
  if files = buckets.delete(category)
@@ -112,8 +132,12 @@ module Mrss
112
132
  puts "Running #{category.to_s.gsub('_', ' ')} tests"
113
133
  FileUtils.rm_f(rspec_json_path)
114
134
  cmd = %w(rspec) + paths
135
+ if randomize?
136
+ cmd += %W(--order rand:#{seed})
137
+ end
115
138
 
116
139
  begin
140
+ puts "Running #{cmd.join(' ')}"
117
141
  ChildProcessHelper.check_call(cmd)
118
142
  ensure
119
143
  if File.exist?(rspec_json_path)
@@ -139,6 +163,9 @@ module Mrss
139
163
  end
140
164
  new.delete('version')
141
165
  new.delete('summary_line')
166
+ # The spec organizer runs all buckets with the same seed, hence
167
+ # we can drop the seed from new results.
168
+ new.delete('seed')
142
169
  unless new.empty?
143
170
  raise "Unhandled rspec results keys: #{new.keys.join(', ')}"
144
171
  end