@@ -18,7 +18,9 @@ package general
1818
1919import (
2020 "context"
21+ "encoding/json"
2122 "fmt"
23+ "strconv"
2224 "testing"
2325 "time"
2426
@@ -893,3 +895,85 @@ func TestFullStatusConnectionPooling(t *testing.T) {
893895 assert .Equal (t , 200 , status )
894896 assert .Equal (t , "null" , resp )
895897}
898+
899+ // TestSemiSyncRecoveryOrdering verifies that when the durability policy changes
900+ // to semi_sync, VTOrc fixes ReplicaSemiSyncMustBeSet before PrimarySemiSyncMustBeSet.
901+ // This ordering is enforced by the AfterAnalyses/BeforeAnalyses dependencies.
902+ func TestSemiSyncRecoveryOrdering (t * testing.T ) {
903+ defer utils .PrintVTOrcLogsOnFailure (t , clusterInfo .ClusterInstance )
904+ // Start with durability "none" so no semi-sync is required initially.
905+ utils .SetupVttabletsAndVTOrcs (t , clusterInfo , 2 , 0 , nil , cluster.VTOrcConfiguration {
906+ PreventCrossCellFailover : true ,
907+ }, cluster .DefaultVtorcsByCell , policy .DurabilityNone )
908+ keyspace := & clusterInfo .ClusterInstance .Keyspaces [0 ]
909+ shard0 := & keyspace .Shards [0 ]
910+
911+ // Wait for primary election and healthy replication.
912+ primary := utils .ShardPrimaryTablet (t , clusterInfo , keyspace , shard0 )
913+ assert .NotNil (t , primary , "should have elected a primary" )
914+ utils .CheckReplication (t , clusterInfo , primary , shard0 .Vttablets , 10 * time .Second )
915+
916+ vtorc := clusterInfo .ClusterInstance .VTOrcProcesses [0 ]
917+ utils .WaitForSuccessfulRecoveryCount (t , vtorc , logic .ElectNewPrimaryRecoveryName , keyspace .Name , shard0 .Name , 1 )
918+
919+ // Change durability to semi_sync. VTOrc should detect that replicas and primary
920+ // need semi-sync enabled, and fix them in the correct order.
921+ out , err := clusterInfo .ClusterInstance .VtctldClientProcess .ExecuteCommandWithOutput (
922+ "SetKeyspaceDurabilityPolicy" , keyspace .Name , "--durability-policy=" + policy .DurabilitySemiSync )
923+ require .NoError (t , err , out )
924+
925+ // Poll the database-state API to verify recovery ordering.
926+ // The topology_recovery table has auto-incremented recovery_id values that
927+ // reflect execution order. All ReplicaSemiSyncMustBeSet recovery_ids should
928+ // be less than any PrimarySemiSyncMustBeSet recovery_id.
929+ type tableState struct {
930+ TableName string
931+ Rows []map [string ]any
932+ }
933+
934+ assert .EventuallyWithT (t , func (c * assert.CollectT ) {
935+ status , response , err := utils .MakeAPICall (t , vtorc , "/api/database-state" )
936+ assert .NoError (c , err )
937+ assert .Equal (c , 200 , status )
938+
939+ var tables []tableState
940+ if ! assert .NoError (c , json .Unmarshal ([]byte (response ), & tables )) {
941+ return
942+ }
943+
944+ var maxReplicaRecoveryID , minPrimaryRecoveryID int
945+ var replicaCount , primaryCount int
946+ for _ , table := range tables {
947+ if table .TableName != "topology_recovery" {
948+ continue
949+ }
950+ for _ , row := range table .Rows {
951+ analysis , _ := row ["analysis" ].(string )
952+ recoveryIDStr , _ := row ["recovery_id" ].(string )
953+ recoveryID , err := strconv .Atoi (recoveryIDStr )
954+ if err != nil {
955+ continue
956+ }
957+ switch inst .AnalysisCode (analysis ) {
958+ case inst .ReplicaSemiSyncMustBeSet :
959+ replicaCount ++
960+ if replicaCount == 1 || recoveryID > maxReplicaRecoveryID {
961+ maxReplicaRecoveryID = recoveryID
962+ }
963+ case inst .PrimarySemiSyncMustBeSet :
964+ primaryCount ++
965+ if primaryCount == 1 || recoveryID < minPrimaryRecoveryID {
966+ minPrimaryRecoveryID = recoveryID
967+ }
968+ }
969+ }
970+ }
971+
972+ assert .Greater (c , replicaCount , 0 , "should have ReplicaSemiSyncMustBeSet recoveries" )
973+ assert .Greater (c , primaryCount , 0 , "should have PrimarySemiSyncMustBeSet recoveries" )
974+ if replicaCount > 0 && primaryCount > 0 {
975+ assert .Less (c , maxReplicaRecoveryID , minPrimaryRecoveryID ,
976+ "all ReplicaSemiSyncMustBeSet recoveries should have lower recovery_id than PrimarySemiSyncMustBeSet" )
977+ }
978+ }, 30 * time .Second , time .Second )
979+ }
0 commit comments