You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@helix.apache.org by hu...@apache.org on 2020/06/13 04:05:20 UTC

[helix] branch master updated: Implement getStat in ZookeeperAccessor (#1089)

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

hulee 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 406802f  Implement getStat in ZookeeperAccessor (#1089)
406802f is described below

commit 406802faada838b6ec74b04f83d2981f92510d95
Author: Hunter Lee <hu...@linkedin.com>
AuthorDate: Fri Jun 12 21:05:10 2020 -0700

    Implement getStat in ZookeeperAccessor (#1089)
    
    Helix REST doesn't provide a way for users to get the Stat object for a ZNode. This commit enables this with a getStat command. Also, all getData() endpoints embed the stat fields by default.
---
 .../java/org/apache/helix/manager/zk/ZKUtil.java   | 37 +++++++++++++++
 .../resources/zookeeper/ZooKeeperAccessor.java     | 53 +++++++++++++++++-----
 .../helix/rest/server/TestZooKeeperAccessor.java   | 41 +++++++++++++++--
 3 files changed, 117 insertions(+), 14 deletions(-)

diff --git a/helix-core/src/main/java/org/apache/helix/manager/zk/ZKUtil.java b/helix-core/src/main/java/org/apache/helix/manager/zk/ZKUtil.java
index 647f3c2..a9cb964 100644
--- a/helix-core/src/main/java/org/apache/helix/manager/zk/ZKUtil.java
+++ b/helix-core/src/main/java/org/apache/helix/manager/zk/ZKUtil.java
@@ -22,7 +22,9 @@ package org.apache.helix.manager.zk;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import org.apache.helix.HelixException;
 import org.apache.helix.InstanceType;
@@ -632,4 +634,39 @@ public final class ZKUtil {
     return DedicatedZkClientFactory.getInstance()
         .buildZkClient(new HelixZkClient.ZkConnectionConfig(zkAddr), clientConfig);
   }
+
+  /**
+   * Convert Stat fields into a Map.
+   *     private long czxid;
+   *     private long mzxid;
+   *     private long ctime;
+   *     private long mtime;
+   *     private int version;
+   *     private int cversion;
+   *     private int aversion;
+   *     private long ephemeralOwner;
+   *     private int dataLength;
+   *     private int numChildren;
+   *     private long pzxid;
+   * @param stat
+   * @return
+   */
+  public static Map<String, String> fromStatToMap(Stat stat) {
+    if (stat == null) {
+      throw new HelixException("Stat cannot be null!");
+    }
+    Map<String, String> statMap = new HashMap<>();
+    statMap.put("czxid", Long.toString(stat.getCzxid()));
+    statMap.put("mzxid", Long.toString(stat.getMzxid()));
+    statMap.put("ctime", Long.toString(stat.getCtime()));
+    statMap.put("mtime", Long.toString(stat.getMtime()));
+    statMap.put("version", Integer.toString(stat.getVersion()));
+    statMap.put("cversion", Integer.toString(stat.getCversion()));
+    statMap.put("aversion", Integer.toString(stat.getAversion()));
+    statMap.put("ephemeralOwner", Long.toString(stat.getEphemeralOwner()));
+    statMap.put("dataLength", Integer.toString(stat.getDataLength()));
+    statMap.put("numChildren", Integer.toString(stat.getNumChildren()));
+    statMap.put("pzxid", Long.toString(stat.getPzxid()));
+    return statMap;
+  }
 }
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/server/resources/zookeeper/ZooKeeperAccessor.java b/helix-rest/src/main/java/org/apache/helix/rest/server/resources/zookeeper/ZooKeeperAccessor.java
index a4aefe5..b73c9bb 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/server/resources/zookeeper/ZooKeeperAccessor.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/server/resources/zookeeper/ZooKeeperAccessor.java
@@ -21,6 +21,7 @@ package org.apache.helix.rest.server.resources.zookeeper;
 
 import java.util.List;
 import java.util.Map;
+import javax.ws.rs.DefaultValue;
 import javax.ws.rs.GET;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
@@ -32,10 +33,12 @@ import com.google.common.base.Enums;
 import com.google.common.collect.ImmutableMap;
 import org.apache.helix.AccessOption;
 import org.apache.helix.BaseDataAccessor;
+import org.apache.helix.manager.zk.ZKUtil;
 import org.apache.helix.msdcommon.util.ZkValidationUtil;
 import org.apache.helix.rest.common.ContextPropertyKeys;
 import org.apache.helix.rest.server.ServerContext;
 import org.apache.helix.rest.server.resources.AbstractResource;
+import org.apache.zookeeper.data.Stat;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -50,7 +53,11 @@ public class ZooKeeperAccessor extends AbstractResource {
   private BaseDataAccessor<byte[]> _zkBaseDataAccessor;
 
   public enum ZooKeeperCommand {
-    exists, getBinaryData, getStringData, getChildren
+    exists,
+    getBinaryData,
+    getStringData,
+    getChildren,
+    getStat
   }
 
   @GET
@@ -85,6 +92,8 @@ public class ZooKeeperAccessor extends AbstractResource {
         return getStringData(_zkBaseDataAccessor, path);
       case getChildren:
         return getChildren(_zkBaseDataAccessor, path);
+      case getStat:
+        return getStat(_zkBaseDataAccessor, path);
       default:
         String errMsg = "Unsupported command: " + commandStr;
         LOG.error(errMsg);
@@ -105,15 +114,17 @@ public class ZooKeeperAccessor extends AbstractResource {
   }
 
   /**
-   * Returns a response containing the binary data.
+   * Returns a response containing the binary data and Stat.
    * @param zkBaseDataAccessor
    * @param path
    * @return
    */
   private Response getBinaryData(BaseDataAccessor<byte[]> zkBaseDataAccessor, String path) {
-    byte[] bytes = readBinaryDataFromZK(zkBaseDataAccessor, path);
-    Map<String, byte[]> binaryResult =
-        ImmutableMap.of(ZooKeeperCommand.getBinaryData.name(), bytes);
+    Stat stat = new Stat();
+    byte[] bytes = readBinaryDataFromZK(zkBaseDataAccessor, path, stat);
+    Map<String, Object> binaryResult = ImmutableMap
+        .of(ZooKeeperCommand.getBinaryData.name(), bytes, ZooKeeperCommand.getStat.name(),
+            ZKUtil.fromStatToMap(stat));
     // Note: this serialization (using ObjectMapper) will convert this byte[] into
     // a Base64 String! The REST client (user) must convert the resulting String back into
     // a byte[] using Base64.
@@ -121,15 +132,17 @@ public class ZooKeeperAccessor extends AbstractResource {
   }
 
   /**
-   * Returns a response containing the string data.
+   * Returns a response containing the string data and Stat.
    * @param zkBaseDataAccessor
    * @param path
    * @return
    */
   private Response getStringData(BaseDataAccessor<byte[]> zkBaseDataAccessor, String path) {
-    byte[] bytes = readBinaryDataFromZK(zkBaseDataAccessor, path);
-    Map<String, String> stringResult =
-        ImmutableMap.of(ZooKeeperCommand.getStringData.name(), new String(bytes));
+    Stat stat = new Stat();
+    byte[] bytes = readBinaryDataFromZK(zkBaseDataAccessor, path, stat);
+    Map<String, Object> stringResult = ImmutableMap
+        .of(ZooKeeperCommand.getStringData.name(), new String(bytes),
+            ZooKeeperCommand.getStat.name(), ZKUtil.fromStatToMap(stat));
     return JSONRepresentation(stringResult);
   }
 
@@ -139,9 +152,10 @@ public class ZooKeeperAccessor extends AbstractResource {
    * @param path
    * @return
    */
-  private byte[] readBinaryDataFromZK(BaseDataAccessor<byte[]> zkBaseDataAccessor, String path) {
+  private byte[] readBinaryDataFromZK(BaseDataAccessor<byte[]> zkBaseDataAccessor, String path,
+      Stat stat) {
     if (zkBaseDataAccessor.exists(path, AccessOption.PERSISTENT)) {
-      return zkBaseDataAccessor.get(path, null, AccessOption.PERSISTENT);
+      return zkBaseDataAccessor.get(path, stat, AccessOption.PERSISTENT);
     } else {
       throw new WebApplicationException(Response.status(Response.Status.NOT_FOUND)
           .entity(String.format("The ZNode at path %s does not exist!", path)).build());
@@ -165,6 +179,23 @@ public class ZooKeeperAccessor extends AbstractResource {
     }
   }
 
+  /**
+   * Returns the ZNode Stat object given the path.
+   * @param zkBaseDataAccessor
+   * @param path
+   * @return
+   */
+  private Response getStat(BaseDataAccessor<byte[]> zkBaseDataAccessor, String path) {
+    Stat stat = zkBaseDataAccessor.getStat(path, AccessOption.PERSISTENT);
+    if (stat == null) {
+      throw new WebApplicationException(Response.status(Response.Status.NOT_FOUND)
+          .entity(String.format("The ZNode at path %s does not exist!", path)).build());
+    }
+    Map<String, String> result = ZKUtil.fromStatToMap(stat);
+    result.put("path", path);
+    return JSONRepresentation(result);
+  }
+
   private ZooKeeperCommand getZooKeeperCommandIfPresent(String command) {
     return Enums.getIfPresent(ZooKeeperCommand.class, command).orNull();
   }
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/server/TestZooKeeperAccessor.java b/helix-rest/src/test/java/org/apache/helix/rest/server/TestZooKeeperAccessor.java
index 23337ba..7d18c25 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/server/TestZooKeeperAccessor.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/server/TestZooKeeperAccessor.java
@@ -28,10 +28,12 @@ import java.util.Map;
 import javax.ws.rs.core.Response;
 
 import org.apache.helix.AccessOption;
+import org.apache.helix.manager.zk.ZKUtil;
 import org.apache.helix.manager.zk.ZkBaseDataAccessor;
 import org.apache.helix.rest.server.util.JerseyUriRequestBuilder;
 import org.apache.helix.zookeeper.zkclient.exception.ZkMarshallingError;
 import org.apache.helix.zookeeper.zkclient.serialize.ZkSerializer;
+import org.apache.zookeeper.data.Stat;
 import org.testng.Assert;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
@@ -105,25 +107,32 @@ public class TestZooKeeperAccessor extends AbstractTestClass {
 
     // Now write data and test
     _testBaseDataAccessor.create(path, content.getBytes(), AccessOption.PERSISTENT);
+    // Get the stat object
+    Stat expectedStat = _testBaseDataAccessor.getStat(path, AccessOption.PERSISTENT);
+    String getStatKey = "getStat";
 
     // Test getStringData
     String getStringDataKey = "getStringData";
     data = new JerseyUriRequestBuilder("zookeeper{}?command=getStringData").format(path)
         .isBodyReturnExpected(true).get(this);
-    Map<String, String> stringResult = OBJECT_MAPPER.readValue(data, Map.class);
+    Map<String, Object> stringResult = OBJECT_MAPPER.readValue(data, Map.class);
     Assert.assertTrue(stringResult.containsKey(getStringDataKey));
     Assert.assertEquals(stringResult.get(getStringDataKey), content);
+    Assert.assertTrue(stringResult.containsKey(getStatKey));
+    Assert.assertEquals(stringResult.get(getStatKey), ZKUtil.fromStatToMap(expectedStat));
 
     // Test getBinaryData
     String getBinaryDataKey = "getBinaryData";
     data = new JerseyUriRequestBuilder("zookeeper{}?command=getBinaryData").format(path)
         .isBodyReturnExpected(true).get(this);
-    Map<String, String> binaryResult = OBJECT_MAPPER.readValue(data, Map.class);
+    Map<String, Object> binaryResult = OBJECT_MAPPER.readValue(data, Map.class);
     Assert.assertTrue(binaryResult.containsKey(getBinaryDataKey));
     // Note: The response's byte array is encoded into a String using Base64 (for safety),
     // so the user must decode with Base64 to get the original byte array back
-    byte[] decodedBytes = Base64.getDecoder().decode(binaryResult.get(getBinaryDataKey));
+    byte[] decodedBytes = Base64.getDecoder().decode((String) binaryResult.get(getBinaryDataKey));
     Assert.assertEquals(decodedBytes, content.getBytes());
+    Assert.assertTrue(binaryResult.containsKey(getStatKey));
+    Assert.assertEquals(binaryResult.get(getStatKey), ZKUtil.fromStatToMap(expectedStat));
 
     // Clean up
     _testBaseDataAccessor.remove(path, AccessOption.PERSISTENT);
@@ -157,4 +166,30 @@ public class TestZooKeeperAccessor extends AbstractTestClass {
     // Clean up
     _testBaseDataAccessor.remove(path, AccessOption.PERSISTENT);
   }
+
+  @Test
+  public void testGetStat() throws IOException {
+    String path = "/path/getStat";
+
+    // Make sure it returns a NOT FOUND if there is no ZNode
+    String data = new JerseyUriRequestBuilder("zookeeper{}?command=getStat").format(path)
+        .isBodyReturnExpected(false)
+        .expectedReturnStatusCode(Response.Status.NOT_FOUND.getStatusCode()).get(this);
+
+    // Create a test ZNode (ephemeral)
+    _testBaseDataAccessor.create(path, null, AccessOption.PERSISTENT);
+    Stat stat = _testBaseDataAccessor.getStat(path, AccessOption.PERSISTENT);
+    Map<String, String> expectedFields = ZKUtil.fromStatToMap(stat);
+    expectedFields.put("path", path);
+
+    // Verify with the REST endpoint
+    data = new JerseyUriRequestBuilder("zookeeper{}?command=getStat").format(path)
+        .isBodyReturnExpected(true).get(this);
+    Map<String, String> result = OBJECT_MAPPER.readValue(data, HashMap.class);
+
+    Assert.assertEquals(result, expectedFields);
+
+    // Clean up
+    _testBaseDataAccessor.remove(path, AccessOption.PERSISTENT);
+  }
 }