Skip to content

Commit 05acac8

Browse files
committed
Merge pull request #607 from estolfo/RUBY-899-server-selector
RUBY-899 Cluster returns empty list if there are no servers
2 parents 5b64dac + 6884966 commit 05acac8

File tree

12 files changed

+195
-78
lines changed

12 files changed

+195
-78
lines changed

lib/mongo/cluster.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ def scan!
181181
#
182182
# @since 2.0.0
183183
def servers
184-
topology.servers(@servers)
184+
topology.servers(@servers.compact).compact
185185
end
186186

187187
# Create a cluster for the provided client, for use when we don't want the

lib/mongo/cluster/topology/unknown.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ def replica_set_name; nil; end
106106
#
107107
# @since 2.0.0
108108
def servers(servers)
109+
[]
109110
end
110111

111112
# An unknown topology is not sharded.

lib/mongo/error.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ class Error < StandardError
7171
require 'mongo/error/invalid_file'
7272
require 'mongo/error/invalid_nonce'
7373
require 'mongo/error/invalid_replacement_document'
74+
require 'mongo/error/invalid_server_preference'
7475
require 'mongo/error/invalid_signature'
7576
require 'mongo/error/invalid_update_document'
7677
require 'mongo/error/invalid_uri'
@@ -79,6 +80,7 @@ class Error < StandardError
7980
require 'mongo/error/max_message_size'
8081
require 'mongo/error/multi_index_drop'
8182
require 'mongo/error/need_primary_server'
83+
require 'mongo/error/no_server_available'
8284
require 'mongo/error/socket_error'
8385
require 'mongo/error/socket_timeout_error'
8486
require 'mongo/error/unsupported_features'
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Copyright (C) 2014-2015 MongoDB, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
module Mongo
16+
class Error
17+
18+
# Raised when an invalid server preference is provided.
19+
#
20+
# @since 2.0.0
21+
class InvalidServerPreference < Error
22+
23+
# Instantiate the new exception.
24+
#
25+
# @example Instantiate the exception.
26+
# Mongo::ServerSelector::InvalidServerPreference.new
27+
#
28+
# @param [ String ] name The preference name.
29+
#
30+
# @since 2.0.0
31+
def initialize(name)
32+
super("This server preference #{name} cannot be combined with tags.")
33+
end
34+
end
35+
end
36+
end
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Copyright (C) 2014-2015 MongoDB, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
module Mongo
16+
class Error
17+
18+
# Raised if there are no servers available matching the preference.
19+
#
20+
# @since 2.0.0
21+
class NoServerAvailable < Error
22+
23+
# Instantiate the new exception.
24+
#
25+
# @example Instantiate the exception.
26+
# Mongo::Error::NoServerAvailable.new(server_selector)
27+
#
28+
# @param [ Hash ] server_selector The server preference that could not be
29+
# satisfied.
30+
#
31+
# @since 2.0.0
32+
def initialize(server_selector)
33+
super("No server is available matching preference: #{server_selector.inspect}")
34+
end
35+
end
36+
end
37+
end

lib/mongo/server_selector.rb

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -58,24 +58,5 @@ def get(preference = {}, options = {})
5858
options
5959
)
6060
end
61-
62-
# Exception raised if there are no servers available matching the preference.
63-
#
64-
# @since 2.0.0
65-
class NoServerAvailable < Error
66-
67-
# Instantiate the new exception.
68-
#
69-
# @example Instantiate the exception.
70-
# Mongo::ServerSelector::NoServerAvailable.new(server_selector)
71-
#
72-
# @param [ Hash ] server_selector The server preference that could not be
73-
# satisfied.
74-
#
75-
# @since 2.0.0
76-
def initialize(server_selector)
77-
super("No server is available matching preference: #{server_selector.inspect}")
78-
end
79-
end
8061
end
8162
end

