This is an automated email from the ASF dual-hosted git repository.

lokiore pushed a commit to branch PHOENIX-7562-feature-new
in repository https://gitbox.apache.org/repos/asf/phoenix.git


The following commit(s) were added to refs/heads/PHOENIX-7562-feature-new by 
this push:
     new 2fbff202b0 PHOENIX-7966 :- Fix flaky HA IT transitClusterRole: expect 
the settled peer role (#2583)
2fbff202b0 is described below

commit 2fbff202b00930018ba2b198b2f0c1a13e69756b
Author: Lokesh Khurana <[email protected]>
AuthorDate: Mon Aug 3 15:38:33 2026 -0700

    PHOENIX-7966 :- Fix flaky HA IT transitClusterRole: expect the settled peer 
role (#2583)
    
    * PHOENIX-7966 :- Fix flaky HA IT transitClusterRole: expect the settled 
peer role
    
    HA integration tests intermittently time out (~1 in 5 runs) inside the
    transitClusterRole test helper when a group transitions under the FAILOVER 
or
    PARALLEL policy with both clusters live. The helper writes an expected
    ClusterRoleRecord to both clusters and then waits for the refreshed record 
to
    equal it. When the local cluster moves toward standby (ACTIVE_TO_STANDBY), a
    live peer's failover-management listener autonomously advances a STANDBY 
peer to
    STANDBY_TO_ACTIVE. Naming the transient pre-advance role in the wait 
therefore
    races that autonomous transition and intermittently never matches, timing 
out.
    
    Two complementary, deterministic fixes, each matching its call site's 
intent:
    
    * FailoverPhoenixConnectionIT and HAGroupMetricsIT (setup-then-assert 
sites):
      the {ACTIVE_TO_STANDBY, STANDBY} transit is only a stepping stone to a 
later
      settled state that the test actually asserts on, so keep the settle-wait 
but
      name the SETTLED peer role STANDBY_TO_ACTIVE -- a fixed point in the
      peer/local transition maps -- so the record matches on the first poll
      regardless of listener timing.
    
    * ParallelPhoenixConnectionIT.testOneClusterATSRoleWithStandby 
(assert-on-the-
      transient-pair site): {ACTIVE_TO_STANDBY, STANDBY} is itself the state 
under
      test and is not reachable as a stable pair with both clusters live, so no
      settled role can be named without changing what the test exercises. Skip 
the
      settle-wait (doRefresh=false) instead; the parallel connection is still
      exercised against the ACTIVE_TO_STANDBY cluster pair and must complete 
without
      error. The now-unused single-argument overload is removed.
    
    The transitClusterRole Javadoc and the cluster-down / cluster-restart 
variants
    are annotated to document the peer-advance exposure and why each is or is 
not
    subject to it.
    
    Test-only change; no production code is touched.
    
    Generated-by: Claude Code (Opus 4.8 (1M context))
---
 .../phoenix/jdbc/FailoverPhoenixConnectionIT.java    | 10 ++++++++--
 .../org/apache/phoenix/jdbc/HAGroupMetricsIT.java    | 10 ++++++++--
 .../phoenix/jdbc/HighAvailabilityTestingUtility.java | 20 ++++++++++++++++++--
 .../phoenix/jdbc/ParallelPhoenixConnectionIT.java    | 12 +++++++-----
 4 files changed, 41 insertions(+), 11 deletions(-)

diff --git 
a/phoenix-core/src/it/java/org/apache/phoenix/jdbc/FailoverPhoenixConnectionIT.java
 
b/phoenix-core/src/it/java/org/apache/phoenix/jdbc/FailoverPhoenixConnectionIT.java
index 38e66bf4f5..d41cc0e91c 100644
--- 
a/phoenix-core/src/it/java/org/apache/phoenix/jdbc/FailoverPhoenixConnectionIT.java
+++ 
b/phoenix-core/src/it/java/org/apache/phoenix/jdbc/FailoverPhoenixConnectionIT.java
@@ -196,7 +196,10 @@ public class FailoverPhoenixConnectionIT extends HABaseIT {
       doTestBasicOperationsWithConnection(conn, tableName, haGroupName);
     }
 
-    CLUSTERS.transitClusterRole(haGroup, ClusterRole.ACTIVE_TO_STANDBY, 
ClusterRole.STANDBY);
+    // role2 = the SETTLED peer role: local ACTIVE_TO_STANDBY drives the peer
+    // STANDBY -> STANDBY_TO_ACTIVE, so the waitFor must expect the settled 
state.
+    CLUSTERS.transitClusterRole(haGroup, ClusterRole.ACTIVE_TO_STANDBY,
+      ClusterRole.STANDBY_TO_ACTIVE);
     CLUSTERS.transitClusterRole(haGroup, ClusterRole.STANDBY, 
ClusterRole.STANDBY);
 
     try {
@@ -565,7 +568,10 @@ public class FailoverPhoenixConnectionIT extends HABaseIT {
     Connection conn = createFailoverConnection();
     doTestBasicOperationsWithConnection(conn, tableName, haGroupName);
 
-    CLUSTERS.transitClusterRole(haGroup, ClusterRole.ACTIVE_TO_STANDBY, 
ClusterRole.STANDBY);
+    // role2 = the SETTLED peer role: local ACTIVE_TO_STANDBY drives the peer
+    // STANDBY -> STANDBY_TO_ACTIVE, so the waitFor must expect the settled 
state.
+    CLUSTERS.transitClusterRole(haGroup, ClusterRole.ACTIVE_TO_STANDBY,
+      ClusterRole.STANDBY_TO_ACTIVE);
     CLUSTERS.transitClusterRole(haGroup, ClusterRole.STANDBY, 
ClusterRole.STANDBY);
 
     try {
diff --git 
a/phoenix-core/src/it/java/org/apache/phoenix/jdbc/HAGroupMetricsIT.java 
b/phoenix-core/src/it/java/org/apache/phoenix/jdbc/HAGroupMetricsIT.java
index 088ccff183..9589204cb5 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/jdbc/HAGroupMetricsIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/jdbc/HAGroupMetricsIT.java
@@ -133,7 +133,10 @@ public class HAGroupMetricsIT extends HABaseIT {
     try (Connection conn = 
DriverManager.getConnection(CLUSTERS.getJdbcHAUrl(), clientProperties)) {
       conn.createStatement().execute("UPSERT INTO " + tableName + " VALUES (2, 
2)");
       conn.commit();
-      CLUSTERS.transitClusterRole(haGroup, ClusterRole.ACTIVE_TO_STANDBY, 
ClusterRole.STANDBY);
+      // role2 = the SETTLED peer role: local ACTIVE_TO_STANDBY drives the peer
+      // STANDBY -> STANDBY_TO_ACTIVE, so the waitFor must expect the settled 
state.
+      CLUSTERS.transitClusterRole(haGroup, ClusterRole.ACTIVE_TO_STANDBY,
+        ClusterRole.STANDBY_TO_ACTIVE);
       // doRefreshHAGroup=false: keep haGroup's CRR snapshot stale on purpose 
so that
       // the next mutation drives StaleClusterRoleRecordException through 
wrapActionDuringFailover,
       // exercising the GLOBAL_HA_STALE_CRR_DETECTED_COUNT increment path.
@@ -250,7 +253,10 @@ public class HAGroupMetricsIT extends HABaseIT {
     long beforeTicks = GLOBAL_HA_POLLER_TICK_COUNT.getMetric().getValue();
     long beforeFailures = 
GLOBAL_HA_POLLER_TICK_FAILURES.getMetric().getValue();
 
-    CLUSTERS.transitClusterRole(haGroup, ClusterRole.ACTIVE_TO_STANDBY, 
ClusterRole.STANDBY);
+    // role2 = the SETTLED peer role: local ACTIVE_TO_STANDBY drives the peer
+    // STANDBY -> STANDBY_TO_ACTIVE, so the waitFor must expect the settled 
state.
+    CLUSTERS.transitClusterRole(haGroup, ClusterRole.ACTIVE_TO_STANDBY,
+      ClusterRole.STANDBY_TO_ACTIVE);
     CLUSTERS.transitClusterRole(haGroup, ClusterRole.STANDBY, 
ClusterRole.STANDBY);
 
     // Allow at least 2 poller ticks at default interval to land.
diff --git 
a/phoenix-core/src/it/java/org/apache/phoenix/jdbc/HighAvailabilityTestingUtility.java
 
b/phoenix-core/src/it/java/org/apache/phoenix/jdbc/HighAvailabilityTestingUtility.java
index d27c5ba4f7..e6ebf11b7a 100644
--- 
a/phoenix-core/src/it/java/org/apache/phoenix/jdbc/HighAvailabilityTestingUtility.java
+++ 
b/phoenix-core/src/it/java/org/apache/phoenix/jdbc/HighAvailabilityTestingUtility.java
@@ -423,9 +423,16 @@ public class HighAvailabilityTestingUtility {
      * Update HAGroup's clusterRoleRecord info to new roles (Update 
HAGroupStoreRecord and SYSTEM
      * Tables) and then do the refresh of the clusterRoleRecord in HAGroup 
based on the param
      * provided.
+     * <p>
+     * Pass the role each cluster SETTLES at, not a transient pre-advance 
role: local
+     * ACTIVE_TO_STANDBY drives the peer's listener to advance a STANDBY peer 
to STANDBY_TO_ACTIVE,
+     * so naming the pre-advance role makes the equality wait race the 
autonomous transition. Naming
+     * the settled role makes that target a fixed point the peer will not 
advance past, so the
+     * equality wait converges deterministically within the settle window 
rather than racing the
+     * listener.
      * @param haGroup the HA group name
-     * @param role1   cluster role for the first cluster in the group
-     * @param role2   cluster role for the second cluster in the group
+     * @param role1   settled cluster role for the first cluster in the group
+     * @param role2   settled cluster role for the second cluster in the group
      */
     public void transitClusterRole(HighAvailabilityGroup haGroup, ClusterRole 
role1,
       ClusterRole role2, boolean doRefreshHAGroup, HighAvailabilityPolicy 
policy) throws Exception {
@@ -487,6 +494,9 @@ public class HighAvailabilityTestingUtility {
 
     public void transitClusterRoleWithCluster1Down(HighAvailabilityGroup 
haGroup, ClusterRole role1,
       ClusterRole role2) throws Exception {
+      // Immune to the peer-advance race: the down cluster is 
client-invalidated (role
+      // UNKNOWN), so no live peer listener can advance the survivor past the 
expected
+      // record; no settled-role naming needed.
       String haGroupName = haGroup.getGroupInfo().getName();
       final Pair<HAGroupStoreRecord, Stat> currentRecord2 =
         haAdmin2.getHAGroupStoreRecordInZooKeeper(haGroupName);
@@ -519,6 +529,9 @@ public class HighAvailabilityTestingUtility {
 
     public void transitClusterRoleWithCluster2Down(HighAvailabilityGroup 
haGroup, ClusterRole role1,
       ClusterRole role2) throws Exception {
+      // Immune to the peer-advance race: the down cluster is 
client-invalidated (role
+      // UNKNOWN), so no live peer listener can advance the survivor past the 
expected
+      // record; no settled-role naming needed.
       String haGroupName = haGroup.getGroupInfo().getName();
       final Pair<HAGroupStoreRecord, Stat> currentRecord1 =
         haAdmin1.getHAGroupStoreRecordInZooKeeper(haGroupName);
@@ -550,6 +563,9 @@ public class HighAvailabilityTestingUtility {
 
     public void 
refreshClusterRoleRecordAfterClusterRestart(HighAvailabilityGroup haGroup,
       ClusterRole role1, ClusterRole role2, int clusterIndex) throws Exception 
{
+      // Both clusters live, so this shares transitClusterRole's peer-advance 
exposure: a
+      // transitional role pair must name the SETTLED peer role. Current 
callers pass steady
+      // pairs, so they are safe.
       String haGroupName = haGroup.getGroupInfo().getName();
       // TODO:- Inducing version change, but how would we know that a URL is 
changed
       // as we don't store url in ZNodes so it doesn't increase version for 
that change
diff --git 
a/phoenix-core/src/it/java/org/apache/phoenix/jdbc/ParallelPhoenixConnectionIT.java
 
b/phoenix-core/src/it/java/org/apache/phoenix/jdbc/ParallelPhoenixConnectionIT.java
index 38821276a4..84ff5199d4 100644
--- 
a/phoenix-core/src/it/java/org/apache/phoenix/jdbc/ParallelPhoenixConnectionIT.java
+++ 
b/phoenix-core/src/it/java/org/apache/phoenix/jdbc/ParallelPhoenixConnectionIT.java
@@ -421,14 +421,16 @@ public class ParallelPhoenixConnectionIT extends HABaseIT 
{
    */
   @Test
   public void testOneClusterATSRoleWithStandby() throws Exception {
-    testOneClusterATSRole(ClusterRole.STANDBY);
-  }
-
-  private void testOneClusterATSRole(ClusterRole otherRole) throws Exception {
-    testOneClusterATSRole(otherRole, true);
+    testOneClusterATSRole(ClusterRole.STANDBY, false);
   }
 
   private void testOneClusterATSRole(ClusterRole otherRole, boolean doRefresh) 
throws Exception {
+    // doRefresh=false: {ACTIVE_TO_STANDBY, STANDBY} (and {ACTIVE_TO_STANDBY, 
ACTIVE}) is not a
+    // stable role pair with both clusters live -- a live peer under a local 
ACTIVE_TO_STANDBY
+    // autonomously advances (STANDBY -> STANDBY_TO_ACTIVE; ACTIVE keeps the 
local draining), so
+    // an equality waitFor against the named transient pair races that advance 
and intermittently
+    // times out. Skipping the settle-wait avoids the race; the PARALLEL 
connection is still
+    // exercised against the ACTIVE_TO_STANDBY cluster pair and must complete 
without error.
     CLUSTERS.transitClusterRole(haGroup, ClusterRole.ACTIVE_TO_STANDBY, 
otherRole, doRefresh,
       PARALLEL);
     try (Connection conn = getParallelConnection()) {

Reply via email to