diff --git a/src/sdam/topology_description.ts b/src/sdam/topology_description.ts index 972680afdd..d35a9ad6e6 100644 --- a/src/sdam/topology_description.ts +++ b/src/sdam/topology_description.ts @@ -373,31 +373,56 @@ function updateRsFromPrimary( return [checkHasPrimary(serverDescriptions), setName, maxSetVersion, maxElectionId]; } - const electionId = serverDescription.electionId ? serverDescription.electionId : null; - if (serverDescription.setVersion && electionId) { - if (maxSetVersion && maxElectionId) { - if ( - maxSetVersion > serverDescription.setVersion || - compareObjectId(maxElectionId, electionId) > 0 - ) { - // this primary is stale, we must remove it - serverDescriptions.set( - serverDescription.address, - new ServerDescription(serverDescription.address) - ); + if (serverDescription.maxWireVersion >= 17) { + const electionIdComparison = compareObjectId(maxElectionId, serverDescription.electionId); + const maxElectionIdIsEqual = electionIdComparison === 0; + const maxElectionIdIsLess = electionIdComparison === -1; + const maxSetVersionIsLessOrEqual = + (maxSetVersion ?? -1) <= (serverDescription.setVersion ?? -1); + + if (maxElectionIdIsLess || (maxElectionIdIsEqual && maxSetVersionIsLessOrEqual)) { + // The reported electionId was greater + // or the electionId was equal and reported setVersion was greater + // Always update both values, they are a tuple + maxElectionId = serverDescription.electionId; + maxSetVersion = serverDescription.setVersion; + } else { + // Stale primary + // replace serverDescription with a default ServerDescription of type "Unknown" + serverDescriptions.set( + serverDescription.address, + new ServerDescription(serverDescription.address) + ); - return [checkHasPrimary(serverDescriptions), setName, maxSetVersion, maxElectionId]; - } + return [checkHasPrimary(serverDescriptions), setName, maxSetVersion, maxElectionId]; } + } else { + const electionId = serverDescription.electionId ? serverDescription.electionId : null; + if (serverDescription.setVersion && electionId) { + if (maxSetVersion && maxElectionId) { + if ( + maxSetVersion > serverDescription.setVersion || + compareObjectId(maxElectionId, electionId) > 0 + ) { + // this primary is stale, we must remove it + serverDescriptions.set( + serverDescription.address, + new ServerDescription(serverDescription.address) + ); + + return [checkHasPrimary(serverDescriptions), setName, maxSetVersion, maxElectionId]; + } + } - maxElectionId = serverDescription.electionId; - } + maxElectionId = serverDescription.electionId; + } - if ( - serverDescription.setVersion != null && - (maxSetVersion == null || serverDescription.setVersion > maxSetVersion) - ) { - maxSetVersion = serverDescription.setVersion; + if ( + serverDescription.setVersion != null && + (maxSetVersion == null || serverDescription.setVersion > maxSetVersion) + ) { + maxSetVersion = serverDescription.setVersion; + } } // We've heard from the primary. Is it the same primary as before? diff --git a/src/utils.ts b/src/utils.ts index f3349b6eca..ccbe87d0cc 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1300,7 +1300,7 @@ export function getMongoDBClientEncryption(): { * - `-1 = oid1 is less than oid2` * - `+0 = oid1 is equal oid2` */ -export function compareObjectId(oid1?: ObjectId, oid2?: ObjectId): 0 | 1 | -1 { +export function compareObjectId(oid1?: ObjectId | null, oid2?: ObjectId | null): 0 | 1 | -1 { if (oid1 == null && oid2 == null) { return 0; } diff --git a/test/spec/server-discovery-and-monitoring/rs/electionId_precedence_setVersion.json b/test/spec/server-discovery-and-monitoring/rs/electionId_precedence_setVersion.json new file mode 100644 index 0000000000..2fcea2bf66 --- /dev/null +++ b/test/spec/server-discovery-and-monitoring/rs/electionId_precedence_setVersion.json @@ -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": 17 + } + ], + [ + "b:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": true, + "hosts": [ + "a:27017", + "b:27017" + ], + "setName": "rs", + "setVersion": 2, + "electionId": { + "$oid": "000000000000000000000001" + }, + "minWireVersion": 0, + "maxWireVersion": 17 + } + ], + [ + "a:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": true, + "hosts": [ + "a:27017", + "b:27017" + ], + "setName": "rs", + "setVersion": 1, + "electionId": { + "$oid": "000000000000000000000002" + }, + "minWireVersion": 0, + "maxWireVersion": 17 + } + ] + ], + "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" + } + } + } + ] +} diff --git a/test/spec/server-discovery-and-monitoring/rs/electionId_precedence_setVersion.yml b/test/spec/server-discovery-and-monitoring/rs/electionId_precedence_setVersion.yml new file mode 100644 index 0000000000..4b9563fcfd --- /dev/null +++ b/test/spec/server-discovery-and-monitoring/rs/electionId_precedence_setVersion.yml @@ -0,0 +1,62 @@ +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: 17 + - - "b:27017" + - ok: 1 + helloOk: true + isWritablePrimary: true + hosts: + - "a:27017" + - "b:27017" + setName: rs + setVersion: 2 # Even though "B" reports the newer setVersion, "A" will report the newer electionId which should allow it to remain the primary + electionId: + $oid: "000000000000000000000001" + minWireVersion: 0 + maxWireVersion: 17 + - - "a:27017" + - ok: 1 + helloOk: true + isWritablePrimary: true + hosts: + - "a:27017" + - "b:27017" + setName: rs + setVersion: 1 + electionId: + $oid: "000000000000000000000002" + minWireVersion: 0 + maxWireVersion: 17 + 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" diff --git a/test/spec/server-discovery-and-monitoring/rs/null_election_id-pre-6.0.json b/test/spec/server-discovery-and-monitoring/rs/null_election_id-pre-6.0.json new file mode 100644 index 0000000000..f1fa2e252e --- /dev/null +++ b/test/spec/server-discovery-and-monitoring/rs/null_election_id-pre-6.0.json @@ -0,0 +1,203 @@ +{ + "description": "Pre 6.0 Primaries with and without electionIds", + "uri": "mongodb://a/?replicaSet=rs", + "phases": [ + { + "responses": [ + [ + "a:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": true, + "hosts": [ + "a:27017", + "b:27017", + "c:27017" + ], + "setVersion": 1, + "setName": "rs", + "minWireVersion": 0, + "maxWireVersion": 6 + } + ] + ], + "outcome": { + "servers": { + "a:27017": { + "type": "RSPrimary", + "setName": "rs", + "setVersion": 1, + "electionId": null + }, + "b:27017": { + "type": "Unknown", + "setName": null, + "electionId": null + }, + "c: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", + "c: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" + } + }, + "c:27017": { + "type": "Unknown", + "setName": null, + "electionId": null + } + }, + "topologyType": "ReplicaSetWithPrimary", + "logicalSessionTimeoutMinutes": null, + "setName": "rs", + "maxSetVersion": 1, + "maxElectionId": { + "$oid": "000000000000000000000002" + } + } + }, + { + "responses": [ + [ + "a:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": true, + "hosts": [ + "a:27017", + "b:27017", + "c:27017" + ], + "setVersion": 1, + "setName": "rs", + "minWireVersion": 0, + "maxWireVersion": 6 + } + ] + ], + "outcome": { + "servers": { + "a:27017": { + "type": "RSPrimary", + "setName": "rs", + "setVersion": 1, + "electionId": null + }, + "b:27017": { + "type": "Unknown", + "setName": null, + "electionId": null + }, + "c:27017": { + "type": "Unknown", + "setName": null, + "electionId": null + } + }, + "topologyType": "ReplicaSetWithPrimary", + "logicalSessionTimeoutMinutes": null, + "setName": "rs", + "maxSetVersion": 1, + "maxElectionId": { + "$oid": "000000000000000000000002" + } + } + }, + { + "responses": [ + [ + "c:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": true, + "hosts": [ + "a:27017", + "b:27017", + "c:27017" + ], + "setName": "rs", + "setVersion": 1, + "electionId": { + "$oid": "000000000000000000000001" + }, + "minWireVersion": 0, + "maxWireVersion": 6 + } + ] + ], + "outcome": { + "servers": { + "a:27017": { + "type": "RSPrimary", + "setName": "rs", + "setVersion": 1, + "electionId": null + }, + "b:27017": { + "type": "Unknown", + "setName": null, + "electionId": null + }, + "c:27017": { + "type": "Unknown", + "setName": null, + "electionId": null + } + }, + "topologyType": "ReplicaSetWithPrimary", + "logicalSessionTimeoutMinutes": null, + "setName": "rs", + "maxSetVersion": 1, + "maxElectionId": { + "$oid": "000000000000000000000002" + } + } + } + ] +} diff --git a/test/spec/server-discovery-and-monitoring/rs/null_election_id-pre-6.0.yml b/test/spec/server-discovery-and-monitoring/rs/null_election_id-pre-6.0.yml new file mode 100644 index 0000000000..7e9bd55021 --- /dev/null +++ b/test/spec/server-discovery-and-monitoring/rs/null_election_id-pre-6.0.yml @@ -0,0 +1,175 @@ +description: "Pre 6.0 Primaries with and without electionIds" + +uri: "mongodb://a/?replicaSet=rs" + +phases: [ + + # Primary A has no electionId. + { + responses: [ + ["a:27017", { + ok: 1, + helloOk: true, + isWritablePrimary: true, + hosts: ["a:27017", "b:27017", "c:27017"], + setVersion: 1, + setName: "rs", + minWireVersion: 0, + maxWireVersion: 6 + }] + ], + + outcome: { + servers: { + "a:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 1, + electionId: + }, + "b:27017": { + type: "Unknown", + setName: , + electionId: + }, + "c:27017": { + type: "Unknown", + setName: , + electionId: + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs", + maxSetVersion: 1, + } + }, + + # B is elected, it has an electionId. + { + responses: [ + ["b:27017", { + ok: 1, + helloOk: true, + isWritablePrimary: true, + hosts: ["a:27017", "b:27017", "c:27017"], + setName: "rs", + setVersion: 1, + electionId: {"$oid": "000000000000000000000002"}, + minWireVersion: 0, + maxWireVersion: 6 + }] + ], + + outcome: { + servers: { + "a:27017": { + type: "Unknown", + setName: , + electionId: + }, + "b:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 1, + electionId: {"$oid": "000000000000000000000002"} + }, + "c:27017": { + type: "Unknown", + setName: , + electionId: + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs", + maxSetVersion: 1, + maxElectionId: {"$oid": "000000000000000000000002"}, + } + }, + + # A still claims to be primary, no electionId, we have to trust it. + { + responses: [ + ["a:27017", { + ok: 1, + helloOk: true, + isWritablePrimary: true, + hosts: ["a:27017", "b:27017", "c:27017"], + setVersion: 1, + setName: "rs", + minWireVersion: 0, + maxWireVersion: 6 + }] + ], + outcome: { + servers: { + "a:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 1, + electionId: + }, + "b:27017": { + type: "Unknown", + setName: , + electionId: + }, + "c:27017": { + type: "Unknown", + setName: , + electionId: + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs", + maxSetVersion: 1, + maxElectionId: {"$oid": "000000000000000000000002"}, + } + }, + + # But we remember B's electionId, so when we finally hear from C + # claiming it is primary, we ignore it due to its outdated electionId + { + responses: [ + ["c:27017", { + ok: 1, + helloOk: true, + isWritablePrimary: true, + hosts: ["a:27017", "b:27017", "c:27017"], + setName: "rs", + setVersion: 1, + electionId: {"$oid": "000000000000000000000001"}, + minWireVersion: 0, + maxWireVersion: 6 + }] + ], + outcome: { + servers: { + # Still primary. + "a:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 1, + electionId: + }, + "b:27017": { + type: "Unknown", + setName: , + electionId: + }, + "c:27017": { + type: "Unknown", + setName: , + electionId: + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs", + maxSetVersion: 1, + maxElectionId: {"$oid": "000000000000000000000002"}, + } + } +] diff --git a/test/spec/server-discovery-and-monitoring/rs/null_election_id.json b/test/spec/server-discovery-and-monitoring/rs/null_election_id.json index 62120e8448..8a99a78475 100644 --- a/test/spec/server-discovery-and-monitoring/rs/null_election_id.json +++ b/test/spec/server-discovery-and-monitoring/rs/null_election_id.json @@ -18,7 +18,7 @@ "setVersion": 1, "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 17 } ] ], @@ -66,7 +66,7 @@ "$oid": "000000000000000000000002" }, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 17 } ] ], @@ -116,23 +116,26 @@ "setVersion": 1, "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 17 } ] ], "outcome": { "servers": { "a:27017": { - "type": "RSPrimary", - "setName": "rs", - "setVersion": 1, - "electionId": null - }, - "b:27017": { "type": "Unknown", "setName": null, + "setVersion": null, "electionId": null }, + "b:27017": { + "type": "RSPrimary", + "setName": "rs", + "setVersion": 1, + "electionId": { + "$oid": "000000000000000000000002" + } + }, "c:27017": { "type": "Unknown", "setName": null, @@ -167,23 +170,26 @@ "$oid": "000000000000000000000001" }, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 17 } ] ], "outcome": { "servers": { "a:27017": { - "type": "RSPrimary", - "setName": "rs", - "setVersion": 1, - "electionId": null - }, - "b:27017": { "type": "Unknown", "setName": null, + "setVersion": null, "electionId": null }, + "b:27017": { + "type": "RSPrimary", + "setName": "rs", + "setVersion": 1, + "electionId": { + "$oid": "000000000000000000000002" + } + }, "c:27017": { "type": "Unknown", "setName": null, diff --git a/test/spec/server-discovery-and-monitoring/rs/null_election_id.yml b/test/spec/server-discovery-and-monitoring/rs/null_election_id.yml index 7de496cd79..54c1af272a 100644 --- a/test/spec/server-discovery-and-monitoring/rs/null_election_id.yml +++ b/test/spec/server-discovery-and-monitoring/rs/null_election_id.yml @@ -15,7 +15,7 @@ phases: [ setVersion: 1, setName: "rs", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 17 }] ], @@ -57,7 +57,7 @@ phases: [ setVersion: 1, electionId: {"$oid": "000000000000000000000002"}, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 17 }] ], @@ -88,7 +88,7 @@ phases: [ } }, - # A still claims to be primary, no electionId, we have to trust it. + # A still claims to be primary, no electionId, we don't trust it. { responses: [ ["a:27017", { @@ -99,22 +99,24 @@ phases: [ setVersion: 1, setName: "rs", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 17 }] ], outcome: { servers: { + # A ignored for missing electionId "a:27017": { - type: "RSPrimary", - setName: "rs", - setVersion: 1, - electionId: - }, - "b:27017": { type: "Unknown", setName: , + setVersion: , electionId: }, + "b:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 1, + electionId: { "$oid": "000000000000000000000002" } + }, "c:27017": { type: "Unknown", setName: , @@ -142,23 +144,23 @@ phases: [ setVersion: 1, electionId: {"$oid": "000000000000000000000001"}, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 17 }] ], outcome: { servers: { - # Still primary. "a:27017": { - type: "RSPrimary", - setName: "rs", - setVersion: 1, - electionId: - }, - "b:27017": { type: "Unknown", setName: , + setVersion: , electionId: }, + "b:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 1, + electionId: { "$oid": "000000000000000000000002" } + }, "c:27017": { type: "Unknown", setName: , diff --git a/test/spec/server-discovery-and-monitoring/rs/secondary_ignore_ok_0-pre-6.0.json b/test/spec/server-discovery-and-monitoring/rs/secondary_ignore_ok_0-pre-6.0.json new file mode 100644 index 0000000000..054425c84c --- /dev/null +++ b/test/spec/server-discovery-and-monitoring/rs/secondary_ignore_ok_0-pre-6.0.json @@ -0,0 +1,83 @@ +{ + "description": "Pre 6.0 New primary", + "uri": "mongodb://a,b/?replicaSet=rs", + "phases": [ + { + "responses": [ + [ + "a:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": true, + "setName": "rs", + "hosts": [ + "a:27017", + "b:27017" + ], + "minWireVersion": 0, + "maxWireVersion": 6 + } + ], + [ + "b:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": false, + "secondary": true, + "setName": "rs", + "hosts": [ + "a:27017", + "b:27017" + ], + "minWireVersion": 0, + "maxWireVersion": 6 + } + ] + ], + "outcome": { + "servers": { + "a:27017": { + "type": "RSPrimary", + "setName": "rs" + }, + "b:27017": { + "type": "RSSecondary", + "setName": "rs" + } + }, + "topologyType": "ReplicaSetWithPrimary", + "logicalSessionTimeoutMinutes": null, + "setName": "rs" + } + }, + { + "responses": [ + [ + "b:27017", + { + "ok": 0, + "minWireVersion": 0, + "maxWireVersion": 6 + } + ] + ], + "outcome": { + "servers": { + "a:27017": { + "type": "RSPrimary", + "setName": "rs" + }, + "b:27017": { + "type": "Unknown", + "setName": null + } + }, + "topologyType": "ReplicaSetWithPrimary", + "logicalSessionTimeoutMinutes": null, + "setName": "rs" + } + } + ] +} diff --git a/test/spec/server-discovery-and-monitoring/rs/secondary_ignore_ok_0-pre-6.0.yml b/test/spec/server-discovery-and-monitoring/rs/secondary_ignore_ok_0-pre-6.0.yml new file mode 100644 index 0000000000..de9048d401 --- /dev/null +++ b/test/spec/server-discovery-and-monitoring/rs/secondary_ignore_ok_0-pre-6.0.yml @@ -0,0 +1,87 @@ +description: "Pre 6.0 New primary" + +uri: "mongodb://a,b/?replicaSet=rs" + +phases: [ + + { + responses: [ + + ["a:27017", { + + ok: 1, + helloOk: true, + isWritablePrimary: true, + setName: "rs", + hosts: ["a:27017", "b:27017"], + minWireVersion: 0, + maxWireVersion: 6 + }], + ["b:27017", { + + ok: 1, + helloOk: true, + isWritablePrimary: false, + secondary: true, + setName: "rs", + hosts: ["a:27017", "b:27017"], + minWireVersion: 0, + maxWireVersion: 6 + }] + ], + + outcome: { + + servers: { + + "a:27017": { + + type: "RSPrimary", + setName: "rs" + }, + + "b:27017": { + + type: "RSSecondary", + setName: "rs" + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs" + } + }, + + { + responses: [ + + ["b:27017", { + + ok: 0, + minWireVersion: 0, + maxWireVersion: 6 + }] + ], + + outcome: { + + servers: { + "a:27017": { + + type: "RSPrimary", + setName: "rs" + }, + + "b:27017": { + + type: "Unknown", + setName: + } + + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs" + } + } +] diff --git a/test/spec/server-discovery-and-monitoring/rs/set_version_can_rollback.json b/test/spec/server-discovery-and-monitoring/rs/set_version_can_rollback.json new file mode 100644 index 0000000000..1cc608a344 --- /dev/null +++ b/test/spec/server-discovery-and-monitoring/rs/set_version_can_rollback.json @@ -0,0 +1,147 @@ +{ + "description": "Set version rolls back after new primary with higher election Id", + "uri": "mongodb://a/?replicaSet=rs", + "phases": [ + { + "responses": [ + [ + "a:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": true, + "hosts": [ + "a:27017", + "b:27017" + ], + "setName": "rs", + "setVersion": 2, + "electionId": { + "$oid": "000000000000000000000001" + }, + "minWireVersion": 0, + "maxWireVersion": 17 + } + ] + ], + "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" + } + } + }, + { + "responses": [ + [ + "b:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": true, + "hosts": [ + "a:27017", + "b:27017" + ], + "setName": "rs", + "setVersion": 1, + "electionId": { + "$oid": "000000000000000000000002" + }, + "minWireVersion": 0, + "maxWireVersion": 17 + } + ] + ], + "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" + } + } + }, + { + "responses": [ + [ + "a:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": true, + "hosts": [ + "a:27017", + "b:27017" + ], + "setName": "rs", + "setVersion": 2, + "electionId": { + "$oid": "000000000000000000000001" + }, + "minWireVersion": 0, + "maxWireVersion": 17 + } + ] + ], + "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" + } + } + } + ] +} diff --git a/test/spec/server-discovery-and-monitoring/rs/set_version_can_rollback.yml b/test/spec/server-discovery-and-monitoring/rs/set_version_can_rollback.yml new file mode 100644 index 0000000000..c30a8660ec --- /dev/null +++ b/test/spec/server-discovery-and-monitoring/rs/set_version_can_rollback.yml @@ -0,0 +1,101 @@ +description: Set version rolls back after new primary with higher election Id +uri: mongodb://a/?replicaSet=rs +phases: + - responses: + - - a:27017 + - ok: 1 + helloOk: true + isWritablePrimary: true + hosts: + - a:27017 + - b:27017 + setName: rs + setVersion: 2 + electionId: + $oid: '000000000000000000000001' + minWireVersion: 0 + maxWireVersion: 17 + 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' + - # Response from new primary with newer election Id + responses: + - - b:27017 + - ok: 1 + helloOk: true + isWritablePrimary: true + hosts: + - a:27017 + - b:27017 + setName: rs + setVersion: 1 + electionId: + $oid: '000000000000000000000002' + minWireVersion: 0 + maxWireVersion: 17 + 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' + - # Response from stale primary + responses: + - - a:27017 + - ok: 1 + helloOk: true + isWritablePrimary: true + hosts: + - a:27017 + - b:27017 + setName: rs + setVersion: 2 + electionId: + $oid: '000000000000000000000001' + minWireVersion: 0 + maxWireVersion: 17 + 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' diff --git a/test/spec/server-discovery-and-monitoring/rs/setversion_equal_max_without_electionid.json b/test/spec/server-discovery-and-monitoring/rs/setversion_equal_max_without_electionid.json new file mode 100644 index 0000000000..3669511c5a --- /dev/null +++ b/test/spec/server-discovery-and-monitoring/rs/setversion_equal_max_without_electionid.json @@ -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": 17 + } + ] + ], + "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": 17 + } + ] + ], + "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 + } + } + ] +} diff --git a/test/spec/server-discovery-and-monitoring/rs/setversion_equal_max_without_electionid.yml b/test/spec/server-discovery-and-monitoring/rs/setversion_equal_max_without_electionid.yml new file mode 100644 index 0000000000..3733a12e56 --- /dev/null +++ b/test/spec/server-discovery-and-monitoring/rs/setversion_equal_max_without_electionid.yml @@ -0,0 +1,78 @@ +description: "setVersion version that is equal is treated the same as greater than if there is no electionId" + +uri: "mongodb://a/?replicaSet=rs" + +phases: [ + + # Primary A is discovered and tells us about B. + { + responses: [ + ["a:27017", { + ok: 1, + helloOk: true, + isWritablePrimary: true, + hosts: ["a:27017", "b:27017"], + setName: "rs", + setVersion: 1, + minWireVersion: 0, + maxWireVersion: 17 + }] + ], + + outcome: { + servers: { + "a:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 1, + electionId: + }, + "b:27017": { + type: "Unknown", + setName: , + electionId: + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs", + maxSetVersion: 1, + } + }, + + # B is elected, its setVersion is older so it is stale + { + responses: [ + ["b:27017", { + ok: 1, + helloOk: true, + isWritablePrimary: true, + hosts: ["a:27017", "b:27017"], + setName: "rs", + setVersion: 1, + minWireVersion: 0, + maxWireVersion: 17 + }] + ], + + outcome: { + servers: { + "a:27017": { + type: "Unknown", + setName: , + electionId: + }, + "b:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 1, + electionId: + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs", + maxSetVersion: 1, # Max is still 1, there wasn't an actual larger setVersion seen + } + } +] diff --git a/test/spec/server-discovery-and-monitoring/rs/setversion_greaterthan_max_without_electionid.json b/test/spec/server-discovery-and-monitoring/rs/setversion_greaterthan_max_without_electionid.json new file mode 100644 index 0000000000..97870d71d5 --- /dev/null +++ b/test/spec/server-discovery-and-monitoring/rs/setversion_greaterthan_max_without_electionid.json @@ -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": 17 + } + ] + ], + "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": 17 + } + ] + ], + "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 + } + } + ] +} diff --git a/test/spec/server-discovery-and-monitoring/rs/setversion_greaterthan_max_without_electionid.yml b/test/spec/server-discovery-and-monitoring/rs/setversion_greaterthan_max_without_electionid.yml new file mode 100644 index 0000000000..3252e0f611 --- /dev/null +++ b/test/spec/server-discovery-and-monitoring/rs/setversion_greaterthan_max_without_electionid.yml @@ -0,0 +1,79 @@ +description: "setVersion that is greater than maxSetVersion is used if there is no electionId" + +uri: "mongodb://a/?replicaSet=rs" + +phases: [ + + # Primary A is discovered and tells us about B. + { + responses: [ + ["a:27017", { + ok: 1, + helloOk: true, + isWritablePrimary: true, + hosts: ["a:27017", "b:27017"], + setName: "rs", + setVersion: 1, + minWireVersion: 0, + maxWireVersion: 17 + }] + ], + + outcome: { + servers: { + "a:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 1, + electionId: + }, + "b:27017": { + type: "Unknown", + setName: , + electionId: + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs", + maxSetVersion: 1, + } + }, + + # B is elected, its setVersion is greater than our current maxSetVersion + # B is primary, A is marked Unknown + { + responses: [ + ["b:27017", { + ok: 1, + helloOk: true, + isWritablePrimary: true, + hosts: ["a:27017", "b:27017"], + setName: "rs", + setVersion: 2, + minWireVersion: 0, + maxWireVersion: 17 + }] + ], + + outcome: { + servers: { + "a:27017": { + type: "Unknown", + setName: , + electionId: + }, + "b:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 2, + electionId: + }, + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs", + maxSetVersion: 2, + } + } +] diff --git a/test/spec/server-discovery-and-monitoring/rs/setversion_without_electionid-pre-6.0.json b/test/spec/server-discovery-and-monitoring/rs/setversion_without_electionid-pre-6.0.json new file mode 100644 index 0000000000..c2e2fe5b9b --- /dev/null +++ b/test/spec/server-discovery-and-monitoring/rs/setversion_without_electionid-pre-6.0.json @@ -0,0 +1,84 @@ +{ + "description": "Pre 6.0 setVersion is ignored 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": 2, + "minWireVersion": 0, + "maxWireVersion": 6 + } + ] + ], + "outcome": { + "servers": { + "a:27017": { + "type": "RSPrimary", + "setName": "rs", + "setVersion": 2, + "electionId": null + }, + "b:27017": { + "type": "Unknown", + "setName": null, + "electionId": null + } + }, + "topologyType": "ReplicaSetWithPrimary", + "logicalSessionTimeoutMinutes": null, + "setName": "rs", + "maxSetVersion": 2 + } + }, + { + "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": 2 + } + } + ] +} diff --git a/test/spec/server-discovery-and-monitoring/rs/setversion_without_electionid-pre-6.0.yml b/test/spec/server-discovery-and-monitoring/rs/setversion_without_electionid-pre-6.0.yml new file mode 100644 index 0000000000..54bfa4d256 --- /dev/null +++ b/test/spec/server-discovery-and-monitoring/rs/setversion_without_electionid-pre-6.0.yml @@ -0,0 +1,79 @@ +description: "Pre 6.0 setVersion is ignored if there is no electionId" + +uri: "mongodb://a/?replicaSet=rs" + +phases: [ + + # Primary A is discovered and tells us about B. + { + responses: [ + ["a: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: "RSPrimary", + setName: "rs", + setVersion: 2 , + electionId: + }, + "b:27017": { + type: "Unknown", + setName: , + electionId: + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs", + maxSetVersion: 2, + } + }, + + # B is elected, its setVersion is older but we believe it anyway, because + # setVersion is only used in conjunction with electionId. + { + 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: , + electionId: + }, + "b:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 1, + electionId: + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs", + maxSetVersion: 2, + } + } +] diff --git a/test/spec/server-discovery-and-monitoring/rs/setversion_without_electionid.json b/test/spec/server-discovery-and-monitoring/rs/setversion_without_electionid.json index 2f68287f1d..256fafe108 100644 --- a/test/spec/server-discovery-and-monitoring/rs/setversion_without_electionid.json +++ b/test/spec/server-discovery-and-monitoring/rs/setversion_without_electionid.json @@ -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": [ { @@ -17,7 +17,7 @@ "setName": "rs", "setVersion": 2, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 17 } ] ], @@ -56,21 +56,21 @@ "setName": "rs", "setVersion": 1, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 17 } ] ], "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 } }, diff --git a/test/spec/server-discovery-and-monitoring/rs/setversion_without_electionid.yml b/test/spec/server-discovery-and-monitoring/rs/setversion_without_electionid.yml index 55c841f24b..04992929a1 100644 --- a/test/spec/server-discovery-and-monitoring/rs/setversion_without_electionid.yml +++ b/test/spec/server-discovery-and-monitoring/rs/setversion_without_electionid.yml @@ -1,4 +1,4 @@ -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" @@ -15,7 +15,7 @@ phases: [ setName: "rs", setVersion: 2, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 17 }] ], @@ -40,8 +40,7 @@ phases: [ } }, - # B is elected, its setVersion is older but we believe it anyway, because - # setVersion is only used in conjunction with electionId. + # B is elected, its setVersion is older so it is stale { responses: [ ["b:27017", { @@ -52,21 +51,21 @@ phases: [ setName: "rs", setVersion: 1, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 17 }] ], outcome: { servers: { "a:27017": { - type: "Unknown", - setName: , + type: "RSPrimary", + setName: "rs", + setVersion: 2 , electionId: }, "b:27017": { - type: "RSPrimary", - setName: "rs", - setVersion: 1, + type: "Unknown", + setName: , electionId: } }, diff --git a/test/spec/server-discovery-and-monitoring/rs/use_setversion_without_electionid-pre-6.0.json b/test/spec/server-discovery-and-monitoring/rs/use_setversion_without_electionid-pre-6.0.json new file mode 100644 index 0000000000..5c58b65614 --- /dev/null +++ b/test/spec/server-discovery-and-monitoring/rs/use_setversion_without_electionid-pre-6.0.json @@ -0,0 +1,138 @@ +{ + "description": "Pre 6.0 Record max setVersion, even from primary without 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, + "electionId": { + "$oid": "000000000000000000000001" + }, + "minWireVersion": 0, + "maxWireVersion": 6 + } + ] + ], + "outcome": { + "servers": { + "a:27017": { + "type": "RSPrimary", + "setName": "rs", + "setVersion": 1, + "electionId": { + "$oid": "000000000000000000000001" + } + }, + "b:27017": { + "type": "Unknown", + "setName": null, + "electionId": null + } + }, + "topologyType": "ReplicaSetWithPrimary", + "logicalSessionTimeoutMinutes": null, + "setName": "rs", + "maxSetVersion": 1, + "maxElectionId": { + "$oid": "000000000000000000000001" + } + } + }, + { + "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 + } + }, + "topologyType": "ReplicaSetWithPrimary", + "logicalSessionTimeoutMinutes": null, + "setName": "rs", + "maxSetVersion": 2, + "maxElectionId": { + "$oid": "000000000000000000000001" + } + } + }, + { + "responses": [ + [ + "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": "Unknown", + "setName": null, + "electionId": null + }, + "b:27017": { + "type": "RSPrimary", + "setName": "rs", + "setVersion": 2 + } + }, + "topologyType": "ReplicaSetWithPrimary", + "logicalSessionTimeoutMinutes": null, + "setName": "rs", + "maxSetVersion": 2, + "maxElectionId": { + "$oid": "000000000000000000000001" + } + } + } + ] +} diff --git a/test/spec/server-discovery-and-monitoring/rs/use_setversion_without_electionid-pre-6.0.yml b/test/spec/server-discovery-and-monitoring/rs/use_setversion_without_electionid-pre-6.0.yml new file mode 100644 index 0000000000..a0434eb45c --- /dev/null +++ b/test/spec/server-discovery-and-monitoring/rs/use_setversion_without_electionid-pre-6.0.yml @@ -0,0 +1,117 @@ +description: "Pre 6.0 Record max setVersion, even from primary without electionId" + +uri: "mongodb://a/?replicaSet=rs" + +phases: [ + + # Primary A has setVersion and electionId, tells us about B. + { + 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 + }] + ], + + outcome: { + servers: { + "a:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 1, + electionId: {"$oid": "000000000000000000000001"} + }, + "b:27017": { + type: "Unknown", + setName: , + electionId: + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs", + maxSetVersion: 1, + maxElectionId: {"$oid": "000000000000000000000001"}, + } + }, + + # Reconfig the set and elect B, it has a new setVersion but no electionId. + { + 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: , + electionId: + }, + "b:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 2 + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs", + maxSetVersion: 2, + maxElectionId: {"$oid": "000000000000000000000001"}, + } + }, + + # Delayed response from A, reporting its reelection. Its setVersion shows + # the election preceded B's so we ignore it. + { + responses: [ + ["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: "Unknown", + setName: , + electionId: + }, + "b:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 2 + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs", + maxSetVersion: 2, + maxElectionId: {"$oid": "000000000000000000000001"}, + } + } +] diff --git a/test/spec/server-discovery-and-monitoring/rs/use_setversion_without_electionid.json b/test/spec/server-discovery-and-monitoring/rs/use_setversion_without_electionid.json index 421ff57c8d..551f3e12c2 100644 --- a/test/spec/server-discovery-and-monitoring/rs/use_setversion_without_electionid.json +++ b/test/spec/server-discovery-and-monitoring/rs/use_setversion_without_electionid.json @@ -20,7 +20,7 @@ "$oid": "000000000000000000000001" }, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 17 } ] ], @@ -64,27 +64,30 @@ "setName": "rs", "setVersion": 2, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 17 } ] ], "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" } @@ -108,29 +111,32 @@ "$oid": "000000000000000000000002" }, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 17 } ] ], "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" } } } diff --git a/test/spec/server-discovery-and-monitoring/rs/use_setversion_without_electionid.yml b/test/spec/server-discovery-and-monitoring/rs/use_setversion_without_electionid.yml index 300f9d9304..68c88bc503 100644 --- a/test/spec/server-discovery-and-monitoring/rs/use_setversion_without_electionid.yml +++ b/test/spec/server-discovery-and-monitoring/rs/use_setversion_without_electionid.yml @@ -4,7 +4,7 @@ uri: "mongodb://a/?replicaSet=rs" phases: [ - # Primary A has setVersion and electionId, tells us about B. + # Primary A has electionId and setVersion, tells us about B. { responses: [ ["a:27017", { @@ -16,7 +16,7 @@ phases: [ setVersion: 1, electionId: {"$oid": "000000000000000000000001"}, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 17 }] ], @@ -42,7 +42,7 @@ phases: [ } }, - # Reconfig the set and elect B, it has a new setVersion but no electionId. + # Reconfig, B reports as primary, B is missing the electionId but reports setVersion { responses: [ ["b:27017", { @@ -53,33 +53,34 @@ phases: [ setName: "rs", setVersion: 2, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 17 }] ], outcome: { servers: { - "a:27017": { + "a:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 1, + electionId: { "$oid": "000000000000000000000001" } + }, + "b:27017": { type: "Unknown", setName: , electionId: - }, - "b:27017": { - type: "RSPrimary", - setName: "rs", - setVersion: 2 } }, topologyType: "ReplicaSetWithPrimary", logicalSessionTimeoutMinutes: null, setName: "rs", - maxSetVersion: 2, + maxSetVersion: 1, maxElectionId: {"$oid": "000000000000000000000001"}, } }, - # Delayed response from A, reporting its reelection. Its setVersion shows - # the election preceded B's so we ignore it. + # A reports as primary, A has been reelection (electionId greater than our recorded maxElectionId). + # A's setVersion is less than our maxSetVersion, but electionId takes precedence so B's primary claim is ignored { responses: [ ["a:27017", { @@ -91,27 +92,28 @@ phases: [ setVersion: 1, electionId: {"$oid": "000000000000000000000002"}, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 17 }] ], outcome: { servers: { "a:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 1, + electionId: {"$oid": "000000000000000000000002"} + }, + "b:27017":{ type: "Unknown", setName: , electionId: - }, - "b:27017": { - type: "RSPrimary", - setName: "rs", - setVersion: 2 } }, topologyType: "ReplicaSetWithPrimary", logicalSessionTimeoutMinutes: null, setName: "rs", - maxSetVersion: 2, - maxElectionId: {"$oid": "000000000000000000000001"}, + maxSetVersion: 1, + maxElectionId: {"$oid": "000000000000000000000002"}, } } ] diff --git a/test/unit/utils.test.ts b/test/unit/utils.test.ts index 2dd0a68730..ae6d91deb0 100644 --- a/test/unit/utils.test.ts +++ b/test/unit/utils.test.ts @@ -3,9 +3,10 @@ import { expect } from 'chai'; import { LEGACY_HELLO_COMMAND } from '../../src/constants'; import { MongoRuntimeError } from '../../src/error'; -import { Promise as PromiseProvider } from '../../src/index'; +import { ObjectId, Promise as PromiseProvider } from '../../src/index'; import { BufferPool, + compareObjectId, eachAsync, HostAddress, isHello, @@ -481,4 +482,48 @@ describe('driver utils', function () { }); }); }); + + describe('compareObjectId()', () => { + const table = [ + { oid1: null, oid2: null, result: 0 }, + { oid1: undefined, oid2: null, result: 0 }, + { oid1: null, oid2: undefined, result: 0 }, + { oid1: undefined, oid2: undefined, result: 0 }, + { oid1: new ObjectId('00'.repeat(12)), oid2: undefined, result: 1 }, + { oid1: new ObjectId('00'.repeat(12)), oid2: null, result: 1 }, + { oid1: undefined, oid2: new ObjectId('00'.repeat(12)), result: -1 }, + { oid1: null, oid2: new ObjectId('00'.repeat(12)), result: -1 }, + { oid1: new ObjectId('00'.repeat(12)), oid2: new ObjectId('00'.repeat(12)), result: 0 }, + { + oid1: new ObjectId('00'.repeat(11) + '01'), + oid2: new ObjectId('00'.repeat(12)), + result: 1 + }, + { + oid1: new ObjectId('00'.repeat(12)), + oid2: new ObjectId('00'.repeat(11) + '01'), + result: -1 + }, + { + oid1: 2, + oid2: 1, + result: 'throws' + } + ]; + + for (const { oid1, oid2, result } of table) { + if (result === 'throws') { + it('passing non-objectId values throw', () => + // @ts-expect-error: Passing bad values to ensure thrown error + expect(() => compareObjectId(oid1, oid2)).to.throw()); + continue; + } + + const title = `comparing ${oid1} to ${oid2} returns ${ + result === 0 ? 'equal' : result === -1 ? 'less than' : 'greater than' + }`; + // @ts-expect-error: not narrowed based on numeric result, but these values are correct + it(title, () => expect(compareObjectId(oid1, oid2)).to.equal(result)); + } + }); });