lib/mongo/server_selector/selectable.rb

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def ==(other)
6767
# @since 2.0.0
6868
def initialize(tag_sets = [], options = {})
6969
if !tag_sets.all? { |set| set.empty? } && !tags_allowed?
70-
raise ServerSelector::InvalidServerPreference.new(name)
70+
raise Error::InvalidServerPreference.new(name)
7171
end
7272
@tag_sets = tag_sets
7373
@options = options
@@ -96,7 +96,7 @@ def select_server(cluster)
9696
return servers.first if servers && !servers.compact.empty?
9797
cluster.scan!
9898
end
99-
raise NoServerAvailable.new(self)
99+
raise Error::NoServerAvailable.new(self)
100100
end
101101

102102
# Get the timeout for server selection.
@@ -187,23 +187,5 @@ def match_tag_sets(candidates)
187187
matches || []
188188
end
189189
end
190-
191-
# Raised when an invalid server preference is provided.
192-
#
193-
# @since 2.0.0
194-
class InvalidServerPreference < Error
195-
196-
# Instantiate the new exception.
197-
#
198-
# @example Instantiate the exception.
199-
# Mongo::ServerSelector::InvalidServerPreference.new
200-
#
201-
# @param [ String ] name The preference name.
202-
#
203-
# @since 2.0.0
204-
def initialize(name)
205-
super("This server preference #{name} cannot be combined with tags.")
206-
end
207-
end
208190
end
209191
end

spec/mongo/cluster_spec.rb

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,5 +149,65 @@
149149
end
150150
end
151151
end
152+
153+
context 'when the cluster has no servers' do
154+
155+
let(:cluster) do
156+
described_class.new(ADDRESSES)
157+
end
158+
159+
let(:servers) do
160+
[nil]
161+
end
162+
163+
before do
164+
cluster.instance_variable_set(:@servers, servers)
165+
cluster.instance_variable_set(:@topology, topology)
166+
end
167+
168+
context 'when topology is Single' do
169+
170+
let(:topology) do
171+
Mongo::Cluster::Topology::Single.new({})
172+
end
173+
174+
it 'returns an empty array' do
175+
expect(cluster.servers).to eq([])
176+
end
177+
end
178+
179+
context 'when topology is ReplicaSet' do
180+
181+
let(:topology) do
182+
Mongo::Cluster::Topology::ReplicaSet.new({})
183+
end
184+
185+
it 'returns an empty array' do
186+
expect(cluster.servers).to eq([])
187+
end
188+
end
189+
190+
context 'when topology is Sharded' do
191+
192+
let(:topology) do
193+
Mongo::Cluster::Topology::Sharded.new({})
194+
end
195+
196+
it 'returns an empty array' do
197+
expect(cluster.servers).to eq([])
198+
end
199+
end
200+
201+
context 'when topology is Unknown' do
202+
203+
let(:topology) do
204+
Mongo::Cluster::Topology::Unknown.new({})
205+
end
206+
207+
it 'returns an empty array' do
208+
expect(cluster.servers).to eq([])
209+
end
210+
end
211+
end
152212
end
153213
end

spec/mongo/database_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@
176176
it 'uses that read preference', unless: sharded? do
177177
expect do
178178
database.command({ ping: 1 }, { read: read })
179-
end.to raise_error(Mongo::ServerSelector::NoServerAvailable)
179+
end.to raise_error(Mongo::Error::NoServerAvailable)
180180
end
181181
end
182182
end

spec/mongo/server_selection_spec.rb

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
allow(cluster).to receive(:scan!).and_return(true)
6262
end
6363

64-
context 'Valid read preference and matching server available', unless: spec.raises_exception? do
64+
context 'Valid read preference and matching server available', if: spec.server_available? do
6565

6666
it 'Finds all suitable servers in the latency window', if: spec.replica_set? do
6767
expect(server_selector.send(:select, cluster.servers)).to eq(in_latency_window)
@@ -72,21 +72,12 @@
7272
end
7373
end
7474

