@@ -69,7 +69,7 @@ func (suite *tsoAPITestSuite) SetupTest() {
6969 pdLeaderServer := suite .pdCluster .GetServer (leaderName )
7070 re .NoError (pdLeaderServer .BootstrapCluster ())
7171 suite .backendEndpoints = pdLeaderServer .GetAddr ()
72- suite .tsoCluster , err = tests .NewTestTSOCluster (suite .ctx , 1 , suite .backendEndpoints )
72+ suite .tsoCluster , err = tests .NewTestTSOCluster (suite .ctx , 2 , suite .backendEndpoints )
7373 re .NoError (err )
7474}
7575
@@ -280,3 +280,62 @@ func (suite *tsoAPITestSuite) TestConfig() {
280280 re .Equal (cfg .GetTSOUpdatePhysicalInterval (), primary .GetConfig ().GetTSOUpdatePhysicalInterval ())
281281 re .Equal (cfg .GetMaxResetTSGap (), primary .GetConfig ().GetMaxResetTSGap ())
282282}
283+
284+ // TestForwardingBehavior specifically tests the API forwarding logic.
285+ func (suite * tsoAPITestSuite ) TestForwardingBehavior () {
286+ re := suite .Require ()
287+
288+ primary := suite .tsoCluster .WaitForDefaultPrimaryServing (re )
289+ re .NotNil (primary )
290+ var follower * tso.Server
291+ for _ , srv := range suite .tsoCluster .GetServers () {
292+ if srv .Name () != primary .Name () {
293+ follower = srv
294+ break
295+ }
296+ }
297+ re .NotNil (follower )
298+ re .True (primary .IsServing ())
299+ re .False (follower .IsServing ())
300+ re .NotEqual (follower .GetConfig ().GetListenAddr (), primary .GetConfig ().GetListenAddr ())
301+
302+ followerAddr := follower .GetAddr ()
303+ followerURL := func (path string ) string {
304+ return fmt .Sprintf ("%s%s%s" , followerAddr , apis .APIPathPrefix , path )
305+ }
306+
307+ // Test: PUT /admin/log should be handled by the follower locally.
308+ logURL := followerURL ("/admin/log" )
309+ level := "debug"
310+ logPayload , err := json .Marshal (level )
311+ re .NoError (err )
312+ req , _ := http .NewRequest (http .MethodPut , logURL , bytes .NewBuffer (logPayload ))
313+ req .Header .Set ("Content-Type" , "application/json" )
314+ resp , err := tests .TestDialClient .Do (req )
315+ re .NoError (err )
316+ defer resp .Body .Close ()
317+ re .Equal (http .StatusOK , resp .StatusCode )
318+
319+ // Test: GET /config should be handled by the follower locally.
320+ configURL := followerURL ("/config" )
321+ var followerCfg tso.Config
322+ err = testutil .ReadGetJSON (re , tests .TestDialClient , configURL , & followerCfg )
323+ re .NoError (err )
324+ re .Equal (follower .GetConfig ().GetListenAddr (), followerCfg .GetListenAddr ())
325+ re .NotEqual (primary .GetConfig ().GetListenAddr (), followerCfg .GetListenAddr ())
326+ re .Equal (level , followerCfg .Log .Level )
327+
328+ // Test: GET /keyspace-groups/members should be handled by the follower locally.
329+ membersURL := followerURL ("/keyspace-groups/members" )
330+ var kgms map [uint32 ]* apis.KeyspaceGroupMember
331+ err = testutil .ReadGetJSON (re , tests .TestDialClient , membersURL , & kgms )
332+ re .NoError (err )
333+ re .Len (kgms , 1 )
334+ kgm := kgms [constant .DefaultKeyspaceGroupID ]
335+ re .NotNil (kgm )
336+ re .Len (kgm .Member .ListenUrls , 1 )
337+ respListenURL := kgm .Member .ListenUrls [0 ]
338+ re .Equal (follower .GetConfig ().GetListenAddr (), respListenURL )
339+ re .NotEqual (primary .GetConfig ().GetListenAddr (), respListenURL )
340+ re .False (kgm .IsPrimary )
341+ }
0 commit comments