1- using System ;
1+ using System ;
22using System . Collections . Generic ;
33using System . IO ;
44using System . Net ;
@@ -12,7 +12,7 @@ namespace StackExchange.Redis.Tests
1212 public class Sentinel : TestBase
1313 {
1414 private string ServiceName => TestConfig . Current . SentinelSeviceName ;
15- private ConfigurationOptions ServiceOptions => new ConfigurationOptions { ServiceName = ServiceName , AllowAdmin = true } ;
15+ private ConfigurationOptions ServiceOptions => new ConfigurationOptions { ServiceName = ServiceName , AllowAdmin = true , Password = "changeme" } ;
1616
1717 private ConnectionMultiplexer Conn { get ; }
1818 private IServer SentinelServerA { get ; }
@@ -28,24 +28,16 @@ public Sentinel(ITestOutputHelper output) : base(output)
2828 Skip . IfNoConfig ( nameof ( TestConfig . Config . SentinelServer ) , TestConfig . Current . SentinelServer ) ;
2929 Skip . IfNoConfig ( nameof ( TestConfig . Config . SentinelSeviceName ) , TestConfig . Current . SentinelSeviceName ) ;
3030
31- var options = new ConfigurationOptions ( )
32- {
33- CommandMap = CommandMap . Sentinel ,
34- EndPoints = {
35- { TestConfig . Current . SentinelServer , TestConfig . Current . SentinelPortA } ,
36- { TestConfig . Current . SentinelServer , TestConfig . Current . SentinelPortB } ,
37- { TestConfig . Current . SentinelServer , TestConfig . Current . SentinelPortC }
38- } ,
39- AllowAdmin = true ,
40- TieBreaker = "" ,
41- ServiceName = TestConfig . Current . SentinelSeviceName ,
42- SyncTimeout = 5000
43- } ;
44- Conn = ConnectionMultiplexer . Connect ( options , ConnectionLog ) ;
31+ var options = ServiceOptions . Clone ( ) ;
32+ options . EndPoints . Add ( TestConfig . Current . SentinelServer , TestConfig . Current . SentinelPortA ) ;
33+ options . EndPoints . Add ( TestConfig . Current . SentinelServer , TestConfig . Current . SentinelPortB ) ;
34+ options . EndPoints . Add ( TestConfig . Current . SentinelServer , TestConfig . Current . SentinelPortC ) ;
35+
36+ Conn = ConnectionMultiplexer . SentinelConnect ( options , ConnectionLog ) ;
4537 for ( var i = 0 ; i < 150 ; i ++ )
4638 {
4739 Thread . Sleep ( 20 ) ;
48- if ( Conn . IsConnected && Conn . GetSentinelMasterConnection ( ServiceOptions ) . IsConnected )
40+ if ( Conn . IsConnected && Conn . GetSentinelMasterConnection ( options ) . IsConnected )
4941 {
5042 break ;
5143 }
@@ -57,6 +49,84 @@ public Sentinel(ITestOutputHelper output) : base(output)
5749 SentinelsServers = new IServer [ ] { SentinelServerA , SentinelServerB , SentinelServerC } ;
5850 }
5951
52+ [ Fact ]
53+ public void MasterConnectTest ( )
54+ {
55+ var options = ServiceOptions . Clone ( ) ;
56+ options . EndPoints . Add ( TestConfig . Current . SentinelServer , TestConfig . Current . SentinelPortA ) ;
57+
58+ var conn = ConnectionMultiplexer . SentinelMasterConnect ( options ) ;
59+ var db = conn . GetDatabase ( ) ;
60+
61+ var test = db . Ping ( ) ;
62+ Log ( "ping to sentinel {0}:{1} took {2} ms" , TestConfig . Current . SentinelServer ,
63+ TestConfig . Current . SentinelPortA , test . TotalMilliseconds ) ;
64+ }
65+
66+ [ Fact ]
67+ public void MasterConnectWithDefaultPortTest ( )
68+ {
69+ var options = ServiceOptions . Clone ( ) ;
70+ options . EndPoints . Add ( TestConfig . Current . SentinelServer ) ;
71+
72+ var conn = ConnectionMultiplexer . SentinelMasterConnect ( options ) ;
73+ var db = conn . GetDatabase ( ) ;
74+
75+ var test = db . Ping ( ) ;
76+ Log ( "ping to sentinel {0}:{1} took {2} ms" , TestConfig . Current . SentinelServer ,
77+ TestConfig . Current . SentinelPortA , test . TotalMilliseconds ) ;
78+ }
79+
80+ [ Fact ]
81+ public void MasterConnectWithStringConfigurationTest ( )
82+ {
83+ var connectionString = $ "{ TestConfig . Current . SentinelServer } :{ TestConfig . Current . SentinelPortA } ,password={ ServiceOptions . Password } ,serviceName={ ServiceOptions . ServiceName } ";
84+ var conn = ConnectionMultiplexer . SentinelMasterConnect ( connectionString ) ;
85+ var db = conn . GetDatabase ( ) ;
86+
87+ var test = db . Ping ( ) ;
88+ Log ( "ping to sentinel {0}:{1} took {2} ms" , TestConfig . Current . SentinelServer ,
89+ TestConfig . Current . SentinelPortA , test . TotalMilliseconds ) ;
90+ }
91+
92+ [ Fact ]
93+ public async Task MasterConnectFailoverTest ( )
94+ {
95+ var options = ServiceOptions . Clone ( ) ;
96+ options . EndPoints . Add ( TestConfig . Current . SentinelServer , TestConfig . Current . SentinelPortA ) ;
97+
98+ // connection is managed and should switch to current master when failover happens
99+ var conn = ConnectionMultiplexer . SentinelMasterConnect ( options ) ;
100+ conn . ConfigurationChanged += ( s , e ) => {
101+ Log ( $ "Configuration changed: { e . EndPoint } ") ;
102+ } ;
103+ var db = conn . GetDatabase ( ) ;
104+
105+ var test = await db . PingAsync ( ) ;
106+ Log ( "ping to sentinel {0}:{1} took {2} ms" , TestConfig . Current . SentinelServer ,
107+ TestConfig . Current . SentinelPortA , test . TotalMilliseconds ) ;
108+
109+ // set string value on current master
110+ var expected = DateTime . Now . Ticks . ToString ( ) ;
111+ Log ( "Tick Key: " + expected ) ;
112+ var key = Me ( ) ;
113+ await db . KeyDeleteAsync ( key , CommandFlags . FireAndForget ) ;
114+ await db . StringSetAsync ( key , expected ) ;
115+
116+ // forces and verifies failover
117+ await DoFailoverAsync ( ) ;
118+
119+ var value = await db . StringGetAsync ( key ) ;
120+ Assert . Equal ( expected , value ) ;
121+
122+ await db . StringSetAsync ( key , expected ) ;
123+ }
124+
125+ private void Conn_ConfigurationChanged ( object sender , EndPointEventArgs e )
126+ {
127+ throw new NotImplementedException ( ) ;
128+ }
129+
60130 [ Fact ]
61131 public void PingTest ( )
62132 {
@@ -584,7 +654,8 @@ public async Task ReadOnlyConnectionSlavesTest()
584654 var config = new ConfigurationOptions
585655 {
586656 TieBreaker = "" ,
587- ServiceName = TestConfig . Current . SentinelSeviceName ,
657+ ServiceName = ServiceOptions . ServiceName ,
658+ Password = ServiceOptions . Password
588659 } ;
589660
590661 foreach ( var kv in slaves )
@@ -604,5 +675,36 @@ public async Task ReadOnlyConnectionSlavesTest()
604675 //Assert.StartsWith("No connection is available to service this operation", ex.Message);
605676
606677 }
678+
679+ private async Task DoFailoverAsync ( )
680+ {
681+ // capture current master and slave
682+ var master = SentinelServerA . SentinelGetMasterAddressByName ( ServiceName ) ;
683+ var slaves = SentinelServerA . SentinelSlaves ( ServiceName ) ;
684+
685+ await Task . Delay ( 1000 ) . ForAwait ( ) ;
686+ try
687+ {
688+ Log ( "Failover attempted initiated" ) ;
689+ SentinelServerA . SentinelFailover ( ServiceName ) ;
690+ Log ( " Success!" ) ;
691+ }
692+ catch ( RedisServerException ex ) when ( ex . Message . Contains ( "NOGOODSLAVE" ) )
693+ {
694+ // Retry once
695+ Log ( " Retry initiated" ) ;
696+ await Task . Delay ( 1000 ) . ForAwait ( ) ;
697+ SentinelServerA . SentinelFailover ( ServiceName ) ;
698+ Log ( " Retry complete" ) ;
699+ }
700+ await Task . Delay ( 2000 ) . ForAwait ( ) ;
701+
702+ var newMaster = SentinelServerA . SentinelGetMasterAddressByName ( ServiceName ) ;
703+ var newSlave = SentinelServerA . SentinelSlaves ( ServiceName ) ;
704+
705+ // make sure master changed
706+ Assert . Equal ( slaves [ 0 ] . ToDictionary ( ) [ "name" ] , newMaster . ToString ( ) ) ;
707+ Assert . Equal ( master . ToString ( ) , newSlave [ 0 ] . ToDictionary ( ) [ "name" ] ) ;
708+ }
607709 }
608710}
0 commit comments