Skip to content

Commit 1738b63

Browse files
committed
Add more tests
Signed-off-by: Adam Fowler <adamfowler71@gmail.com>
1 parent 7b42e7b commit 1738b63

2 files changed

Lines changed: 149 additions & 18 deletions

File tree

Sources/Valkey/Sentinel/ValkeySentinelClient.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ package final class ValkeySentinelClient: Sendable {
137137
}
138138

139139
// get sentinel clients from statemachine
140-
private func getSentinelClients() async throws -> [ValkeyNodeClient] {
140+
package func getSentinelClients() async throws -> [ValkeyNodeClient] {
141141
try await withCheckedThrowingContinuation { (cont: CheckedContinuation<Void, any Error>) in
142142
switch self.stateMachine.withLock({ $0.waitForDiscovery(cont) }) {
143143
case .complete:

Tests/ValkeyTests/ValkeySentinelTests.swift

Lines changed: 148 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,6 @@ actor TestSentinelTopology {
1919
case replica
2020
case sentinel
2121
}
22-
enum Flag: Substring {
23-
case primary = "master"
24-
case replica = "slave"
25-
case sentinel
26-
case disconnected
27-
case s_down
28-
// case o_down
29-
case master_down
30-
}
3122
struct Address: Hashable, CustomStringConvertible {
3223
let host: String
3324
let port: Int
@@ -36,7 +27,7 @@ actor TestSentinelTopology {
3627
}
3728
struct Node {
3829
var flags: Set<SentinelInstance.Flag>
39-
var address: Address
30+
let address: Address
4031

4132
var respValue: RESP3Value {
4233
.map([
@@ -51,12 +42,28 @@ actor TestSentinelTopology {
5142
var primary: Node
5243
var replicas: [Node]
5344
var keyValueMap: [String: String]
45+
46+
mutating func editNode(address: Address, operation: (inout Node) -> Void) {
47+
for index in self.sentinels.indices {
48+
if self.sentinels[index].address == address {
49+
operation(&self.sentinels[index])
50+
}
51+
}
52+
if self.primary.address == address {
53+
operation(&self.primary)
54+
}
55+
for index in self.replicas.indices {
56+
if self.replicas[index].address == address {
57+
operation(&self.replicas[index])
58+
}
59+
}
60+
}
5461
}
5562

5663
var namedPrimaries: [String: Topology]
57-
private(set) var addressMap: [Address: (role: Role, name: String)]
64+
private(set) var addressMap: [Address: (role: Role, name: String, flags: Set<SentinelInstance.Flag>)]
5865

59-
init(primaries: [String: (sentinels: [Address], primary: Address, replicas: [Address])]) async {
66+
init(_ primaries: [String: (sentinels: [Address], primary: Address, replicas: [Address])]) async {
6067
self.namedPrimaries = primaries.mapValues {
6168
.init(
6269
sentinels: $0.sentinels.map { .init(flags: [.sentinel], address: $0) },
@@ -73,15 +80,23 @@ actor TestSentinelTopology {
7380
self.addressMap = [:]
7481
for (key, value) in namedPrimaries {
7582
for sentinel in value.sentinels {
76-
self.addressMap[sentinel.address] = (role: .sentinel, name: key)
83+
self.addressMap[sentinel.address] = (role: .sentinel, name: key, flags: sentinel.flags)
7784
}
78-
self.addressMap[value.primary.address] = (role: .primary, name: key)
85+
self.addressMap[value.primary.address] = (role: .primary, name: key, flags: value.primary.flags)
7986
for replica in value.replicas {
80-
self.addressMap[replica.address] = (role: .replica, name: key)
87+
self.addressMap[replica.address] = (role: .replica, name: key, flags: replica.flags)
8188
}
8289
}
8390
}
8491

92+
func shutdownNode(address: Address) {
93+
guard let details = self.addressMap[address] else { return }
94+
self.namedPrimaries[details.name]?.editNode(address: address) { node in
95+
node.flags.insert(.disconnected)
96+
}
97+
self.updateAddressMap()
98+
}
99+
85100
func setKey(primary: String, key: String, value: String) {
86101
self.namedPrimaries[primary]?.keyValueMap[key] = value
87102
}
@@ -102,6 +117,7 @@ actor TestSentinelTopology {
102117
/// Add Valkey node to mock connections
103118
func addNode(to mockConnections: MockServerConnections, address: Address, logger: Logger) async {
104119
guard let addressDetails = self.addressMap[address] else { return }
120+
guard addressDetails.flags.intersection([.disconnected, .s_down]).isEmpty else { return }
105121

106122
switch addressDetails.role {
107123
case .primary:
@@ -216,7 +232,7 @@ struct ValkeySentinelTests {
216232
var healthyThreeSentinelOnePrimaryTwoReplicas: TestSentinelTopology {
217233
get async {
218234
await .init(
219-
primaries: [
235+
[
220236
"TestPrimary": (
221237
sentinels: [
222238
.init(host: "127.0.0.1", port: 16000),
@@ -225,7 +241,16 @@ struct ValkeySentinelTests {
225241
],
226242
primary: .init(host: "127.0.0.1", port: 9000),
227243
replicas: [.init(host: "127.0.0.1", port: 9001), .init(host: "127.0.0.1", port: 9002)]
228-
)
244+
),
245+
"TestPrimary2": (
246+
sentinels: [
247+
.init(host: "127.0.0.1", port: 16010),
248+
.init(host: "127.0.0.1", port: 16011),
249+
.init(host: "127.0.0.1", port: 16012),
250+
],
251+
primary: .init(host: "127.0.0.1", port: 9100),
252+
replicas: [.init(host: "127.0.0.1", port: 9101), .init(host: "127.0.0.1", port: 9102)]
253+
),
229254
]
230255
)
231256
}
@@ -278,6 +303,16 @@ struct ValkeySentinelTests {
278303
#expect(nodes.primary == .hostname("127.0.0.1", port: 9000))
279304
#expect(nodes.replicas == [.hostname("127.0.0.1", port: 9001), .hostname("127.0.0.1", port: 9002)])
280305
}
306+
try await withValkeySentinelClient(
307+
primaryName: "TestPrimary2",
308+
address: .hostname("127.0.0.1", port: 16000),
309+
mockConnections: mockConnections,
310+
logger: logger
311+
) { client in
312+
let nodes = try await client.getNodes()
313+
#expect(nodes.primary == .hostname("127.0.0.1", port: 9100))
314+
#expect(nodes.replicas == [.hostname("127.0.0.1", port: 9101), .hostname("127.0.0.1", port: 9102)])
315+
}
281316
}
282317

283318
@Test
@@ -299,4 +334,100 @@ struct ValkeySentinelTests {
299334
}
300335
}
301336
}
337+
338+
@Test
339+
@available(valkeySwift 1.0, *)
340+
func testTwoSentinels() async throws {
341+
var logger = Logger(label: "Valkey")
342+
logger.logLevel = .debug
343+
let topology = await TestSentinelTopology([
344+
"TwoSentinels": (
345+
sentinels: [.init(host: "127.0.0.1", port: 16000), .init(host: "127.0.0.1", port: 16001)],
346+
primary: .init(host: "127.0.0.1", port: 9000),
347+
replicas: []
348+
)
349+
])
350+
let mockConnections = await topology.mock(logger: logger)
351+
async let _ = mockConnections.run()
352+
try await withValkeySentinelClient(
353+
primaryName: "TwoSentinels",
354+
address: .hostname("127.0.0.1", port: 16000),
355+
mockConnections: mockConnections,
356+
logger: logger
357+
) { client in
358+
let nodes = try await client.getNodes()
359+
#expect(nodes.primary == .hostname("127.0.0.1", port: 9000))
360+
#expect(nodes.replicas == [])
361+
}
362+
}
363+
364+
@Test
365+
@available(valkeySwift 1.0, *)
366+
func testOneSentinel() async throws {
367+
var logger = Logger(label: "Valkey")
368+
logger.logLevel = .debug
369+
let topology = await TestSentinelTopology([
370+
"OneSentinel": (
371+
sentinels: [.init(host: "127.0.0.1", port: 16000)],
372+
primary: .init(host: "127.0.0.1", port: 9000),
373+
replicas: []
374+
)
375+
])
376+
let mockConnections = await topology.mock(logger: logger)
377+
async let _ = mockConnections.run()
378+
try await withValkeySentinelClient(
379+
primaryName: "OneSentinel",
380+
address: .hostname("127.0.0.1", port: 16000),
381+
mockConnections: mockConnections,
382+
logger: logger
383+
) { client in
384+
let nodes = try await client.getNodes()
385+
#expect(nodes.primary == .hostname("127.0.0.1", port: 9000))
386+
#expect(nodes.replicas == [])
387+
}
388+
}
389+
390+
@Test
391+
@available(valkeySwift 1.0, *)
392+
func testShutdownSentinel() async throws {
393+
var logger = Logger(label: "Valkey")
394+
logger.logLevel = .debug
395+
let topology = await self.healthyThreeSentinelOnePrimaryTwoReplicas
396+
await topology.shutdownNode(address: .init(host: "127.0.0.1", port: 16001))
397+
let mockConnections = await topology.mock(logger: logger)
398+
async let _ = mockConnections.run()
399+
try await withValkeySentinelClient(
400+
primaryName: "TestPrimary",
401+
address: .hostname("127.0.0.1", port: 16000),
402+
mockConnections: mockConnections,
403+
logger: logger
404+
) { client in
405+
let nodes = try await client.getNodes()
406+
#expect(nodes.primary == .hostname("127.0.0.1", port: 9000))
407+
#expect(nodes.replicas == [.hostname("127.0.0.1", port: 9001), .hostname("127.0.0.1", port: 9002)])
408+
let sentinels = try await client.getSentinelClients()
409+
#expect(Set(sentinels.map { $0.serverAddress }) == Set([.hostname("127.0.0.1", port: 16000), .hostname("127.0.0.1", port: 16002)]))
410+
}
411+
}
412+
413+
@Test
414+
@available(valkeySwift 1.0, *)
415+
func testShutdownReplica() async throws {
416+
var logger = Logger(label: "Valkey")
417+
logger.logLevel = .debug
418+
let topology = await self.healthyThreeSentinelOnePrimaryTwoReplicas
419+
await topology.shutdownNode(address: .init(host: "127.0.0.1", port: 9001))
420+
let mockConnections = await topology.mock(logger: logger)
421+
async let _ = mockConnections.run()
422+
try await withValkeySentinelClient(
423+
primaryName: "TestPrimary",
424+
address: .hostname("127.0.0.1", port: 16000),
425+
mockConnections: mockConnections,
426+
logger: logger
427+
) { client in
428+
let nodes = try await client.getNodes()
429+
#expect(nodes.primary == .hostname("127.0.0.1", port: 9000))
430+
#expect(nodes.replicas == [.hostname("127.0.0.1", port: 9002)])
431+
}
432+
}
302433
}

0 commit comments

Comments
 (0)