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

asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/helix.git


The following commit(s) were added to refs/heads/master by this push:
     new 3332c2223 Support Helix REST API isClusterStable
3332c2223 is described below

commit 3332c222364f3bfdc3964db7d5a4bd1c066a0795
Author: Junkai Xue <[email protected]>
AuthorDate: Mon Jun 29 13:23:05 2026 -0700

    Support Helix REST API isClusterStable
---
 .../server/resources/helix/ClusterAccessor.java    | 34 ++++++++++++++++++++++
 .../helix/rest/server/TestClusterAccessor.java     | 20 +++++++++++++
 2 files changed, 54 insertions(+)

diff --git 
a/helix-rest/src/main/java/org/apache/helix/rest/server/resources/helix/ClusterAccessor.java
 
b/helix-rest/src/main/java/org/apache/helix/rest/server/resources/helix/ClusterAccessor.java
index 1e752f3fe..c35db12d6 100644
--- 
a/helix-rest/src/main/java/org/apache/helix/rest/server/resources/helix/ClusterAccessor.java
+++ 
b/helix-rest/src/main/java/org/apache/helix/rest/server/resources/helix/ClusterAccessor.java
@@ -77,6 +77,7 @@ import org.apache.helix.rest.server.service.ClusterService;
 import org.apache.helix.rest.server.service.ClusterServiceImpl;
 import org.apache.helix.rest.server.service.VirtualTopologyGroupService;
 import org.apache.helix.tools.ClusterSetup;
+import 
org.apache.helix.tools.ClusterVerifiers.BestPossibleExternalViewVerifier;
 import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.apache.zookeeper.data.Stat;
@@ -402,6 +403,39 @@ public class ClusterAccessor extends AbstractHelixResource 
{
     return JSONRepresentation(responseMap);
   }
 
+  @ClusterAuth
+  @ResponseMetered(name = HttpConstants.READ_REQUEST)
+  @Timed(name = HttpConstants.READ_REQUEST)
+  @GET
+  @Path("{clusterId}/isStable")
+  @ApiOperation(value = "Check whether the cluster is stable",
+      notes = "Returns true if every resource's ExternalView matches its best 
possible state, "
+          + "meaning there are no ongoing state transitions. Optional query 
param 'timeout' (ms) "
+          + "bounds how long to wait for convergence; defaults to 0 for a 
single immediate check.")
+  public Response isClusterStable(@PathParam("clusterId") String clusterId,
+      @DefaultValue("0") @QueryParam("timeout") long timeout) {
+    if (!doesClusterExist(clusterId)) {
+      return notFound(String.format("Cluster %s does not exist", clusterId));
+    }
+
+    boolean stable;
+    // Reuse the shared REST server ZkClient; the verifier must not close it 
on our behalf.
+    try (BestPossibleExternalViewVerifier verifier =
+        new BestPossibleExternalViewVerifier.Builder(clusterId)
+            .setZkClient(getRealmAwareZkClient())
+            .build()) {
+      stable = verifier.verify(timeout);
+    } catch (Exception e) {
+      LOG.error("Failed to verify cluster stability for cluster {}", 
clusterId, e);
+      return serverError(e);
+    }
+
+    Map<String, Object> responseMap = new HashMap<>();
+    responseMap.put("cluster", clusterId);
+    responseMap.put("stable", stable);
+    return JSONRepresentation(responseMap);
+  }
+
   private Map<String, Object> getManagementModeDetails(String clusterId,
       ClusterManagementMode mode) {
     Map<String, Object> details = new HashMap<>();
diff --git 
a/helix-rest/src/test/java/org/apache/helix/rest/server/TestClusterAccessor.java
 
b/helix-rest/src/test/java/org/apache/helix/rest/server/TestClusterAccessor.java
index f9af0a9c2..93f4481fa 100644
--- 
a/helix-rest/src/test/java/org/apache/helix/rest/server/TestClusterAccessor.java
+++ 
b/helix-rest/src/test/java/org/apache/helix/rest/server/TestClusterAccessor.java
@@ -176,6 +176,26 @@ public class TestClusterAccessor extends AbstractTestClass 
{
     System.out.println("End test :" + TestHelper.getTestMethodName());
   }
 
+  @Test(dependsOnMethods = "testGetClusters")
+  public void testIsClusterStable() throws IOException {
+    System.out.println("Start test :" + TestHelper.getTestMethodName());
+    String cluster = "TestCluster_1";
+
+    // Allow some time for the cluster to converge before asserting stability.
+    String body = get("clusters/" + cluster + "/isStable",
+        ImmutableMap.of("timeout", "10000"), 
Response.Status.OK.getStatusCode(), true);
+    JsonNode node = OBJECT_MAPPER.readTree(body);
+    Assert.assertEquals(node.get("cluster").asText(), cluster);
+    Assert.assertTrue(node.get("stable").asBoolean(),
+        "Cluster " + cluster + " should be stable");
+
+    // Non-existent cluster should return 404.
+    get("clusters/nonExistentCluster/isStable", null, 
Response.Status.NOT_FOUND.getStatusCode(),
+        false);
+
+    System.out.println("End test :" + TestHelper.getTestMethodName());
+  }
+
   @Test(dependsOnMethods = "testGetClusters")
   public void testGetClusterTopology() {
     System.out.println("Start test :" + TestHelper.getTestMethodName());

Reply via email to