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);
+ }
}