PYTHON-2970 Prioritize electionId over setVersion for stale primary check (#845)

This commit is contained in:
Shane Harvey 2022-03-09 11:13:18 -08:00 committed by GitHub
parent f081297a86
commit 225d131c2d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 481 additions and 50 deletions

View File

@ -26,6 +26,21 @@ PyMongo 4.1 brings a number of improvements including:
- :meth:`gridfs.GridOut.seek` now returns the new position in the file, to
conform to the behavior of :meth:`io.IOBase.seek`.
Bug fixes
.........
- Fixed a bug where the client could be unable to discover the new primary
after a simultaneous replica set election and reconfig (`PYTHON-2970`_).
.. _PYTHON-2970: https://jira.mongodb.org/browse/PYTHON-2970
Issues Resolved
...............
See the `PyMongo 4.1 release notes in JIRA`_ for the list of resolved issues
in this release.
.. _PyMongo 4.1 release notes in JIRA: https://jira.mongodb.org/secure/ReleaseNote.jspa?projectId=10004&version=30619
Changes in Version 4.0
----------------------

View File

@ -17,6 +17,7 @@
from random import sample
from typing import Any, Callable, Dict, List, NamedTuple, Optional, Tuple
from bson.min_key import MinKey
from bson.objectid import ObjectId
from pymongo import common
from pymongo.errors import ConfigurationError
@ -531,24 +532,16 @@ def _update_rs_from_primary(
sds.pop(server_description.address)
return (_check_has_primary(sds), replica_set_name, max_set_version, max_election_id)
max_election_tuple = max_set_version, max_election_id
if None not in server_description.election_tuple:
if (
None not in max_election_tuple
and max_election_tuple > server_description.election_tuple
):
# Stale primary, set to type Unknown.
sds[server_description.address] = server_description.to_unknown()
return (_check_has_primary(sds), replica_set_name, max_set_version, max_election_id)
max_election_id = server_description.election_id
if server_description.set_version is not None and (
max_set_version is None or server_description.set_version > max_set_version
):
max_set_version = server_description.set_version
new_election_tuple = server_description.election_id, server_description.set_version
max_election_tuple = max_election_id, max_set_version
new_election_safe = tuple(MinKey() if i is None else i for i in new_election_tuple)
max_election_safe = tuple(MinKey() if i is None else i for i in max_election_tuple)
if new_election_safe >= max_election_safe:
max_election_id, max_set_version = new_election_tuple
else:
# Stale primary, set to type Unknown.
sds[server_description.address] = server_description.to_unknown()
return _check_has_primary(sds), replica_set_name, max_set_version, max_election_id
# We've heard from the primary. Is it the same primary as before?
for server in sds.values():

View File

@ -0,0 +1,92 @@
{
"description": "ElectionId is considered higher precedence than setVersion",
"uri": "mongodb://a/?replicaSet=rs",
"phases": [
{
"responses": [
[
"a:27017",
{
"ok": 1,
"helloOk": true,
"isWritablePrimary": true,
"hosts": [
"a:27017",
"b:27017"
],
"setName": "rs",
"setVersion": 1,
"electionId": {
"$oid": "000000000000000000000001"
},
"minWireVersion": 0,
"maxWireVersion": 6
}
],
[
"b:27017",
{
"ok": 1,
"helloOk": true,
"isWritablePrimary": true,
"hosts": [
"a:27017",
"b:27017"
],
"setName": "rs",
"setVersion": 2,
"electionId": {
"$oid": "000000000000000000000001"
},
"minWireVersion": 0,
"maxWireVersion": 6
}
],
[
"a:27017",
{
"ok": 1,
"helloOk": true,
"isWritablePrimary": true,
"hosts": [
"a:27017",
"b:27017"
],
"setName": "rs",
"setVersion": 1,
"electionId": {
"$oid": "000000000000000000000002"
},
"minWireVersion": 0,
"maxWireVersion": 6
}
]
],
"outcome": {
"servers": {
"a:27017": {
"type": "RSPrimary",
"setName": "rs",
"setVersion": 1,
"electionId": {
"$oid": "000000000000000000000002"
}
},
"b:27017": {
"type": "Unknown",
"setName": null,
"setVersion": null,
"electionId": null
}
},
"topologyType": "ReplicaSetWithPrimary",
"logicalSessionTimeoutMinutes": null,
"setName": "rs",
"maxSetVersion": 1,
"maxElectionId": {
"$oid": "000000000000000000000002"
}
}
}
]
}

View File

@ -123,15 +123,18 @@
"outcome": {
"servers": {
"a:27017": {
"type": "RSPrimary",
"setName": "rs",
"setVersion": 1,
"type": "Unknown",
"setName": null,
"setVersion": null,
"electionId": null
},
"b:27017": {
"type": "Unknown",
"setName": null,
"electionId": null
"type": "RSPrimary",
"setName": "rs",
"setVersion": 1,
"electionId": {
"$oid": "000000000000000000000002"
}
},
"c:27017": {
"type": "Unknown",
@ -174,15 +177,18 @@
"outcome": {
"servers": {
"a:27017": {
"type": "RSPrimary",
"setName": "rs",
"setVersion": 1,
"type": "Unknown",
"setName": null,
"setVersion": null,
"electionId": null
},
"b:27017": {
"type": "Unknown",
"setName": null,
"electionId": null
"type": "RSPrimary",
"setName": "rs",
"setVersion": 1,
"electionId": {
"$oid": "000000000000000000000002"
}
},
"c:27017": {
"type": "Unknown",

View File

@ -1,5 +1,5 @@
{
"description": "New primary",
"description": "Secondary ignored when ok is zero",
"uri": "mongodb://a,b/?replicaSet=rs",
"phases": [
{

View File

@ -0,0 +1,149 @@
{
"description": "Set version rolls back after new primary with higher election Id",
"uri": "mongodb://a/?replicaSet=rs",
"phases": [
{
"responses": [
[
"a:27017",
{
"ok": 1,
"hello": true,
"isWritablePrimary": true,
"hosts": [
"a:27017",
"b:27017"
],
"setName": "rs",
"setVersion": 2,
"electionId": {
"$oid": "000000000000000000000001"
},
"minWireVersion": 0,
"maxWireVersion": 6
}
]
],
"outcome": {
"servers": {
"a:27017": {
"type": "RSPrimary",
"setName": "rs",
"setVersion": 2,
"electionId": {
"$oid": "000000000000000000000001"
}
},
"b:27017": {
"type": "Unknown",
"setName": null,
"electionId": null
}
},
"topologyType": "ReplicaSetWithPrimary",
"logicalSessionTimeoutMinutes": null,
"setName": "rs",
"maxSetVersion": 2,
"maxElectionId": {
"$oid": "000000000000000000000001"
}
}
},
{
"_comment": "Response from new primary with newer election Id",
"responses": [
[
"b:27017",
{
"ok": 1,
"hello": true,
"isWritablePrimary": true,
"hosts": [
"a:27017",
"b:27017"
],
"setName": "rs",
"setVersion": 1,
"electionId": {
"$oid": "000000000000000000000002"
},
"minWireVersion": 0,
"maxWireVersion": 6
}
]
],
"outcome": {
"servers": {
"a:27017": {
"type": "Unknown",
"setName": null,
"electionId": null
},
"b:27017": {
"type": "RSPrimary",
"setName": "rs",
"setVersion": 1,
"electionId": {
"$oid": "000000000000000000000002"
}
}
},
"topologyType": "ReplicaSetWithPrimary",
"logicalSessionTimeoutMinutes": null,
"setName": "rs",
"maxSetVersion": 1,
"maxElectionId": {
"$oid": "000000000000000000000002"
}
}
},
{
"_comment": "Response from stale primary",
"responses": [
[
"a:27017",
{
"ok": 1,
"hello": true,
"isWritablePrimary": true,
"hosts": [
"a:27017",
"b:27017"
],
"setName": "rs",
"setVersion": 2,
"electionId": {
"$oid": "000000000000000000000001"
},
"minWireVersion": 0,
"maxWireVersion": 6
}
]
],
"outcome": {
"servers": {
"a:27017": {
"type": "Unknown",
"setName": null,
"electionId": null
},
"b:27017": {
"type": "RSPrimary",
"setName": "rs",
"setVersion": 1,
"electionId": {
"$oid": "000000000000000000000002"
}
}
},
"topologyType": "ReplicaSetWithPrimary",
"logicalSessionTimeoutMinutes": null,
"setName": "rs",
"maxSetVersion": 1,
"maxElectionId": {
"$oid": "000000000000000000000002"
}
}
}
]
}

View File

@ -0,0 +1,84 @@
{
"description": "setVersion version that is equal is treated the same as greater than if there is no electionId",
"uri": "mongodb://a/?replicaSet=rs",
"phases": [
{
"responses": [
[
"a:27017",
{
"ok": 1,
"helloOk": true,
"isWritablePrimary": true,
"hosts": [
"a:27017",
"b:27017"
],
"setName": "rs",
"setVersion": 1,
"minWireVersion": 0,
"maxWireVersion": 6
}
]
],
"outcome": {
"servers": {
"a:27017": {
"type": "RSPrimary",
"setName": "rs",
"setVersion": 1,
"electionId": null
},
"b:27017": {
"type": "Unknown",
"setName": null,
"electionId": null
}
},
"topologyType": "ReplicaSetWithPrimary",
"logicalSessionTimeoutMinutes": null,
"setName": "rs",
"maxSetVersion": 1
}
},
{
"responses": [
[
"b:27017",
{
"ok": 1,
"helloOk": true,
"isWritablePrimary": true,
"hosts": [
"a:27017",
"b:27017"
],
"setName": "rs",
"setVersion": 1,
"minWireVersion": 0,
"maxWireVersion": 6
}
]
],
"outcome": {
"servers": {
"a:27017": {
"type": "Unknown",
"setName": null,
"electionId": null
},
"b:27017": {
"type": "RSPrimary",
"setName": "rs",
"setVersion": 1,
"electionId": null
}
},
"topologyType": "ReplicaSetWithPrimary",
"logicalSessionTimeoutMinutes": null,
"setName": "rs",
"maxSetVersion": 1
}
}
]
}

View File

@ -0,0 +1,84 @@
{
"description": "setVersion that is greater than maxSetVersion is used if there is no electionId",
"uri": "mongodb://a/?replicaSet=rs",
"phases": [
{
"responses": [
[
"a:27017",
{
"ok": 1,
"helloOk": true,
"isWritablePrimary": true,
"hosts": [
"a:27017",
"b:27017"
],
"setName": "rs",
"setVersion": 1,
"minWireVersion": 0,
"maxWireVersion": 6
}
]
],
"outcome": {
"servers": {
"a:27017": {
"type": "RSPrimary",
"setName": "rs",
"setVersion": 1,
"electionId": null
},
"b:27017": {
"type": "Unknown",
"setName": null,
"electionId": null
}
},
"topologyType": "ReplicaSetWithPrimary",
"logicalSessionTimeoutMinutes": null,
"setName": "rs",
"maxSetVersion": 1
}
},
{
"responses": [
[
"b:27017",
{
"ok": 1,
"helloOk": true,
"isWritablePrimary": true,
"hosts": [
"a:27017",
"b:27017"
],
"setName": "rs",
"setVersion": 2,
"minWireVersion": 0,
"maxWireVersion": 6
}
]
],
"outcome": {
"servers": {
"a:27017": {
"type": "Unknown",
"setName": null,
"electionId": null
},
"b:27017": {
"type": "RSPrimary",
"setName": "rs",
"setVersion": 2,
"electionId": null
}
},
"topologyType": "ReplicaSetWithPrimary",
"logicalSessionTimeoutMinutes": null,
"setName": "rs",
"maxSetVersion": 2
}
}
]
}

View File

@ -1,5 +1,5 @@
{
"description": "setVersion is ignored if there is no electionId",
"description": "setVersion that is less than maxSetVersion is ignored if there is no electionId",
"uri": "mongodb://a/?replicaSet=rs",
"phases": [
{
@ -63,14 +63,14 @@
"outcome": {
"servers": {
"a:27017": {
"type": "Unknown",
"setName": null,
"type": "RSPrimary",
"setName": "rs",
"setVersion": 2,
"electionId": null
},
"b:27017": {
"type": "RSPrimary",
"setName": "rs",
"setVersion": 1,
"type": "Unknown",
"setName": null,
"electionId": null
}
},

View File

@ -71,20 +71,23 @@
"outcome": {
"servers": {
"a:27017": {
"type": "RSPrimary",
"setName": "rs",
"setVersion": 1,
"electionId": {
"$oid": "000000000000000000000001"
}
},
"b:27017": {
"type": "Unknown",
"setName": null,
"electionId": null
},
"b:27017": {
"type": "RSPrimary",
"setName": "rs",
"setVersion": 2
}
},
"topologyType": "ReplicaSetWithPrimary",
"logicalSessionTimeoutMinutes": null,
"setName": "rs",
"maxSetVersion": 2,
"maxSetVersion": 1,
"maxElectionId": {
"$oid": "000000000000000000000001"
}
@ -115,22 +118,25 @@
"outcome": {
"servers": {
"a:27017": {
"type": "RSPrimary",
"setName": "rs",
"setVersion": 1,
"electionId": {
"$oid": "000000000000000000000002"
}
},
"b:27017": {
"type": "Unknown",
"setName": null,
"electionId": null
},
"b:27017": {
"type": "RSPrimary",
"setName": "rs",
"setVersion": 2
}
},
"topologyType": "ReplicaSetWithPrimary",
"logicalSessionTimeoutMinutes": null,
"setName": "rs",
"maxSetVersion": 2,
"maxSetVersion": 1,
"maxElectionId": {
"$oid": "000000000000000000000001"
"$oid": "000000000000000000000002"
}
}
}

View File

@ -220,6 +220,8 @@ def create_tests():
dirname = os.path.split(dirpath)[-1]
for filename in filenames:
if os.path.splitext(filename)[1] != ".json":
continue
with open(os.path.join(dirpath, filename)) as scenario_stream:
scenario_def = json_util.loads(scenario_stream.read())