Skip to content

Commit 376915a

Browse files
authored
RUBY-1426 Don't remove unknown secondaries servers from the topology (#1003)
1 parent 87f32d3 commit 376915a

File tree

4 files changed

+147
-14
lines changed

4 files changed

+147
-14
lines changed

lib/mongo/cluster/topology/replica_set.rb

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,12 @@ def remove_hosts?(description)
213213
end
214214

215215
# Whether a specific server in the cluster can be removed, given a description.
216+
# As described in the SDAM spec, a server should be removed if the server's
217+
# address does not match the "me" field of the isMaster response, if the server
218+
# has a different replica set name, or if an isMaster response from the primary
219+
# does not contain the server's address in the list of known hosts. Note that as
220+
# described by the spec, a server determined to be of type Unknown from its
221+
# isMaster response is NOT removed from the topology.
216222
#
217223
# @example Check if a specific server can be removed from the cluster.
218224
# topology.remove_server?(description, server)
@@ -224,8 +230,10 @@ def remove_hosts?(description)
224230
#
225231
# @since 2.0.6
226232
def remove_server?(description, server)
233+
((server.address == description.address) && description.me_mismatch?) ||
227234
remove_self?(description, server) ||
228235
(member_of_this_set?(description) &&
236+
description.server_type == :primary &&
229237
!description.servers.empty? &&
230238
!description.lists_server?(server))
231239
end
@@ -316,10 +324,14 @@ def member_of_this_set?(description)
316324
description.replica_set_name == replica_set_name
317325
end
318326

327+
# As described by the SDAM spec, a server should be removed from the
328+
# topology upon receiving its isMaster response if no error occurred
329+
# and replica set name does not match that of the topology.
319330
def remove_self?(description, server)
320-
!member_of_this_set?(description) &&
321-
description.is_server?(server) &&
322-
!description.ghost?
331+
!description.unknown? &&
332+
!member_of_this_set?(description) &&
333+
description.is_server?(server) &&
334+
!description.ghost?
323335
end
324336
end
325337
end

lib/mongo/server/description.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -640,7 +640,7 @@ def replica_set_member?
640640
#
641641
# @since 2.0.6
642642
def me_mismatch?
643-
!!(address.to_s != me if me)
643+
!!(address.to_s.downcase != me.downcase if me)
644644
end
645645

646646
# Check equality of two descriptions.

spec/mongo/cluster/topology/replica_set_spec.rb

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,9 @@
514514
allow(d).to receive(:replica_set_name).and_return('test')
515515
allow(d).to receive(:is_server?).and_return(true)
516516
allow(d).to receive(:ghost?).and_return(false)
517+
allow(d).to receive(:address).and_return(address)
518+
allow(d).to receive(:me_mismatch?).and_return(false)
519+
allow(d).to receive(:unknown?).and_return(false)
517520
end
518521
end
519522

@@ -533,6 +536,10 @@
533536
allow(d).to receive(:replica_set_name).and_return('testing')
534537
allow(d).to receive(:lists_server?).and_return(true)
535538
allow(d).to receive(:servers).and_return([double('server')])
539+
allow(d).to receive(:address).and_return(address)
540+
allow(d).to receive(:me_mismatch?).and_return(false)
541+
allow(d).to receive(:unknown?).and_return(false)
542+
allow(d).to receive(:server_type).and_return(:secondary)
536543
end
537544
end
538545

@@ -543,19 +550,46 @@
543550

544551
context 'when the description does not include the server in question' do
545552

