File: sdam_spec.rb

package info (click to toggle)
ruby-mongo 2.21.3-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 14,764 kB
  • sloc: ruby: 108,806; makefile: 5; sh: 2
file content (246 lines) | stat: -rw-r--r-- 9,145 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# frozen_string_literal: true
# rubocop:todo all

require 'lite_spec_helper'

require 'runners/sdam'
require 'runners/sdam/verifier'

describe 'Server Discovery and Monitoring' do
  include Mongo::SDAM

  class Executor
    include Mongo::Operation::Executable

    def session
      nil
    end
  end

  SERVER_DISCOVERY_TESTS.each do |file|

    spec = Mongo::SDAM::Spec.new(file)

    context("#{spec.description} (#{file.sub(%r'.*/data/sdam/', '')})") do
      before(:all) do
        class Mongo::Server::Monitor
          alias_method :run_saved!, :run!

          # Replace run! method to do nothing, to avoid races between
          # the background thread started by Server.new and our mocking.
          # Replace with refinements once ruby 1.9 support is dropped
          def run!
          end
        end
      end

      after(:all) do
        class Mongo::Server::Monitor
          alias_method :run!, :run_saved!
        end
      end

      before(:all) do
        # Since we supply all server descriptions and drive events,
        # background monitoring only gets in the way. Disable it.
        @client = new_local_client_nmio(spec.uri_string,
          heartbeat_frequency: 1000, connect_timeout: 0.1)
      end

      before do
        @client.reconnect if @client.closed?
      end

      after(:all) do
        @client && @client.close
      end

      def raise_application_error(error, connection = nil)
        case error.type
        when :network
          exc = Mongo::Error::SocketError.new
          exc.generation = error.generation
          raise exc
        when :timeout
          exc = Mongo::Error::SocketTimeoutError.new
          exc.generation = error.generation
          raise exc
        when :command
          result = error.result
          if error.generation
            allow(connection).to receive(:generation).and_return(error.generation)
          end
          Executor.new.send(:process_result_for_sdam, result, connection)
        else
          raise NotImplementedError, "Error type #{error.type} is not implemented"
        end
      end

      spec.phases.each_with_index do |phase, index|

        context("Phase: #{index + 1}") do

          before do
            allow(@client.cluster).to receive(:connected?).and_return(true)

            phase.responses&.each do |response|
              server = find_server(@client, response.address)
              unless server
                server = Mongo::Server.new(
                    Mongo::Address.new(response.address),
                    @client.cluster,
                    @client.send(:monitoring),
                    @client.cluster.send(:event_listeners),
                    @client.cluster.options,
                )
              end
              monitor = server.instance_variable_get(:@monitor)
              result = response.hello
              # Spec tests do not always specify wire versions, but the
              # driver requires them. Set them to zero which was
              # the legacy default in the driver.
              result['minWireVersion'] ||= 0
              result['maxWireVersion'] ||= 0
              new_description = Mongo::Server::Description.new(
                server.description.address, result, average_round_trip_time: 0.5)
              @client.cluster.run_sdam_flow(server.description, new_description)
            end

            phase.application_errors&.each do |error|
              server = find_server(@client, error.address_str)
              unless server
                raise NotImplementedError, 'Errors can only be produced on known servers'
              end

              begin
                case error.when
                when :before_handshake_completes
                  connection = Mongo::Server::Connection.new(server,
                    generation: server.pool.generation)
                  server.handle_handshake_failure! do
                    raise_application_error(error, connection)
                  end
                when :after_handshake_completes
                  connection = Mongo::Server::Connection.new(server,
                    generation: server.pool.generation)
                  allow(connection).to receive(:description).and_return(server.description)
                  connection.send(:handle_errors) do
                    raise_application_error(error, connection)
                  end
                else
                  raise NotImplementedError, "Error position #{error.when} is not implemented"
                end
              rescue Mongo::Error
                # This was the exception we raised
              end
            end
          end

          if phase.outcome.compatible?

            let(:cluster_addresses) do
              @client.cluster.instance_variable_get(:@servers).
                  collect(&:address).collect(&:to_s).uniq.sort
            end

            let(:phase_addresses) do
              phase.outcome.servers.keys.sort
            end

            it "sets the cluster topology to #{phase.outcome.topology_type}" do
              expect(@client.cluster).to be_topology(phase.outcome.topology_type)
            end

            it "sets the cluster replica set name to #{phase.outcome.set_name.inspect}" do
              expect(@client.cluster.replica_set_name).to eq(phase.outcome.set_name)
            end

            it "sets the cluster logical session timeout minutes to #{phase.outcome.logical_session_timeout.inspect}" do
              expect(@client.cluster.logical_session_timeout).to eq(phase.outcome.logical_session_timeout)
            end

            it "has the expected servers in the cluster" do
              expect(cluster_addresses).to eq(phase_addresses)
            end

            # If compatible is not expliticly specified in the fixture,
            # wire protocol versions aren't either and the topology
            # is actually incompatible
            if phase.outcome.compatible_specified?
              it 'is compatible' do
                expect(@client.cluster.topology.compatible?).to be true
              end
            end

            phase.outcome.servers.each do |address_str, server_spec|

              it "sets #{address_str} server to #{server_spec['type']}" do
                server = find_server(@client, address_str)
                unless server_of_type?(server, server_spec['type'])
                  raise RSpec::Expectations::ExpectationNotMetError,
                    "Server #{server.summary} not of type #{server_spec['type']}"
                end
              end

              it "sets #{address_str} server replica set name to #{server_spec['setName'].inspect}" do
                expect(find_server(@client, address_str).replica_set_name).to eq(server_spec['setName'])
              end

              it "sets #{address_str} server description in topology to match server description in cluster" do
                desc = @client.cluster.topology.server_descriptions[address_str]
                server = find_server(@client, address_str)
                # eql doesn't work here because it's aliased to eq
                # and two unknowns are not eql as a result,
                # compare by object id
                unless desc.object_id == server.description.object_id
                  unless desc == server.description
                    expect(desc).to be_unknown
                    expect(server.description).to be_unknown
                  end
                end
              end

              let(:verifier) { Sdam::Verifier.new }

              it "#{address_str} server description has expected values" do
                actual = @client.cluster.topology.server_descriptions[address_str]

                verifier.verify_description_matches(server_spec, actual)
              end
            end

            if %w(ReplicaSetWithPrimary ReplicaSetNoPrimary).include?(phase.outcome.topology_type)
              it 'has expected max election id' do
                expect(@client.cluster.topology.max_election_id).to eq(phase.outcome.max_election_id)
              end

              it 'has expected max set version' do
                expect(@client.cluster.topology.max_set_version).to eq(phase.outcome.max_set_version)
              end
            end

          else

            before do
              @client.cluster.servers.each do |server|
                allow(server).to receive(:connectable?).and_return(true)
              end
            end

            it 'is incompatible' do
              expect(@client.cluster.topology.compatible?).to be false
            end

            it 'raises an UnsupportedFeatures error' do
              expect {
                p = Mongo::ServerSelector.primary.select_server(@client.cluster)
                s = Mongo::ServerSelector.get(mode: :secondary).select_server(@client.cluster)
                raise "UnsupportedFeatures not raised but we got #{p.inspect} as primary and #{s.inspect} as secondary"
              }.to raise_exception(Mongo::Error::UnsupportedFeatures)
            end
          end
        end
      end
    end
  end
end