You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@helix.apache.org by ji...@apache.org on 2020/11/18 19:04:26 UTC

[helix] 17/18: Add REST API for cluster topology (#1416)

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

jiajunwang pushed a commit to branch helix-0.9.x
in repository https://gitbox.apache.org/repos/asf/helix.git

commit 790e158788295295455e934ec588ffd87f5eac94
Author: Meng Zhang <mn...@linkedin.com>
AuthorDate: Tue Oct 6 22:09:52 2020 -0700

    Add REST API for cluster topology (#1416)
    
    This commit provides a few REST endpoints for user to retrieve cluster topology information. The APIs are added in ClusterAccessor.
---
 .../server/resources/helix/ClusterAccessor.java    | 26 ++++++
 .../helix/rest/server/AbstractTestClass.java       |  5 +-
 .../helix/rest/server/TestClusterAccessor.java     | 97 ++++++++++++++++++++++
 3 files changed, 126 insertions(+), 2 deletions(-)

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 ccbe12a..d0a4997 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
@@ -300,6 +300,32 @@ public class ClusterAccessor extends AbstractHelixResource {
     return OK(objectMapper.writeValueAsString(clusterTopology));
   }
 
+  @GET
+  @Path("{clusterId}/topologymap")
+  public Response getClusterTopologyMap(@PathParam("clusterId") String clusterId) {
+    HelixAdmin admin = getHelixAdmin();
+    Map<String, List<String>> topologyMap;
+    try {
+      topologyMap = admin.getClusterTopology(clusterId).getTopologyMap();
+    } catch (HelixException ex) {
+      return badRequest(ex.getMessage());
+    }
+    return JSONRepresentation(topologyMap);
+  }
+
+  @GET
+  @Path("{clusterId}/faultzonemap")
+  public Response getClusterFaultZoneMap(@PathParam("clusterId") String clusterId) {
+    HelixAdmin admin = getHelixAdmin();
+    Map<String, List<String>> faultZoneMap;
+    try {
+      faultZoneMap = admin.getClusterTopology(clusterId).getFaultZoneMap();
+    } catch (HelixException ex) {
+      return badRequest(ex.getMessage());
+    }
+    return JSONRepresentation(faultZoneMap);
+  }
+
   @POST
   @Path("{clusterId}/configs")
   public Response updateClusterConfig(
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/server/AbstractTestClass.java b/helix-rest/src/test/java/org/apache/helix/rest/server/AbstractTestClass.java
index 347be89..5e73c37 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/server/AbstractTestClass.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/server/AbstractTestClass.java
@@ -473,8 +473,9 @@ public class AbstractTestClass extends JerseyTestNg.ContainerPerClassTest {
     final Response response = webTarget.request().get();
     Assert.assertEquals(response.getStatus(), expectedReturnStatus);
 
-    // NOT_FOUND will throw text based html
-    if (expectedReturnStatus != Response.Status.NOT_FOUND.getStatusCode()) {
+    // NOT_FOUND and BAD_REQUEST will throw text based html
+    if (expectedReturnStatus != Response.Status.NOT_FOUND.getStatusCode()
+        && expectedReturnStatus != Response.Status.BAD_REQUEST.getStatusCode()) {
       Assert.assertEquals(response.getMediaType().getType(), "application");
     } else {
       Assert.assertEquals(response.getMediaType().getType(), "text");
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 3dfb883..06a02c1 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
@@ -23,6 +23,7 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -121,6 +122,102 @@ public class TestClusterAccessor extends AbstractTestClass {
   }
 
   @Test(dependsOnMethods = "testGetClusterTopology")
+  public void testGetClusterTopologyAndFaultZoneMap() throws IOException {
+    System.out.println("Start test :" + TestHelper.getTestMethodName());
+    String topologyMapUrlBase = "clusters/TestCluster_1/topologymap/";
+    String faultZoneUrlBase = "clusters/TestCluster_1/faultzonemap/";
+
+    // test invalid case where instance config and cluster topology have not been set.
+    get(topologyMapUrlBase, null, Response.Status.BAD_REQUEST.getStatusCode(), true);
+    get(faultZoneUrlBase, null, Response.Status.BAD_REQUEST.getStatusCode(), true);
+
+    String cluster = "TestCluster_1";
+    for (int i = 0; i < 5; i++) {
+      String instance = cluster + "localhost_129" + String.valueOf(18 + i);
+      HelixDataAccessor helixDataAccessor = new ZKHelixDataAccessor(cluster, _baseAccessor);
+      InstanceConfig instanceConfig =
+          helixDataAccessor.getProperty(helixDataAccessor.keyBuilder().instanceConfig(instance));
+      instanceConfig.setDomain("helixZoneId=zone0,instance=" + instance);
+      helixDataAccessor
+          .setProperty(helixDataAccessor.keyBuilder().instanceConfig(instance), instanceConfig);
+    }
+
+    for (int i = 0; i < 5; i++) {
+      String instance = cluster + "localhost_129" + String.valueOf(23 + i);
+      HelixDataAccessor helixDataAccessor = new ZKHelixDataAccessor(cluster, _baseAccessor);
+      InstanceConfig instanceConfig =
+          helixDataAccessor.getProperty(helixDataAccessor.keyBuilder().instanceConfig(instance));
+      instanceConfig.setDomain("helixZoneId=zone1,instance=" + instance);
+      helixDataAccessor
+          .setProperty(helixDataAccessor.keyBuilder().instanceConfig(instance), instanceConfig);
+    }
+
+    // test invalid case where instance config is set, but cluster topology has not been set.
+    get(topologyMapUrlBase, null, Response.Status.BAD_REQUEST.getStatusCode(), true);
+    get(faultZoneUrlBase, null, Response.Status.BAD_REQUEST.getStatusCode(), true);
+
+    ClusterConfig configDelta = new ClusterConfig(cluster);
+    configDelta.getRecord().setSimpleField("TOPOLOGY", "/helixZoneId/instance");
+    updateClusterConfigFromRest(cluster, configDelta, Command.update);
+
+    //get valid cluster topology map
+    String topologyMapDef = get(topologyMapUrlBase, null, Response.Status.OK.getStatusCode(), true);
+    Map<String, Object> topologyMap =
+        OBJECT_MAPPER.readValue(topologyMapDef, new TypeReference<HashMap<String, Object>>() {
+        });
+    Assert.assertEquals(topologyMap.size(), 2);
+    Assert.assertTrue(topologyMap.get("/helixZoneId:zone0") instanceof List);
+    List<String> instances = (List<String>) topologyMap.get("/helixZoneId:zone0");
+    Assert.assertEquals(instances.size(), 5);
+    Assert.assertTrue(instances.containsAll(new HashSet<>(Arrays
+        .asList("/instance:TestCluster_1localhost_12918",
+            "/instance:TestCluster_1localhost_12919",
+            "/instance:TestCluster_1localhost_12920",
+            "/instance:TestCluster_1localhost_12921",
+            "/instance:TestCluster_1localhost_12922"))));
+
+    Assert.assertTrue(topologyMap.get("/helixZoneId:zone1") instanceof List);
+    instances = (List<String>) topologyMap.get("/helixZoneId:zone1");
+    Assert.assertEquals(instances.size(), 5);
+    Assert.assertTrue(instances.containsAll(new HashSet<>(Arrays
+        .asList("/instance:TestCluster_1localhost_12923",
+            "/instance:TestCluster_1localhost_12924",
+            "/instance:TestCluster_1localhost_12925",
+            "/instance:TestCluster_1localhost_12926",
+            "/instance:TestCluster_1localhost_12927"))));
+
+    configDelta = new ClusterConfig(cluster);
+    configDelta.getRecord().setSimpleField("FAULT_ZONE_TYPE", "helixZoneId");
+    updateClusterConfigFromRest(cluster, configDelta, Command.update);
+
+    //get valid cluster fault zone map
+    String faultZoneMapDef = get(faultZoneUrlBase, null, Response.Status.OK.getStatusCode(), true);
+    Map<String, Object> faultZoneMap =
+        OBJECT_MAPPER.readValue(faultZoneMapDef, new TypeReference<HashMap<String, Object>>() {
+        });
+    Assert.assertEquals(faultZoneMap.size(), 2);
+    Assert.assertTrue(faultZoneMap.get("/helixZoneId:zone0") instanceof List);
+    instances = (List<String>) faultZoneMap.get("/helixZoneId:zone0");
+    Assert.assertEquals(instances.size(), 5);
+    Assert.assertTrue(instances.containsAll(new HashSet<>(Arrays
+        .asList("/instance:TestCluster_1localhost_12918",
+            "/instance:TestCluster_1localhost_12919",
+            "/instance:TestCluster_1localhost_12920",
+            "/instance:TestCluster_1localhost_12921",
+            "/instance:TestCluster_1localhost_12922"))));
+
+    Assert.assertTrue(faultZoneMap.get("/helixZoneId:zone1") instanceof List);
+    instances = (List<String>) faultZoneMap.get("/helixZoneId:zone1");
+    Assert.assertEquals(instances.size(), 5);
+    Assert.assertTrue(instances.containsAll(new HashSet<>(Arrays
+        .asList("/instance:TestCluster_1localhost_12923",
+            "/instance:TestCluster_1localhost_12924",
+            "/instance:TestCluster_1localhost_12925",
+            "/instance:TestCluster_1localhost_12926",
+            "/instance:TestCluster_1localhost_12927"))));
+  }
+
+  @Test(dependsOnMethods = "testGetClusterTopologyAndFaultZoneMap")
   public void testAddConfigFields() throws IOException {
     System.out.println("Start test :" + TestHelper.getTestMethodName());
     String cluster = _clusters.iterator().next();