Skip to content

Commit 0d5a7fb

Browse files
authored
SWIFT-1329 Use a client per mongos in unified test runner (#667)
1 parent 7dfea9a commit 0d5a7fb

File tree

7 files changed

+78
-114
lines changed

7 files changed

+78
-114
lines changed

Sources/MongoSwift/Operations/RunCommandOperation.swift

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -35,51 +35,19 @@ internal struct RunCommandOperation: Operation {
3535
private let database: MongoDatabase
3636
private let command: BSONDocument
3737
private let options: RunCommandOptions?
38-
private let serverAddress: ServerAddress?
3938

4039
internal init(
4140
database: MongoDatabase,
4241
command: BSONDocument,
43-
options: RunCommandOptions?,
44-
serverAddress: ServerAddress? = nil
42+
options: RunCommandOptions?
4543
) {
4644
self.database = database
4745
self.command = command
4846
self.options = options
49-
self.serverAddress = serverAddress
5047
}
5148

5249
internal func execute(using connection: Connection, session: ClientSession?) throws -> BSONDocument {
53-
let serverId: UInt32? = try self.serverAddress.map { address in
54-
let id: UInt32? = connection.withMongocConnection { connection in
55-
var numServers = 0
56-
let sds = mongoc_client_get_server_descriptions(connection, &numServers)
57-
defer { mongoc_server_descriptions_destroy_all(sds, numServers) }
58-
59-
guard numServers > 0 else {
60-
return nil
61-
}
62-
63-
let buffer = UnsafeBufferPointer(start: sds, count: numServers)
64-
// the buffer is documented as always containing non-nil pointers (if non-empty).
65-
// swiftlint:disable:next force_unwrapping
66-
let servers = Array(buffer).map { ServerDescription($0!) }
67-
return servers.first { $0.address == address }?.serverId
68-
}
69-
70-
guard let out = id else {
71-
throw MongoError.ServerSelectionError(message: "No known host with address \(address)")
72-
}
73-
74-
return out
75-
}
76-
77-
var opts = try encodeOptions(options: self.options, session: session)
78-
if let id = serverId {
79-
opts = opts ?? [:]
80-
opts?["serverId"] = .int64(Int64(id))
81-
}
82-
50+
let opts = try encodeOptions(options: self.options, session: session)
8351
return try self.database.withMongocDatabase(from: connection) { dbPtr in
8452
try ReadPreference.withOptionalMongocReadPreference(from: self.options?.readPreference) { rpPtr in
8553
try runMongocCommandWithReply(

Sources/TestsCommon/SpecTestUtils.swift

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,6 @@ extension MongoSwiftTestCase {
2020
}
2121
}
2222

23-
extension MongoDatabase {
24-
@discardableResult
25-
public func runCommand(
26-
_ command: BSONDocument,
27-
on server: ServerAddress,
28-
options: RunCommandOptions? = nil,
29-
session: ClientSession? = nil
30-
) -> EventLoopFuture<BSONDocument> {
31-
let operation = RunCommandOperation(database: self, command: command, options: options, serverAddress: server)
32-
return self._client.operationExecutor.execute(operation, client: self._client, on: nil, session: session)
33-
}
34-
}
35-
3623
/// Given a spec folder name (e.g. "crud") and optionally a subdirectory name for a folder (e.g. "read") retrieves an
3724
/// array of [(filename, file decoded to type T)].
3825
public func retrieveSpecTestFiles<T: Decodable>(

Tests/MongoSwiftSyncTests/SpecTestRunner/FailPoint.swift

Lines changed: 10 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,19 @@ extension FailPointConfigured {
1414
/// Sets the active fail point to the provided fail point and enables it.
1515
internal mutating func activateFailPoint(
1616
_ failPoint: FailPoint,
17-
using client: MongoClient,
18-
on serverAddress: ServerAddress? = nil
17+
using client: MongoClient
1918
) throws {
2019
self.activeFailPoint = failPoint
21-
try self.activeFailPoint?.enable(using: client, on: serverAddress)
22-
self.targetedHost = serverAddress
20+
try self.activeFailPoint?.enable(using: client)
2321
}
2422

2523
/// If a fail point is active, it is disabled and cleared.
2624
internal mutating func disableActiveFailPoint(using client: MongoClient) {
2725
guard let failPoint = self.activeFailPoint else {
2826
return
2927
}
30-
failPoint.disable(using: client, on: self.targetedHost)
28+
failPoint.disable(using: client)
3129
self.activeFailPoint = nil
32-
self.targetedHost = nil
3330
}
3431
}
3532

@@ -39,17 +36,14 @@ class FailPointGuard {
3936
let failPoint: FailPoint
4037
/// Client to use when disabling the failpoint.
4138
let client: MongoClient
42-
/// Optional server address to disable the failpoint on.
43-
let serverAddress: ServerAddress?
4439

45-
init(failPoint: FailPoint, client: MongoClient, serverAddress: ServerAddress?) {
40+
init(failPoint: FailPoint, client: MongoClient) {
4641
self.failPoint = failPoint
4742
self.client = client
48-
self.serverAddress = serverAddress
4943
}
5044

5145
deinit {
52-
self.failPoint.disable(using: self.client, on: self.serverAddress)
46+
self.failPoint.disable(using: self.client)
5347
}
5448
}
5549

@@ -90,33 +84,27 @@ internal struct FailPoint: Decodable {
9084

9185
internal func enable(
9286
using client: MongoClient,
93-
on serverAddress: ServerAddress? = nil,
9487
options: RunCommandOptions? = nil
9588
) throws {
96-
if let address = serverAddress {
97-
try client.db("admin").runCommand(self.failPoint, on: address, options: options)
98-
} else {
99-
try client.db("admin").runCommand(self.failPoint, options: options)
100-
}
89+
try client.db("admin").runCommand(self.failPoint, options: options)
10190
}
10291

10392
/// Enables the failpoint, and returns a `FailPointGuard` which will automatically disable the failpoint
10493
/// upon deinitialization.
10594
internal func enableWithGuard(
10695
using client: MongoClient,
107-
on serverAddress: ServerAddress? = nil,
10896
options: RunCommandOptions? = nil
10997
) throws -> FailPointGuard {
110-
try self.enable(using: client, on: serverAddress, options: options)
111-
return FailPointGuard(failPoint: self, client: client, serverAddress: serverAddress)
98+
try self.enable(using: client, options: options)
99+
return FailPointGuard(failPoint: self, client: client)
112100
}
113101

114102
internal func enable() throws {
115103
let client = try MongoClient.makeTestClient()
116104
try self.enable(using: client)
117105
}
118106

119-
internal func disable(using client: MongoClient? = nil, on address: ServerAddress? = nil) {
107+
internal func disable(using client: MongoClient? = nil) {
120108
do {
121109
let clientToUse: MongoClient
122110
if let client = client {
@@ -126,15 +114,7 @@ internal struct FailPoint: Decodable {
126114
}
127115

128116
let command: BSONDocument = ["configureFailPoint": .string(self.name), "mode": "off"]
129-
130-
if let addr = address {
131-
try clientToUse.db("admin").runCommand(command, on: addr)
132-
} else {
133-
try clientToUse.db("admin").runCommand(command)
134-
}
135-
} catch _ as MongoError.ServerSelectionError {
136-
// this often means the server that the failpoint was set against was marked as unknown
137-
// due to the failpoint firing, so we just ignore
117+
try clientToUse.db("admin").runCommand(command)
138118
} catch {
139119
print("Failed to disable failpoint: \(error)")
140120
}

Tests/MongoSwiftSyncTests/SyncTestUtils.swift

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -154,18 +154,6 @@ internal func captureCommandEvents(
154154
return monitor.events(withEventTypes: eventTypes, withNames: commandNames)
155155
}
156156

157-
extension MongoDatabase {
158-
@discardableResult
159-
public func runCommand(
160-
_ command: BSONDocument,
161-
on server: ServerAddress,
162-
options: RunCommandOptions? = nil,
163-
session: MongoSwiftSync.ClientSession? = nil
164-
) throws -> BSONDocument {
165-
try self.asyncDB.runCommand(command, on: server, options: options, session: session?.asyncSession).wait()
166-
}
167-
}
168-
169157
extension MongoSwiftSync.MongoCollection {
170158
public var _client: MongoSwiftSync.MongoClient {
171159
self.client

Tests/MongoSwiftSyncTests/UnifiedTestRunner/Context.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ class Context {
1111
/// Fail points that have been set during test execution and should be disabled on completion.
1212
var enabledFailPoints: [FailPointGuard] = []
1313

14-
let internalClient: MongoClient
14+
let internalClient: UnifiedTestRunner.InternalClient
1515

16-
init(path: [String], entities: EntityMap, internalClient: MongoClient) {
16+
init(path: [String], entities: EntityMap, internalClient: UnifiedTestRunner.InternalClient) {
1717
self.path = path
1818
self.entities = entities
1919
self.internalClient = internalClient

Tests/MongoSwiftSyncTests/UnifiedTestRunner/SpecialTestOperations.swift

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import MongoSwiftSync
44
@testable import class MongoSwiftSync.ClientSession
55
import Nimble
6+
import TestsCommon
67

78
struct UnifiedFailPoint: UnifiedOperationProtocol {
89
/// The failpoint to set.
@@ -36,7 +37,7 @@ struct UnifiedAssertCollectionExists: UnifiedOperationProtocol {
3637
}
3738

3839
func execute(on _: UnifiedOperation.Object, context: Context) throws -> UnifiedOperationResult {
39-
let db = context.internalClient.db(self.databaseName)
40+
let db = context.internalClient.anyClient.db(self.databaseName)
4041
expect(try db.listCollectionNames()).to(
4142
contain(self.collectionName),
4243
description: "Expected db \(self.databaseName) to contain collection \(self.collectionName)." +
@@ -58,7 +59,7 @@ struct UnifiedAssertCollectionNotExists: UnifiedOperationProtocol {
5859
}
5960

6061
func execute(on _: UnifiedOperation.Object, context: Context) throws -> UnifiedOperationResult {
61-
let db = context.internalClient.db(self.databaseName)
62+
let db = context.internalClient.anyClient.db(self.databaseName)
6263
expect(try db.listCollectionNames()).toNot(
6364
contain(self.collectionName),
6465
description: "Expected db \(self.databaseName) to not contain collection \(self.collectionName)." +
@@ -83,7 +84,7 @@ struct UnifiedAssertIndexExists: UnifiedOperationProtocol {
8384
}
8485

8586
func execute(on _: UnifiedOperation.Object, context: Context) throws -> UnifiedOperationResult {
86-
let collection = context.internalClient.db(self.databaseName).collection(self.collectionName)
87+
let collection = context.internalClient.anyClient.db(self.databaseName).collection(self.collectionName)
8788
expect(try collection.listIndexNames()).to(
8889
contain(self.indexName),
8990
description: "Expected collection \(collection.namespace) to have index \(self.indexName)."
@@ -108,7 +109,7 @@ struct UnifiedAssertIndexNotExists: UnifiedOperationProtocol {
108109
}
109110

110111
func execute(on _: UnifiedOperation.Object, context: Context) throws -> UnifiedOperationResult {
111-
let collection = context.internalClient.db(self.databaseName).collection(self.collectionName)
112+
let collection = context.internalClient.anyClient.db(self.databaseName).collection(self.collectionName)
112113
expect(try collection.listIndexNames()).toNot(
113114
contain(self.indexName),
114115
description: "Expected collection \(collection.namespace) to not have index \(self.indexName)."
@@ -276,11 +277,11 @@ struct UnifiedTargetedFailPoint: UnifiedOperationProtocol {
276277
beNil(),
277278
description: "Session \(self.session) unexpectedly not pinned to a mongos. Path: \(context.path)"
278279
)
279-
let pinnedMongos = session.pinnedServerAddress!
280-
// The test runner SHOULD use the client entity associated with the session to execute the configureFailPoint
281-
// command.
282-
let client = session.client
283-
let fpGuard = try self.failPoint.enableWithGuard(using: client, on: pinnedMongos)
280+
let mongosClients = try context.internalClient.asMongosClients()
281+
guard let clientForPinnedMongos = mongosClients[session.pinnedServerAddress!] else {
282+
throw TestError(message: "Unexpectedly missing client for mongos \(session.pinnedServerAddress!)")
283+
}
284+
let fpGuard = try self.failPoint.enableWithGuard(using: clientForPinnedMongos)
284285
// add to context's list of enabled failpoints to ensure we disable it later.
285286
context.enabledFailPoints.append(fpGuard)
286287
return .none

Tests/MongoSwiftSyncTests/UnifiedTestRunner/UnifiedTestRunner.swift

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,36 @@ import Nimble
33
import TestsCommon
44

55
struct UnifiedTestRunner {
6-
let internalClient: MongoClient
6+
enum InternalClient {
7+
/// For all topologies besides sharded, we use a single client.
8+
case single(MongoClient)
9+
/// For sharded topologies, we often need to target commands to particular mongoses, so we use a separate
10+
/// client for each host we're connecting to.
11+
case mongosClients([ServerAddress: MongoClient])
12+
13+
/// Returns an internal client; for usage in situations where any client connected to the topology will do -
14+
/// that is, if the topology is sharded, we do not care about targeting a particular mongos.
15+
var anyClient: MongoClient {
16+
switch self {
17+
case let .single(c):
18+
return c
19+
case let .mongosClients(clientMap):
20+
return clientMap.first!.1
21+
}
22+
}
23+
24+
/// If the internal client is a map of per-mongos clients, returns that map; otherwise throws an erorr.
25+
func asMongosClients() throws -> [ServerAddress: MongoClient] {
26+
guard case let .mongosClients(clientMap) = self else {
27+
throw TestError(
28+
message: "Runner unexpectedly did not create per-mongos clients"
29+
)
30+
}
31+
return clientMap
32+
}
33+
}
34+
35+
let internalClient: InternalClient
736
let serverVersion: ServerVersion
837
let topologyType: TestTopologyConfiguration
938
let serverParameters: BSONDocument
@@ -12,11 +41,22 @@ struct UnifiedTestRunner {
1241
static let maxSchemaVersion = SchemaVersion(rawValue: "1.5.0")!
1342

1443
init() throws {
15-
let connStr = MongoSwiftTestCase.getConnectionString(singleMongos: false).toString()
16-
self.internalClient = try MongoClient.makeTestClient(connStr)
17-
self.serverVersion = try self.internalClient.serverVersion()
18-
self.topologyType = try self.internalClient.topologyType()
19-
self.serverParameters = try self.internalClient.serverParameters()
44+
switch MongoSwiftTestCase.topologyType {
45+
case .sharded:
46+
var mongosClients = [ServerAddress: MongoClient]()
47+
for host in MongoSwiftTestCase.getHosts() {
48+
let connString = MongoSwiftTestCase.getConnectionString(forHost: host)
49+
let client = try MongoClient.makeTestClient(connString.toString())
50+
mongosClients[host] = client
51+
}
52+
self.internalClient = .mongosClients(mongosClients)
53+
default:
54+
let client = try MongoClient.makeTestClient()
55+
self.internalClient = .single(client)
56+
}
57+
self.serverVersion = try self.internalClient.anyClient.serverVersion()
58+
self.topologyType = try self.internalClient.anyClient.topologyType()
59+
self.serverParameters = try self.internalClient.anyClient.serverParameters()
2060
}
2161

2262
func terminateOpenTransactions() throws {
@@ -32,12 +72,12 @@ struct UnifiedTestRunner {
3272
// SERVER-38335.
3373
do {
3474
let opts = RunCommandOptions(readPreference: .primary)
35-
_ = try self.internalClient.db("admin").runCommand(["killAllSessions": []], options: opts)
75+
_ = try self.internalClient.anyClient.db("admin").runCommand(["killAllSessions": []], options: opts)
3676
} catch let commandError as MongoError.CommandError where commandError.code == 11601 {}
3777
case .sharded, .shardedReplicaSet:
38-
for address in MongoSwiftTestCase.getHosts() {
78+
for (_, client) in try self.internalClient.asMongosClients() {
3979
do {
40-
_ = try self.internalClient.db("admin").runCommand(["killAllSessions": []], on: address)
80+
_ = try client.db("admin").runCommand(["killAllSessions": []])
4181
} catch let commandError as MongoError.CommandError where commandError.code == 11601 {
4282
continue
4383
}
@@ -113,7 +153,7 @@ struct UnifiedTestRunner {
113153
// The test runner MUST use the internal MongoClient for these operations.
114154
if let initialData = file.initialData {
115155
for collData in initialData {
116-
let db = self.internalClient.db(collData.databaseName)
156+
let db = self.internalClient.anyClient.db(collData.databaseName)
117157
let collOpts = MongoCollectionOptions(writeConcern: .majority)
118158
let coll = db.collection(collData.collectionName, options: collOpts)
119159
try coll.drop()
@@ -141,11 +181,10 @@ struct UnifiedTestRunner {
141181
// the implementation, test runners MAY execute distinct before every test.
142182
if self.topologyType.isSharded && !MongoSwiftTestCase.serverless {
143183
let collEntities = context.entities.values.compactMap { try? $0.asCollection() }
144-
for address in MongoSwiftTestCase.getHosts() {
184+
for (_, client) in try self.internalClient.asMongosClients() {
145185
for entity in collEntities {
146-
_ = try self.internalClient.db(entity.namespace.db).runCommand(
147-
["distinct": .string(entity.name), "key": "_id"],
148-
on: address
186+
_ = try client.db(entity.namespace.db).runCommand(
187+
["distinct": .string(entity.name), "key": "_id"]
149188
)
150189
}
151190
}
@@ -188,6 +227,7 @@ struct UnifiedTestRunner {
188227
if let expectedOutcome = test.outcome {
189228
for collectionData in expectedOutcome {
190229
let collection = self.internalClient
230+
.anyClient
191231
.db(collectionData.databaseName)
192232
.collection(collectionData.collectionName)
193233
let opts = FindOptions(

0 commit comments

Comments
 (0)