Skip to content

Commit 7dfea9a

Browse files
authored
SWIFT-1309, SWIFT-1318, SWIFT-1241, SWIFT-1323, SWIFT-1324 Support testing against a load balancer locally (#666)
1 parent 11c441d commit 7dfea9a

File tree

13 files changed

+270
-114
lines changed

13 files changed

+270
-114
lines changed

Sources/CLibMongoC/include/CLibMongoC_mongoc-server-description.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ MONGOC_EXPORT (int32_t)
6666
mongoc_server_description_compressor_id (
6767
const mongoc_server_description_t *description);
6868

69+
/* mongoc_global_mock_service_id is only used for testing. The test runner sets
70+
* this to true when testing against a load balanced deployment to mock the
71+
* presence of a serviceId field in the "hello" response. The purpose of this is
72+
* further described in the Load Balancer test README. */
73+
extern bool mongoc_global_mock_service_id;
74+
6975
BSON_END_DECLS
7076

7177
#endif

Sources/CLibMongoC/mongoc/mongoc-server-description-private.h

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -200,10 +200,4 @@ bool
200200
mongoc_server_description_has_service_id (
201201
const mongoc_server_description_t *description);
202202

203-
/* mongoc_global_mock_service_id is only used for testing. The test runner sets
204-
* this to true when testing against a load balanced deployment to mock the
205-
* presence of a serviceId field in the "hello" response. The purpose of this is
206-
* further described in the Load Balancer test README. */
207-
extern bool mongoc_global_mock_service_id;
208-
209203
#endif

Sources/MongoSwift/SDAM.swift

Lines changed: 95 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import CLibMongoC
22
import Foundation
33

44
/// A struct representing a network address, consisting of a host and port.
5-
public struct ServerAddress: Equatable {
5+
public struct ServerAddress: Equatable, Hashable {
66
/// The hostname or IP address.
77
public let host: String
88

@@ -67,25 +67,68 @@ private struct HelloResponse: Decodable {
6767
/// A struct describing a mongod or mongos process.
6868
public struct ServerDescription {
6969
/// The possible types for a server.
70-
public enum ServerType: String, Equatable {
70+
public struct ServerType: RawRepresentable, Equatable {
7171
/// A standalone mongod server.
72-
case standalone = "Standalone"
72+
public static let standalone = ServerType(.standalone)
73+
7374
/// A router to a sharded cluster, i.e. a mongos server.
74-
case mongos = "Mongos"
75+
public static let mongos = ServerType(.mongos)
76+
7577
/// A replica set member which is not yet checked, but another member thinks it is the primary.
76-
case possiblePrimary = "PossiblePrimary"
78+
public static let possiblePrimary = ServerType(.possiblePrimary)
79+
7780
/// A replica set primary.
78-
case rsPrimary = "RSPrimary"
81+
public static let rsPrimary = ServerType(.rsPrimary)
82+
7983
/// A replica set secondary.
80-
case rsSecondary = "RSSecondary"
84+
public static let rsSecondary = ServerType(.rsSecondary)
85+
8186
/// A replica set arbiter.
82-
case rsArbiter = "RSArbiter"
87+
public static let rsArbiter = ServerType(.rsArbiter)
88+
8389
/// A replica set member that is none of the other types (a passive, for example).
84-
case rsOther = "RSOther"
90+
public static let rsOther = ServerType(.rsOther)
91+
8592
/// A replica set member that does not report a set name or a hosts list.
86-
case rsGhost = "RSGhost"
93+
public static let rsGhost = ServerType(.rsGhost)
94+
8795
/// A server type that is not yet known.
88-
case unknown = "Unknown"
96+
public static let unknown = ServerType(.unknown)
97+
98+
/// A load balancer.
99+
public static let loadBalancer = ServerType(.loadBalancer)
100+
101+
/// Internal representation of server type. If enums could be marked non-exhaustive in Swift, this would be the
102+
/// public representation too.
103+
private enum _ServerType: String, Equatable {
104+
case standalone = "Standalone"
105+
case mongos = "Mongos"
106+
case possiblePrimary = "PossiblePrimary"
107+
case rsPrimary = "RSPrimary"
108+
case rsSecondary = "RSSecondary"
109+
case rsArbiter = "RSArbiter"
110+
case rsOther = "RSOther"
111+
case rsGhost = "RSGhost"
112+
case unknown = "Unknown"
113+
case loadBalancer = "LoadBalancer"
114+
}
115+
116+
private let _serverType: _ServerType
117+
118+
private init(_ _type: _ServerType) {
119+
self._serverType = _type
120+
}
121+
122+
public var rawValue: String {
123+
self._serverType.rawValue
124+
}
125+
126+
public init?(rawValue: String) {
127+
guard let _type = _ServerType(rawValue: rawValue) else {
128+
return nil
129+
}
130+
self._serverType = _type
131+
}
89132
}
90133

91134
/// The hostname or IP and the port number that the client connects to. Note that this is not the "me" field in the
@@ -206,17 +249,52 @@ extension ServerDescription: Equatable {
206249
/// which servers are up, what type of servers they are, which is primary, and so on.
207250
public struct TopologyDescription: Equatable {
208251
/// The possible types for a topology.
209-
public enum TopologyType: String, Equatable {
252+
public struct TopologyType: RawRepresentable, Equatable {
210253
/// A single mongod server.
211-
case single = "Single"
254+
public static let single = TopologyType(.single)
255+
212256
/// A replica set with no primary.
213-
case replicaSetNoPrimary = "ReplicaSetNoPrimary"
257+
public static let replicaSetNoPrimary = TopologyType(.replicaSetNoPrimary)
258+
214259
/// A replica set with a primary.
215-
case replicaSetWithPrimary = "ReplicaSetWithPrimary"
260+
public static let replicaSetWithPrimary = TopologyType(.replicaSetWithPrimary)
261+
216262
/// Sharded topology.
217-
case sharded = "Sharded"
263+
public static let sharded = TopologyType(.sharded)
264+
218265
/// A topology whose type is not yet known.
219-
case unknown = "Unknown"
266+
public static let unknown = TopologyType(.unknown)
267+
268+
/// A topology with a load balancer in front.
269+
public static let loadBalanced = TopologyType(.loadBalanced)
270+
271+
/// Internal representation of topology type. If enums could be marked non-exhaustive in Swift, this would be
272+
/// the public representation too.
273+
private enum _TopologyType: String, Equatable {
274+
case single = "Single"
275+
case replicaSetNoPrimary = "ReplicaSetNoPrimary"
276+
case replicaSetWithPrimary = "ReplicaSetWithPrimary"
277+
case sharded = "Sharded"
278+
case unknown = "Unknown"
279+
case loadBalanced = "LoadBalanced"
280+
}
281+
282+
private let _topologyType: _TopologyType
283+
284+
private init(_ _type: _TopologyType) {
285+
self._topologyType = _type
286+
}
287+
288+
public var rawValue: String {
289+
self._topologyType.rawValue
290+
}
291+
292+
public init?(rawValue: String) {
293+
guard let _type = _TopologyType(rawValue: rawValue) else {
294+
return nil
295+
}
296+
self._topologyType = _type
297+
}
220298
}
221299

222300
/// The type of this topology.

Sources/TestsCommon/CommonTestUtils.swift

Lines changed: 82 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,29 @@ open class MongoSwiftTestCase: XCTestCase {
4242
/// will return a default of "mongodb://127.0.0.1/". If singleMongos is true and this is a sharded topology, will
4343
/// edit $MONGODB_URI as needed so that it only contains a single host.
4444
public static func getConnectionString(singleMongos: Bool = true) -> ConnectionString {
45-
// we only need to manipulate the URI if singleMongos is requested and the topology is sharded.
46-
guard singleMongos && MongoSwiftTestCase.topologyType == .sharded else {
45+
switch (MongoSwiftTestCase.topologyType, singleMongos) {
46+
case (.sharded, true):
47+
let hosts = self.getHosts()
48+
var output = Self.uri
49+
// remove all but the first host so we connect to a single mongos.
50+
for host in hosts[1...] {
51+
output.removeSubstring(",\(host.description)")
52+
}
53+
return try! ConnectionString(output)
54+
case (.loadBalanced, true):
55+
guard let uri = Self.singleMongosLoadBalancedURI else {
56+
fatalError("Missing SINGLE_MONGOS_LB_URI environment variable")
57+
}
58+
return try! ConnectionString(uri)
59+
case (.loadBalanced, false):
60+
guard let uri = Self.multipleMongosLoadBalancedURI else {
61+
fatalError("Missing MULTIPLE_MONGOS_LB_URI environment variable")
62+
}
63+
return try! ConnectionString(uri)
64+
default:
65+
// just return as-is.
4766
return try! ConnectionString(Self.uri)
4867
}
49-
50-
let hosts = self.getHosts()
51-
var output = Self.uri
52-
// remove all but the first host so we connect to a single mongos.
53-
for host in hosts[1...] {
54-
output.removeSubstring(",\(host.description)")
55-
}
56-
return try! ConnectionString(output)
5768
}
5869

5970
/// Get a connection string for the specified host only.
@@ -116,6 +127,14 @@ open class MongoSwiftTestCase: XCTestCase {
116127
return uri
117128
}
118129

130+
public static var singleMongosLoadBalancedURI: String? {
131+
ProcessInfo.processInfo.environment["SINGLE_MONGOS_LB_URI"]
132+
}
133+
134+
public static var multipleMongosLoadBalancedURI: String? {
135+
ProcessInfo.processInfo.environment["MULTIPLE_MONGOS_LB_URI"]
136+
}
137+
119138
/// Indicates that we are running the tests with SSL enabled, determined by the environment variable $SSL.
120139
public static var ssl: Bool {
121140
ProcessInfo.processInfo.environment["SSL"] == "ssl"
@@ -168,7 +187,7 @@ public enum TestTopologyConfiguration: String, Decodable {
168187
/// A standalone server.
169188
case single
170189
/// A load balancer.
171-
case loadBalancer = "load-balanced"
190+
case loadBalanced = "load-balanced"
172191

173192
/// Returns a Bool indicating whether this topology is either sharded configuration.
174193
public var isSharded: Bool {
@@ -184,6 +203,9 @@ public enum TestTopologyConfiguration: String, Decodable {
184203
helloReply["isreplicaset"] != true
185204
{
186205
self = .single
206+
// TODO: SWIFT-1319: eventually, we should be able to just check for serviceId here.
207+
} else if MongoSwiftTestCase.topologyType == .loadBalanced {
208+
self = .loadBalanced
187209
} else if helloReply["msg"] == "isdbgrid" {
188210
// Serverless proxy reports as a mongos but presents no shards
189211
guard !shards.isEmpty || MongoSwiftTestCase.serverless else {
@@ -509,6 +531,8 @@ extension TopologyDescription.TopologyType {
509531
self = .sharded
510532
case "replicaset", "replica_set":
511533
self = .replicaSetWithPrimary
534+
case "loadbalanced", "load_balanced":
535+
self = .loadBalanced
512536
default:
513537
self = .single
514538
}
@@ -680,3 +704,50 @@ extension InsertManyResult {
680704
InsertManyResult(from: result)
681705
}
682706
}
707+
708+
// TODO: SWIFT-1319 Remove this method and usages of it.
709+
/// Sets the libmongoc global variable indicating whether to include mock serviceIds in hello responses to the
710+
/// provided value. This is necessary for load balancer testing until SERVER-58500 is complete.
711+
public func setMockServiceId(to value: Bool) {
712+
mongoc_global_mock_service_id = value
713+
}
714+
715+
/// Resolves programmatically provided client options with those specified via environment variables.
716+
public func resolveClientOptions(_ options: MongoClientOptions? = nil) -> MongoClientOptions {
717+
var opts = options ?? MongoClientOptions()
718+
// if SSL is on and custom TLS options were not provided, enable them
719+
if MongoSwiftTestCase.ssl {
720+
opts.tls = true
721+
if let caPath = MongoSwiftTestCase.sslCAFilePath {
722+
opts.tlsCAFile = URL(string: caPath)
723+
}
724+
if let certPath = MongoSwiftTestCase.sslPEMKeyFilePath {
725+
opts.tlsCertificateKeyFile = URL(string: certPath)
726+
}
727+
}
728+
if let apiVersion = MongoSwiftTestCase.apiVersion {
729+
if opts.serverAPI == nil {
730+
opts.serverAPI = MongoServerAPI(version: apiVersion)
731+
} else {
732+
opts.serverAPI!.version = apiVersion
733+
}
734+
}
735+
736+
if MongoSwiftTestCase.auth {
737+
if let scramUser = MongoSwiftTestCase.scramUser, let scramPass = MongoSwiftTestCase.scramPassword {
738+
opts.credential = MongoCredential(username: scramUser, password: scramPass)
739+
}
740+
}
741+
742+
// serverless tests are required to use compression.
743+
if MongoSwiftTestCase.serverless {
744+
opts.compressors = [.zlib]
745+
}
746+
747+
// TODO: SWIFT-1319 Remove this.
748+
if MongoSwiftTestCase.topologyType == .loadBalanced {
749+
setMockServiceId(to: true)
750+
}
751+
752+
return opts
753+
}

0 commit comments

Comments
 (0)