546-
let(:description) do
547-
double('description').tap do |d|
548-
allow(d).to receive(:config).and_return({ 'setName' => 'testing' })
549-
allow(d).to receive(:replica_set_member?).and_return(true)
550-
allow(d).to receive(:replica_set_name).and_return('testing')
551-
allow(d).to receive(:is_server?).and_return(false)
552-
allow(d).to receive(:lists_server?).and_return(false)
553-
allow(d).to receive(:servers).and_return([double('server')])
553+
context 'when the description is primary' do
554+
let(:description) do
555+
double('description').tap do |d|
556+
allow(d).to receive(:config).and_return({ 'setName' => 'testing' })
557+
allow(d).to receive(:replica_set_member?).and_return(true)
558+
allow(d).to receive(:replica_set_name).and_return('testing')
559+
allow(d).to receive(:is_server?).and_return(false)
560+
allow(d).to receive(:lists_server?).and_return(false)
561+
allow(d).to receive(:servers).and_return([double('server')])
562+
allow(d).to receive(:address).and_return("127.0.0.1:27018")
563+
allow(d).to receive(:me_mismatch?).and_return(false)
564+
allow(d).to receive(:unknown?).and_return(false)
565+
allow(d).to receive(:server_type).and_return(:primary)
566+
end
567+
end
568+
569+
it 'returns true' do
570+
expect(topology.remove_server?(description, secondary)).to eq(true)
554571
end
555572
end
556573

557-
it 'returns true' do
558-
expect(topology.remove_server?(description, secondary)).to eq(true)
574+
context 'when the description is not' do
575+
let(:description) do
576+
double('description').tap do |d|
577+
allow(d).to receive(:config).and_return({ 'setName' => 'testing' })
578+
allow(d).to receive(:replica_set_member?).and_return(true)
579+
allow(d).to receive(:replica_set_name).and_return('testing')
580+
allow(d).to receive(:is_server?).and_return(false)
581+
allow(d).to receive(:lists_server?).and_return(false)
582+
allow(d).to receive(:servers).and_return([double('server')])
583+
allow(d).to receive(:address).and_return('127.0.0.1:27018')
584+
allow(d).to receive(:me_mismatch?).and_return(false)
585+
allow(d).to receive(:unknown?).and_return(false)
586+
allow(d).to receive(:server_type).and_return(:secondary)
587+
end
588+
end
589+
590+
it 'returns false' do
591+
expect(topology.remove_server?(description, secondary)).to eq(false)
592+
end
559593
end
560594
end
561595
end
@@ -568,6 +602,8 @@
568602
allow(d).to receive(:replica_set_member?).and_return(true)
569603
allow(d).to receive(:replica_set_name).and_return('test')
570604
allow(d).to receive(:is_server?).and_return(false)
605+
allow(d).to receive(:address).and_return('127.0.0.1:27018')
606+
allow(d).to receive(:unknown?).and_return(false)
571607
end
572608
end
573609

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
description: "New primary"
2+
3+
uri: "mongodb://a,b/?replicaSet=rs"
4+
5+
phases: [
6+
7+
{
8+
responses: [
9+
10+
["a:27017", {
11+
12+
ok: 1,
13+
ismaster: true,
14+
setName: "rs",
15+
hosts: ["a:27017", "b:27017"],
16+
minWireVersion: 0,
17+
maxWireVersion: 6
18+
}],
19+
["b:27017", {
20+
21+
ok: 1,
22+
ismaster: false,
23+
secondary: true,
24+
setName: "rs",
25+
hosts: ["a:27017", "b:27017"],
26+
minWireVersion: 0,
27+
maxWireVersion: 6
28+
}]
29+
],
30+
31+
outcome: {
32+
33+
servers: {
34+
35+
"a:27017": {
36+
37+
type: "RSPrimary",
38+
setName: "rs"
39+
},
40+
41+
"b:27017": {
42+
43+
type: "RSSecondary",
44+
setName: "rs"
45+
}
46+
},
47+
topologyType: "ReplicaSetWithPrimary",
48+
logicalSessionTimeoutMinutes: null,
49+
setName: "rs"
50+
}
51+
},
52+
53+
{
54+
responses: [
55+
56+
["b:27017", {
57+
58+
ok: 0,
59+
minWireVersion: 0,
60+
maxWireVersion: 6
61+
}]
62+
],
63+
64+
outcome: {
65+
66+
servers: {
67+
"a:27017": {
68+
69+
type: "RSPrimary",
70+
setName: "rs"
71+
},
72+
73+
"b:27017": {
74+
75+
type: "Unknown",
76+
setName:
77+
}
78+
79+
},
80+
topologyType: "ReplicaSetWithPrimary",
81+
logicalSessionTimeoutMinutes: null,
82+
setName: "rs"
83+
}
84+
}
85+
]

0 commit comments

Comments
 (0)