Skip to content

Commit 1fc66ad

Browse files
committed
Remove from the cluster description a replica set member whose "me" hostname:port does not match the one that was used to connect to it.
JAVA-1815
1 parent efee1d5 commit 1fc66ad

File tree

10 files changed

+250
-6
lines changed

10 files changed

+250
-6
lines changed

driver-core/src/main/com/mongodb/connection/DescriptionHelper.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ static ServerDescription createServerDescription(final ServerAddress serverAddre
6767
.version(serverVersion)
6868
.address(serverAddress)
6969
.type(getServerType(isMasterResult))
70+
.canonicalAddress(isMasterResult.containsKey("me") ? isMasterResult.getString("me").getValue() : null)
7071
.hosts(listToSet(isMasterResult.getArray("hosts", new BsonArray())))
7172
.passives(listToSet(isMasterResult.getArray("passives", new BsonArray())))
7273
.arbiters(listToSet(isMasterResult.getArray("arbiters", new BsonArray())))

driver-core/src/main/com/mongodb/connection/MultiServerCluster.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,11 @@ private boolean handleReplicaSetMemberChanged(final ServerDescription newDescrip
196196

197197
ensureServers(newDescription);
198198

199+
if (newDescription.getCanonicalAddress() != null && !newDescription.getAddress().sameHost(newDescription.getCanonicalAddress())) {
200+
removeServer(newDescription.getAddress());
201+
return true;
202+
}
203+
199204
if (newDescription.isPrimary()) {
200205
if (newDescription.getElectionId() != null) {
201206
if (maxElectionId != null && maxElectionId.compareTo(newDescription.getElectionId()) > 0) {
@@ -249,7 +254,10 @@ private void addServer(final ServerAddress serverAddress) {
249254
}
250255

251256
private void removeServer(final ServerAddress serverAddress) {
252-
addressToServerTupleMap.remove(serverAddress).server.close();
257+
ServerTuple removed = addressToServerTupleMap.remove(serverAddress);
258+
if (removed != null) {
259+
removed.server.close();
260+
}
253261
}
254262

255263
private void invalidateOldPrimaries(final ServerAddress newPrimary) {

driver-core/src/main/com/mongodb/connection/ServerDescription.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public class ServerDescription {
5151
private final ServerAddress address;
5252

5353
private final ServerType type;
54+
private final String canonicalAddress;
5455
private final Set<String> hosts;
5556
private final Set<String> passives;
5657
private final Set<String> arbiters;
@@ -79,12 +80,23 @@ public static Builder builder() {
7980
return new Builder();
8081
}
8182

83+
/**
84+
* Gets the string representing the host name and port that this member of a replica set was configured with,
85+
* e.g. {@code "somehost:27019"}. This is typically derived from the "me" field from the "isMaster" command response.
86+
*
87+
* @return the host name and port that this replica set member is configured with.
88+
*/
89+
public String getCanonicalAddress() {
90+
return canonicalAddress;
91+
}
92+
8293
/**
8394
* A builder for creating ServerDescription.
8495
*/
8596
public static class Builder {
8697
private ServerAddress address;
8798
private ServerType type = UNKNOWN;
99+
private String canonicalAddress;
88100
private Set<String> hosts = Collections.emptySet();
89101
private Set<String> passives = Collections.emptySet();
90102
private Set<String> arbiters = Collections.emptySet();
@@ -112,6 +124,19 @@ public Builder address(final ServerAddress address) {
112124
return this;
113125
}
114126

127+
/**
128+
* Sets the canonical host name and port of this server. This is typically derived from the "me" field contained in the "isMaster"
129+
* command. response.
130+
*
131+
* @param canonicalAddress the host name and port as a string
132+
*
133+
* @return this
134+
*/
135+
public Builder canonicalAddress(final String canonicalAddress) {
136+
this.canonicalAddress = canonicalAddress;
137+
return this;
138+
}
139+
115140
/**
116141
* Sets the type of the server, for example whether it's a standalone or in a replica set.
117142
*
@@ -614,6 +639,9 @@ public boolean equals(final Object o) {
614639
if (!arbiters.equals(that.arbiters)) {
615640
return false;
616641
}
642+
if (canonicalAddress != null ? !canonicalAddress.equals(that.canonicalAddress) : that.canonicalAddress != null) {
643+
return false;
644+
}
617645
if (!hosts.equals(that.hosts)) {
618646
return false;
619647
}
@@ -668,6 +696,7 @@ public boolean equals(final Object o) {
668696
public int hashCode() {
669697
int result = address.hashCode();
670698
result = 31 * result + type.hashCode();
699+
result = 31 * result + (canonicalAddress != null ? canonicalAddress.hashCode() : 0);
671700
result = 31 * result + hosts.hashCode();
672701
result = 31 * result + passives.hashCode();
673702
result = 31 * result + arbiters.hashCode();
@@ -705,6 +734,7 @@ public String toString() {
705734
+ (isReplicaSetMember()
706735
?
707736
", setName='" + setName + '\''
737+
+ ", host=" + canonicalAddress
708738
+ ", hosts=" + hosts
709739
+ ", passives=" + passives
710740
+ ", arbiters=" + arbiters
@@ -758,6 +788,7 @@ private String getRoundTripFormattedInMilliseconds() {
758788
type = notNull("type", builder.type);
759789
state = notNull("state", builder.state);
760790
version = notNull("version", builder.version);
791+
canonicalAddress = builder.canonicalAddress;
761792
hosts = builder.hosts;
762793
passives = builder.passives;
763794
arbiters = builder.arbiters;
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"description": "Primary mismatched me",
3+
"phases": [
4+
{
5+
"outcome": {
6+
"servers": {
7+
"a:27017": {
8+
"setName": null,
9+
"type": "Unknown"
10+
},
11+
"b:27017": {
12+
"setName": null,
13+
"type": "Unknown"
14+
}
15+
},
16+
"setName": "rs",
17+
"topologyType": "ReplicaSetNoPrimary"
18+
},
19+
"responses": [
20+
[
21+
"localhost:27017",
22+
{
23+
"hosts": [
24+
"a:27017",
25+
"b:27017"
26+
],
27+
"ismaster": true,
28+
"me": "a:27017",
29+
"ok": 1,
30+
"setName": "rs"
31+
}
32+
]
33+
]
34+
}
35+
],
36+
"uri": "mongodb://localhost:27017/?replicaSet=rs"
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
{
2+
"description": "Primary to no primary with mismatched me",
3+
"phases": [
4+
{
5+
"outcome": {
6+
"servers": {
7+
"a:27017": {
8+
"setName": "rs",
9+
"type": "RSPrimary"
10+
},
11+
"b:27017": {
12+
"setName": null,
13+
"type": "Unknown"
14+
}
15+
},
16+
"setName": "rs",
17+
"topologyType": "ReplicaSetWithPrimary"
18+
},
19+
"responses": [
20+
[
21+
"a:27017",
22+
{
23+
"hosts": [
24+
"a:27017",
25+
"b:27017"
26+
],
27+
"ismaster": true,
28+
"me": "a:27017",
29+
"ok": 1,
30+
"setName": "rs"
31+
}
32+
]
33+
]
34+
},
35+
{
36+
"outcome": {
37+
"servers": {
38+
"c:27017": {
39+
"setName": null,
40+
"type": "Unknown"
41+
},
42+
"d:27017": {
43+
"setName": null,
44+
"type": "Unknown"
45+
}
46+
},
47+
"setName": "rs",
48+
"topologyType": "ReplicaSetNoPrimary"
49+
},
50+
"responses": [
51+
[
52+
"a:27017",
53+
{
54+
"hosts": [
55+
"c:27017",
56+
"d:27017"
57+
],
58+
"ismaster": true,
59+
"me": "c:27017",
60+
"ok": 1,
61+
"setName": "rs"
62+
}
63+
]
64+
]
65+
}
66+
],
67+
"uri": "mongodb://a/?replicaSet=rs"
68+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"description": "Secondary mismatched me",
3+
"phases": [
4+
{
5+
"outcome": {
6+
"servers": {
7+
"a:27017": {
8+
"setName": null,
9+
"type": "Unknown"
10+
},
11+
"b:27017": {
12+
"setName": null,
13+
"type": "Unknown"
14+
}
15+
},
16+
"setName": "rs",
17+
"topologyType": "ReplicaSetNoPrimary"
18+
},
19+
"responses": [
20+
[
21+
"localhost:27017",
22+
{
23+
"hosts": [
24+
"a:27017",
25+
"b:27017"
26+
],
27+
"ismaster": false,
28+
"me": "a:27017",
29+
"ok": 1,
30+
"setName": "rs"
31+
}
32+
]
33+
]
34+
}
35+
],
36+
"uri": "mongodb://localhost:27017/?replicaSet=rs"
37+
}

driver-core/src/test/unit/com/mongodb/connection/DescriptionHelperSpecification.groovy

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ class DescriptionHelperSpecification extends Specification {
166166
.maxDocumentSize(16777216)
167167
.type(ServerType.REPLICA_SET_SECONDARY)
168168
.setName('replset')
169+
.canonicalAddress('localhost:27017')
169170
.hosts(['localhost:27017', 'localhost:27018', 'localhost:27019'] as Set)
170171
.arbiters(['localhost:27020'] as Set)
171172
.build()
@@ -211,6 +212,7 @@ class DescriptionHelperSpecification extends Specification {
211212
.type(ServerType.REPLICA_SET_PRIMARY)
212213
.setName('replset')
213214
.primary('localhost:27017')
215+
.canonicalAddress('localhost:27017')
214216
.hosts(['localhost:27017', 'localhost:27018', 'localhost:27019'] as Set)
215217
.arbiters(['localhost:27020'] as Set)
216218
.tagSet(new TagSet([new Tag('dc', 'east'), new Tag('use', 'production')]))
@@ -254,6 +256,7 @@ class DescriptionHelperSpecification extends Specification {
254256
.type(ServerType.REPLICA_SET_ARBITER)
255257
.setName('replset')
256258
.primary('localhost:27017')
259+
.canonicalAddress('localhost:27020' )
257260
.hosts(['localhost:27017', 'localhost:27018', 'localhost:27019'] as Set)
258261
.arbiters(['localhost:27020'] as Set)
259262
.build()
@@ -296,6 +299,7 @@ class DescriptionHelperSpecification extends Specification {
296299
.type(ServerType.REPLICA_SET_OTHER)
297300
.setName('replset')
298301
.primary('localhost:27017')
302+
.canonicalAddress('localhost:27020')
299303
.hosts(['localhost:27017', 'localhost:27018', 'localhost:27019'] as Set)
300304
.arbiters(['localhost:27020'] as Set)
301305
.build()
@@ -324,6 +328,7 @@ class DescriptionHelperSpecification extends Specification {
324328
.address(serverAddress)
325329
.state(ServerConnectionState.CONNECTED)
326330
.version(serverVersion)
331+
.canonicalAddress('localhost:27020' )
327332
.maxWireVersion(3)
328333
.maxDocumentSize(16777216)
329334
.type(ServerType.REPLICA_SET_GHOST)

driver-core/src/test/unit/com/mongodb/connection/MultiServerClusterSpecification.groovy

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,34 @@ class MultiServerClusterSpecification extends Specification {
121121
cluster.getDescription().all == factory.getDescriptions(firstServer, secondServer, thirdServer)
122122
}
123123

124+
def 'should remove a secondary server whose reported host name does not match the address connected to'() {
125+
given:
126+
def seedListAddress = new ServerAddress('127.0.0.1:27017')
127+
def cluster = new MultiServerCluster(CLUSTER_ID,
128+
ClusterSettings.builder().hosts([seedListAddress]).build(), factory,
129+
CLUSTER_LISTENER);
130+
131+
when:
132+
factory.sendNotification(seedListAddress, REPLICA_SET_SECONDARY, [firstServer, secondServer], firstServer)
133+
134+
then:
135+
cluster.getCurrentDescription().all == factory.getDescriptions(firstServer, secondServer)
136+
}
137+
138+
def 'should remove a primary server whose reported host name does not match the address connected to'() {
139+
given:
140+
def seedListAddress = new ServerAddress('127.0.0.1:27017')
141+
def cluster = new MultiServerCluster(CLUSTER_ID,
142+
ClusterSettings.builder().hosts([seedListAddress]).build(), factory,
143+
CLUSTER_LISTENER);
144+
145+
when:
146+
factory.sendNotification(seedListAddress, REPLICA_SET_PRIMARY, [firstServer, secondServer], firstServer)
147+
148+
then:
149+
cluster.getCurrentDescription().all == factory.getDescriptions(firstServer, secondServer)
150+
}
151+
124152
def 'should remove a server when it no longer appears in hosts reported by the primary'() {
125153
given:
126154
def cluster = new MultiServerCluster(CLUSTER_ID,

driver-core/src/test/unit/com/mongodb/connection/ServerDescriptionTest.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ public void testDefaults() throws UnknownHostException {
8181
assertNull(serverDescription.getPrimary());
8282
assertEquals(Collections.<String>emptySet(), serverDescription.getHosts());
8383
assertEquals(new TagSet(), serverDescription.getTagSet());
84+
assertNull(serverDescription.getCanonicalAddress());
8485
assertEquals(Collections.<String>emptySet(), serverDescription.getHosts());
8586
assertEquals(Collections.<String>emptySet(), serverDescription.getPassives());
8687
assertNull(serverDescription.getSetName());
@@ -102,6 +103,7 @@ public void testBuilder() throws UnknownHostException {
102103
.maxDocumentSize(100)
103104
.roundTripTime(50000, java.util.concurrent.TimeUnit.NANOSECONDS)
104105
.primary("localhost:27017")
106+
.canonicalAddress("localhost:27018")
105107
.hosts(new HashSet<String>(asList("localhost:27017",
106108
"localhost:27018",
107109
"localhost:27019",
@@ -135,6 +137,7 @@ public void testBuilder() throws UnknownHostException {
135137
assertEquals(100, serverDescription.getMaxDocumentSize());
136138

137139
assertEquals("localhost:27017", serverDescription.getPrimary());
140+
assertEquals("localhost:27018", serverDescription.getCanonicalAddress());
138141
assertEquals(new HashSet<String>(asList("localhost:27017", "localhost:27018", "localhost:27019", "localhost:27020")),
139142
serverDescription.getHosts());
140143
assertEquals(new TagSet(new Tag("dc", "ny")), serverDescription.getTagSet());
@@ -158,6 +161,7 @@ public void testObjectOverrides() throws UnknownHostException {
158161
.maxDocumentSize(100)
159162
.roundTripTime(50000, java.util.concurrent.TimeUnit.NANOSECONDS)
160163
.primary("localhost:27017")
164+
.canonicalAddress("localhost:27017")
161165
.hosts(new HashSet<String>(asList("localhost:27017",
162166
"localhost:27018")))
163167
.passives(new HashSet<String>(asList("localhost:27019")))

0 commit comments

Comments
 (0)