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
|