@@ -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