75-
context 'Invalid read preference', if: spec.invalid_server_preference? do
75+
context 'No matching server available', if: !spec.server_available? do
7676

7777
it 'Raises exception' do
7878
expect do
7979
server_selector.select_server(cluster)
80-
end.to raise_exception(Mongo::ServerSelector::InvalidServerPreference)
81-
end
82-
end
83-
84-
context 'No matching server available', unless: spec.server_available? do
85-
86-
it 'Raises exception' do
87-
expect do
88-
server_selector.select_server(cluster)
89-
end.to raise_exception(Mongo::ServerSelector::NoServerAvailable)
80+
end.to raise_exception(Mongo::Error::NoServerAvailable)
9081
end
9182
end
9283
end

spec/mongo/server_selector_spec.rb

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,59 @@
9494
it 'raises a NoServerAvailable error' do
9595
expect do
9696
read_pref.select_server(cluster)
97-
end.to raise_exception(Mongo::ServerSelector::NoServerAvailable)
97+
end.to raise_exception(Mongo::Error::NoServerAvailable)
9898
end
9999
end
100100
end
101+
102+
shared_context 'a ServerSelector' do
103+
104+
context 'when cluster#servers is empty' do
105+
106+
let(:servers) { [] }
107+
108+
let(:cluster) do
109+
double('cluster').tap do |c|
110+
allow(c).to receive(:servers).and_return(servers)
111+
allow(c).to receive(:single?).and_return(single)
112+
allow(c).to receive(:sharded?).and_return(sharded)
113+
allow(c).to receive(:scan!).and_return(true)
114+
end
115+
end
116+
117+
let(:read_pref) do
118+
described_class.get({ mode: :primary }, server_selection_timeout: 1)
119+
end
120+
121+
it 'raises a NoServerAvailable error' do
122+
expect do
123+
read_pref.select_server(cluster)
124+
end.to raise_exception(Mongo::Error::NoServerAvailable)
125+
end
126+
end
127+
end
128+
129+
context 'when the cluster has a Single topology' do
130+
131+
let(:single) { true }
132+
let(:sharded) { false }
133+
134+
it_behaves_like 'a ServerSelector'
135+
end
136+
137+
context 'when the cluster has a ReplicaSet topology' do
138+
139+
let(:single) { false }
140+
let(:sharded) { false }
141+
142+
it_behaves_like 'a ServerSelector'
143+
end
144+
145+
context 'when the cluster has a Sharded topology' do
146+
147+
let(:single) { false }
148+
let(:sharded) { true }
149+
150+
it_behaves_like 'a ServerSelector'
151+
end
101152
end

spec/support/server_selection.rb

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -92,18 +92,6 @@ def replica_set?
9292
type == Mongo::Cluster::Topology::ReplicaSet
9393
end
9494

95-
# Does this spec raise an exception.
96-
#
97-
# @example Determine if the spec raises an exception.
98-
# spec.raises_exception?
99-
#
100-
# @return [true, false] If the spec raises an exception.
101-
#
102-
# @since 2.0.0
103-
def raises_exception?
104-
!server_available? || invalid_server_preference?
105-
end
106-
10795
# Does this spec expect a server to be found.
10896
#
10997
# @example Will a server be found with this spec.
@@ -116,18 +104,6 @@ def server_available?
116104
!in_latency_window.empty?
117105
end
118106

119-
# Is the read preference defined in the spec invalid.
120-
#
121-
# @example Determine if the spec's read preference is invalid.
122-
# spec.invalid_server_preference?
123-
#
124-
# @return [true, false] If the spec's read preference is invalid.
125-
#
126-
# @since 2.0.0
127-
def invalid_server_preference?
128-
read_preference['mode'] == 'Primary' && read_preference['tag_sets']
129-
end
130-
131107
# The subset of suitable servers that falls within the allowable latency
132108
# window.
133109
# We have to correct for our server selection algorithm that adds the primary

0 commit comments

Comments
 (0)