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/04/01 22:46:55 UTC

[helix] branch zooscalability updated (71171a8 -> be20362)

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

hulee pushed a change to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git.


    omit 71171a8  Fix getClusters() in ZKHelixAdmin for multi-zk mode (#916)
    omit a154593  Make multiZkEnabled configurable in HelixRestNamespace (#915)
    omit e473cec  Make Helix REST realm-aware (#908)
    omit 30ea659  Use Java Generics and inheritance to reduce duplicate code in Helix API Builders (#899)
    omit f92b5a5  Fix setRoutingData boolean handling; fix leader forwarding url construction (#902)
    omit 1faaaca  Add integration tests for Helix Java APIs (#892)
    omit c8998f7  Make ZkUtil realm-aware (#896)
    omit 9a4f308  Make ZkBucketDataAccessor realm-aware (#894)
    omit 054e107  Update listClusters() in ZkHelixAdmin (#895)
    omit e3a3385  Reformat ZkBaseDataAccessor (#893)
    omit 57ce75c  Make ZKHelixAdmin and ZKHelixManager Realm-aware (#846)
    omit 1085e7d  Make ZkBaseDataAccessor realm-aware (#855)
    omit 6ca403b  Make ZkHelixClusterVerifier and its child classes realm-aware (#867)
    omit 0bb4150  Make ZkCacheBaseDataAccessor and ZkHelixPropertyStore realm-aware (#863)
    omit a8111bd  Make ClusterSetup realm-aware (#861)
    omit cb27e26  Add rerunFailingTestsCount config to surefire-plugin (#865)
    omit afd1524  Instrument ConfigAccessor's constructors (#856)
    omit 0709544  Make RealmAwareZkClient implementations use HttpRoutingDataReader for routing data (#819)
    omit 8e7c5e1  Implement setRoutingData for MetadataStoreDirectoryService (#844)
    omit 02f2a90  Make ConfigAccessor and ZkUtil realm-aware (#838)
    omit 06450c3  Add FederatedZkClient (#789)
    omit 105dc0d  Make MSDS endpoint configurable for HttpRoutingDataReader (#836)
    omit 9e49246  Improve MetadataStoreDirectoryAccessor endpoints and fix bugs in ZkRoutingDataReader/Writer
    omit d607e99  Add SharedZkClient/InnerSharedZkClient implementation (#796)
    omit 082f2df  Update bump-up.command and ivy imports (#824)
    omit 388d0c0  Fix InvalidRoutingData error message in tests (#821)
    omit aa1f484  Implement request forwarding for ZkRoutingDataWriter (#788)
    omit 3f29468  Add getShardingKeyInPath to MetadataStoreRoutingData (#817)
    omit 7e3688f  Add HttpRoutingDataReader (#775)
    omit ae53ed2  [helix-rest] Add endpoint to get namespace routing data (#799)
    omit 3e28ad6  Add REST read endpoints to helix-rest for metadata store directory (#761)
    omit 53ffe74  Add DedicatedZkClient and update DedicatedZkClientFactory (#765)
    omit c3685a0  Fix tests in apache/zooscalability and rebase from apache/master (#787)
    omit 889a9b8  Create metadata-store-directory-common module (#771)
    omit cfba5f8  Add validation logic to MSD write operations (#759)
    omit 0ab3a1e  Add RealmAwareZkClient and RealmAwareZkClientFactory interfaces (#745)
    omit 3e73a82  Add write REST endpoints to helix rest for metadata store directory (#757)
    omit 33962f1  Implement getAllMappingUnderPath and getMetadataStoreRealm in ZkMetadataStoreDirectory
    omit bc67f8b  Rebase ZooScalability from upstream master
    omit ff93b28  Add REST read endpoints to Helix Rest to provide resource access to metadata store directory (#744)
    omit ab62683  Add MetadataStoreRoutingDataWriter with DistributedLeaderElection (#727)
    omit eae0416  Add TrieRoutingData constructor (#731)
    omit 899e04e  Add MetadataStoreDirectory and ZkMetadataStoreDirectory (#720)
    omit 9984133  Add MetadataStoreRoutingDataReader interface and ZkRoutingDataReader class to helix-rest (#714)
    omit cc7846c  Add MockMetadataStoreDirectoryServer (#719)
    omit 952809a  Upgrade ZkTestBase with multi-ZK support in helix-core (#712)
    omit 3d70668  Upgrade AbstractTestClass with multi-ZK support in helix-rest (#717)
    omit c1a3132  Enable two test runs with multiZk system property (#710)
    omit f2540fd  Add MetadataStoreRoutingData interface and TrieRoutingData class to helix-rest
     add d109c64  Fix the concurrent modification error happens during the HelixManager initHandlers() call (#904)
     add 5e529ad  Fix TestZNRecordSerializeWriteSizeLimit (#911)
     add 4355430  Refresh live instance while fetching the current state information in the RoutingTableProvider. (#920)
     new 3b8b621  Add MetadataStoreRoutingData interface and TrieRoutingData class to helix-rest
     new fac1d05  Enable two test runs with multiZk system property (#710)
     new 6d09be6  Upgrade AbstractTestClass with multi-ZK support in helix-rest (#717)
     new eda7677  Upgrade ZkTestBase with multi-ZK support in helix-core (#712)
     new 4b3b54a  Add MockMetadataStoreDirectoryServer (#719)
     new dda53d1  Add MetadataStoreRoutingDataReader interface and ZkRoutingDataReader class to helix-rest (#714)
     new 4906b4c  Add MetadataStoreDirectory and ZkMetadataStoreDirectory (#720)
     new 9bf2269  Add TrieRoutingData constructor (#731)
     new fbeefcf  Add MetadataStoreRoutingDataWriter with DistributedLeaderElection (#727)
     new 7c3c946  Add REST read endpoints to Helix Rest to provide resource access to metadata store directory (#744)
     new fb579ba  Rebase ZooScalability from upstream master
     new ba99639  Implement getAllMappingUnderPath and getMetadataStoreRealm in ZkMetadataStoreDirectory
     new 3c54e43  Add write REST endpoints to helix rest for metadata store directory (#757)
     new e429c52  Add RealmAwareZkClient and RealmAwareZkClientFactory interfaces (#745)
     new 59ca671  Add validation logic to MSD write operations (#759)
     new b633ddd  Create metadata-store-directory-common module (#771)
     new fe9c576  Fix tests in apache/zooscalability and rebase from apache/master (#787)
     new c132ace  Add DedicatedZkClient and update DedicatedZkClientFactory (#765)
     new ac4be5a  Add REST read endpoints to helix-rest for metadata store directory (#761)
     new 7cc4841  [helix-rest] Add endpoint to get namespace routing data (#799)
     new 93f3d92  Add HttpRoutingDataReader (#775)
     new 33519e0  Add getShardingKeyInPath to MetadataStoreRoutingData (#817)
     new 7442ef8  Implement request forwarding for ZkRoutingDataWriter (#788)
     new c3f9ed9  Fix InvalidRoutingData error message in tests (#821)
     new 6e5e3b3  Update bump-up.command and ivy imports (#824)
     new edda273  Add SharedZkClient/InnerSharedZkClient implementation (#796)
     new 2a0214a  Improve MetadataStoreDirectoryAccessor endpoints and fix bugs in ZkRoutingDataReader/Writer
     new 3c4c248  Make MSDS endpoint configurable for HttpRoutingDataReader (#836)
     new 4b28762  Add FederatedZkClient (#789)
     new 69c9ce3  Make ConfigAccessor and ZkUtil realm-aware (#838)
     new b788bf5  Implement setRoutingData for MetadataStoreDirectoryService (#844)
     new 0e7ab0e  Make RealmAwareZkClient implementations use HttpRoutingDataReader for routing data (#819)
     new 1ee2712  Instrument ConfigAccessor's constructors (#856)
     new 3796827  Add rerunFailingTestsCount config to surefire-plugin (#865)
     new dd4c383  Make ClusterSetup realm-aware (#861)
     new 8d47269  Make ZkCacheBaseDataAccessor and ZkHelixPropertyStore realm-aware (#863)
     new de2a100  Make ZkHelixClusterVerifier and its child classes realm-aware (#867)
     new 4effd7b  Make ZkBaseDataAccessor realm-aware (#855)
     new efcc852  Make ZKHelixAdmin and ZKHelixManager Realm-aware (#846)
     new c3bdbae  Reformat ZkBaseDataAccessor (#893)
     new 4565d88  Update listClusters() in ZkHelixAdmin (#895)
     new 9ae4b5f  Make ZkBucketDataAccessor realm-aware (#894)
     new 873a2a4  Make ZkUtil realm-aware (#896)
     new 859646f  Add integration tests for Helix Java APIs (#892)
     new a1f58f4  Fix setRoutingData boolean handling; fix leader forwarding url construction (#902)
     new 503e589  Use Java Generics and inheritance to reduce duplicate code in Helix API Builders (#899)
     new c7cdf98  Make Helix REST realm-aware (#908)
     new cf7364a  Make multiZkEnabled configurable in HelixRestNamespace (#915)
     new be20362  Fix getClusters() in ZKHelixAdmin for multi-zk mode (#916)

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (71171a8)
            \
             N -- N -- N   refs/heads/zooscalability (be20362)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 49 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../apache/helix/manager/zk/ZKHelixManager.java    |  11 +-
 .../apache/helix/spectator/RoutingDataCache.java   |  11 ++
 .../TestRoutingTableProviderFromCurrentStates.java | 174 +++++++++++++++++++--
 ...andleNewSession.java => TestHandleSession.java} |  94 ++++++++++-
 .../TestZNRecordSerializeWriteSizeLimit.java       | 125 ++++++++-------
 5 files changed, 326 insertions(+), 89 deletions(-)
 rename helix-core/src/test/java/org/apache/helix/manager/zk/{TestHandleNewSession.java => TestHandleSession.java} (84%)


[helix] 30/49: Make ConfigAccessor and ZkUtil realm-aware (#838)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit 69c9ce3ee08deb60b1a9a963b9994cb83b0b2c2f
Author: Hunter Lee <hu...@linkedin.com>
AuthorDate: Mon Mar 2 20:48:10 2020 -0800

    Make ConfigAccessor and ZkUtil realm-aware (#838)
    
    To make Helix Java APIs realm-aware, we first make ConfigAccessor and ZkUtil realm-aware by instrumenting these APIs with a Builder and RealmAwareZkClients.
    
    The Builder pattern is chosen because it is a scalable option when there are a lot of configurable parameters. It makes it easy to validate the given parameters as well.
---
 .../main/java/org/apache/helix/ConfigAccessor.java | 100 +++++++++++++++++-
 .../java/org/apache/helix/manager/zk/ZKUtil.java   |  54 ++++++----
 metadata-store-directory-common/pom.xml            |   7 --
 .../helix/zookeeper/api/client/HelixZkClient.java  |   5 +-
 .../zookeeper/api/client/RealmAwareZkClient.java   | 112 +++++++++++++++++++--
 .../impl/client/RealmAwareZkClientTestBase.java    |   7 +-
 6 files changed, 239 insertions(+), 46 deletions(-)

diff --git a/helix-core/src/main/java/org/apache/helix/ConfigAccessor.java b/helix-core/src/main/java/org/apache/helix/ConfigAccessor.java
index 5d3aded..0751886 100644
--- a/helix-core/src/main/java/org/apache/helix/ConfigAccessor.java
+++ b/helix-core/src/main/java/org/apache/helix/ConfigAccessor.java
@@ -19,6 +19,7 @@ package org.apache.helix;
  * under the License.
  */
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -38,9 +39,12 @@ import org.apache.helix.model.RESTConfig;
 import org.apache.helix.model.ResourceConfig;
 import org.apache.helix.model.builder.HelixConfigScopeBuilder;
 import org.apache.helix.util.HelixUtil;
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
 import org.apache.helix.util.StringTemplate;
-import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
+import org.apache.helix.zookeeper.datamodel.ZNRecord;
+import org.apache.helix.zookeeper.impl.client.FederatedZkClient;
 import org.apache.helix.zookeeper.impl.factory.SharedZkClientFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -73,12 +77,37 @@ public class ConfigAccessor {
     // @formatter:on
   }
 
-  private final HelixZkClient _zkClient;
+  private final RealmAwareZkClient _zkClient;
   // true if ConfigAccessor was instantiated with a HelixZkClient, false otherwise
   // This is used for close() to determine how ConfigAccessor should close the underlying ZkClient
   private final boolean _usesExternalZkClient;
 
   /**
+   * Constructor that creates a realm-aware ConfigAccessor using a builder.
+   * @param builder
+   */
+  private ConfigAccessor(Builder builder) throws IOException, InvalidRoutingDataException {
+    switch (builder._realmMode) {
+      case MULTI_REALM:
+        // TODO: make sure FederatedZkClient is created correctly
+        // TODO: pass in MSDS endpoint or pass in _realmAwareZkConnectionConfig
+        String msdsEndpoint = builder._realmAwareZkConnectionConfig.getMsdsEndpoint();
+        _zkClient = new FederatedZkClient();
+        break;
+      case SINGLE_REALM:
+        // Create a HelixZkClient: Use a SharedZkClient because ConfigAccessor does not need to do
+        // ephemeral operations
+        _zkClient = SharedZkClientFactory.getInstance()
+            .buildZkClient(builder._realmAwareZkConnectionConfig.createZkConnectionConfig(),
+                builder._realmAwareZkClientConfig.createHelixZkClientConfig());
+        break;
+      default:
+        throw new HelixException("Invalid RealmMode given: " + builder._realmMode);
+    }
+    _usesExternalZkClient = false;
+  }
+
+  /**
    * Initialize an accessor with a Zookeeper client
    * Note: it is recommended to use the other constructor instead to avoid having to create a
    * HelixZkClient.
@@ -884,4 +913,71 @@ public class ConfigAccessor {
       _zkClient.close();
     }
   }
+
+  public static class Builder {
+    private String _zkAddress;
+    private RealmAwareZkClient.RealmMode _realmMode;
+    private RealmAwareZkClient.RealmAwareZkConnectionConfig _realmAwareZkConnectionConfig;
+    private RealmAwareZkClient.RealmAwareZkClientConfig _realmAwareZkClientConfig;
+
+    public Builder() {
+    }
+
+    public Builder setZkAddress(String zkAddress) {
+      _zkAddress = zkAddress;
+      return this;
+    }
+
+    public Builder setRealmMode(RealmAwareZkClient.RealmMode realmMode) {
+      _realmMode = realmMode;
+      return this;
+    }
+
+    public Builder setRealmAwareZkConnectionConfig(
+        RealmAwareZkClient.RealmAwareZkConnectionConfig realmAwareZkConnectionConfig) {
+      _realmAwareZkConnectionConfig = realmAwareZkConnectionConfig;
+      return this;
+    }
+
+    public Builder setRealmAwareZkClientConfig(
+        RealmAwareZkClient.RealmAwareZkClientConfig realmAwareZkClientConfig) {
+      _realmAwareZkClientConfig = realmAwareZkClientConfig;
+      return this;
+    }
+
+    public ConfigAccessor build() throws Exception {
+      validate();
+      return new ConfigAccessor(this);
+    }
+
+    /**
+     * Validate the given parameters before creating an instance of ConfigAccessor.
+     */
+    private void validate() {
+      // Resolve RealmMode based on other parameters
+      boolean isZkAddressSet = _zkAddress != null && !_zkAddress.isEmpty();
+      if (_realmMode == RealmAwareZkClient.RealmMode.SINGLE_REALM && !isZkAddressSet) {
+        throw new HelixException(
+            "ConfigAccessor: RealmMode cannot be single-realm without a valid ZkAddress set!");
+      }
+      if (_realmMode == null) {
+        _realmMode = isZkAddressSet ? RealmAwareZkClient.RealmMode.SINGLE_REALM
+            : RealmAwareZkClient.RealmMode.MULTI_REALM;
+      }
+
+      // Resolve RealmAwareZkClientConfig
+      boolean isZkClientConfigSet = _realmAwareZkClientConfig != null;
+      // Resolve which clientConfig to use
+      _realmAwareZkClientConfig =
+          isZkClientConfigSet ? _realmAwareZkClientConfig.createHelixZkClientConfig()
+              : new HelixZkClient.ZkClientConfig().setZkSerializer(new ZNRecordSerializer());
+
+      // Resolve RealmAwareZkConnectionConfig
+      if (_realmAwareZkConnectionConfig == null) {
+        // If not set, create a default one
+        _realmAwareZkConnectionConfig =
+            new RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder().build();
+      }
+    }
+  }
 }
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 1c5784f..70042ff 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
@@ -23,11 +23,11 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
-import org.apache.helix.BaseDataAccessor;
 import org.apache.helix.HelixException;
 import org.apache.helix.InstanceType;
 import org.apache.helix.PropertyPathBuilder;
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.apache.helix.zookeeper.impl.factory.DedicatedZkClientFactory;
 import org.apache.helix.zookeeper.zkclient.DataUpdater;
@@ -36,6 +36,7 @@ import org.apache.zookeeper.data.Stat;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+
 /**
  * Using this ZKUtil class for production purposes is NOT recommended since a lot of the static
  * methods require a ZkClient instance to be passed in.
@@ -65,7 +66,7 @@ public final class ZKUtil {
     return result;
   }
 
-  public static boolean isClusterSetup(String clusterName, HelixZkClient zkClient) {
+  public static boolean isClusterSetup(String clusterName, RealmAwareZkClient zkClient) {
     if (clusterName == null) {
       logger.info("Fail to check cluster setup : cluster name is null!");
       return false;
@@ -75,7 +76,7 @@ public final class ZKUtil {
       logger.info("Fail to check cluster setup : zookeeper client is null!");
       return false;
     }
-    ArrayList<String> requiredPaths = new ArrayList<String>();
+    List<String> requiredPaths = new ArrayList<>();
     requiredPaths.add(PropertyPathBuilder.idealState(clusterName));
     requiredPaths.add(PropertyPathBuilder.clusterConfig(clusterName));
     requiredPaths.add(PropertyPathBuilder.instanceConfig(clusterName));
@@ -92,15 +93,21 @@ public final class ZKUtil {
     requiredPaths.add(PropertyPathBuilder.controllerHistory(clusterName));
     boolean isValid = true;
 
-    BaseDataAccessor<Object> baseAccessor = new ZkBaseDataAccessor<Object>(zkClient);
-    boolean[] ret = baseAccessor.exists(requiredPaths, 0);
+    boolean[] ret = new boolean[requiredPaths.size()];
+    for (int i = 0; i < requiredPaths.size(); i++) {
+      try {
+        ret[i] = zkClient.exists(requiredPaths.get(i));
+      } catch (Exception e) {
+        ret[i] = false;
+      }
+    }
     StringBuilder errorMsg = new StringBuilder();
 
     for (int i = 0; i < ret.length; i++) {
       if (!ret[i]) {
         isValid = false;
-        errorMsg
-            .append(("Invalid cluster setup, missing znode path: " + requiredPaths.get(i)) + "\n");
+        errorMsg.append("Invalid cluster setup, missing znode path: ").append(requiredPaths.get(i))
+            .append("\n");
       }
     }
 
@@ -132,10 +139,10 @@ public final class ZKUtil {
     return result;
   }
 
-  public static boolean isInstanceSetup(HelixZkClient zkclient, String clusterName,
+  public static boolean isInstanceSetup(RealmAwareZkClient zkclient, String clusterName,
       String instanceName, InstanceType type) {
     if (type == InstanceType.PARTICIPANT || type == InstanceType.CONTROLLER_PARTICIPANT) {
-      ArrayList<String> requiredPaths = new ArrayList<>();
+      List<String> requiredPaths = new ArrayList<>();
       requiredPaths.add(PropertyPathBuilder.instanceConfig(clusterName, instanceName));
       requiredPaths.add(PropertyPathBuilder.instanceMessage(clusterName, instanceName));
       requiredPaths.add(PropertyPathBuilder.instanceCurrentState(clusterName, instanceName));
@@ -157,7 +164,6 @@ public final class ZKUtil {
         if (!zkclient.exists(historyPath)) {
           zkclient.createPersistent(historyPath, true);
         }
-
       }
       return isValid;
     }
@@ -181,7 +187,8 @@ public final class ZKUtil {
     }
   }
 
-  public static void createChildren(HelixZkClient client, String parentPath, List<ZNRecord> list) {
+  public static void createChildren(RealmAwareZkClient client, String parentPath,
+      List<ZNRecord> list) {
     client.createPersistent(parentPath, true);
     if (list != null) {
       for (ZNRecord record : list) {
@@ -206,7 +213,8 @@ public final class ZKUtil {
     }
   }
 
-  public static void createChildren(HelixZkClient client, String parentPath, ZNRecord nodeRecord) {
+  public static void createChildren(RealmAwareZkClient client, String parentPath,
+      ZNRecord nodeRecord) {
     client.createPersistent(parentPath, true);
 
     String id = nodeRecord.getId();
@@ -230,7 +238,8 @@ public final class ZKUtil {
     }
   }
 
-  public static void dropChildren(HelixZkClient client, String parentPath, List<ZNRecord> list) {
+  public static void dropChildren(RealmAwareZkClient client, String parentPath,
+      List<ZNRecord> list) {
     // TODO: check if parentPath exists
     if (list != null) {
       for (ZNRecord record : list) {
@@ -255,7 +264,8 @@ public final class ZKUtil {
     }
   }
 
-  public static void dropChildren(HelixZkClient client, String parentPath, ZNRecord nodeRecord) {
+  public static void dropChildren(RealmAwareZkClient client, String parentPath,
+      ZNRecord nodeRecord) {
     // TODO: check if parentPath exists
     String id = nodeRecord.getId();
     String temp = parentPath + "/" + id;
@@ -280,7 +290,7 @@ public final class ZKUtil {
     return result;
   }
 
-  public static List<ZNRecord> getChildren(HelixZkClient client, String path) {
+  public static List<ZNRecord> getChildren(RealmAwareZkClient client, String path) {
     // parent watch will be set by zkClient
     List<String> children = client.getChildren(path);
     if (children == null || children.size() == 0) {
@@ -321,7 +331,7 @@ public final class ZKUtil {
     }
   }
 
-  public static void updateIfExists(HelixZkClient client, String path, final ZNRecord record,
+  public static void updateIfExists(RealmAwareZkClient client, String path, final ZNRecord record,
       boolean mergeOnUpdate) {
     if (client.exists(path)) {
       DataUpdater<Object> updater = new DataUpdater<Object>() {
@@ -353,7 +363,7 @@ public final class ZKUtil {
     }
   }
 
-  public static void createOrMerge(HelixZkClient client, String path, final ZNRecord record,
+  public static void createOrMerge(RealmAwareZkClient client, String path, final ZNRecord record,
       final boolean persistent, final boolean mergeOnUpdate) {
     int retryCount = 0;
     while (retryCount < RETRYLIMIT) {
@@ -408,7 +418,7 @@ public final class ZKUtil {
     }
   }
 
-  public static void createOrUpdate(HelixZkClient client, String path, final ZNRecord record,
+  public static void createOrUpdate(RealmAwareZkClient client, String path, final ZNRecord record,
       final boolean persistent, final boolean mergeOnUpdate) {
     int retryCount = 0;
     while (retryCount < RETRYLIMIT) {
@@ -457,8 +467,8 @@ public final class ZKUtil {
     }
   }
 
-  public static void asyncCreateOrMerge(HelixZkClient client, String path, final ZNRecord record,
-      final boolean persistent, final boolean mergeOnUpdate) {
+  public static void asyncCreateOrMerge(RealmAwareZkClient client, String path,
+      final ZNRecord record, final boolean persistent, final boolean mergeOnUpdate) {
     try {
       if (client.exists(path)) {
         if (mergeOnUpdate) {
@@ -510,7 +520,7 @@ public final class ZKUtil {
     }
   }
 
-  public static void createOrReplace(HelixZkClient client, String path, final ZNRecord record,
+  public static void createOrReplace(RealmAwareZkClient client, String path, final ZNRecord record,
       final boolean persistent) {
     int retryCount = 0;
     while (retryCount < RETRYLIMIT) {
@@ -553,7 +563,7 @@ public final class ZKUtil {
     }
   }
 
-  public static void subtract(HelixZkClient client, final String path,
+  public static void subtract(RealmAwareZkClient client, final String path,
       final ZNRecord recordTosubtract) {
     int retryCount = 0;
     while (retryCount < RETRYLIMIT) {
diff --git a/metadata-store-directory-common/pom.xml b/metadata-store-directory-common/pom.xml
index 1b0d964..a38d287 100644
--- a/metadata-store-directory-common/pom.xml
+++ b/metadata-store-directory-common/pom.xml
@@ -33,8 +33,6 @@ under the License.
 
   <properties>
     <osgi.import>
-      org.apache.commons.cli*,
-      org.apache.commons.io*;version="[1.4,2)",
       org.slf4j*;version="[1.6,2)",
       *
     </osgi.import>
@@ -69,11 +67,6 @@ under the License.
       <version>3.8.1</version>
     </dependency>
     <dependency>
-      <groupId>commons-cli</groupId>
-      <artifactId>commons-cli</artifactId>
-      <version>1.2</version>
-    </dependency>
-    <dependency>
       <groupId>org.testng</groupId>
       <artifactId>testng</artifactId>
       <scope>test</scope>
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/HelixZkClient.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/HelixZkClient.java
index 03bf000..d9f7461 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/HelixZkClient.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/HelixZkClient.java
@@ -19,7 +19,6 @@ package org.apache.helix.zookeeper.api.client;
  * under the License.
  */
 
-import org.apache.helix.zookeeper.zkclient.ZkClient;
 import org.apache.helix.zookeeper.zkclient.serialize.BasicZkSerializer;
 import org.apache.helix.zookeeper.zkclient.serialize.PathBasedZkSerializer;
 import org.apache.helix.zookeeper.zkclient.serialize.ZkSerializer;
@@ -150,8 +149,8 @@ public interface HelixZkClient extends RealmAwareZkClient {
     }
 
     @Override
-    public ZkClientConfig setConnectInitTimeout(long _connectInitTimeout) {
-      this._connectInitTimeout = _connectInitTimeout;
+    public ZkClientConfig setConnectInitTimeout(long connectInitTimeout) {
+      this._connectInitTimeout = connectInitTimeout;
       return this;
     }
   }
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/RealmAwareZkClient.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/RealmAwareZkClient.java
index e466d36..fb10073 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/RealmAwareZkClient.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/RealmAwareZkClient.java
@@ -19,9 +19,13 @@ package org.apache.helix.zookeeper.api.client;
  * under the License.
  */
 
+import java.io.IOException;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
+import org.apache.helix.zookeeper.exception.ZkClientException;
+import org.apache.helix.zookeeper.util.HttpRoutingDataReader;
 import org.apache.helix.zookeeper.zkclient.DataUpdater;
 import org.apache.helix.zookeeper.zkclient.IZkChildListener;
 import org.apache.helix.zookeeper.zkclient.IZkDataListener;
@@ -54,9 +58,8 @@ public interface RealmAwareZkClient {
    * SINGLE_REALM: CRUD, change subscription, and EPHEMERAL CreateMode are supported.
    * MULTI_REALM: CRUD and change subscription are supported. Operations involving EPHEMERAL CreateMode will throw an UnsupportedOperationException.
    */
-  enum MODE {
-    SINGLE_REALM,
-    MULTI_REALM
+  enum RealmMode {
+    SINGLE_REALM, MULTI_REALM
   }
 
   int DEFAULT_OPERATION_TIMEOUT = Integer.MAX_VALUE;
@@ -325,16 +328,18 @@ public interface RealmAwareZkClient {
    * ZkConnection-related configs for creating an instance of RealmAwareZkClient.
    */
   class RealmAwareZkConnectionConfig {
-
     /**
      * zkRealmShardingKey: used to deduce which ZK realm this RealmAwareZkClientConfig should connect to.
-     * NOTE: this field will be ignored if MODE is MULTI_REALM!
+     * NOTE: this field will be ignored if RealmMode is MULTI_REALM!
      */
-    private final String _zkRealmShardingKey;
-    private int _sessionTimeout = DEFAULT_SESSION_TIMEOUT;
-
-    public RealmAwareZkConnectionConfig(String zkRealmShardingKey) {
-      _zkRealmShardingKey = zkRealmShardingKey;
+    private String _zkRealmShardingKey;
+    private String _msdsEndpoint;
+    private int _sessionTimeout;
+
+    private RealmAwareZkConnectionConfig(Builder builder) {
+      _zkRealmShardingKey = builder._zkRealmShardingKey;
+      _msdsEndpoint = builder._msdsEndpoint;
+      _sessionTimeout = builder._sessionTimeout;
     }
 
     @Override
@@ -373,6 +378,81 @@ public interface RealmAwareZkClient {
     public int getSessionTimeout() {
       return _sessionTimeout;
     }
+
+    public String getMsdsEndpoint() {
+      return _msdsEndpoint;
+    }
+
+    public HelixZkClient.ZkConnectionConfig createZkConnectionConfig()
+        throws IOException, InvalidRoutingDataException {
+      // Convert to a single-realm HelixZkClient's ZkConnectionConfig
+      if (_zkRealmShardingKey == null || _zkRealmShardingKey.isEmpty()) {
+        throw new ZkClientException(
+            "Cannot create ZkConnectionConfig because ZK realm sharding key is either null or empty!");
+      }
+
+      String zkAddress;
+      // Look up the ZK address for the given ZK realm sharding key
+      if (_msdsEndpoint == null || _msdsEndpoint.isEmpty()) {
+        zkAddress = HttpRoutingDataReader.getMetadataStoreRoutingData()
+            .getMetadataStoreRealm(_zkRealmShardingKey);
+      } else {
+        zkAddress = HttpRoutingDataReader.getMetadataStoreRoutingData(_msdsEndpoint)
+            .getMetadataStoreRealm(_zkRealmShardingKey);
+      }
+
+      return new HelixZkClient.ZkConnectionConfig(zkAddress).setSessionTimeout(_sessionTimeout);
+    }
+
+    public static class Builder {
+      private RealmMode _realmMode;
+      private String _zkRealmShardingKey;
+      private String _msdsEndpoint;
+      private int _sessionTimeout = DEFAULT_SESSION_TIMEOUT;
+
+      public Builder() {
+      }
+
+      public Builder setRealmMode(RealmMode mode) {
+        _realmMode = mode;
+        return this;
+      }
+
+      public Builder setZkRealmShardingKey(String shardingKey) {
+        _zkRealmShardingKey = shardingKey;
+        return this;
+      }
+
+      public Builder setMsdsEndpoint(String msdsEndpoint) {
+        _msdsEndpoint = msdsEndpoint;
+        return this;
+      }
+
+      public Builder setSessionTimeout(int sessionTimeout) {
+        _sessionTimeout = sessionTimeout;
+        return this;
+      }
+
+      public RealmAwareZkConnectionConfig build() {
+        validate();
+        return new RealmAwareZkConnectionConfig(this);
+      }
+
+      /**
+       * Validate the internal fields of the builder before creating an instance.
+       */
+      private void validate() {
+        boolean isShardingKeySet = _zkRealmShardingKey != null && !_zkRealmShardingKey.isEmpty();
+        if (_realmMode == RealmMode.MULTI_REALM && isShardingKeySet) {
+          throw new IllegalArgumentException(
+              "ZK sharding key cannot be set on multi-realm mode! Sharding key: "
+                  + _zkRealmShardingKey);
+        }
+        if (_realmMode == RealmMode.SINGLE_REALM && !isShardingKeySet) {
+          throw new IllegalArgumentException("ZK sharding key must be set on single-realm mode!");
+        }
+      }
+    }
   }
 
   /**
@@ -479,5 +559,17 @@ public interface RealmAwareZkClient {
     public long getConnectInitTimeout() {
       return _connectInitTimeout;
     }
+
+    /**
+     * Create HelixZkClient.ZkClientConfig based on RealmAwareZkClientConfig.
+     * @return
+     */
+    public HelixZkClient.ZkClientConfig createHelixZkClientConfig() {
+      return new HelixZkClient.ZkClientConfig().setZkSerializer(_zkSerializer)
+          .setMonitorType(_monitorType).setMonitorKey(_monitorKey)
+          .setMonitorInstanceName(_monitorInstanceName).setMonitorRootPathOnly(_monitorRootPathOnly)
+          .setOperationRetryTimeout(_operationRetryTimeout)
+          .setConnectInitTimeout(_connectInitTimeout);
+    }
   }
 }
diff --git a/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/RealmAwareZkClientTestBase.java b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/RealmAwareZkClientTestBase.java
index d500ce4..323d5f4 100644
--- a/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/RealmAwareZkClientTestBase.java
+++ b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/RealmAwareZkClientTestBase.java
@@ -85,8 +85,10 @@ public abstract class RealmAwareZkClientTestBase extends ZkTestBase {
         new RealmAwareZkClient.RealmAwareZkClientConfig();
 
     // Create a connection config with the invalid sharding key
+    RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder builder =
+        new RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder();
     RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig =
-        new RealmAwareZkClient.RealmAwareZkConnectionConfig(invalidShardingKey);
+        builder.setZkRealmShardingKey(invalidShardingKey).build();
 
     try {
       _realmAwareZkClient = _realmAwareZkClientFactory
@@ -98,7 +100,8 @@ public abstract class RealmAwareZkClientTestBase extends ZkTestBase {
 
     // Use a valid sharding key this time around
     String validShardingKey = ZK_SHARDING_KEY_PREFIX + "_" + 0; // Use TEST_SHARDING_KEY_0
-    connectionConfig = new RealmAwareZkClient.RealmAwareZkConnectionConfig(validShardingKey);
+    builder.setZkRealmShardingKey(validShardingKey);
+    connectionConfig = builder.build();
     _realmAwareZkClient = _realmAwareZkClientFactory
         .buildZkClient(connectionConfig, clientConfig, _metadataStoreRoutingData);
   }


[helix] 48/49: Make multiZkEnabled configurable in HelixRestNamespace (#915)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit cf7364af13c9e96258e12bc966be6910a27320b7
Author: Hunter Lee <hu...@linkedin.com>
AuthorDate: Thu Mar 26 17:54:58 2020 -0700

    Make multiZkEnabled configurable in HelixRestNamespace (#915)
    
    It was observed that we need more fine-grained control over this multiZkEnabled config because there could exists namespaces with differing modes. Because multiple namespaces may be co-deployed, we cannot simply make it a system config.
---
 .../org/apache/helix/manager/zk/ZKHelixAdmin.java  |  6 ++-
 .../helix/rest/common/HelixRestNamespace.java      | 18 ++++++++-
 .../apache/helix/rest/server/HelixRestMain.java    |  4 +-
 .../apache/helix/rest/server/HelixRestServer.java  |  3 +-
 .../apache/helix/rest/server/ServerContext.java    | 43 ++++++++++++++--------
 5 files changed, 52 insertions(+), 22 deletions(-)

diff --git a/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixAdmin.java b/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixAdmin.java
index 8cc327a..65e6ec0 100644
--- a/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixAdmin.java
+++ b/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixAdmin.java
@@ -935,8 +935,10 @@ public class ZKHelixAdmin implements HelixAdmin {
   public List<String> getClusters() {
     if (Boolean.getBoolean(SystemPropertyKeys.MULTI_ZK_ENABLED)
         || _zkClient instanceof FederatedZkClient) {
-      throw new UnsupportedOperationException(
-          "getClusters() is not supported in multi-realm mode! Use Metadata Store Directory Service instead!");
+      String errMsg =
+          "getClusters() is not supported in multi-realm mode! Use Metadata Store Directory Service instead!";
+      LOG.error(errMsg);
+      throw new UnsupportedOperationException(errMsg);
     }
 
     List<String> zkToplevelPathes = _zkClient.getChildren("/");
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/common/HelixRestNamespace.java b/helix-rest/src/main/java/org/apache/helix/rest/common/HelixRestNamespace.java
index 0632f36..29b1561 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/common/HelixRestNamespace.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/common/HelixRestNamespace.java
@@ -35,6 +35,7 @@ public class HelixRestNamespace {
     METADATA_STORE_TYPE,
     METADATA_STORE_ADDRESS,
     IS_DEFAULT,
+    MULTI_ZK_ENABLED,
     MSDS_ENDPOINT
   }
 
@@ -67,6 +68,11 @@ public class HelixRestNamespace {
   private boolean _isDefault;
 
   /**
+   * Flag indicating whether this namespace should have multi-zk feature enabled.
+   */
+  private boolean _isMultiZkEnabled;
+
+  /**
    * Endpoint for accessing MSDS for this namespace.
    */
   private String _msdsEndpoint;
@@ -77,15 +83,17 @@ public class HelixRestNamespace {
 
   public HelixRestNamespace(String name, HelixMetadataStoreType metadataStoreType,
       String metadataStoreAddress, boolean isDefault) throws IllegalArgumentException {
-    this(name, metadataStoreType, metadataStoreAddress, isDefault, null);
+    this(name, metadataStoreType, metadataStoreAddress, isDefault, false, null);
   }
 
   public HelixRestNamespace(String name, HelixMetadataStoreType metadataStoreType,
-      String metadataStoreAddress, boolean isDefault, String msdsEndpoint) {
+      String metadataStoreAddress, boolean isDefault, boolean isMultiZkEnabled,
+      String msdsEndpoint) {
     _name = name;
     _metadataStoreAddress = metadataStoreAddress;
     _metadataStoreType = metadataStoreType;
     _isDefault = isDefault;
+    _isMultiZkEnabled = isMultiZkEnabled;
     _msdsEndpoint = msdsEndpoint;
     validate();
   }
@@ -119,9 +127,15 @@ public class HelixRestNamespace {
     Map<String, String> ret = new HashMap<>();
     ret.put(HelixRestNamespaceProperty.NAME.name(), _name);
     ret.put(HelixRestNamespaceProperty.IS_DEFAULT.name(), String.valueOf(_isDefault));
+    ret.put(HelixRestNamespaceProperty.MULTI_ZK_ENABLED.name(), String.valueOf(_isMultiZkEnabled));
+    ret.put(HelixRestNamespaceProperty.MSDS_ENDPOINT.name(), String.valueOf(_msdsEndpoint));
     return ret;
   }
 
+  public boolean isMultiZkEnabled() {
+    return _isMultiZkEnabled;
+  }
+
   public String getMsdsEndpoint() {
     return _msdsEndpoint;
   }
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/server/HelixRestMain.java b/helix-rest/src/main/java/org/apache/helix/rest/server/HelixRestMain.java
index 49940c3..ffc8aed 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/server/HelixRestMain.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/server/HelixRestMain.java
@@ -151,7 +151,9 @@ public class HelixRestMain {
           HelixRestNamespace.HelixMetadataStoreType.valueOf(
               config.get(HelixRestNamespace.HelixRestNamespaceProperty.METADATA_STORE_TYPE.name())),
           config.get(HelixRestNamespace.HelixRestNamespaceProperty.METADATA_STORE_ADDRESS.name()),
-          false, config.get(HelixRestNamespace.HelixRestNamespaceProperty.MSDS_ENDPOINT.name())));
+          false, Boolean.parseBoolean(
+          config.get(HelixRestNamespace.HelixRestNamespaceProperty.MULTI_ZK_ENABLED.name())),
+          config.get(HelixRestNamespace.HelixRestNamespaceProperty.MSDS_ENDPOINT.name())));
     }
   }
 
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/server/HelixRestServer.java b/helix-rest/src/main/java/org/apache/helix/rest/server/HelixRestServer.java
index e6b5b34..cb199cb 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/server/HelixRestServer.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/server/HelixRestServer.java
@@ -149,7 +149,8 @@ public class HelixRestServer {
     // Enable the default statistical monitoring MBean for Jersey server
     cfg.property(ServerProperties.MONITORING_STATISTICS_MBEANS_ENABLED, true);
     cfg.property(ContextPropertyKeys.SERVER_CONTEXT.name(),
-        new ServerContext(namespace.getMetadataStoreAddress(), namespace.getMsdsEndpoint()));
+        new ServerContext(namespace.getMetadataStoreAddress(), namespace.isMultiZkEnabled(),
+            namespace.getMsdsEndpoint()));
     if (type == ServletType.DEFAULT_SERVLET) {
       cfg.property(ContextPropertyKeys.ALL_NAMESPACES.name(), _helixNamespaces);
     } else {
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/server/ServerContext.java b/helix-rest/src/main/java/org/apache/helix/rest/server/ServerContext.java
index 52f1738..c6632c2 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/server/ServerContext.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/server/ServerContext.java
@@ -61,6 +61,7 @@ public class ServerContext implements IZkDataListener, IZkChildListener, IZkStat
   private static final Logger LOG = LoggerFactory.getLogger(ServerContext.class);
 
   private final String _zkAddr;
+  private boolean _isMultiZkEnabled;
   private final String _msdsEndpoint;
   private volatile RealmAwareZkClient _zkClient;
 
@@ -83,16 +84,18 @@ public class ServerContext implements IZkDataListener, IZkChildListener, IZkStat
   private RealmAwareZkClient _zkClientForListener;
 
   public ServerContext(String zkAddr) {
-    this(zkAddr, null);
+    this(zkAddr, false, null);
   }
 
   /**
    * Initializes a ServerContext for this namespace.
    * @param zkAddr routing ZK address (on multi-zk mode)
+   * @param isMultiZkEnabled boolean flag for whether multi-zk mode is enabled
    * @param msdsEndpoint if given, this server context will try to read routing data from this MSDS.
    */
-  public ServerContext(String zkAddr, String msdsEndpoint) {
+  public ServerContext(String zkAddr, boolean isMultiZkEnabled, String msdsEndpoint) {
     _zkAddr = zkAddr;
+    _isMultiZkEnabled = isMultiZkEnabled;
     _msdsEndpoint = msdsEndpoint; // only applicable on multi-zk mode
 
     // We should NOT initiate _zkClient and anything that depends on _zkClient in
@@ -111,20 +114,9 @@ public class ServerContext implements IZkDataListener, IZkChildListener, IZkStat
       synchronized (this) {
         if (_zkClient == null) {
           // If the multi ZK config is enabled, use FederatedZkClient on multi-realm mode
-          if (Boolean.parseBoolean(System.getProperty(SystemPropertyKeys.MULTI_ZK_ENABLED))) {
-            LOG.info("ServerContext: initializing FederatedZkClient with routing ZK at {}!",
-                _zkAddr);
+          if (_isMultiZkEnabled || Boolean
+              .parseBoolean(System.getProperty(SystemPropertyKeys.MULTI_ZK_ENABLED))) {
             try {
-              RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder connectionConfigBuilder =
-                  new RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder();
-              // If MSDS endpoint is set for this namespace, use that instead.
-              if (_msdsEndpoint != null && !_msdsEndpoint.isEmpty()) {
-                connectionConfigBuilder.setMsdsEndpoint(_msdsEndpoint);
-              }
-              _zkClient = new FederatedZkClient(connectionConfigBuilder.build(),
-                  new RealmAwareZkClient.RealmAwareZkClientConfig()
-                      .setZkSerializer(new ZNRecordSerializer()));
-
               // Make sure the ServerContext is subscribed to routing data change so that it knows
               // when to reset ZkClient and Helix APIs
               if (_zkClientForListener == null) {
@@ -136,7 +128,20 @@ public class ServerContext implements IZkDataListener, IZkChildListener, IZkStat
               // Refresh data subscription
               _zkClientForListener.unsubscribeAll();
               _zkClientForListener.subscribeRoutingDataChanges(this, this);
+              LOG.info("ServerContext: subscribed to routing data in routing ZK at {}!", _zkAddr);
+
+              RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder connectionConfigBuilder =
+                  new RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder();
+              // If MSDS endpoint is set for this namespace, use that instead.
+              if (_msdsEndpoint != null && !_msdsEndpoint.isEmpty()) {
+                connectionConfigBuilder.setMsdsEndpoint(_msdsEndpoint);
+              }
+              _zkClient = new FederatedZkClient(connectionConfigBuilder.build(),
+                  new RealmAwareZkClient.RealmAwareZkClientConfig()
+                      .setZkSerializer(new ZNRecordSerializer()));
+              LOG.info("ServerContext: FederatedZkClient created successfully!");
             } catch (IOException | InvalidRoutingDataException | IllegalStateException e) {
+              LOG.error("Failed to create FederatedZkClient!", e);
               throw new HelixException("Failed to create FederatedZkClient!", e);
             }
           } else {
@@ -279,7 +284,13 @@ public class ServerContext implements IZkDataListener, IZkChildListener, IZkStat
 
   @Override
   public void handleDataDeleted(String dataPath) {
-    // NOP because this is covered by handleChildChange()
+    if (_zkClientForListener == null || _zkClientForListener.isClosed()) {
+      return;
+    }
+    // Resubscribe
+    _zkClientForListener.unsubscribeAll();
+    _zkClientForListener.subscribeRoutingDataChanges(this, this);
+    resetZkResources();
   }
 
   @Override


[helix] 10/49: Add REST read endpoints to Helix Rest to provide resource access to metadata store directory (#744)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit 7c3c94637e2ab87005862be7e3be4d444e7dd131
Author: Huizhi Lu <hu...@linkedin.com>
AuthorDate: Tue Feb 11 18:09:25 2020 -0800

    Add REST read endpoints to Helix Rest to provide resource access to metadata store directory (#744)
    
    Change list:
    - Add read endpoints to read sharding keys and realms
    - Add 3 unit tests to test the new endpoints in TestMetadataStoreDirectoryAccessor
---
 .../org/apache/helix/rest/common/ServletType.java  |   7 +-
 .../constant/MetadataStoreRoutingConstants.java    |  11 ++
 .../MetadataStoreDirectoryAccessor.java            | 160 +++++++++++++++++++
 .../TestMetadataStoreDirectoryAccessor.java        | 175 +++++++++++++++++++++
 4 files changed, 351 insertions(+), 2 deletions(-)

diff --git a/helix-rest/src/main/java/org/apache/helix/rest/common/ServletType.java b/helix-rest/src/main/java/org/apache/helix/rest/common/ServletType.java
index f068f95..7c77429 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/common/ServletType.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/common/ServletType.java
@@ -21,6 +21,7 @@ package org.apache.helix.rest.common;
 
 import org.apache.helix.rest.server.resources.helix.AbstractHelixResource;
 import org.apache.helix.rest.server.resources.metadata.NamespacesAccessor;
+import org.apache.helix.rest.server.resources.metadatastore.MetadataStoreDirectoryAccessor;
 import org.apache.helix.rest.server.resources.zookeeper.ZooKeeperAccessor;
 
 
@@ -32,7 +33,8 @@ public enum ServletType {
       new String[] {
           AbstractHelixResource.class.getPackage().getName(),
           NamespacesAccessor.class.getPackage().getName(),
-          ZooKeeperAccessor.class.getPackage().getName()
+          ZooKeeperAccessor.class.getPackage().getName(),
+          MetadataStoreDirectoryAccessor.class.getPackage().getName()
       }),
 
   /**
@@ -41,7 +43,8 @@ public enum ServletType {
   COMMON_SERVLET("/namespaces/%s/*",
       new String[] {
           AbstractHelixResource.class.getPackage().getName(),
-          ZooKeeperAccessor.class.getPackage().getName()
+          ZooKeeperAccessor.class.getPackage().getName(),
+          MetadataStoreDirectoryAccessor.class.getPackage().getName()
       });
 
   private final String _servletPathSpecTemplate;
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/constant/MetadataStoreRoutingConstants.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/constant/MetadataStoreRoutingConstants.java
index e4240e7..846aa30 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/constant/MetadataStoreRoutingConstants.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/constant/MetadataStoreRoutingConstants.java
@@ -27,4 +27,15 @@ public class MetadataStoreRoutingConstants {
 
   // Leader election ZNode for ZkRoutingDataWriter
   public static final String LEADER_ELECTION_ZNODE = "/_ZK_ROUTING_DATA_WRITER_LEADER";
+
+  // Field name in JSON REST response of getting metadata store realms in one namespace.
+  public static final String METADATA_STORE_REALMS = "metadataStoreRealms";
+
+  // Field name in JSON REST response of getting sharding keys in one realm.
+  public static final String SINGLE_METADATA_STORE_REALM = "metadataStoreRealm";
+
+  // Field name in JSON REST response of getting sharding keys.
+  public static final String SHARDING_KEYS = "shardingKeys";
+
+
 }
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/server/resources/metadatastore/MetadataStoreDirectoryAccessor.java b/helix-rest/src/main/java/org/apache/helix/rest/server/resources/metadatastore/MetadataStoreDirectoryAccessor.java
new file mode 100644
index 0000000..78543d6
--- /dev/null
+++ b/helix-rest/src/main/java/org/apache/helix/rest/server/resources/metadatastore/MetadataStoreDirectoryAccessor.java
@@ -0,0 +1,160 @@
+package org.apache.helix.rest.server.resources.metadatastore;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import javax.annotation.PostConstruct;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Response;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.helix.rest.common.ContextPropertyKeys;
+import org.apache.helix.rest.common.HelixRestNamespace;
+import org.apache.helix.rest.common.HelixRestUtils;
+import org.apache.helix.rest.metadatastore.MetadataStoreDirectory;
+import org.apache.helix.rest.metadatastore.ZkMetadataStoreDirectory;
+import org.apache.helix.rest.metadatastore.constant.MetadataStoreRoutingConstants;
+import org.apache.helix.rest.metadatastore.exceptions.InvalidRoutingDataException;
+import org.apache.helix.rest.server.resources.AbstractResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Provides REST endpoints for accessing metadata store directory service,
+ * which responds to read/write requests of metadata store realms, sharding keys, etc..
+ */
+@Path("")
+public class MetadataStoreDirectoryAccessor extends AbstractResource {
+  private static final Logger LOG = LoggerFactory.getLogger(MetadataStoreDirectoryAccessor.class);
+
+  private HelixRestNamespace _namespace;
+
+  // Double-checked locking for thread-safe object.
+  private MetadataStoreDirectory _metadataStoreDirectory;
+
+  @PostConstruct
+  private void postConstruct() {
+    getHelixNamespace();
+    buildMetadataStoreDirectory();
+  }
+
+  /**
+   * Gets all metadata store realms in a namespace with the endpoint.
+   *
+   * @return Json representation of all realms.
+   */
+  @GET
+  @Path("/metadata-store-realms")
+  public Response getAllMetadataStoreRealms() {
+    Map<String, Collection<String>> responseMap;
+    try {
+      Collection<String> realms =
+          _metadataStoreDirectory.getAllMetadataStoreRealms(_namespace.getName());
+
+      responseMap = new HashMap<>(1);
+      responseMap.put(MetadataStoreRoutingConstants.METADATA_STORE_REALMS, realms);
+    } catch (NoSuchElementException ex) {
+      return notFound(ex.getMessage());
+    }
+
+    return JSONRepresentation(responseMap);
+  }
+
+  /**
+   * Gets sharding keys mapped at path "HTTP GET /sharding-keys" which returns all sharding keys in
+   * a namespace, or path "HTTP GET /sharding-keys?realm={realmName}" which returns sharding keys in
+   * a realm.
+   *
+   * @param realm Query param in endpoint path
+   * @return Json representation of a map: shardingKeys -> collection of sharding keys.
+   */
+  @GET
+  @Path("/sharding-keys")
+  public Response getShardingKeys(@QueryParam("realm") String realm) {
+    Map<String, Object> responseMap;
+    Collection<String> shardingKeys;
+    try {
+      // If realm is not set in query param, the endpoint is: "/sharding-keys"
+      // to get all sharding keys in a namespace.
+      if (realm == null) {
+        shardingKeys =
+            _metadataStoreDirectory.getAllShardingKeys(_namespace.getName());
+        // To avoid allocating unnecessary resource, limit the map's capacity only for
+        // SHARDING_KEYS.
+        responseMap = new HashMap<>(1);
+      } else {
+        // For endpoint: "/sharding-keys?realm={realmName}"
+        shardingKeys = _metadataStoreDirectory
+            .getAllShardingKeysInRealm(_namespace.getName(), realm);
+        // To avoid allocating unnecessary resource, limit the map's capacity only for
+        // SHARDING_KEYS and SINGLE_METADATA_STORE_REALM.
+        responseMap = new HashMap<>(2);
+        responseMap.put(MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_REALM, realm);
+      }
+    } catch (NoSuchElementException ex) {
+      return notFound(ex.getMessage());
+    }
+
+    responseMap.put(MetadataStoreRoutingConstants.SHARDING_KEYS, shardingKeys);
+
+    return JSONRepresentation(responseMap);
+  }
+
+  private void getHelixNamespace() {
+    // A default servlet does not have context property key METADATA, so the namespace
+    // is retrieved from property ALL_NAMESPACES.
+    if (HelixRestUtils.isDefaultServlet(_servletRequest.getServletPath())) {
+      // It is safe to ignore uncheck warnings for this cast.
+      @SuppressWarnings("unchecked")
+      List<HelixRestNamespace> namespaces = (List<HelixRestNamespace>) _application.getProperties()
+          .get(ContextPropertyKeys.ALL_NAMESPACES.name());
+      for (HelixRestNamespace ns : namespaces) {
+        if (HelixRestNamespace.DEFAULT_NAMESPACE_NAME.equals(ns.getName())) {
+          _namespace = ns;
+          break;
+        }
+      }
+    } else {
+      // Get namespace from property METADATA for a common servlet.
+      _namespace = (HelixRestNamespace) _application.getProperties()
+          .get(ContextPropertyKeys.METADATA.name());
+    }
+  }
+
+  private void buildMetadataStoreDirectory() {
+    Map<String, String> routingZkAddressMap =
+        ImmutableMap.of(_namespace.getName(), _namespace.getMetadataStoreAddress());
+    try {
+      _metadataStoreDirectory = new ZkMetadataStoreDirectory(routingZkAddressMap);
+    } catch (InvalidRoutingDataException ex) {
+      // In this case, the InvalidRoutingDataException should not happen because routing
+      // ZK address is always valid here.
+      LOG.warn("Unable to create metadata store directory for routing ZK address: {}",
+          routingZkAddressMap, ex);
+    }
+  }
+}
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/server/resources/zookeeper/TestMetadataStoreDirectoryAccessor.java b/helix-rest/src/test/java/org/apache/helix/rest/server/resources/zookeeper/TestMetadataStoreDirectoryAccessor.java
new file mode 100644
index 0000000..7242c84
--- /dev/null
+++ b/helix-rest/src/test/java/org/apache/helix/rest/server/resources/zookeeper/TestMetadataStoreDirectoryAccessor.java
@@ -0,0 +1,175 @@
+package org.apache.helix.rest.server.resources.zookeeper;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.ws.rs.core.Response;
+
+import com.google.common.collect.ImmutableSet;
+import org.apache.helix.ZNRecord;
+import org.apache.helix.manager.zk.ZNRecordSerializer;
+import org.apache.helix.rest.metadatastore.constant.MetadataStoreRoutingConstants;
+import org.apache.helix.rest.server.AbstractTestClass;
+import org.apache.helix.rest.server.util.JerseyUriRequestBuilder;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+
+public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
+  /*
+   * The following are constants to be used for testing.
+   */
+  private static final String TEST_REALM_1 = "testRealm1";
+  private static final List<String> TEST_SHARDING_KEYS_1 =
+      Arrays.asList("/sharding/key/1/a", "/sharding/key/1/b", "/sharding/key/1/c");
+  private static final String TEST_REALM_2 = "testRealm2";
+  private static final List<String> TEST_SHARDING_KEYS_2 =
+      Arrays.asList("/sharding/key/1/d", "/sharding/key/1/e", "/sharding/key/1/f");
+
+  // List of all ZK addresses, each of which corresponds to a namespace/routing ZK
+  private List<String> _zkList;
+
+  @BeforeClass
+  public void beforeClass() {
+    _zkList = new ArrayList<>(ZK_SERVER_MAP.keySet());
+
+    // Write dummy mappings in ZK
+    // Create a node that represents a realm address and add 3 sharding keys to it
+    ZNRecord znRecord = new ZNRecord("RoutingInfo");
+
+    _zkList.forEach(zk -> {
+      ZK_SERVER_MAP.get(zk).getZkClient().setZkSerializer(new ZNRecordSerializer());
+      // Write first realm and sharding keys pair
+      znRecord.setListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY,
+          TEST_SHARDING_KEYS_1);
+      ZK_SERVER_MAP.get(zk).getZkClient()
+          .createPersistent(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + TEST_REALM_1,
+              true);
+      ZK_SERVER_MAP.get(zk).getZkClient()
+          .writeData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + TEST_REALM_1,
+              znRecord);
+
+      // Create another realm and sharding keys pair
+      znRecord.setListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY,
+          TEST_SHARDING_KEYS_2);
+      ZK_SERVER_MAP.get(zk).getZkClient()
+          .createPersistent(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + TEST_REALM_2,
+              true);
+      ZK_SERVER_MAP.get(zk).getZkClient()
+          .writeData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + TEST_REALM_2,
+              znRecord);
+    });
+  }
+
+  @Test
+  public void testGetAllMetadataStoreRealms() throws IOException {
+    String responseBody =
+        get("metadata-store-realms", null, Response.Status.OK.getStatusCode(), true);
+    // It is safe to cast the object and suppress warnings.
+    @SuppressWarnings("unchecked")
+    Map<String, Collection<String>> queriedRealmsMap =
+        OBJECT_MAPPER.readValue(responseBody, Map.class);
+
+    Assert.assertTrue(
+        queriedRealmsMap.containsKey(MetadataStoreRoutingConstants.METADATA_STORE_REALMS));
+
+    Set<String> queriedRealmsSet =
+        new HashSet<>(queriedRealmsMap.get(MetadataStoreRoutingConstants.METADATA_STORE_REALMS));
+    Set<String> expectedRealms = ImmutableSet.of(TEST_REALM_1, TEST_REALM_2);
+
+    Assert.assertEquals(queriedRealmsSet, expectedRealms);
+  }
+
+  /*
+   * Tests REST endpoints: "/sharding-keys"
+   */
+  @Test
+  public void testGetShardingKeysInNamespace() throws IOException {
+    String responseBody = get("/sharding-keys", null, Response.Status.OK.getStatusCode(), true);
+    // It is safe to cast the object and suppress warnings.
+    @SuppressWarnings("unchecked")
+    Map<String, Collection<String>> queriedShardingKeysMap =
+        OBJECT_MAPPER.readValue(responseBody, Map.class);
+
+    Assert.assertTrue(
+        queriedShardingKeysMap.containsKey(MetadataStoreRoutingConstants.SHARDING_KEYS));
+
+    Set<String> queriedShardingKeys =
+        new HashSet<>(queriedShardingKeysMap.get(MetadataStoreRoutingConstants.SHARDING_KEYS));
+    Set<String> expectedShardingKeys = new HashSet<>();
+    expectedShardingKeys.addAll(TEST_SHARDING_KEYS_1);
+    expectedShardingKeys.addAll(TEST_SHARDING_KEYS_2);
+
+    Assert.assertEquals(queriedShardingKeys, expectedShardingKeys);
+  }
+
+  /*
+   * Tests REST endpoint: "/sharding-keys?realm={realmName}"
+   */
+  @Test
+  public void testGetShardingKeysInRealm() throws IOException {
+    // Test NOT_FOUND response for a non existed realm.
+    new JerseyUriRequestBuilder("/sharding-keys?realm=nonExistedRealm")
+        .expectedReturnStatusCode(Response.Status.NOT_FOUND.getStatusCode()).get(this);
+
+    // Query param realm is set to empty, so NOT_FOUND response is returned.
+    new JerseyUriRequestBuilder("/sharding-keys?realm=")
+        .expectedReturnStatusCode(Response.Status.NOT_FOUND.getStatusCode()).get(this);
+
+    // Success response.
+    String responseBody = new JerseyUriRequestBuilder("/sharding-keys?realm=" + TEST_REALM_1)
+        .isBodyReturnExpected(true).get(this);
+    // It is safe to cast the object and suppress warnings.
+    @SuppressWarnings("unchecked")
+    Map<String, Object> queriedShardingKeysMap = OBJECT_MAPPER.readValue(responseBody, Map.class);
+
+    // Check realm name in json response.
+    Assert.assertTrue(queriedShardingKeysMap
+        .containsKey(MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_REALM));
+    Assert.assertEquals(
+        queriedShardingKeysMap.get(MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_REALM),
+        TEST_REALM_1);
+    Assert.assertTrue(
+        queriedShardingKeysMap.containsKey(MetadataStoreRoutingConstants.SHARDING_KEYS));
+
+    // It is safe to cast the object and suppress warnings.
+    @SuppressWarnings("unchecked")
+    Set<String> queriedShardingKeys = new HashSet<>((Collection<String>) queriedShardingKeysMap
+        .get(MetadataStoreRoutingConstants.SHARDING_KEYS));
+    Set<String> expectedShardingKeys = new HashSet<>(TEST_SHARDING_KEYS_1);
+
+    Assert.assertEquals(queriedShardingKeys, expectedShardingKeys);
+  }
+
+  @AfterClass
+  public void afterClass() {
+    _zkList.forEach(zk -> ZK_SERVER_MAP.get(zk).getZkClient()
+        .deleteRecursive(MetadataStoreRoutingConstants.ROUTING_DATA_PATH));
+  }
+}


[helix] 40/49: Reformat ZkBaseDataAccessor (#893)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit c3bdbae9930e9f4086408243ddd5835303d29aab
Author: Hunter Lee <hu...@linkedin.com>
AuthorDate: Fri Mar 13 17:19:34 2020 -0700

    Reformat ZkBaseDataAccessor (#893)
    
    Changelist:
    1. Add generic type markers to Builder (<T>)
    2. Fix a bug in validate function
    3. Default to ZNRecordSerializer to preserve existing behavior
---
 .../helix/manager/zk/ZkBaseDataAccessor.java       | 301 +++++++++++----------
 1 file changed, 159 insertions(+), 142 deletions(-)

diff --git a/helix-core/src/main/java/org/apache/helix/manager/zk/ZkBaseDataAccessor.java b/helix-core/src/main/java/org/apache/helix/manager/zk/ZkBaseDataAccessor.java
index 1d60c7b..32f33f8 100644
--- a/helix-core/src/main/java/org/apache/helix/manager/zk/ZkBaseDataAccessor.java
+++ b/helix-core/src/main/java/org/apache/helix/manager/zk/ZkBaseDataAccessor.java
@@ -52,6 +52,7 @@ import org.apache.helix.zookeeper.zkclient.exception.ZkException;
 import org.apache.helix.zookeeper.zkclient.exception.ZkNoNodeException;
 import org.apache.helix.zookeeper.zkclient.exception.ZkNodeExistsException;
 import org.apache.helix.zookeeper.zkclient.serialize.ZkSerializer;
+import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
 import org.apache.zookeeper.CreateMode;
 import org.apache.zookeeper.KeeperException.Code;
 import org.apache.zookeeper.data.Stat;
@@ -59,6 +60,7 @@ import org.apache.zookeeper.server.DataTree;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+
 public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
 
   // Designates which mode ZkBaseDataAccessor should be created in. If not specified, it will be
@@ -139,7 +141,7 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
     _usesExternalZkClient = true;
   }
 
-  private ZkBaseDataAccessor(Builder builder) {
+  private ZkBaseDataAccessor(Builder<T> builder) {
     switch (builder.realmMode) {
       case MULTI_REALM:
         try {
@@ -397,16 +399,16 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
           result._pathCreated.addAll(res._pathCreated);
           RetCode rc = res._retCode;
           switch (rc) {
-          case OK:
-            // not set stat if node is created (instead of set)
-            break;
-          case NODE_EXISTS:
-            retry = true;
-            break;
-          default:
-            LOG.error("Fail to set path by creating: " + path);
-            result._retCode = RetCode.ERROR;
-            return result;
+            case OK:
+              // not set stat if node is created (instead of set)
+              break;
+            case NODE_EXISTS:
+              retry = true;
+              break;
+            default:
+              LOG.error("Fail to set path by creating: " + path);
+              result._retCode = RetCode.ERROR;
+              return result;
           }
         } catch (Exception e1) {
           LOG.error("Exception while setting path by creating: " + path, e);
@@ -477,16 +479,16 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
             rc = RetCode.OK;
           }
           switch (rc) {
-          case OK:
-            updatedData = newData;
-            break;
-          case NODE_EXISTS:
-            retry = true;
-            break;
-          default:
-            LOG.error("Fail to update path by creating: " + path);
-            result._retCode = RetCode.ERROR;
-            return result;
+            case OK:
+              updatedData = newData;
+              break;
+            case NODE_EXISTS:
+              retry = true;
+              break;
+            default:
+              LOG.error("Fail to update path by creating: " + path);
+              result._retCode = RetCode.ERROR;
+              return result;
           }
         } catch (Exception e1) {
           LOG.error("Exception while updating path by creating: " + path, e1);
@@ -553,17 +555,19 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
     // init stats
     if (stats != null) {
       stats.clear();
-      stats.addAll(Collections.<Stat> nCopies(paths.size(), null));
+      stats.addAll(Collections.<Stat>nCopies(paths.size(), null));
     }
 
     long startT = System.nanoTime();
 
     try {
       // issue asyn get requests
-      ZkAsyncCallbacks.GetDataCallbackHandler[] cbList = new ZkAsyncCallbacks.GetDataCallbackHandler[paths.size()];
+      ZkAsyncCallbacks.GetDataCallbackHandler[] cbList =
+          new ZkAsyncCallbacks.GetDataCallbackHandler[paths.size()];
       for (int i = 0; i < paths.size(); i++) {
-        if (!needRead[i])
+        if (!needRead[i]) {
           continue;
+        }
 
         String path = paths.get(i);
         cbList[i] = new ZkAsyncCallbacks.GetDataCallbackHandler();
@@ -572,19 +576,21 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
 
       // wait for completion
       for (int i = 0; i < cbList.length; i++) {
-        if (!needRead[i])
+        if (!needRead[i]) {
           continue;
+        }
 
         ZkAsyncCallbacks.GetDataCallbackHandler cb = cbList[i];
         cb.waitForSuccess();
       }
 
       // construct return results
-      List<T> records = new ArrayList<T>(Collections.<T> nCopies(paths.size(), null));
+      List<T> records = new ArrayList<T>(Collections.<T>nCopies(paths.size(), null));
       Map<String, Integer> pathFailToRead = new HashMap<>();
       for (int i = 0; i < paths.size(); i++) {
-        if (!needRead[i])
+        if (!needRead[i]) {
           continue;
+        }
 
         ZkAsyncCallbacks.GetDataCallbackHandler cb = cbList[i];
         if (Code.get(cb.getRc()) == Code.OK) {
@@ -610,8 +616,9 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
     } finally {
       long endT = System.nanoTime();
       if (LOG.isTraceEnabled()) {
-        LOG.trace("getData_async, size: " + paths.size() + ", paths: " + paths.get(0)
-            + ",... time: " + (endT - startT) + " ns");
+        LOG.trace(
+            "getData_async, size: " + paths.size() + ", paths: " + paths.get(0) + ",... time: " + (
+                endT - startT) + " ns");
       }
     }
   }
@@ -752,15 +759,16 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
   /**
    * async create. give up on error other than NONODE
    */
-  ZkAsyncCallbacks.CreateCallbackHandler[] create(List<String> paths, List<T> records, boolean[] needCreate,
-      List<List<String>> pathsCreated, int options) {
+  ZkAsyncCallbacks.CreateCallbackHandler[] create(List<String> paths, List<T> records,
+      boolean[] needCreate, List<List<String>> pathsCreated, int options) {
     if ((records != null && records.size() != paths.size()) || needCreate.length != paths.size()
         || (pathsCreated != null && pathsCreated.size() != paths.size())) {
       throw new IllegalArgumentException(
           "paths, records, needCreate, and pathsCreated should be of same size");
     }
 
-    ZkAsyncCallbacks.CreateCallbackHandler[] cbList = new ZkAsyncCallbacks.CreateCallbackHandler[paths.size()];
+    ZkAsyncCallbacks.CreateCallbackHandler[] cbList =
+        new ZkAsyncCallbacks.CreateCallbackHandler[paths.size()];
 
     CreateMode mode = AccessOption.getMode(options);
     if (mode == null) {
@@ -773,8 +781,9 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
       retry = false;
 
       for (int i = 0; i < paths.size(); i++) {
-        if (!needCreate[i])
+        if (!needCreate[i]) {
           continue;
+        }
 
         String path = paths.get(i);
         T record = records == null ? null : records.get(i);
@@ -782,12 +791,13 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
         _zkClient.asyncCreate(path, record, mode, cbList[i]);
       }
 
-      List<String> parentPaths = new ArrayList<>(Collections.<String> nCopies(paths.size(), null));
+      List<String> parentPaths = new ArrayList<>(Collections.<String>nCopies(paths.size(), null));
       boolean failOnNoNode = false;
 
       for (int i = 0; i < paths.size(); i++) {
-        if (!needCreate[i])
+        if (!needCreate[i]) {
           continue;
+        }
 
         ZkAsyncCallbacks.CreateCallbackHandler cb = cbList[i];
         cb.waitForSuccess();
@@ -819,8 +829,9 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
             create(parentPaths, null, needCreateParent, pathsCreated, AccessOption.PERSISTENT);
         for (int i = 0; i < parentCbList.length; i++) {
           ZkAsyncCallbacks.CreateCallbackHandler parentCb = parentCbList[i];
-          if (parentCb == null)
+          if (parentCb == null) {
             continue;
+          }
 
           Code rc = Code.get(parentCb.getRc());
 
@@ -853,12 +864,13 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
     boolean[] needCreate = new boolean[paths.size()];
     Arrays.fill(needCreate, true);
     List<List<String>> pathsCreated =
-        new ArrayList<>(Collections.<List<String>> nCopies(paths.size(), null));
+        new ArrayList<>(Collections.<List<String>>nCopies(paths.size(), null));
 
     long startT = System.nanoTime();
     try {
 
-      ZkAsyncCallbacks.CreateCallbackHandler[] cbList = create(paths, records, needCreate, pathsCreated, options);
+      ZkAsyncCallbacks.CreateCallbackHandler[] cbList =
+          create(paths, records, needCreate, pathsCreated, options);
 
       for (int i = 0; i < cbList.length; i++) {
         ZkAsyncCallbacks.CreateCallbackHandler cb = cbList[i];
@@ -866,12 +878,12 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
       }
 
       return success;
-
     } finally {
       long endT = System.nanoTime();
       if (LOG.isTraceEnabled()) {
-        LOG.trace("create_async, size: " + paths.size() + ", paths: " + paths.get(0) + ",... time: "
-            + (endT - startT) + " ns");
+        LOG.trace(
+            "create_async, size: " + paths.size() + ", paths: " + paths.get(0) + ",... time: " + (
+                endT - startT) + " ns");
       }
     }
   }
@@ -894,8 +906,8 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
       return new boolean[0];
     }
 
-    if ((records != null && records.size() != paths.size())
-        || (pathsCreated != null && pathsCreated.size() != paths.size())) {
+    if ((records != null && records.size() != paths.size()) || (pathsCreated != null
+        && pathsCreated.size() != paths.size())) {
       throw new IllegalArgumentException("paths, records, and pathsCreated should be of same size");
     }
 
@@ -907,8 +919,9 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
       return success;
     }
 
-    List<Stat> setStats = new ArrayList<>(Collections.<Stat> nCopies(paths.size(), null));
-    ZkAsyncCallbacks.SetDataCallbackHandler[] cbList = new ZkAsyncCallbacks.SetDataCallbackHandler[paths.size()];
+    List<Stat> setStats = new ArrayList<>(Collections.<Stat>nCopies(paths.size(), null));
+    ZkAsyncCallbacks.SetDataCallbackHandler[] cbList =
+        new ZkAsyncCallbacks.SetDataCallbackHandler[paths.size()];
     ZkAsyncCallbacks.CreateCallbackHandler[] createCbList = null;
     boolean[] needSet = new boolean[paths.size()];
     Arrays.fill(needSet, true);
@@ -921,14 +934,14 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
         retry = false;
 
         for (int i = 0; i < paths.size(); i++) {
-          if (!needSet[i])
+          if (!needSet[i]) {
             continue;
+          }
 
           String path = paths.get(i);
           T record = records.get(i);
           cbList[i] = new ZkAsyncCallbacks.SetDataCallbackHandler();
           _zkClient.asyncSetData(path, record, -1, cbList[i]);
-
         }
 
         boolean failOnNoNode = false;
@@ -938,18 +951,18 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
           cb.waitForSuccess();
           Code rc = Code.get(cb.getRc());
           switch (rc) {
-          case OK:
-            setStats.set(i, cb.getStat());
-            needSet[i] = false;
-            break;
-          case NONODE:
-            // if fail on NoNode, try create the node
-            failOnNoNode = true;
-            break;
-          default:
-            // if fail on error other than NoNode, give up
-            needSet[i] = false;
-            break;
+            case OK:
+              setStats.set(i, cb.getStat());
+              needSet[i] = false;
+              break;
+            case NONODE:
+              // if fail on NoNode, try create the node
+              failOnNoNode = true;
+              break;
+            default:
+              // if fail on error other than NoNode, give up
+              needSet[i] = false;
+              break;
           }
         }
 
@@ -965,18 +978,18 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
 
             Code rc = Code.get(createCb.getRc());
             switch (rc) {
-            case OK:
-              setStats.set(i, ZNode.ZERO_STAT);
-              needSet[i] = false;
-              break;
-            case NODEEXISTS:
-              retry = true;
-              break;
-            default:
-              // if creation fails on error other than NodeExists
-              // no need to retry set
-              needSet[i] = false;
-              break;
+              case OK:
+                setStats.set(i, ZNode.ZERO_STAT);
+                needSet[i] = false;
+                break;
+              case NODEEXISTS:
+                retry = true;
+                break;
+              default:
+                // if creation fails on error other than NodeExists
+                // no need to retry set
+                needSet[i] = false;
+                break;
             }
           }
         }
@@ -1006,13 +1019,15 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
     } finally {
       long endT = System.nanoTime();
       if (LOG.isTraceEnabled()) {
-        LOG.trace("setData_async, size: " + paths.size() + ", paths: " + paths.get(0)
-            + ",... time: " + (endT - startT) + " ns");
+        LOG.trace(
+            "setData_async, size: " + paths.size() + ", paths: " + paths.get(0) + ",... time: " + (
+                endT - startT) + " ns");
       }
     }
   }
 
   // TODO: rename to update
+
   /**
    * async update
    */
@@ -1039,14 +1054,14 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
       return Collections.emptyList();
     }
 
-    if (updaters.size() != paths.size()
-        || (pathsCreated != null && pathsCreated.size() != paths.size())) {
+    if (updaters.size() != paths.size() || (pathsCreated != null && pathsCreated.size() != paths
+        .size())) {
       throw new IllegalArgumentException(
           "paths, updaters, and pathsCreated should be of same size");
     }
 
-    List<Stat> setStats = new ArrayList<Stat>(Collections.<Stat> nCopies(paths.size(), null));
-    List<T> updateData = new ArrayList<T>(Collections.<T> nCopies(paths.size(), null));
+    List<Stat> setStats = new ArrayList<Stat>(Collections.<Stat>nCopies(paths.size(), null));
+    List<T> updateData = new ArrayList<T>(Collections.<T>nCopies(paths.size(), null));
 
     CreateMode mode = AccessOption.getMode(options);
     if (mode == null) {
@@ -1054,7 +1069,8 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
       return updateData;
     }
 
-    ZkAsyncCallbacks.SetDataCallbackHandler[] cbList = new ZkAsyncCallbacks.SetDataCallbackHandler[paths.size()];
+    ZkAsyncCallbacks.SetDataCallbackHandler[] cbList =
+        new ZkAsyncCallbacks.SetDataCallbackHandler[paths.size()];
     ZkAsyncCallbacks.CreateCallbackHandler[] createCbList = null;
     boolean[] needUpdate = new boolean[paths.size()];
     Arrays.fill(needUpdate, true);
@@ -1104,29 +1120,30 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
 
         for (int i = 0; i < paths.size(); i++) {
           ZkAsyncCallbacks.SetDataCallbackHandler cb = cbList[i];
-          if (cb == null)
+          if (cb == null) {
             continue;
+          }
 
           cb.waitForSuccess();
 
           switch (Code.get(cb.getRc())) {
-          case OK:
-            updateData.set(i, newDataList.get(i));
-            setStats.set(i, cb.getStat());
-            needUpdate[i] = false;
-            break;
-          case NONODE:
-            failOnNoNode = true;
-            needCreate[i] = true;
-            break;
-          case BADVERSION:
-            failOnBadVersion = true;
-            break;
-          default:
-            // if fail on error other than NoNode or BadVersion
-            // will not retry
-            needUpdate[i] = false;
-            break;
+            case OK:
+              updateData.set(i, newDataList.get(i));
+              setStats.set(i, cb.getStat());
+              needUpdate[i] = false;
+              break;
+            case NONODE:
+              failOnNoNode = true;
+              needCreate[i] = true;
+              break;
+            case BADVERSION:
+              failOnBadVersion = true;
+              break;
+            default:
+              // if fail on error other than NoNode or BadVersion
+              // will not retry
+              needUpdate[i] = false;
+              break;
           }
         }
 
@@ -1140,19 +1157,19 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
             }
 
             switch (Code.get(createCb.getRc())) {
-            case OK:
-              needUpdate[i] = false;
-              updateData.set(i, newDataList.get(i));
-              setStats.set(i, ZNode.ZERO_STAT);
-              break;
-            case NODEEXISTS:
-              retry = true;
-              break;
-            default:
-              // if fail on error other than NodeExists
-              // will not retry
-              needUpdate[i] = false;
-              break;
+              case OK:
+                needUpdate[i] = false;
+                updateData.set(i, newDataList.get(i));
+                setStats.set(i, ZNode.ZERO_STAT);
+                break;
+              case NODEEXISTS:
+                retry = true;
+                break;
+              default:
+                // if fail on error other than NodeExists
+                // will not retry
+                needUpdate[i] = false;
+                break;
             }
           }
         }
@@ -1172,11 +1189,11 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
     } finally {
       long endT = System.nanoTime();
       if (LOG.isTraceEnabled()) {
-        LOG.trace("setData_async, size: " + paths.size() + ", paths: " + paths.get(0)
-            + ",... time: " + (endT - startT) + " ns");
+        LOG.trace(
+            "setData_async, size: " + paths.size() + ", paths: " + paths.get(0) + ",... time: " + (
+                endT - startT) + " ns");
       }
     }
-
   }
 
   /**
@@ -1209,7 +1226,8 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
     long startT = System.nanoTime();
 
     try {
-      ZkAsyncCallbacks.ExistsCallbackHandler[] cbList = new ZkAsyncCallbacks.ExistsCallbackHandler[paths.size()];
+      ZkAsyncCallbacks.ExistsCallbackHandler[] cbList =
+          new ZkAsyncCallbacks.ExistsCallbackHandler[paths.size()];
       for (int i = 0; i < paths.size(); i++) {
         String path = paths.get(i);
         cbList[i] = new ZkAsyncCallbacks.ExistsCallbackHandler();
@@ -1226,8 +1244,9 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
     } finally {
       long endT = System.nanoTime();
       if (LOG.isTraceEnabled()) {
-        LOG.trace("exists_async, size: " + paths.size() + ", paths: " + paths.get(0) + ",... time: "
-            + (endT - startT) + " ns");
+        LOG.trace(
+            "exists_async, size: " + paths.size() + ", paths: " + paths.get(0) + ",... time: " + (
+                endT - startT) + " ns");
       }
     }
   }
@@ -1243,7 +1262,8 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
 
     boolean[] success = new boolean[paths.size()];
 
-    ZkAsyncCallbacks.DeleteCallbackHandler[] cbList = new ZkAsyncCallbacks.DeleteCallbackHandler[paths.size()];
+    ZkAsyncCallbacks.DeleteCallbackHandler[] cbList =
+        new ZkAsyncCallbacks.DeleteCallbackHandler[paths.size()];
 
     long startT = System.nanoTime();
 
@@ -1264,8 +1284,9 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
     } finally {
       long endT = System.nanoTime();
       if (LOG.isTraceEnabled()) {
-        LOG.trace("delete_async, size: " + paths.size() + ", paths: " + paths.get(0) + ",... time: "
-            + (endT - startT) + " ns");
+        LOG.trace(
+            "delete_async, size: " + paths.size() + ", paths: " + paths.get(0) + ",... time: " + (
+                endT - startT) + " ns");
       }
     }
   }
@@ -1321,39 +1342,38 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
   }
 
   // TODO: refactor Builder class to remove duplicate code with other Helix Java APIs
-  public static class Builder {
+  public static class Builder<T> {
     private String zkAddress;
-    private RealmAwareZkClient.RealmMode realmMode;
     private ZkBaseDataAccessor.ZkClientType zkClientType;
+    private RealmAwareZkClient.RealmMode realmMode;
     private RealmAwareZkClient.RealmAwareZkConnectionConfig realmAwareZkConnectionConfig;
     private RealmAwareZkClient.RealmAwareZkClientConfig realmAwareZkClientConfig;
 
     public Builder() {
     }
 
-    public ZkBaseDataAccessor.Builder setZkAddress(String zkAddress) {
+    public Builder<T> setZkAddress(String zkAddress) {
       this.zkAddress = zkAddress;
       return this;
     }
 
-    public ZkBaseDataAccessor.Builder setRealmMode(RealmAwareZkClient.RealmMode realmMode) {
+    public Builder<T> setRealmMode(RealmAwareZkClient.RealmMode realmMode) {
       this.realmMode = realmMode;
       return this;
     }
 
-    public ZkBaseDataAccessor.Builder setZkClientType(
-        ZkBaseDataAccessor.ZkClientType zkClientType) {
+    public Builder<T> setZkClientType(ZkClientType zkClientType) {
       this.zkClientType = zkClientType;
       return this;
     }
 
-    public ZkBaseDataAccessor.Builder setRealmAwareZkConnectionConfig(
+    public Builder<T> setRealmAwareZkConnectionConfig(
         RealmAwareZkClient.RealmAwareZkConnectionConfig realmAwareZkConnectionConfig) {
       this.realmAwareZkConnectionConfig = realmAwareZkConnectionConfig;
       return this;
     }
 
-    public ZkBaseDataAccessor.Builder setRealmAwareZkClientConfig(
+    public Builder<T> setRealmAwareZkClientConfig(
         RealmAwareZkClient.RealmAwareZkClientConfig realmAwareZkClientConfig) {
       this.realmAwareZkClientConfig = realmAwareZkClientConfig;
       return this;
@@ -1362,11 +1382,11 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
     /**
      * Returns a <code>ZkBaseDataAccessor</code> instance.
      * <p>
-     * Note: in multi-realm mode, if and only if ZK client type is set to <code>FEDERATED</code>,
-     * <code>ZkBaseDataAccessor</code> can access to multi-realm. Otherwise, it can only access to
-     * single-ream.
+     * Note: ZK client type must be set to <code>FEDERATED</code> in order for
+     * <code>ZkBaseDataAccessor</code> can access multiple ZKs. Otherwise, it can only access
+     * single-ZK.
      */
-    public ZkBaseDataAccessor<?> build() {
+    public ZkBaseDataAccessor<T> build() {
       validate();
       return new ZkBaseDataAccessor<>(this);
     }
@@ -1381,28 +1401,24 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
 
       // If ZkClientType is set, RealmMode must either be single-realm or not set.
       if (isZkClientTypeSet && realmMode == RealmAwareZkClient.RealmMode.MULTI_REALM) {
-        throw new HelixException(
-            "ZkClientType cannot be set on multi-realm mode!");
+        throw new HelixException("ZkClientType cannot be set on multi-realm mode!");
       }
-      // If ZkClientType is not set, default to SHARED
-      if (!isZkClientTypeSet) {
-        zkClientType = ZkBaseDataAccessor.ZkClientType.SHARED;
+      // If ZkClientType is not set and realmMode is single-realm, default to SHARED
+      if (!isZkClientTypeSet && realmMode == RealmAwareZkClient.RealmMode.SINGLE_REALM) {
+        zkClientType = ZkClientType.SHARED;
       }
 
       if (realmMode == RealmAwareZkClient.RealmMode.SINGLE_REALM && !isZkAddressSet) {
-        throw new HelixException(
-            "RealmMode cannot be single-realm without a valid ZkAddress set!");
+        throw new HelixException("RealmMode cannot be single-realm without a valid ZkAddress set!");
       }
 
       if (realmMode == RealmAwareZkClient.RealmMode.MULTI_REALM && isZkAddressSet) {
-        throw new HelixException(
-            "ZkAddress cannot be set on multi-realm mode!");
+        throw new HelixException("ZkAddress cannot be set on multi-realm mode!");
       }
 
       if (realmMode == RealmAwareZkClient.RealmMode.SINGLE_REALM
           && zkClientType == ZkClientType.FEDERATED) {
-        throw new HelixException(
-            "FederatedZkClient cannot be set on single-realm mode!");
+        throw new HelixException("FederatedZkClient cannot be set on single-realm mode!");
       }
 
       if (realmMode == null) {
@@ -1412,7 +1428,8 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
 
       // Resolve RealmAwareZkClientConfig
       if (realmAwareZkClientConfig == null) {
-        realmAwareZkClientConfig = new RealmAwareZkClient.RealmAwareZkClientConfig();
+        realmAwareZkClientConfig = new RealmAwareZkClient.RealmAwareZkClientConfig()
+            .setZkSerializer(new ZNRecordSerializer());
       }
 
       // Resolve RealmAwareZkConnectionConfig


[helix] 13/49: Add write REST endpoints to helix rest for metadata store directory (#757)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit 3c54e432524408bbbed001b479d88e482e4267f3
Author: Huizhi Lu <ih...@gmail.com>
AuthorDate: Thu Feb 13 15:35:46 2020 -0800

    Add write REST endpoints to helix rest for metadata store directory (#757)
    
    We have metadata store directory service to help scale out zookeeper. Metadata store directory service provides REST APIs to access. This commit adds MSDS write endpoints to Helix REST.
    
    Changelist:
    - Add REST write endpoints to MetadataStoreDirectoryAccessor
    - Add unit tests for the new REST write endpoints
    - Fix unit tests by cleaning up routing data path in ZK
---
 .../MetadataStoreDirectoryAccessor.java            |  86 ++++++++--
 .../accessor/TestZkRoutingDataReader.java          |  21 ++-
 .../TestMetadataStoreDirectoryAccessor.java        | 173 +++++++++++++++++++--
 3 files changed, 249 insertions(+), 31 deletions(-)

diff --git a/helix-rest/src/main/java/org/apache/helix/rest/server/resources/metadatastore/MetadataStoreDirectoryAccessor.java b/helix-rest/src/main/java/org/apache/helix/rest/server/resources/metadatastore/MetadataStoreDirectoryAccessor.java
index 78543d6..e731b28 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/server/resources/metadatastore/MetadataStoreDirectoryAccessor.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/server/resources/metadatastore/MetadataStoreDirectoryAccessor.java
@@ -25,8 +25,11 @@ import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
 import javax.annotation.PostConstruct;
+import javax.ws.rs.DELETE;
 import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
 import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.Response;
 
@@ -51,15 +54,15 @@ import org.slf4j.LoggerFactory;
 public class MetadataStoreDirectoryAccessor extends AbstractResource {
   private static final Logger LOG = LoggerFactory.getLogger(MetadataStoreDirectoryAccessor.class);
 
-  private HelixRestNamespace _namespace;
-
-  // Double-checked locking for thread-safe object.
+  private String _namespace;
   private MetadataStoreDirectory _metadataStoreDirectory;
 
   @PostConstruct
   private void postConstruct() {
-    getHelixNamespace();
-    buildMetadataStoreDirectory();
+    HelixRestNamespace helixRestNamespace = getHelixNamespace();
+    _namespace = helixRestNamespace.getName();
+
+    buildMetadataStoreDirectory(_namespace, helixRestNamespace.getMetadataStoreAddress());
   }
 
   /**
@@ -72,8 +75,7 @@ public class MetadataStoreDirectoryAccessor extends AbstractResource {
   public Response getAllMetadataStoreRealms() {
     Map<String, Collection<String>> responseMap;
     try {
-      Collection<String> realms =
-          _metadataStoreDirectory.getAllMetadataStoreRealms(_namespace.getName());
+      Collection<String> realms = _metadataStoreDirectory.getAllMetadataStoreRealms(_namespace);
 
       responseMap = new HashMap<>(1);
       responseMap.put(MetadataStoreRoutingConstants.METADATA_STORE_REALMS, realms);
@@ -84,6 +86,30 @@ public class MetadataStoreDirectoryAccessor extends AbstractResource {
     return JSONRepresentation(responseMap);
   }
 
+  @PUT
+  @Path("/metadata-store-realms/{realm}")
+  public Response addMetadataStoreRealm(@PathParam("realm") String realm) {
+    try {
+      _metadataStoreDirectory.addMetadataStoreRealm(_namespace, realm);
+    } catch (IllegalArgumentException ex) {
+      return notFound(ex.getMessage());
+    }
+
+    return created();
+  }
+
+  @DELETE
+  @Path("/metadata-store-realms/{realm}")
+  public Response deleteMetadataStoreRealm(@PathParam("realm") String realm) {
+    try {
+      _metadataStoreDirectory.deleteMetadataStoreRealm(_namespace, realm);
+    } catch (IllegalArgumentException ex) {
+      return notFound(ex.getMessage());
+    }
+
+    return OK();
+  }
+
   /**
    * Gets sharding keys mapped at path "HTTP GET /sharding-keys" which returns all sharding keys in
    * a namespace, or path "HTTP GET /sharding-keys?realm={realmName}" which returns sharding keys in
@@ -101,15 +127,13 @@ public class MetadataStoreDirectoryAccessor extends AbstractResource {
       // If realm is not set in query param, the endpoint is: "/sharding-keys"
       // to get all sharding keys in a namespace.
       if (realm == null) {
-        shardingKeys =
-            _metadataStoreDirectory.getAllShardingKeys(_namespace.getName());
+        shardingKeys = _metadataStoreDirectory.getAllShardingKeys(_namespace);
         // To avoid allocating unnecessary resource, limit the map's capacity only for
         // SHARDING_KEYS.
         responseMap = new HashMap<>(1);
       } else {
         // For endpoint: "/sharding-keys?realm={realmName}"
-        shardingKeys = _metadataStoreDirectory
-            .getAllShardingKeysInRealm(_namespace.getName(), realm);
+        shardingKeys = _metadataStoreDirectory.getAllShardingKeysInRealm(_namespace, realm);
         // To avoid allocating unnecessary resource, limit the map's capacity only for
         // SHARDING_KEYS and SINGLE_METADATA_STORE_REALM.
         responseMap = new HashMap<>(2);
@@ -124,7 +148,34 @@ public class MetadataStoreDirectoryAccessor extends AbstractResource {
     return JSONRepresentation(responseMap);
   }
 
-  private void getHelixNamespace() {
+  @PUT
+  @Path("/metadata-store-realms/{realm}/sharding-keys/{sharding-key: .+}")
+  public Response addShardingKey(@PathParam("realm") String realm,
+      @PathParam("sharding-key") String shardingKey) {
+    try {
+      _metadataStoreDirectory.addShardingKey(_namespace, realm, shardingKey);
+    } catch (IllegalArgumentException ex) {
+      return notFound(ex.getMessage());
+    }
+
+    return created();
+  }
+
+  @DELETE
+  @Path("/metadata-store-realms/{realm}/sharding-keys/{sharding-key: .+}")
+  public Response deleteShardingKey(@PathParam("realm") String realm,
+      @PathParam("sharding-key") String shardingKey) {
+    try {
+      _metadataStoreDirectory.deleteShardingKey(_namespace, realm, shardingKey);
+    } catch (IllegalArgumentException ex) {
+      return notFound(ex.getMessage());
+    }
+
+    return OK();
+  }
+
+  private HelixRestNamespace getHelixNamespace() {
+    HelixRestNamespace helixRestNamespace = null;
     // A default servlet does not have context property key METADATA, so the namespace
     // is retrieved from property ALL_NAMESPACES.
     if (HelixRestUtils.isDefaultServlet(_servletRequest.getServletPath())) {
@@ -134,20 +185,21 @@ public class MetadataStoreDirectoryAccessor extends AbstractResource {
           .get(ContextPropertyKeys.ALL_NAMESPACES.name());
       for (HelixRestNamespace ns : namespaces) {
         if (HelixRestNamespace.DEFAULT_NAMESPACE_NAME.equals(ns.getName())) {
-          _namespace = ns;
+          helixRestNamespace = ns;
           break;
         }
       }
     } else {
       // Get namespace from property METADATA for a common servlet.
-      _namespace = (HelixRestNamespace) _application.getProperties()
+      helixRestNamespace = (HelixRestNamespace) _application.getProperties()
           .get(ContextPropertyKeys.METADATA.name());
     }
+
+    return helixRestNamespace;
   }
 
-  private void buildMetadataStoreDirectory() {
-    Map<String, String> routingZkAddressMap =
-        ImmutableMap.of(_namespace.getName(), _namespace.getMetadataStoreAddress());
+  private void buildMetadataStoreDirectory(String namespace, String address) {
+    Map<String, String> routingZkAddressMap = ImmutableMap.of(namespace, address);
     try {
       _metadataStoreDirectory = new ZkMetadataStoreDirectory(routingZkAddressMap);
     } catch (InvalidRoutingDataException ex) {
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataReader.java b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataReader.java
index 5781a85..c188840 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataReader.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataReader.java
@@ -25,6 +25,7 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.helix.AccessOption;
+import org.apache.helix.TestHelper;
 import org.apache.helix.rest.metadatastore.constant.MetadataStoreRoutingConstants;
 import org.apache.helix.rest.metadatastore.exceptions.InvalidRoutingDataException;
 import org.apache.helix.rest.server.AbstractTestClass;
@@ -41,13 +42,15 @@ public class TestZkRoutingDataReader extends AbstractTestClass {
   private MetadataStoreRoutingDataReader _zkRoutingDataReader;
 
   @BeforeClass
-  public void beforeClass() {
+  public void beforeClass() throws Exception {
+    deleteRoutingDataPath();
     _zkRoutingDataReader = new ZkRoutingDataReader(DUMMY_NAMESPACE, ZK_ADDR, null);
   }
 
   @AfterClass
-  public void afterClass() {
+  public void afterClass() throws Exception {
     _zkRoutingDataReader.close();
+    deleteRoutingDataPath();
   }
 
   @AfterMethod
@@ -126,4 +129,18 @@ public class TestZkRoutingDataReader extends AbstractTestClass {
       Assert.fail("Not expecting InvalidRoutingDataException");
     }
   }
+
+  private void deleteRoutingDataPath() throws Exception {
+    Assert.assertTrue(TestHelper.verify(() -> {
+      ZK_SERVER_MAP.get(ZK_ADDR).getZkClient()
+          .deleteRecursively(MetadataStoreRoutingConstants.ROUTING_DATA_PATH);
+
+      if (ZK_SERVER_MAP.get(ZK_ADDR).getZkClient()
+          .exists(MetadataStoreRoutingConstants.ROUTING_DATA_PATH)) {
+        return false;
+      }
+
+      return true;
+    }, TestHelper.WAIT_DURATION), "Routing data path should be deleted after the tests.");
+  }
 }
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/server/resources/zookeeper/TestMetadataStoreDirectoryAccessor.java b/helix-rest/src/test/java/org/apache/helix/rest/server/resources/zookeeper/TestMetadataStoreDirectoryAccessor.java
index 7242c84..c08d845 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/server/resources/zookeeper/TestMetadataStoreDirectoryAccessor.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/server/resources/zookeeper/TestMetadataStoreDirectoryAccessor.java
@@ -27,14 +27,21 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
-import org.apache.helix.ZNRecord;
-import org.apache.helix.manager.zk.ZNRecordSerializer;
+import org.apache.helix.TestHelper;
+import org.apache.helix.rest.common.HelixRestNamespace;
+import org.apache.helix.rest.metadatastore.MetadataStoreDirectory;
+import org.apache.helix.rest.metadatastore.ZkMetadataStoreDirectory;
 import org.apache.helix.rest.metadatastore.constant.MetadataStoreRoutingConstants;
 import org.apache.helix.rest.server.AbstractTestClass;
 import org.apache.helix.rest.server.util.JerseyUriRequestBuilder;
+import org.apache.helix.zookeeper.datamodel.ZNRecord;
+import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
 import org.testng.Assert;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
@@ -45,20 +52,33 @@ public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
   /*
    * The following are constants to be used for testing.
    */
+  private static final String TEST_NAMESPACE_URI_PREFIX = "/namespaces/" + TEST_NAMESPACE;
+  private static final String NON_EXISTING_NAMESPACE_URI_PREFIX =
+      "/namespaces/not-existed-namespace/metadata-store-realms/";
   private static final String TEST_REALM_1 = "testRealm1";
   private static final List<String> TEST_SHARDING_KEYS_1 =
       Arrays.asList("/sharding/key/1/a", "/sharding/key/1/b", "/sharding/key/1/c");
   private static final String TEST_REALM_2 = "testRealm2";
   private static final List<String> TEST_SHARDING_KEYS_2 =
       Arrays.asList("/sharding/key/1/d", "/sharding/key/1/e", "/sharding/key/1/f");
+  private static final String TEST_REALM_3 = "testRealm3";
+  private static final String TEST_SHARDING_KEY = "/sharding/key/1/x";
 
   // List of all ZK addresses, each of which corresponds to a namespace/routing ZK
   private List<String> _zkList;
+  private MetadataStoreDirectory _metadataStoreDirectory;
 
   @BeforeClass
-  public void beforeClass() {
+  public void beforeClass() throws Exception {
     _zkList = new ArrayList<>(ZK_SERVER_MAP.keySet());
 
+    deleteRoutingDataPath();
+
+    // Populate routingZkAddrMap according namespaces in helix rest server.
+    // <Namespace, ZkAddr> mapping
+    Map<String, String> routingZkAddrMap = ImmutableMap
+        .of(HelixRestNamespace.DEFAULT_NAMESPACE_NAME, ZK_ADDR, TEST_NAMESPACE, _zkAddrTestNS);
+
     // Write dummy mappings in ZK
     // Create a node that represents a realm address and add 3 sharding keys to it
     ZNRecord znRecord = new ZNRecord("RoutingInfo");
@@ -85,12 +105,18 @@ public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
           .writeData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + TEST_REALM_2,
               znRecord);
     });
+
+    // Create metadataStoreDirectory
+    _metadataStoreDirectory = new ZkMetadataStoreDirectory(routingZkAddrMap);
   }
 
   @Test
   public void testGetAllMetadataStoreRealms() throws IOException {
-    String responseBody =
-        get("metadata-store-realms", null, Response.Status.OK.getStatusCode(), true);
+    get(NON_EXISTING_NAMESPACE_URI_PREFIX + "metadata-store-realms", null,
+        Response.Status.NOT_FOUND.getStatusCode(), false);
+
+    String responseBody = get(TEST_NAMESPACE_URI_PREFIX + "/metadata-store-realms", null,
+        Response.Status.OK.getStatusCode(), true);
     // It is safe to cast the object and suppress warnings.
     @SuppressWarnings("unchecked")
     Map<String, Collection<String>> queriedRealmsMap =
@@ -106,12 +132,70 @@ public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
     Assert.assertEquals(queriedRealmsSet, expectedRealms);
   }
 
+  @Test
+  public void testAddMetadataStoreRealm() {
+    Collection<String> previousRealms =
+        _metadataStoreDirectory.getAllMetadataStoreRealms(TEST_NAMESPACE);
+    Set<String> expectedRealmsSet = new HashSet<>(previousRealms);
+
+    Assert.assertFalse(expectedRealmsSet.contains(TEST_REALM_3),
+        "Metadata store directory should not have realm: " + TEST_REALM_3);
+
+    // Test a request that has not found response.
+    put(NON_EXISTING_NAMESPACE_URI_PREFIX + TEST_REALM_3, null,
+        Entity.entity("", MediaType.APPLICATION_JSON_TYPE),
+        Response.Status.NOT_FOUND.getStatusCode());
+
+    // Successful request.
+    put(TEST_NAMESPACE_URI_PREFIX + "/metadata-store-realms/" + TEST_REALM_3, null,
+        Entity.entity("", MediaType.APPLICATION_JSON_TYPE),
+        Response.Status.CREATED.getStatusCode());
+
+    Collection<String> updatedRealms =
+        _metadataStoreDirectory.getAllMetadataStoreRealms(TEST_NAMESPACE);
+    Set<String> updateRealmsSet = new HashSet<>(updatedRealms);
+    expectedRealmsSet.add(TEST_REALM_3);
+
+    // TODO: enable asserts and add verify for refreshed MSD once write operations are ready.
+//    Assert.assertEquals(updateRealmsSet, previousRealms);
+  }
+
+  @Test(dependsOnMethods = "testAddMetadataStoreRealm")
+  public void testDeleteMetadataStoreRealm() {
+    Collection<String> previousRealms =
+        _metadataStoreDirectory.getAllMetadataStoreRealms(TEST_NAMESPACE);
+    Set<String> expectedRealmsSet = new HashSet<>(previousRealms);
+
+//    Assert.assertTrue(expectedRealmsSet.contains(TEST_REALM_3),
+//        "Metadata store directory should have realm: " + TEST_REALM_3);
+
+    // Test a request that has not found response.
+    delete(NON_EXISTING_NAMESPACE_URI_PREFIX + TEST_REALM_3,
+        Response.Status.NOT_FOUND.getStatusCode());
+
+    // Successful request.
+    delete(TEST_NAMESPACE_URI_PREFIX + "/metadata-store-realms/" + TEST_REALM_3,
+        Response.Status.OK.getStatusCode());
+
+    Collection<String> updatedRealms =
+        _metadataStoreDirectory.getAllMetadataStoreRealms(TEST_NAMESPACE);
+    Set<String> updateRealmsSet = new HashSet<>(updatedRealms);
+    expectedRealmsSet.remove(TEST_REALM_3);
+
+//    Assert.assertEquals(updateRealmsSet, previousRealms);
+  }
+
   /*
    * Tests REST endpoints: "/sharding-keys"
    */
   @Test
   public void testGetShardingKeysInNamespace() throws IOException {
-    String responseBody = get("/sharding-keys", null, Response.Status.OK.getStatusCode(), true);
+    get(NON_EXISTING_NAMESPACE_URI_PREFIX + "sharding-keys", null,
+        Response.Status.NOT_FOUND.getStatusCode(), true);
+
+    String responseBody =
+        get(TEST_NAMESPACE_URI_PREFIX + "/sharding-keys", null, Response.Status.OK.getStatusCode(),
+            true);
     // It is safe to cast the object and suppress warnings.
     @SuppressWarnings("unchecked")
     Map<String, Collection<String>> queriedShardingKeysMap =
@@ -135,15 +219,16 @@ public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
   @Test
   public void testGetShardingKeysInRealm() throws IOException {
     // Test NOT_FOUND response for a non existed realm.
-    new JerseyUriRequestBuilder("/sharding-keys?realm=nonExistedRealm")
+    new JerseyUriRequestBuilder(TEST_NAMESPACE_URI_PREFIX + "/sharding-keys?realm=nonExistedRealm")
         .expectedReturnStatusCode(Response.Status.NOT_FOUND.getStatusCode()).get(this);
 
     // Query param realm is set to empty, so NOT_FOUND response is returned.
-    new JerseyUriRequestBuilder("/sharding-keys?realm=")
+    new JerseyUriRequestBuilder(TEST_NAMESPACE_URI_PREFIX + "/sharding-keys?realm=")
         .expectedReturnStatusCode(Response.Status.NOT_FOUND.getStatusCode()).get(this);
 
     // Success response.
-    String responseBody = new JerseyUriRequestBuilder("/sharding-keys?realm=" + TEST_REALM_1)
+    String responseBody = new JerseyUriRequestBuilder(
+        TEST_NAMESPACE_URI_PREFIX + "/sharding-keys?realm=" + TEST_REALM_1)
         .isBodyReturnExpected(true).get(this);
     // It is safe to cast the object and suppress warnings.
     @SuppressWarnings("unchecked")
@@ -167,9 +252,73 @@ public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
     Assert.assertEquals(queriedShardingKeys, expectedShardingKeys);
   }
 
+  @Test
+  public void testAddShardingKey() {
+    Set<String> expectedShardingKeysSet = new HashSet<>(
+        _metadataStoreDirectory.getAllShardingKeysInRealm(TEST_NAMESPACE, TEST_REALM_1));
+
+    Assert.assertFalse(expectedShardingKeysSet.contains(TEST_SHARDING_KEY),
+        "Realm does not have sharding key: " + TEST_SHARDING_KEY);
+
+    // Request that gets not found response.
+    put(NON_EXISTING_NAMESPACE_URI_PREFIX + TEST_REALM_1 + "/sharding-keys/" + TEST_SHARDING_KEY,
+        null, Entity.entity("", MediaType.APPLICATION_JSON_TYPE),
+        Response.Status.NOT_FOUND.getStatusCode());
+
+    // Successful request.
+    put(TEST_NAMESPACE_URI_PREFIX + "/metadata-store-realms/" + TEST_REALM_1 + "/sharding-keys/"
+            + TEST_SHARDING_KEY, null, Entity.entity("", MediaType.APPLICATION_JSON_TYPE),
+        Response.Status.CREATED.getStatusCode());
+
+    Set<String> updatedShardingKeysSet = new HashSet<>(
+        _metadataStoreDirectory.getAllShardingKeysInRealm(TEST_NAMESPACE, TEST_REALM_1));
+    expectedShardingKeysSet.add(TEST_SHARDING_KEY);
+
+//    Assert.assertEquals(updatedShardingKeysSet, expectedShardingKeysSet);
+  }
+
+  @Test(dependsOnMethods = "testAddShardingKey")
+  public void testDeleteShardingKey() {
+    Set<String> expectedShardingKeysSet = new HashSet<>(
+        _metadataStoreDirectory.getAllShardingKeysInRealm(TEST_NAMESPACE, TEST_REALM_1));
+
+//    Assert.assertTrue(expectedShardingKeysSet.contains(TEST_SHARDING_KEY),
+//        "Realm should have sharding key: " + TEST_SHARDING_KEY);
+
+    // Request that gets not found response.
+    delete(NON_EXISTING_NAMESPACE_URI_PREFIX + TEST_REALM_1 + "/sharding-keys/" + TEST_SHARDING_KEY,
+        Response.Status.NOT_FOUND.getStatusCode());
+
+    // Successful request.
+    delete(TEST_NAMESPACE_URI_PREFIX + "/metadata-store-realms/" + TEST_REALM_1 + "/sharding-keys/"
+        + TEST_SHARDING_KEY, Response.Status.OK.getStatusCode());
+
+    Set<String> updatedShardingKeysSet = new HashSet<>(
+        _metadataStoreDirectory.getAllShardingKeysInRealm(TEST_NAMESPACE, TEST_REALM_1));
+    expectedShardingKeysSet.remove(TEST_SHARDING_KEY);
+
+//    Assert.assertEquals(updatedShardingKeysSet, expectedShardingKeysSet);
+  }
+
   @AfterClass
-  public void afterClass() {
-    _zkList.forEach(zk -> ZK_SERVER_MAP.get(zk).getZkClient()
-        .deleteRecursive(MetadataStoreRoutingConstants.ROUTING_DATA_PATH));
+  public void afterClass() throws Exception {
+    _metadataStoreDirectory.close();
+    deleteRoutingDataPath();
+  }
+
+  private void deleteRoutingDataPath() throws Exception {
+    Assert.assertTrue(TestHelper.verify(() -> {
+      _zkList.forEach(zk -> ZK_SERVER_MAP.get(zk).getZkClient()
+          .deleteRecursively(MetadataStoreRoutingConstants.ROUTING_DATA_PATH));
+
+      for (String zk : _zkList) {
+        if (ZK_SERVER_MAP.get(zk).getZkClient()
+            .exists(MetadataStoreRoutingConstants.ROUTING_DATA_PATH)) {
+          return false;
+        }
+      }
+
+      return true;
+    }, TestHelper.WAIT_DURATION), "Routing data path should be deleted after the tests.");
   }
 }


[helix] 24/49: Fix InvalidRoutingData error message in tests (#821)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit c3f9ed9061197811fd731078f8f3eb881070866a
Author: Neal Sun <ne...@gmail.com>
AuthorDate: Wed Feb 26 17:24:22 2020 -0800

    Fix InvalidRoutingData error message in tests (#821)
    
    
    This PR aims to fix the InvalidRoutingDataException error message that shows up in tests. The source of this error was due to two test cases in TestZkMetadataStoreDirectory, in which both of the test cases inserted "/a/b/c/d/e" directly into the same set of routing data. This renders the routing data invalid because one sharding key is pointing to two realms.
    This was not caught at insertion because the insertion was done directly instead of through a dedicated API (because the code in question is a test case). The tests didn't fail because they relied on the raw routing data for correctness. The construction of MetadataStoreRoutingData failed "correctly" as can be seen from the error messages, however the construction failure was after raw routing data being fetched, therefore making the situation undetected.
---
 .../apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java
index 6fe5f32..00a328f 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java
@@ -253,7 +253,7 @@ public class TestZkMetadataStoreDirectory extends AbstractTestClass {
     // Since there was a child change callback, make sure data change works on the new child (realm) as well by adding a key
     // This tests removing all subscriptions and subscribing with new children list
     // For all namespaces (Routing ZKs), add an extra sharding key to TEST_REALM_3
-    String newKey = "/a/b/c/d/e";
+    String newKey = "/x/y/z";
     _zkList.forEach(zk -> {
       ZNRecord znRecord = ZK_SERVER_MAP.get(zk).getZkClient()
           .readData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + TEST_REALM_3);


[helix] 05/49: Add MockMetadataStoreDirectoryServer (#719)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit 4b3b54a15320b42144f40d6ec8d353e1c594a11d
Author: Hunter Lee <hu...@linkedin.com>
AuthorDate: Tue Feb 4 15:20:52 2020 -0800

    Add MockMetadataStoreDirectoryServer (#719)
    
    * Add MockMetadataStoreDirectoryServer
    
    In order to support ZK horizontal scalability, we need to have Metadata Store Directory Service, which is a feature provided by Helix REST. Helix APIs that talk to ZooKeeper will query against this service at initialization to fetch all metadata store routing keys.
    For Helix application developers, this means that there's potentially a lot to do for setting up a testing environment assuming multiple ZKs. This MockMetadataStoreDirectoryServer makes it easy to test by abstracting out the work of having to set up and write metadata store routing information to the routing ZK.
    
    Changelist:
    1. Implement Mock MSDS
    2. Write a test in main()
---
 .../mock/MockMetadataStoreDirectoryServer.java     | 127 +++++++++++++++++++++
 .../mock/TestMockMetadataStoreDirectoryServer.java |  84 ++++++++++++++
 2 files changed, 211 insertions(+)

diff --git a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/mock/MockMetadataStoreDirectoryServer.java b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/mock/MockMetadataStoreDirectoryServer.java
new file mode 100644
index 0000000..ae0f85d
--- /dev/null
+++ b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/mock/MockMetadataStoreDirectoryServer.java
@@ -0,0 +1,127 @@
+package org.apache.helix.rest.metadatastore.mock;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadPoolExecutor;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.sun.net.httpserver.HttpServer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Mock HTTP server that serves GET of metadata store routing data only.
+ * Helix applications may use this to write unit/integration tests without having to set up the routing ZooKeeper and creating routing data ZNodes.
+ */
+public class MockMetadataStoreDirectoryServer {
+  private static final Logger LOG = LoggerFactory.getLogger(MockMetadataStoreDirectoryServer.class);
+
+  protected static final String REST_PREFIX = "/admin/v2/namespaces/";
+  protected static final String ZK_REALM_ENDPOINT = "/METADATA_STORE_ROUTING_DATA/";
+  protected static final int NOT_IMPLEMENTED = 501;
+  protected static final int OK = 200;
+  protected static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+  protected final String _hostname;
+  protected final int _mockServerPort;
+  protected final Map<String, List<String>> _routingDataMap;
+  protected final String _namespace;
+  protected HttpServer _server;
+  protected final ThreadPoolExecutor _executor =
+      (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
+
+  protected enum SupportedHttpVerbs {
+    GET
+  }
+
+  /**
+   * Constructs a Mock MSDS.
+   * A sample GET might look like the following:
+   *     curl localhost:11000/admin/v2/namespaces/MY-HELIX-NAMESPACE/METADATA_STORE_ROUTING_DATA/zk-1
+   * @param hostname hostname for the REST server. E.g.) "localhost"
+   * @param port port to use. E.g.) 11000
+   * @param namespace the Helix REST namespace to mock. E.g.) "MY-HELIX-NAMESPACE"
+   * @param routingData <ZK realm, List of ZK path sharding keys>
+   */
+  public MockMetadataStoreDirectoryServer(String hostname, int port, String namespace,
+      Map<String, List<String>> routingData) {
+    if (hostname == null || hostname.isEmpty()) {
+      throw new IllegalArgumentException("hostname cannot be null or empty!");
+    }
+    if (port < 0 || port > 65535) {
+      throw new IllegalArgumentException("port is not a valid port!");
+    }
+    if (namespace == null || namespace.isEmpty()) {
+      throw new IllegalArgumentException("namespace cannot be null or empty!");
+    }
+    if (routingData == null || routingData.isEmpty()) {
+      throw new IllegalArgumentException("routingData cannot be null or empty!");
+    }
+    _hostname = hostname;
+    _mockServerPort = port;
+    _namespace = namespace;
+    _routingDataMap = routingData;
+  }
+
+  public void startServer()
+      throws IOException {
+    _server = HttpServer.create(new InetSocketAddress(_hostname, _mockServerPort), 0);
+    generateContexts();
+    _server.setExecutor(_executor);
+    _server.start();
+    LOG.info(
+        "Started MockMetadataStoreDirectoryServer at " + _hostname + ":" + _mockServerPort + "!");
+  }
+
+  public void stopServer() {
+    _server.stop(0);
+    _executor.shutdown();
+    LOG.info(
+        "Stopped MockMetadataStoreDirectoryServer at " + _hostname + ":" + _mockServerPort + "!");
+  }
+
+  /**
+   * Dynamically generates HTTP server contexts based on the routing data given.
+   */
+  private void generateContexts() {
+    _routingDataMap.forEach((zkRealm, shardingKeyList) -> _server
+        .createContext(REST_PREFIX + _namespace + ZK_REALM_ENDPOINT + zkRealm, httpExchange -> {
+          OutputStream outputStream = httpExchange.getResponseBody();
+          String htmlResponse;
+          if (SupportedHttpVerbs.GET.name().equals(httpExchange.getRequestMethod())) {
+            htmlResponse = OBJECT_MAPPER.writeValueAsString(shardingKeyList);
+            httpExchange.sendResponseHeaders(OK, htmlResponse.length());
+          } else {
+            htmlResponse = httpExchange.getRequestMethod() + " is not supported!\n";
+            httpExchange.sendResponseHeaders(NOT_IMPLEMENTED, htmlResponse.length());
+          }
+          outputStream.write(htmlResponse.getBytes());
+          outputStream.flush();
+          outputStream.close();
+        }));
+  }
+}
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/mock/TestMockMetadataStoreDirectoryServer.java b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/mock/TestMockMetadataStoreDirectoryServer.java
new file mode 100644
index 0000000..5e71089
--- /dev/null
+++ b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/mock/TestMockMetadataStoreDirectoryServer.java
@@ -0,0 +1,84 @@
+package org.apache.helix.rest.metadatastore.mock;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.google.common.collect.ImmutableList;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.junit.Test;
+import org.testng.Assert;
+
+
+public class TestMockMetadataStoreDirectoryServer {
+  @Test
+  public void testMockMetadataStoreDirectoryServer()
+      throws IOException {
+    // Create fake routing data
+    Map<String, List<String>> routingData = new HashMap<>();
+    routingData.put("zk-0", ImmutableList.of("sharding-key-0", "sharding-key-1", "sharding-key-2"));
+    routingData.put("zk-1", ImmutableList.of("sharding-key-3", "sharding-key-4", "sharding-key-5"));
+    routingData.put("zk-2", ImmutableList.of("sharding-key-6", "sharding-key-7", "sharding-key-8"));
+
+    // Start MockMSDS
+    String host = "localhost";
+    int port = 11000;
+    String endpoint = "http://" + host + ":" + port;
+    String namespace = "MY-HELIX-NAMESPACE";
+    MockMetadataStoreDirectoryServer server =
+        new MockMetadataStoreDirectoryServer(host, port, namespace, routingData);
+    server.startServer();
+    CloseableHttpClient httpClient = HttpClients.createDefault();
+
+    // Send a GET request
+    String testZkRealm = "zk-0";
+    HttpGet getRequest = new HttpGet(
+        endpoint + MockMetadataStoreDirectoryServer.REST_PREFIX + namespace
+            + MockMetadataStoreDirectoryServer.ZK_REALM_ENDPOINT + testZkRealm);
+    try {
+      CloseableHttpResponse getResponse = httpClient.execute(getRequest);
+      List<String> shardingKeyList = MockMetadataStoreDirectoryServer.OBJECT_MAPPER
+          .readValue(getResponse.getEntity().getContent(), List.class);
+      Assert.assertEquals(shardingKeyList, routingData.get(testZkRealm));
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+
+    // Try sending a POST request (not supported)
+    HttpPost postRequest = new HttpPost(
+        endpoint + MockMetadataStoreDirectoryServer.REST_PREFIX + namespace
+            + MockMetadataStoreDirectoryServer.ZK_REALM_ENDPOINT + testZkRealm);
+    try {
+      CloseableHttpResponse postResponse = httpClient.execute(postRequest);
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+
+    // Shutdown
+    server.stopServer();
+  }
+}


[helix] 39/49: Make ZKHelixAdmin and ZKHelixManager Realm-aware (#846)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit efcc852305f258ad2f4f7e3fd7d7f190360f62a8
Author: Huizhi Lu <ih...@gmail.com>
AuthorDate: Thu Mar 12 09:44:38 2020 -0700

    Make ZKHelixAdmin and ZKHelixManager Realm-aware (#846)
    
    To make Helix Java APIs realm-aware, we need to make both ZKHelixAdmin and ZKHelixManager realm-aware. This commit adds a Builder to set client config and connection config for building realm-aware ZkClients underneath.
---
 .../apache/helix/manager/zk/CallbackHandler.java   |   7 +-
 .../helix/manager/zk/ParticipantManager.java       |   5 +-
 .../org/apache/helix/manager/zk/ZKHelixAdmin.java  | 202 +++++++++++++++++----
 .../apache/helix/manager/zk/ZKHelixManager.java    | 102 ++++++++---
 .../test/java/org/apache/helix/ZkTestHelper.java   |  21 ++-
 .../integration/TestResourceGroupEndtoEnd.java     |   3 +-
 .../controller/TestControllerLeadershipChange.java |   4 +-
 .../manager/ClusterControllerManager.java          |   3 +-
 .../manager/ClusterDistributedController.java      |   3 +-
 .../manager/MockParticipantManager.java            |   3 +-
 .../helix/integration/manager/ZkTestManager.java   |   5 +-
 .../apache/helix/manager/zk/TestHandleSession.java |   5 +-
 .../impl/factory/HelixZkClientFactory.java         |   2 +-
 13 files changed, 281 insertions(+), 84 deletions(-)

diff --git a/helix-core/src/main/java/org/apache/helix/manager/zk/CallbackHandler.java b/helix-core/src/main/java/org/apache/helix/manager/zk/CallbackHandler.java
index 82c9b29..bbb8788 100644
--- a/helix-core/src/main/java/org/apache/helix/manager/zk/CallbackHandler.java
+++ b/helix-core/src/main/java/org/apache/helix/manager/zk/CallbackHandler.java
@@ -64,6 +64,7 @@ import org.apache.helix.model.Message;
 import org.apache.helix.model.ResourceConfig;
 import org.apache.helix.monitoring.mbeans.HelixCallbackMonitor;
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.apache.helix.zookeeper.zkclient.IZkChildListener;
 import org.apache.helix.zookeeper.zkclient.IZkDataListener;
@@ -107,7 +108,7 @@ public class CallbackHandler implements IZkChildListener, IZkDataListener {
   private final Set<EventType> _eventTypes;
   private final HelixDataAccessor _accessor;
   private final ChangeType _changeType;
-  private final HelixZkClient _zkClient;
+  private final RealmAwareZkClient _zkClient;
   private final AtomicLong _lastNotificationTimeStamp;
   private final HelixManager _manager;
   private final PropertyKey _propertyKey;
@@ -191,12 +192,12 @@ public class CallbackHandler implements IZkChildListener, IZkDataListener {
    */
   private List<NotificationContext.Type> _expectTypes = nextNotificationType.get(Type.FINALIZE);
 
-  public CallbackHandler(HelixManager manager, HelixZkClient client, PropertyKey propertyKey,
+  public CallbackHandler(HelixManager manager, RealmAwareZkClient client, PropertyKey propertyKey,
       Object listener, EventType[] eventTypes, ChangeType changeType) {
     this(manager, client, propertyKey, listener, eventTypes, changeType, null);
   }
 
-  public CallbackHandler(HelixManager manager, HelixZkClient client, PropertyKey propertyKey,
+  public CallbackHandler(HelixManager manager, RealmAwareZkClient client, PropertyKey propertyKey,
       Object listener, EventType[] eventTypes, ChangeType changeType,
       HelixCallbackMonitor monitor) {
     if (listener == null) {
diff --git a/helix-core/src/main/java/org/apache/helix/manager/zk/ParticipantManager.java b/helix-core/src/main/java/org/apache/helix/manager/zk/ParticipantManager.java
index 411d937..5090a86 100644
--- a/helix-core/src/main/java/org/apache/helix/manager/zk/ParticipantManager.java
+++ b/helix-core/src/main/java/org/apache/helix/manager/zk/ParticipantManager.java
@@ -47,6 +47,7 @@ import org.apache.helix.model.builder.HelixConfigScopeBuilder;
 import org.apache.helix.participant.StateMachineEngine;
 import org.apache.helix.participant.statemachine.ScheduledTaskStateModelFactory;
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.apache.helix.zookeeper.datamodel.ZNRecordBucketizer;
 import org.apache.helix.zookeeper.zkclient.DataUpdater;
@@ -62,7 +63,7 @@ import org.slf4j.LoggerFactory;
 public class ParticipantManager {
   private static Logger LOG = LoggerFactory.getLogger(ParticipantManager.class);
 
-  final HelixZkClient _zkclient;
+  final RealmAwareZkClient _zkclient;
   final HelixManager _manager;
   final PropertyKey.Builder _keyBuilder;
   final String _clusterName;
@@ -81,7 +82,7 @@ public class ParticipantManager {
   // session race condition when handling new session for the participant.
   private final String _sessionId;
 
-  public ParticipantManager(HelixManager manager, HelixZkClient zkclient, int sessionTimeout,
+  public ParticipantManager(HelixManager manager, RealmAwareZkClient zkclient, int sessionTimeout,
       LiveInstanceInfoProvider liveInstanceInfoProvider, List<PreConnectCallback> preConnectCallbacks,
       final String sessionId) {
     _zkclient = zkclient;
diff --git a/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixAdmin.java b/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixAdmin.java
index d7c40ff..ea9b55a 100644
--- a/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixAdmin.java
+++ b/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixAdmin.java
@@ -47,9 +47,9 @@ import org.apache.helix.HelixDefinedState;
 import org.apache.helix.HelixException;
 import org.apache.helix.InstanceType;
 import org.apache.helix.PropertyKey;
-import org.apache.helix.PropertyKey.Builder;
 import org.apache.helix.PropertyPathBuilder;
 import org.apache.helix.PropertyType;
+import org.apache.helix.SystemPropertyKeys;
 import org.apache.helix.controller.rebalancer.DelayedAutoRebalancer;
 import org.apache.helix.controller.rebalancer.strategy.CrushEdRebalanceStrategy;
 import org.apache.helix.controller.rebalancer.strategy.RebalanceStrategy;
@@ -74,12 +74,14 @@ import org.apache.helix.model.Message.MessageType;
 import org.apache.helix.model.PauseSignal;
 import org.apache.helix.model.ResourceConfig;
 import org.apache.helix.model.StateModelDefinition;
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
 import org.apache.helix.tools.DefaultIdealStateCalculator;
 import org.apache.helix.util.HelixUtil;
 import org.apache.helix.util.RebalanceUtil;
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
 import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
+import org.apache.helix.zookeeper.impl.client.FederatedZkClient;
 import org.apache.helix.zookeeper.impl.factory.SharedZkClientFactory;
 import org.apache.helix.zookeeper.zkclient.DataUpdater;
 import org.apache.helix.zookeeper.zkclient.exception.ZkException;
@@ -90,18 +92,27 @@ import org.slf4j.LoggerFactory;
 
 
 public class ZKHelixAdmin implements HelixAdmin {
+  private static final Logger LOG = LoggerFactory.getLogger(ZKHelixAdmin.class);
+
   public static final String CONNECTION_TIMEOUT = "helixAdmin.timeOutInSec";
   private static final String MAINTENANCE_ZNODE_ID = "maintenance";
   private static final int DEFAULT_SUPERCLUSTER_REPLICA = 3;
 
   private final RealmAwareZkClient _zkClient;
   private final ConfigAccessor _configAccessor;
-  // true if ZKHelixAdmin was instantiated with a HelixZkClient, false otherwise
+  // true if ZKHelixAdmin was instantiated with a RealmAwareZkClient, false otherwise
   // This is used for close() to determine how ZKHelixAdmin should close the underlying ZkClient
   private final boolean _usesExternalZkClient;
 
   private static Logger logger = LoggerFactory.getLogger(ZKHelixAdmin.class);
 
+  /**
+   * @deprecated it is recommended to use the builder constructor {@link Builder}
+   * instead to avoid having to manually create and maintain a RealmAwareZkClient
+   * outside of ZKHelixAdmin.
+   *
+   * @param zkClient A created RealmAwareZkClient
+   */
   @Deprecated
   public ZKHelixAdmin(RealmAwareZkClient zkClient) {
     _zkClient = zkClient;
@@ -109,14 +120,69 @@ public class ZKHelixAdmin implements HelixAdmin {
     _usesExternalZkClient = true;
   }
 
+  /**
+   * There are 2 realm-aware modes to connect to ZK:
+   * 1. if system property {@link SystemPropertyKeys#MULTI_ZK_ENABLED} is set to <code>"true"</code>,
+   * it will connect on multi-realm mode;
+   * 2. otherwise, it will connect on single-realm mode to the <code>zkAddress</code> provided.
+   *
+   * @param zkAddress ZK address
+   * @exception HelixException if not able to connect on multi-realm mode
+   *
+   * @deprecated it is recommended to use the builder constructor {@link Builder}
+   */
+  @Deprecated
   public ZKHelixAdmin(String zkAddress) {
     int timeOutInSec = Integer.parseInt(System.getProperty(CONNECTION_TIMEOUT, "30"));
-    HelixZkClient.ZkClientConfig clientConfig = new HelixZkClient.ZkClientConfig();
-    clientConfig.setZkSerializer(new ZNRecordSerializer())
-        .setConnectInitTimeout(timeOutInSec * 1000);
-    _zkClient = SharedZkClientFactory.getInstance()
-        .buildZkClient(new HelixZkClient.ZkConnectionConfig(zkAddress), clientConfig);
-    _zkClient.waitUntilConnected(timeOutInSec, TimeUnit.SECONDS);
+    RealmAwareZkClient.RealmAwareZkClientConfig clientConfig =
+        new RealmAwareZkClient.RealmAwareZkClientConfig()
+            .setConnectInitTimeout(timeOutInSec * 1000L)
+            .setZkSerializer(new ZNRecordSerializer());
+
+    RealmAwareZkClient zkClient;
+
+    if (Boolean.getBoolean(SystemPropertyKeys.MULTI_ZK_ENABLED)) {
+      try {
+        zkClient = new FederatedZkClient(
+            new RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder().build(), clientConfig);
+      } catch (IllegalStateException | IOException | InvalidRoutingDataException e) {
+        throw new HelixException("Not able to connect on multi-realm mode.", e);
+      }
+    } else {
+      zkClient = SharedZkClientFactory.getInstance()
+          .buildZkClient(new HelixZkClient.ZkConnectionConfig(zkAddress),
+              clientConfig.createHelixZkClientConfig());
+      zkClient.waitUntilConnected(timeOutInSec, TimeUnit.SECONDS);
+    }
+
+    _zkClient = zkClient;
+    _configAccessor = new ConfigAccessor(_zkClient);
+    _usesExternalZkClient = false;
+  }
+
+  private ZKHelixAdmin(Builder builder) {
+    RealmAwareZkClient zkClient;
+    switch (builder.realmMode) {
+      case MULTI_REALM:
+        try {
+          zkClient = new FederatedZkClient(builder.realmAwareZkConnectionConfig,
+              builder.realmAwareZkClientConfig);
+        } catch (IOException | InvalidRoutingDataException | IllegalStateException e) {
+          throw new HelixException("Not able to connect on multi-realm mode.", e);
+        }
+        break;
+      case SINGLE_REALM:
+        // Create a HelixZkClient: Use a SharedZkClient because ZKHelixAdmin does not need to do
+        // ephemeral operations
+        zkClient = SharedZkClientFactory.getInstance()
+            .buildZkClient(new HelixZkClient.ZkConnectionConfig(builder.zkAddress),
+                builder.realmAwareZkClientConfig.createHelixZkClientConfig());
+        break;
+      default:
+        throw new HelixException("Invalid RealmMode given: " + builder.realmMode);
+    }
+
+    _zkClient = zkClient;
     _configAccessor = new ConfigAccessor(_zkClient);
     _usesExternalZkClient = false;
   }
@@ -206,7 +272,7 @@ public class ZKHelixAdmin implements HelixAdmin {
 
     HelixDataAccessor accessor =
         new ZKHelixDataAccessor(clusterName, new ZkBaseDataAccessor<ZNRecord>(_zkClient));
-    Builder keyBuilder = accessor.keyBuilder();
+    PropertyKey.Builder keyBuilder = accessor.keyBuilder();
 
     return accessor.getProperty(keyBuilder.instanceConfig(instanceName));
   }
@@ -382,7 +448,7 @@ public class ZKHelixAdmin implements HelixAdmin {
         reason == null ? "NULL" : reason);
     HelixDataAccessor accessor =
         new ZKHelixDataAccessor(clusterName, new ZkBaseDataAccessor<ZNRecord>(_zkClient));
-    Builder keyBuilder = accessor.keyBuilder();
+    PropertyKey.Builder keyBuilder = accessor.keyBuilder();
 
     if (enabled) {
       accessor.removeProperty(keyBuilder.pause());
@@ -407,7 +473,7 @@ public class ZKHelixAdmin implements HelixAdmin {
   public boolean isInMaintenanceMode(String clusterName) {
     HelixDataAccessor accessor =
         new ZKHelixDataAccessor(clusterName, new ZkBaseDataAccessor<ZNRecord>(_zkClient));
-    Builder keyBuilder = accessor.keyBuilder();
+    PropertyKey.Builder keyBuilder = accessor.keyBuilder();
     return accessor.getBaseDataAccessor()
         .exists(keyBuilder.maintenance().getPath(), AccessOption.PERSISTENT);
   }
@@ -448,7 +514,7 @@ public class ZKHelixAdmin implements HelixAdmin {
       final MaintenanceSignal.TriggeringEntity triggeringEntity) {
     HelixDataAccessor accessor =
         new ZKHelixDataAccessor(clusterName, new ZkBaseDataAccessor<ZNRecord>(_zkClient));
-    Builder keyBuilder = accessor.keyBuilder();
+    PropertyKey.Builder keyBuilder = accessor.keyBuilder();
     logger.info("Cluster {} {} {} maintenance mode for reason {}.", clusterName,
         triggeringEntity == MaintenanceSignal.TriggeringEntity.CONTROLLER ? "automatically"
             : "manually", enabled ? "enters" : "exits", reason == null ? "NULL" : reason);
@@ -516,7 +582,7 @@ public class ZKHelixAdmin implements HelixAdmin {
         instanceName, clusterName);
     HelixDataAccessor accessor =
         new ZKHelixDataAccessor(clusterName, new ZkBaseDataAccessor<ZNRecord>(_zkClient));
-    Builder keyBuilder = accessor.keyBuilder();
+    PropertyKey.Builder keyBuilder = accessor.keyBuilder();
 
     // check the instance is alive
     LiveInstance liveInstance = accessor.getProperty(keyBuilder.liveInstance(instanceName));
@@ -634,7 +700,7 @@ public class ZKHelixAdmin implements HelixAdmin {
         instanceNames == null ? "NULL" : HelixUtil.serializeByComma(instanceNames), clusterName);
     HelixDataAccessor accessor =
         new ZKHelixDataAccessor(clusterName, new ZkBaseDataAccessor<ZNRecord>(_zkClient));
-    Builder keyBuilder = accessor.keyBuilder();
+    PropertyKey.Builder keyBuilder = accessor.keyBuilder();
     List<ExternalView> extViews = accessor.getChildValues(keyBuilder.externalViews());
 
     Set<String> resetInstanceNames = new HashSet<String>(instanceNames);
@@ -662,7 +728,7 @@ public class ZKHelixAdmin implements HelixAdmin {
         resourceNames == null ? "NULL" : HelixUtil.serializeByComma(resourceNames), clusterName);
     HelixDataAccessor accessor =
         new ZKHelixDataAccessor(clusterName, new ZkBaseDataAccessor<ZNRecord>(_zkClient));
-    Builder keyBuilder = accessor.keyBuilder();
+    PropertyKey.Builder keyBuilder = accessor.keyBuilder();
     List<ExternalView> extViews = accessor.getChildValues(keyBuilder.externalViews());
 
     Set<String> resetResourceNames = new HashSet<String>(resourceNames);
@@ -790,7 +856,7 @@ public class ZKHelixAdmin implements HelixAdmin {
 
     HelixDataAccessor accessor =
         new ZKHelixDataAccessor(clusterName, new ZkBaseDataAccessor<ZNRecord>(_zkClient));
-    Builder keyBuilder = accessor.keyBuilder();
+    PropertyKey.Builder keyBuilder = accessor.keyBuilder();
 
     for (String instanceName : instances) {
       InstanceConfig config = accessor.getProperty(keyBuilder.instanceConfig(instanceName));
@@ -909,7 +975,7 @@ public class ZKHelixAdmin implements HelixAdmin {
 
     HelixDataAccessor accessor =
         new ZKHelixDataAccessor(clusterName, new ZkBaseDataAccessor<ZNRecord>(_zkClient));
-    Builder keyBuilder = accessor.keyBuilder();
+    PropertyKey.Builder keyBuilder = accessor.keyBuilder();
 
     for (String resourceName : getResourcesInCluster(clusterName)) {
       IdealState is = accessor.getProperty(keyBuilder.idealStates(resourceName));
@@ -925,7 +991,7 @@ public class ZKHelixAdmin implements HelixAdmin {
   public IdealState getResourceIdealState(String clusterName, String resourceName) {
     HelixDataAccessor accessor =
         new ZKHelixDataAccessor(clusterName, new ZkBaseDataAccessor<ZNRecord>(_zkClient));
-    Builder keyBuilder = accessor.keyBuilder();
+    PropertyKey.Builder keyBuilder = accessor.keyBuilder();
 
     return accessor.getProperty(keyBuilder.idealStates(resourceName));
   }
@@ -938,7 +1004,7 @@ public class ZKHelixAdmin implements HelixAdmin {
             clusterName, idealState == null ? "NULL" : idealState.toString());
     HelixDataAccessor accessor =
         new ZKHelixDataAccessor(clusterName, new ZkBaseDataAccessor<ZNRecord>(_zkClient));
-    Builder keyBuilder = accessor.keyBuilder();
+    PropertyKey.Builder keyBuilder = accessor.keyBuilder();
 
     accessor.setProperty(keyBuilder.idealStates(resourceName), idealState);
   }
@@ -981,7 +1047,7 @@ public class ZKHelixAdmin implements HelixAdmin {
   public ExternalView getResourceExternalView(String clusterName, String resourceName) {
     HelixDataAccessor accessor =
         new ZKHelixDataAccessor(clusterName, new ZkBaseDataAccessor<ZNRecord>(_zkClient));
-    Builder keyBuilder = accessor.keyBuilder();
+    PropertyKey.Builder keyBuilder = accessor.keyBuilder();
     return accessor.getProperty(keyBuilder.externalView(resourceName));
   }
 
@@ -1015,7 +1081,7 @@ public class ZKHelixAdmin implements HelixAdmin {
 
     HelixDataAccessor accessor =
         new ZKHelixDataAccessor(clusterName, new ZkBaseDataAccessor<ZNRecord>(_zkClient));
-    Builder keyBuilder = accessor.keyBuilder();
+    PropertyKey.Builder keyBuilder = accessor.keyBuilder();
     accessor.setProperty(keyBuilder.stateModelDef(stateModelDef), stateModel);
   }
 
@@ -1024,7 +1090,7 @@ public class ZKHelixAdmin implements HelixAdmin {
     logger.info("Drop resource {} from cluster {}", resourceName, clusterName);
     HelixDataAccessor accessor =
         new ZKHelixDataAccessor(clusterName, new ZkBaseDataAccessor<ZNRecord>(_zkClient));
-    Builder keyBuilder = accessor.keyBuilder();
+    PropertyKey.Builder keyBuilder = accessor.keyBuilder();
 
     accessor.removeProperty(keyBuilder.idealStates(resourceName));
     accessor.removeProperty(keyBuilder.resourceConfig(resourceName));
@@ -1039,7 +1105,7 @@ public class ZKHelixAdmin implements HelixAdmin {
   public StateModelDefinition getStateModelDef(String clusterName, String stateModelName) {
     HelixDataAccessor accessor =
         new ZKHelixDataAccessor(clusterName, new ZkBaseDataAccessor<ZNRecord>(_zkClient));
-    Builder keyBuilder = accessor.keyBuilder();
+    PropertyKey.Builder keyBuilder = accessor.keyBuilder();
 
     return accessor.getProperty(keyBuilder.stateModelDef(stateModelName));
   }
@@ -1049,7 +1115,7 @@ public class ZKHelixAdmin implements HelixAdmin {
     logger.info("Deleting cluster {}.", clusterName);
     HelixDataAccessor accessor =
         new ZKHelixDataAccessor(clusterName, new ZkBaseDataAccessor<ZNRecord>(_zkClient));
-    Builder keyBuilder = accessor.keyBuilder();
+    PropertyKey.Builder keyBuilder = accessor.keyBuilder();
 
     String root = "/" + clusterName;
     if (accessor.getChildNames(keyBuilder.liveInstances()).size() > 0) {
@@ -1093,7 +1159,7 @@ public class ZKHelixAdmin implements HelixAdmin {
 
     ZKHelixDataAccessor accessor =
         new ZKHelixDataAccessor(grandCluster, new ZkBaseDataAccessor<ZNRecord>(_zkClient));
-    Builder keyBuilder = accessor.keyBuilder();
+    PropertyKey.Builder keyBuilder = accessor.keyBuilder();
 
     accessor.setProperty(keyBuilder.idealStates(idealState.getResourceName()), idealState);
   }
@@ -1288,7 +1354,7 @@ public class ZKHelixAdmin implements HelixAdmin {
         constraintId, clusterName);
     BaseDataAccessor<ZNRecord> baseAccessor = new ZkBaseDataAccessor<ZNRecord>(_zkClient);
 
-    Builder keyBuilder = new Builder(clusterName);
+    PropertyKey.Builder keyBuilder = new PropertyKey.Builder(clusterName);
     String path = keyBuilder.constraint(constraintType.toString()).getPath();
 
     baseAccessor.update(path, new DataUpdater<ZNRecord>() {
@@ -1311,7 +1377,7 @@ public class ZKHelixAdmin implements HelixAdmin {
         constraintId, clusterName);
     BaseDataAccessor<ZNRecord> baseAccessor = new ZkBaseDataAccessor<ZNRecord>(_zkClient);
 
-    Builder keyBuilder = new Builder(clusterName);
+    PropertyKey.Builder keyBuilder = new PropertyKey.Builder(clusterName);
     String path = keyBuilder.constraint(constraintType.toString()).getPath();
 
     baseAccessor.update(path, new DataUpdater<ZNRecord>() {
@@ -1333,7 +1399,7 @@ public class ZKHelixAdmin implements HelixAdmin {
     HelixDataAccessor accessor =
         new ZKHelixDataAccessor(clusterName, new ZkBaseDataAccessor<ZNRecord>(_zkClient));
 
-    Builder keyBuilder = new Builder(clusterName);
+    PropertyKey.Builder keyBuilder = new PropertyKey.Builder(clusterName);
     return accessor.getProperty(keyBuilder.constraint(constraintType.toString()));
   }
 
@@ -1415,7 +1481,7 @@ public class ZKHelixAdmin implements HelixAdmin {
     }
     HelixDataAccessor accessor =
         new ZKHelixDataAccessor(clusterName, new ZkBaseDataAccessor<ZNRecord>(_zkClient));
-    Builder keyBuilder = accessor.keyBuilder();
+    PropertyKey.Builder keyBuilder = accessor.keyBuilder();
 
     InstanceConfig config = accessor.getProperty(keyBuilder.instanceConfig(instanceName));
     config.addTag(tag);
@@ -1436,7 +1502,7 @@ public class ZKHelixAdmin implements HelixAdmin {
     }
     ZKHelixDataAccessor accessor =
         new ZKHelixDataAccessor(clusterName, new ZkBaseDataAccessor<ZNRecord>(_zkClient));
-    Builder keyBuilder = accessor.keyBuilder();
+    PropertyKey.Builder keyBuilder = accessor.keyBuilder();
 
     InstanceConfig config = accessor.getProperty(keyBuilder.instanceConfig(instanceName));
     config.removeTag(tag);
@@ -1457,7 +1523,7 @@ public class ZKHelixAdmin implements HelixAdmin {
     }
     HelixDataAccessor accessor =
         new ZKHelixDataAccessor(clusterName, new ZkBaseDataAccessor<ZNRecord>(_zkClient));
-    Builder keyBuilder = accessor.keyBuilder();
+    PropertyKey.Builder keyBuilder = accessor.keyBuilder();
 
     InstanceConfig config = accessor.getProperty(keyBuilder.instanceConfig(instanceName));
     config.setZoneId(zoneId);
@@ -1649,7 +1715,7 @@ public class ZKHelixAdmin implements HelixAdmin {
 
     HelixDataAccessor accessor =
         new ZKHelixDataAccessor(clusterName, new ZkBaseDataAccessor<>(_zkClient));
-    Builder keyBuilder = accessor.keyBuilder();
+    PropertyKey.Builder keyBuilder = accessor.keyBuilder();
     List<IdealState> idealStates = accessor.getChildValues(keyBuilder.idealStates());
     List<String> nullIdealStates = new ArrayList<>();
     for (int i = 0; i < idealStates.size(); i++) {
@@ -1690,7 +1756,7 @@ public class ZKHelixAdmin implements HelixAdmin {
     // Ensure that all instances are valid
     HelixDataAccessor accessor =
         new ZKHelixDataAccessor(clusterName, new ZkBaseDataAccessor<>(_zkClient));
-    Builder keyBuilder = accessor.keyBuilder();
+    PropertyKey.Builder keyBuilder = accessor.keyBuilder();
     List<String> instances = accessor.getChildNames(keyBuilder.instanceConfigs());
     if (validateInstancesForWagedRebalance(clusterName, instances).containsValue(false)) {
       throw new HelixException(String
@@ -1800,4 +1866,74 @@ public class ZKHelixAdmin implements HelixAdmin {
             clusterConfig));
     return true;
   }
+
+  // TODO: refactor builder to reduce duplicate code with other Helix Java APIs
+  public static class Builder {
+    private String zkAddress;
+    private RealmAwareZkClient.RealmMode realmMode;
+    private RealmAwareZkClient.RealmAwareZkConnectionConfig realmAwareZkConnectionConfig;
+    private RealmAwareZkClient.RealmAwareZkClientConfig realmAwareZkClientConfig;
+
+    public Builder() {
+    }
+
+    public ZKHelixAdmin.Builder setZkAddress(String zkAddress) {
+      this.zkAddress = zkAddress;
+      return this;
+    }
+
+    public ZKHelixAdmin.Builder setRealmMode(RealmAwareZkClient.RealmMode realmMode) {
+      this.realmMode = realmMode;
+      return this;
+    }
+
+    public ZKHelixAdmin.Builder setRealmAwareZkConnectionConfig(
+        RealmAwareZkClient.RealmAwareZkConnectionConfig realmAwareZkConnectionConfig) {
+      realmAwareZkConnectionConfig = realmAwareZkConnectionConfig;
+      return this;
+    }
+
+    public ZKHelixAdmin.Builder setRealmAwareZkClientConfig(
+        RealmAwareZkClient.RealmAwareZkClientConfig realmAwareZkClientConfig) {
+      realmAwareZkClientConfig = realmAwareZkClientConfig;
+      return this;
+    }
+
+    public ZKHelixAdmin build() {
+      validate();
+      return new ZKHelixAdmin(this);
+    }
+
+    /*
+     * Validates the given parameters before creating an instance of ZKHelixAdmin.
+     */
+    private void validate() {
+      // Resolve RealmMode based on other parameters
+      boolean isZkAddressSet = zkAddress != null && !zkAddress.isEmpty();
+      if (realmMode == RealmAwareZkClient.RealmMode.SINGLE_REALM && !isZkAddressSet) {
+        throw new HelixException(
+            "RealmMode cannot be single-realm without a valid ZkAddress set!");
+      }
+      if (realmMode == RealmAwareZkClient.RealmMode.MULTI_REALM && isZkAddressSet) {
+        throw new HelixException(
+            "ZkAddress cannot be set on multi-realm mode!");
+      }
+      if (realmMode == null) {
+        realmMode = isZkAddressSet ? RealmAwareZkClient.RealmMode.SINGLE_REALM
+            : RealmAwareZkClient.RealmMode.MULTI_REALM;
+      }
+
+      // Resolve RealmAwareZkClientConfig
+      if (realmAwareZkClientConfig == null) {
+        realmAwareZkClientConfig = new RealmAwareZkClient.RealmAwareZkClientConfig();
+      }
+
+      // Resolve RealmAwareZkConnectionConfig
+      if (realmAwareZkConnectionConfig == null) {
+        // If not set, create a default one
+        realmAwareZkConnectionConfig =
+            new RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder().build();
+      }
+    }
+  }
 }
diff --git a/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixManager.java b/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixManager.java
index 5fa307b..9434f7c 100644
--- a/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixManager.java
+++ b/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixManager.java
@@ -19,6 +19,7 @@ package org.apache.helix.manager.zk;
  * under the License.
  */
 
+import java.io.IOException;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
@@ -73,14 +74,17 @@ import org.apache.helix.model.LiveInstance;
 import org.apache.helix.monitoring.ZKPathDataDumpTask;
 import org.apache.helix.monitoring.mbeans.HelixCallbackMonitor;
 import org.apache.helix.monitoring.mbeans.MonitorLevel;
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
 import org.apache.helix.participant.HelixStateMachineEngine;
 import org.apache.helix.participant.StateMachineEngine;
 import org.apache.helix.store.zk.AutoFallbackPropertyStore;
 import org.apache.helix.store.zk.ZkHelixPropertyStore;
 import org.apache.helix.util.HelixUtil;
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.apache.helix.zookeeper.impl.factory.DedicatedZkClientFactory;
+import org.apache.helix.zookeeper.impl.factory.HelixZkClientFactory;
 import org.apache.helix.zookeeper.impl.factory.SharedZkClientFactory;
 import org.apache.helix.zookeeper.zkclient.IZkStateListener;
 import org.apache.helix.zookeeper.zkclient.exception.ZkInterruptedException;
@@ -117,7 +121,7 @@ public class ZKHelixManager implements HelixManager, IZkStateListener {
   private final String _version;
   private int _reportLatency;
 
-  protected HelixZkClient _zkclient = null;
+  protected RealmAwareZkClient _zkclient;
   private final DefaultMessagingService _messagingService;
   private Map<ChangeType, HelixCallbackMonitor> _callbackMonitors;
 
@@ -652,30 +656,7 @@ public class ZKHelixManager implements HelixManager, IZkStateListener {
   }
 
   void createClient() throws Exception {
-    PathBasedZkSerializer zkSerializer =
-        ChainedPathZkSerializer.builder(new ZNRecordSerializer()).build();
-
-    HelixZkClient.ZkConnectionConfig connectionConfig = new HelixZkClient.ZkConnectionConfig(_zkAddress);
-    connectionConfig.setSessionTimeout(_sessionTimeout);
-    HelixZkClient.ZkClientConfig clientConfig = new HelixZkClient.ZkClientConfig();
-    clientConfig
-        .setZkSerializer(zkSerializer)
-        .setConnectInitTimeout(_connectionInitTimeout)
-        .setMonitorType(_instanceType.name())
-        .setMonitorKey(_clusterName)
-        .setMonitorInstanceName(_instanceName)
-        .setMonitorRootPathOnly(isMonitorRootPathOnly());
-
-    HelixZkClient newClient;
-    switch (_instanceType) {
-    case ADMINISTRATOR:
-      newClient = SharedZkClientFactory.getInstance().buildZkClient(connectionConfig, clientConfig);
-      break;
-    default:
-      newClient = DedicatedZkClientFactory
-          .getInstance().buildZkClient(connectionConfig, clientConfig);
-      break;
-    }
+    final RealmAwareZkClient newClient = createSingleRealmZkClient();
 
     synchronized (this) {
       if (_zkclient != null) {
@@ -1286,4 +1267,75 @@ public class ZKHelixManager implements HelixManager, IZkStateListener {
   public Long getSessionStartTime() {
     return _sessionStartTime;
   }
+
+  /*
+   * Prepares connection config and client config based on the internal parameters given to
+   * HelixManager in order to create a ZkClient instance to use. Note that a shared ZkClient
+   * instance will be created if connecting as an ADMINISTRATOR to minimize the cost of creating
+   * ZkConnections.
+   */
+  private RealmAwareZkClient createSingleRealmZkClient() {
+    final String shardingKey = buildShardingKey();
+    PathBasedZkSerializer zkSerializer =
+        ChainedPathZkSerializer.builder(new ZNRecordSerializer()).build();
+
+    RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig =
+        new RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder()
+            .setRealmMode(RealmAwareZkClient.RealmMode.SINGLE_REALM)
+            .setZkRealmShardingKey(shardingKey)
+            .setSessionTimeout(_sessionTimeout).build();
+
+    RealmAwareZkClient.RealmAwareZkClientConfig clientConfig =
+        new RealmAwareZkClient.RealmAwareZkClientConfig();
+
+    clientConfig.setZkSerializer(zkSerializer)
+        .setConnectInitTimeout(_connectionInitTimeout)
+        .setMonitorType(_instanceType.name())
+        .setMonitorKey(_clusterName)
+        .setMonitorInstanceName(_instanceName)
+        .setMonitorRootPathOnly(isMonitorRootPathOnly());
+
+    if (_instanceType == InstanceType.ADMINISTRATOR) {
+      return resolveZkClient(SharedZkClientFactory.getInstance(), connectionConfig,
+          clientConfig);
+    }
+
+    return resolveZkClient(DedicatedZkClientFactory.getInstance(), connectionConfig,
+        clientConfig);
+  }
+
+  /*
+   * Resolves what type of ZkClient this HelixManager should use based on whether MULTI_ZK_ENABLED
+   * System config is set or not. Two types of ZkClients are available:
+   * 1) If MULTI_ZK_ENABLED is set to true, we create a dedicated RealmAwareZkClient
+   * that provides full ZkClient functionalities and connects to the correct ZK by querying
+   * MetadataStoreDirectoryService.
+   * 2) Otherwise, we create a dedicated HelixZkClient which plainly connects to
+   * the ZK address given.
+   */
+  private RealmAwareZkClient resolveZkClient(HelixZkClientFactory zkClientFactory,
+      RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig,
+      RealmAwareZkClient.RealmAwareZkClientConfig clientConfig) {
+    if (Boolean.getBoolean(SystemPropertyKeys.MULTI_ZK_ENABLED)) {
+      try {
+        // Create realm-aware ZkClient.
+        return zkClientFactory.buildZkClient(connectionConfig, clientConfig);
+      } catch (IllegalArgumentException | IOException | InvalidRoutingDataException e) {
+        throw new HelixException("Not able to connect on realm-aware mode for sharding key: "
+            + connectionConfig.getZkRealmShardingKey(), e);
+      }
+    }
+
+    // If multi-zk mode is not enabled, create HelixZkClient with the provided zk address.
+    HelixZkClient.ZkClientConfig helixZkClientConfig = clientConfig.createHelixZkClientConfig();
+    HelixZkClient.ZkConnectionConfig helixZkConnectionConfig =
+        new HelixZkClient.ZkConnectionConfig(_zkAddress)
+            .setSessionTimeout(connectionConfig.getSessionTimeout());
+
+    return zkClientFactory.buildZkClient(helixZkConnectionConfig, helixZkClientConfig);
+  }
+
+  private String buildShardingKey() {
+    return _clusterName.charAt(0) == '/' ? _clusterName : "/" + _clusterName;
+  }
 }
diff --git a/helix-core/src/test/java/org/apache/helix/ZkTestHelper.java b/helix-core/src/test/java/org/apache/helix/ZkTestHelper.java
index c2b3d35..73dded4 100644
--- a/helix-core/src/test/java/org/apache/helix/ZkTestHelper.java
+++ b/helix-core/src/test/java/org/apache/helix/ZkTestHelper.java
@@ -43,6 +43,7 @@ import org.apache.helix.manager.zk.ZKHelixDataAccessor;
 import org.apache.helix.manager.zk.ZkBaseDataAccessor;
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
 import org.apache.helix.model.ExternalView;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 import org.apache.helix.zookeeper.zkclient.IZkChildListener;
 import org.apache.helix.zookeeper.zkclient.IZkDataListener;
 import org.apache.helix.zookeeper.zkclient.IZkStateListener;
@@ -71,7 +72,7 @@ public class ZkTestHelper {
   /**
    * Simulate a zk state change by calling {@link ZkClient#process(WatchedEvent)} directly
    */
-  public static void simulateZkStateReconnected(HelixZkClient client) {
+  public static void simulateZkStateReconnected(RealmAwareZkClient client) {
     ZkClient zkClient = (ZkClient) client;
     WatchedEvent event = new WatchedEvent(EventType.None, KeeperState.Disconnected, null);
     zkClient.process(event);
@@ -84,7 +85,7 @@ public class ZkTestHelper {
    * @param client
    * @return
    */
-  public static String getSessionId(HelixZkClient client) {
+  public static String getSessionId(RealmAwareZkClient client) {
     ZkConnection connection = (ZkConnection) ((ZkClient) client).getConnection();
     ZooKeeper curZookeeper = connection.getZookeeper();
     return Long.toHexString(curZookeeper.getSessionId());
@@ -146,7 +147,7 @@ public class ZkTestHelper {
     LOG.info("After expiry. sessionId: " + Long.toHexString(curZookeeper.getSessionId()));
   }
 
-  public static void expireSession(HelixZkClient client) throws Exception {
+  public static void expireSession(RealmAwareZkClient client) throws Exception {
     final CountDownLatch waitNewSession = new CountDownLatch(1);
     final ZkClient zkClient = (ZkClient) client;
 
@@ -213,7 +214,7 @@ public class ZkTestHelper {
    * @param client
    * @throws Exception
    */
-  public static void asyncExpireSession(HelixZkClient client) throws Exception {
+  public static void asyncExpireSession(RealmAwareZkClient client) throws Exception {
     final ZkClient zkClient = (ZkClient) client;
     ZkConnection connection = ((ZkConnection) zkClient.getConnection());
     ZooKeeper curZookeeper = connection.getZookeeper();
@@ -245,7 +246,7 @@ public class ZkTestHelper {
   /*
    * stateMap: partition->instance->state
    */
-  public static boolean verifyState(HelixZkClient zkclient, String clusterName, String resourceName,
+  public static boolean verifyState(RealmAwareZkClient zkclient, String clusterName, String resourceName,
       Map<String, Map<String, String>> expectStateMap, String op) {
     boolean result = true;
     ZkBaseDataAccessor<ZNRecord> baseAccessor = new ZkBaseDataAccessor<ZNRecord>(zkclient);
@@ -391,7 +392,7 @@ public class ZkTestHelper {
     }
   }
 
-  public static Map<String, List<String>> getZkWatch(HelixZkClient client) throws Exception {
+  public static Map<String, List<String>> getZkWatch(RealmAwareZkClient client) throws Exception {
     Map<String, List<String>> lists = new HashMap<String, List<String>>();
     ZkClient zkClient = (ZkClient) client;
 
@@ -424,7 +425,7 @@ public class ZkTestHelper {
     return lists;
   }
 
-  public static Map<String, Set<IZkDataListener>> getZkDataListener(HelixZkClient client)
+  public static Map<String, Set<IZkDataListener>> getZkDataListener(RealmAwareZkClient client)
       throws Exception {
     java.lang.reflect.Field field = getField(client.getClass(), "_dataListener");
     field.setAccessible(true);
@@ -433,7 +434,7 @@ public class ZkTestHelper {
     return dataListener;
   }
 
-  public static Map<String, Set<IZkChildListener>> getZkChildListener(HelixZkClient client)
+  public static Map<String, Set<IZkChildListener>> getZkChildListener(RealmAwareZkClient client)
       throws Exception {
     java.lang.reflect.Field field = getField(client.getClass(), "_childListener");
     field.setAccessible(true);
@@ -442,7 +443,7 @@ public class ZkTestHelper {
     return childListener;
   }
 
-  public static boolean tryWaitZkEventsCleaned(HelixZkClient zkclient) throws Exception {
+  public static boolean tryWaitZkEventsCleaned(RealmAwareZkClient zkclient) throws Exception {
     java.lang.reflect.Field field = getField(zkclient.getClass(), "_eventThread");
     field.setAccessible(true);
     Object eventThread = field.get(zkclient);
@@ -468,7 +469,7 @@ public class ZkTestHelper {
     return false;
   }
 
-  public static void injectExpire(HelixZkClient client)
+  public static void injectExpire(RealmAwareZkClient client)
       throws ExecutionException, InterruptedException {
     final ZkClient zkClient = (ZkClient) client;
     Future future = _executor.submit(new Runnable() {
diff --git a/helix-core/src/test/java/org/apache/helix/integration/TestResourceGroupEndtoEnd.java b/helix-core/src/test/java/org/apache/helix/integration/TestResourceGroupEndtoEnd.java
index 0aa5787..0313a77 100644
--- a/helix-core/src/test/java/org/apache/helix/integration/TestResourceGroupEndtoEnd.java
+++ b/helix-core/src/test/java/org/apache/helix/integration/TestResourceGroupEndtoEnd.java
@@ -47,6 +47,7 @@ import org.apache.helix.participant.statemachine.StateModel;
 import org.apache.helix.participant.statemachine.StateModelFactory;
 import org.apache.helix.spectator.RoutingTableProvider;
 import org.apache.helix.tools.ClusterStateVerifier;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.Assert;
@@ -427,7 +428,7 @@ public class TestResourceGroupEndtoEnd extends ZkTestBase {
     }
 
     @Override
-    public HelixZkClient getZkClient() {
+    public RealmAwareZkClient getZkClient() {
       return _zkclient;
     }
 
diff --git a/helix-core/src/test/java/org/apache/helix/integration/controller/TestControllerLeadershipChange.java b/helix-core/src/test/java/org/apache/helix/integration/controller/TestControllerLeadershipChange.java
index 8a1ff03..5aa9a91 100644
--- a/helix-core/src/test/java/org/apache/helix/integration/controller/TestControllerLeadershipChange.java
+++ b/helix-core/src/test/java/org/apache/helix/integration/controller/TestControllerLeadershipChange.java
@@ -34,12 +34,12 @@ import org.apache.helix.common.ZkTestBase;
 import org.apache.helix.integration.manager.ClusterControllerManager;
 import org.apache.helix.integration.manager.MockParticipantManager;
 import org.apache.helix.manager.zk.CallbackHandler;
-import org.apache.helix.zookeeper.api.client.HelixZkClient;
 import org.apache.helix.model.IdealState;
 import org.apache.helix.model.LiveInstance;
 import org.apache.helix.monitoring.mbeans.MonitorDomainNames;
 import org.apache.helix.tools.ClusterVerifiers.BestPossibleExternalViewVerifier;
 import org.apache.helix.tools.ClusterVerifiers.ZkHelixClusterVerifier;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.Assert;
@@ -148,7 +148,7 @@ public class TestControllerLeadershipChange extends ZkTestBase {
     callbackHandlers.forEach(callbackHandler -> Assert.assertTrue(callbackHandler.isReady()));
 
     // check the zk connection is open
-    HelixZkClient zkClient = controller.getZkClient();
+    RealmAwareZkClient zkClient = controller.getZkClient();
     Assert.assertFalse(zkClient.isClosed());
     Long sessionId = zkClient.getSessionId();
     Assert.assertNotNull(sessionId);
diff --git a/helix-core/src/test/java/org/apache/helix/integration/manager/ClusterControllerManager.java b/helix-core/src/test/java/org/apache/helix/integration/manager/ClusterControllerManager.java
index 7d810fb..9281e2d 100644
--- a/helix-core/src/test/java/org/apache/helix/integration/manager/ClusterControllerManager.java
+++ b/helix-core/src/test/java/org/apache/helix/integration/manager/ClusterControllerManager.java
@@ -27,6 +27,7 @@ import org.apache.helix.InstanceType;
 import org.apache.helix.manager.zk.CallbackHandler;
 import org.apache.helix.manager.zk.ZKHelixManager;
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -94,7 +95,7 @@ public class ClusterControllerManager extends ZKHelixManager implements Runnable
   }
 
   @Override
-  public HelixZkClient getZkClient() {
+  public RealmAwareZkClient getZkClient() {
     return _zkclient;
   }
 
diff --git a/helix-core/src/test/java/org/apache/helix/integration/manager/ClusterDistributedController.java b/helix-core/src/test/java/org/apache/helix/integration/manager/ClusterDistributedController.java
index 8e15928..397fae5 100644
--- a/helix-core/src/test/java/org/apache/helix/integration/manager/ClusterDistributedController.java
+++ b/helix-core/src/test/java/org/apache/helix/integration/manager/ClusterDistributedController.java
@@ -28,6 +28,7 @@ import org.apache.helix.manager.zk.ZKHelixManager;
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
 import org.apache.helix.participant.DistClusterControllerStateModelFactory;
 import org.apache.helix.participant.StateMachineEngine;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -83,7 +84,7 @@ public class ClusterDistributedController extends ZKHelixManager implements Runn
   }
 
   @Override
-  public HelixZkClient getZkClient() {
+  public RealmAwareZkClient getZkClient() {
     return _zkclient;
   }
 
diff --git a/helix-core/src/test/java/org/apache/helix/integration/manager/MockParticipantManager.java b/helix-core/src/test/java/org/apache/helix/integration/manager/MockParticipantManager.java
index 0036e0d..84bc334 100644
--- a/helix-core/src/test/java/org/apache/helix/integration/manager/MockParticipantManager.java
+++ b/helix-core/src/test/java/org/apache/helix/integration/manager/MockParticipantManager.java
@@ -33,6 +33,7 @@ import org.apache.helix.mock.participant.MockSchemataModelFactory;
 import org.apache.helix.mock.participant.MockTransition;
 import org.apache.helix.model.BuiltInStateModelDefinitions;
 import org.apache.helix.participant.StateMachineEngine;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -128,7 +129,7 @@ public class MockParticipantManager extends ZKHelixManager implements Runnable,
   }
 
   @Override
-  public HelixZkClient getZkClient() {
+  public RealmAwareZkClient getZkClient() {
     return _zkclient;
   }
 
diff --git a/helix-core/src/test/java/org/apache/helix/integration/manager/ZkTestManager.java b/helix-core/src/test/java/org/apache/helix/integration/manager/ZkTestManager.java
index 1a6903a..93e4b53 100644
--- a/helix-core/src/test/java/org/apache/helix/integration/manager/ZkTestManager.java
+++ b/helix-core/src/test/java/org/apache/helix/integration/manager/ZkTestManager.java
@@ -22,10 +22,11 @@ package org.apache.helix.integration.manager;
 import java.util.List;
 
 import org.apache.helix.manager.zk.CallbackHandler;
-import org.apache.helix.zookeeper.api.client.HelixZkClient;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
+
 
 public interface ZkTestManager {
-  HelixZkClient getZkClient();
+  RealmAwareZkClient getZkClient();
 
   List<CallbackHandler> getHandlers();
 
diff --git a/helix-core/src/test/java/org/apache/helix/manager/zk/TestHandleSession.java b/helix-core/src/test/java/org/apache/helix/manager/zk/TestHandleSession.java
index 92eb3c4..54d38ee 100644
--- a/helix-core/src/test/java/org/apache/helix/manager/zk/TestHandleSession.java
+++ b/helix-core/src/test/java/org/apache/helix/manager/zk/TestHandleSession.java
@@ -41,6 +41,7 @@ import org.apache.helix.controller.GenericHelixController;
 import org.apache.helix.integration.manager.MockParticipantManager;
 import org.apache.helix.model.LiveInstance;
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 import org.apache.helix.zookeeper.impl.client.ZkClient;
 import org.testng.Assert;
 import org.testng.annotations.Test;
@@ -570,7 +571,7 @@ public class TestHandleSession extends ZkTestBase {
       return _handlers;
     }
 
-    HelixZkClient getZkClient() {
+    RealmAwareZkClient getZkClient() {
       return _zkclient;
     }
 
@@ -616,7 +617,7 @@ public class TestHandleSession extends ZkTestBase {
       return _handlers;
     }
 
-    HelixZkClient getZkClient() {
+    RealmAwareZkClient getZkClient() {
       return _zkclient;
     }
 
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/HelixZkClientFactory.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/HelixZkClientFactory.java
index ca13321..1f792ee 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/HelixZkClientFactory.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/HelixZkClientFactory.java
@@ -29,7 +29,7 @@ import org.apache.helix.zookeeper.zkclient.ZkConnection;
 /**
  * Abstract class of the ZkClient factory.
  */
-abstract class HelixZkClientFactory implements RealmAwareZkClientFactory {
+public abstract class HelixZkClientFactory implements RealmAwareZkClientFactory {
 
   /**
    * Build a ZkClient using specified connection config and client config


[helix] 36/49: Make ZkCacheBaseDataAccessor and ZkHelixPropertyStore realm-aware (#863)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit 8d472699042fb88ae2aa95ce712ed4cdab10f627
Author: Hunter Lee <hu...@linkedin.com>
AuthorDate: Wed Mar 11 19:44:26 2020 -0700

    Make ZkCacheBaseDataAccessor and ZkHelixPropertyStore realm-aware (#863)
    
    This commit makes both ZkCacheBaseDataAccessor and ZkHelixPropertyStore realm-aware by choosing the appropriate realm-aware ZkClients in the constructor. Also, we add a Builder here to give users options to set Connection config and Client config.
    Note that ZkHelixPropertyStore extends CacheBaseDataAccessor so there is no change needed.
---
 .../helix/manager/zk/ZkBaseDataAccessor.java       |  11 +-
 .../helix/manager/zk/ZkCacheBaseDataAccessor.java  | 274 ++++++++++++++++++---
 2 files changed, 243 insertions(+), 42 deletions(-)

diff --git a/helix-core/src/main/java/org/apache/helix/manager/zk/ZkBaseDataAccessor.java b/helix-core/src/main/java/org/apache/helix/manager/zk/ZkBaseDataAccessor.java
index f08ba55..f287c22 100644
--- a/helix-core/src/main/java/org/apache/helix/manager/zk/ZkBaseDataAccessor.java
+++ b/helix-core/src/main/java/org/apache/helix/manager/zk/ZkBaseDataAccessor.java
@@ -58,6 +58,7 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
 
   // Designates which mode ZkBaseDataAccessor should be created in. If not specified, it will be
   // created on SHARED mode.
+  // TODO: move this to RealmAwareZkClient
   public enum ZkClientType {
     /*
      * When ZkBaseDataAccessor is created with the DEDICATED type, it supports ephemeral node
@@ -70,7 +71,12 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
      * When ZkBaseDataAccessor is created with the SHARED type, it only supports CRUD
      * functionalities. This will be the default mode of creation.
      */
-    SHARED
+    SHARED,
+    /*
+     * Uses FederatedZkClient (applicable on multi-realm mode only) that queries Metadata Store
+     * Directory Service for routing data
+     */
+    FEDERATED
   }
 
   enum RetCode {
@@ -104,7 +110,8 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
   private static Logger LOG = LoggerFactory.getLogger(ZkBaseDataAccessor.class);
 
   private final RealmAwareZkClient _zkClient;
-  // true if ZkBaseDataAccessor was instantiated with a HelixZkClient, false otherwise
+
+  // true if ZkBaseDataAccessor was instantiated with a RealmAwareZkClient, false otherwise
   // This is used for close() to determine how ZkBaseDataAccessor should close the underlying
   // ZkClient
   private final boolean _usesExternalZkClient;
diff --git a/helix-core/src/main/java/org/apache/helix/manager/zk/ZkCacheBaseDataAccessor.java b/helix-core/src/main/java/org/apache/helix/manager/zk/ZkCacheBaseDataAccessor.java
index 6d2c5cf..bd05ea7 100644
--- a/helix-core/src/main/java/org/apache/helix/manager/zk/ZkCacheBaseDataAccessor.java
+++ b/helix-core/src/main/java/org/apache/helix/manager/zk/ZkCacheBaseDataAccessor.java
@@ -19,10 +19,10 @@ package org.apache.helix.manager.zk;
  * under the License.
  */
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
@@ -31,12 +31,16 @@ import java.util.concurrent.locks.ReentrantLock;
 
 import org.apache.helix.AccessOption;
 import org.apache.helix.HelixException;
+import org.apache.helix.SystemPropertyKeys;
 import org.apache.helix.manager.zk.ZkBaseDataAccessor.RetCode;
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
 import org.apache.helix.store.HelixPropertyListener;
 import org.apache.helix.store.HelixPropertyStore;
 import org.apache.helix.store.zk.ZNode;
 import org.apache.helix.util.PathUtils;
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
+import org.apache.helix.zookeeper.impl.client.FederatedZkClient;
 import org.apache.helix.zookeeper.impl.factory.DedicatedZkClientFactory;
 import org.apache.helix.zookeeper.impl.factory.SharedZkClientFactory;
 import org.apache.helix.zookeeper.zkclient.DataUpdater;
@@ -58,7 +62,15 @@ public class ZkCacheBaseDataAccessor<T> implements HelixPropertyStore<T> {
   protected ZkCallbackCache<T> _zkCache;
 
   final ZkBaseDataAccessor<T> _baseAccessor;
-  final Map<String, Cache<T>> _cacheMap;
+
+  // TODO: need to make sure no overlap between wtCachePaths and zkCachePaths
+  // TreeMap key is ordered by key string length, so more general (i.e. short) prefix
+  // comes first
+  final Map<String, Cache<T>> _cacheMap = new TreeMap<>((o1, o2) -> {
+    int len1 = o1.split("/").length;
+    int len2 = o2.split("/").length;
+    return len1 - len2;
+  });
 
   final String _chrootPath;
   final List<String> _wtCachePaths;
@@ -70,12 +82,14 @@ public class ZkCacheBaseDataAccessor<T> implements HelixPropertyStore<T> {
   private final ReentrantLock _eventLock = new ReentrantLock();
   private ZkCacheEventThread _eventThread;
 
-  private HelixZkClient _zkClient = null;
+  private RealmAwareZkClient _zkClient;
 
+  @Deprecated
   public ZkCacheBaseDataAccessor(ZkBaseDataAccessor<T> baseAccessor, List<String> wtCachePaths) {
     this(baseAccessor, null, wtCachePaths, null);
   }
 
+  @Deprecated
   public ZkCacheBaseDataAccessor(ZkBaseDataAccessor<T> baseAccessor, String chrootPath,
       List<String> wtCachePaths, List<String> zkCachePaths) {
     _baseAccessor = baseAccessor;
@@ -90,50 +104,62 @@ public class ZkCacheBaseDataAccessor<T> implements HelixPropertyStore<T> {
     _wtCachePaths = wtCachePaths;
     _zkCachePaths = zkCachePaths;
 
-    // TODO: need to make sure no overlap between wtCachePaths and zkCachePaths
-    // TreeMap key is ordered by key string length, so more general (i.e. short) prefix
-    // comes first
-    _cacheMap = new TreeMap<>(new Comparator<String>() {
-      @Override
-      public int compare(String o1, String o2) {
-        int len1 = o1.split("/").length;
-        int len2 = o2.split("/").length;
-        return len1 - len2;
-      }
-    });
-
     start();
   }
 
+  @Deprecated
   public ZkCacheBaseDataAccessor(String zkAddress, ZkSerializer serializer, String chrootPath,
       List<String> wtCachePaths, List<String> zkCachePaths) {
     this(zkAddress, serializer, chrootPath, wtCachePaths, zkCachePaths, null, null,
         ZkBaseDataAccessor.ZkClientType.SHARED);
   }
 
+  @Deprecated
   public ZkCacheBaseDataAccessor(String zkAddress, ZkSerializer serializer, String chrootPath,
       List<String> wtCachePaths, List<String> zkCachePaths, String monitorType, String monitorkey) {
     this(zkAddress, serializer, chrootPath, wtCachePaths, zkCachePaths, monitorType, monitorkey,
         ZkBaseDataAccessor.ZkClientType.SHARED);
   }
 
+  @Deprecated
   public ZkCacheBaseDataAccessor(String zkAddress, ZkSerializer serializer, String chrootPath,
       List<String> wtCachePaths, List<String> zkCachePaths, String monitorType, String monitorkey,
       ZkBaseDataAccessor.ZkClientType zkClientType) {
-    HelixZkClient.ZkClientConfig clientConfig = new HelixZkClient.ZkClientConfig();
-    clientConfig.setZkSerializer(serializer).setMonitorType(monitorType).setMonitorKey(monitorkey);
-    switch (zkClientType) {
-    case DEDICATED:
-      _zkClient = DedicatedZkClientFactory.getInstance().buildZkClient(
-          new HelixZkClient.ZkConnectionConfig(zkAddress),
-          new HelixZkClient.ZkClientConfig().setZkSerializer(serializer));
-      break;
-    case SHARED:
-    default:
-      _zkClient = SharedZkClientFactory.getInstance()
-          .buildZkClient(new HelixZkClient.ZkConnectionConfig(zkAddress), clientConfig);
-    }
-    _zkClient.waitUntilConnected(HelixZkClient.DEFAULT_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS);
+
+    // If the multi ZK config is enabled, use multi-realm mode with FederatedZkClient
+    if (Boolean.parseBoolean(System.getProperty(SystemPropertyKeys.MULTI_ZK_ENABLED))) {
+      try {
+        RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder connectionConfigBuilder =
+            new RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder();
+        RealmAwareZkClient.RealmAwareZkClientConfig clientConfig =
+            new RealmAwareZkClient.RealmAwareZkClientConfig();
+        clientConfig.setZkSerializer(serializer).setMonitorType(monitorType)
+            .setMonitorKey(monitorkey);
+        // Use a federated zk client
+        _zkClient = new FederatedZkClient(connectionConfigBuilder.build(), clientConfig);
+      } catch (IOException | InvalidRoutingDataException | IllegalStateException e) {
+        // Note: IllegalStateException is for HttpRoutingDataReader if MSDS endpoint cannot be
+        // found
+        throw new HelixException("Failed to create ZkCacheBaseDataAccessor!", e);
+      }
+    } else {
+      HelixZkClient.ZkClientConfig clientConfig = new HelixZkClient.ZkClientConfig();
+      clientConfig.setZkSerializer(serializer).setMonitorType(monitorType)
+          .setMonitorKey(monitorkey);
+      switch (zkClientType) {
+        case DEDICATED:
+          _zkClient = DedicatedZkClientFactory.getInstance()
+              .buildZkClient(new HelixZkClient.ZkConnectionConfig(zkAddress),
+                  new HelixZkClient.ZkClientConfig().setZkSerializer(serializer));
+          break;
+        case SHARED:
+        default:
+          _zkClient = SharedZkClientFactory.getInstance()
+              .buildZkClient(new HelixZkClient.ZkConnectionConfig(zkAddress), clientConfig);
+      }
+      _zkClient.waitUntilConnected(HelixZkClient.DEFAULT_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS);
+    }
+
     _baseAccessor = new ZkBaseDataAccessor<>(_zkClient);
 
     if (chrootPath == null || chrootPath.equals("/")) {
@@ -146,17 +172,67 @@ public class ZkCacheBaseDataAccessor<T> implements HelixPropertyStore<T> {
     _wtCachePaths = wtCachePaths;
     _zkCachePaths = zkCachePaths;
 
-    // TODO: need to make sure no overlap between wtCachePaths and zkCachePaths
-    // TreeMap key is ordered by key string length, so more general (i.e. short) prefix
-    // comes first
-    _cacheMap = new TreeMap<>(new Comparator<String>() {
-      @Override
-      public int compare(String o1, String o2) {
-        int len1 = o1.split("/").length;
-        int len2 = o2.split("/").length;
-        return len1 - len2;
-      }
-    });
+    start();
+  }
+
+  /**
+   * Constructor using a Builder that allows users to set connection and client configs.
+   * @param builder
+   */
+  private ZkCacheBaseDataAccessor(Builder builder) {
+    _chrootPath = builder._chrootPath;
+    _wtCachePaths = builder._wtCachePaths;
+    _zkCachePaths = builder._zkCachePaths;
+
+    RealmAwareZkClient zkClient;
+    switch (builder._realmMode) {
+      case MULTI_REALM:
+        try {
+          if (builder._zkClientType == ZkBaseDataAccessor.ZkClientType.DEDICATED) {
+            // Use a realm-aware dedicated zk client
+            zkClient = DedicatedZkClientFactory.getInstance()
+                .buildZkClient(builder._realmAwareZkConnectionConfig,
+                    builder._realmAwareZkClientConfig);
+          } else if (builder._zkClientType == ZkBaseDataAccessor.ZkClientType.SHARED) {
+            // Use a realm-aware shared zk client
+            zkClient = SharedZkClientFactory.getInstance()
+                .buildZkClient(builder._realmAwareZkConnectionConfig,
+                    builder._realmAwareZkClientConfig);
+          } else {
+            // Use a federated zk client
+            zkClient = new FederatedZkClient(builder._realmAwareZkConnectionConfig,
+                builder._realmAwareZkClientConfig);
+          }
+          break; // Must break out of the switch statement here since zkClient has been created
+        } catch (IOException | InvalidRoutingDataException | IllegalStateException e) {
+          // Note: IllegalStateException is for HttpRoutingDataReader if MSDS endpoint cannot be
+          // found
+          throw new HelixException("Failed to create ZkCacheBaseDataAccessor!", e);
+        }
+      case SINGLE_REALM:
+        switch (builder._zkClientType) {
+          case DEDICATED:
+            // If DEDICATED, then we use a dedicated HelixZkClient because we must support ephemeral
+            // operations
+            zkClient = DedicatedZkClientFactory.getInstance()
+                .buildZkClient(new HelixZkClient.ZkConnectionConfig(builder._zkAddress),
+                    builder._realmAwareZkClientConfig.createHelixZkClientConfig());
+            break;
+          case SHARED:
+          default:
+            zkClient = SharedZkClientFactory.getInstance()
+                .buildZkClient(new HelixZkClient.ZkConnectionConfig(builder._zkAddress),
+                    builder._realmAwareZkClientConfig.createHelixZkClientConfig());
+            zkClient.waitUntilConnected(HelixZkClient.DEFAULT_CONNECTION_TIMEOUT,
+                TimeUnit.MILLISECONDS);
+            break;
+        }
+      default:
+        throw new HelixException("Invalid RealmMode given: " + builder._realmMode);
+    }
+
+    _zkClient = zkClient;
+    _baseAccessor = new ZkBaseDataAccessor<>(_zkClient);
 
     start();
   }
@@ -842,4 +918,122 @@ public class ZkCacheBaseDataAccessor<T> implements HelixPropertyStore<T> {
       _zkClient.close();
     }
   }
+
+  public static class Builder {
+    private String _zkAddress;
+    private RealmAwareZkClient.RealmMode _realmMode;
+    private RealmAwareZkClient.RealmAwareZkConnectionConfig _realmAwareZkConnectionConfig;
+    private RealmAwareZkClient.RealmAwareZkClientConfig _realmAwareZkClientConfig;
+
+    /** ZkCacheBaseDataAccessor-specific parameters */
+    private String _chrootPath;
+    private List<String> _wtCachePaths;
+    private List<String> _zkCachePaths;
+    private ZkBaseDataAccessor.ZkClientType _zkClientType;
+
+    public Builder() {
+    }
+
+    public Builder setZkAddress(String zkAddress) {
+      _zkAddress = zkAddress;
+      return this;
+    }
+
+    public Builder setRealmMode(RealmAwareZkClient.RealmMode realmMode) {
+      _realmMode = realmMode;
+      return this;
+    }
+
+    public Builder setRealmAwareZkConnectionConfig(
+        RealmAwareZkClient.RealmAwareZkConnectionConfig realmAwareZkConnectionConfig) {
+      _realmAwareZkConnectionConfig = realmAwareZkConnectionConfig;
+      return this;
+    }
+
+    public Builder setRealmAwareZkClientConfig(
+        RealmAwareZkClient.RealmAwareZkClientConfig realmAwareZkClientConfig) {
+      _realmAwareZkClientConfig = realmAwareZkClientConfig;
+      return this;
+    }
+
+    public Builder setChrootPath(String chrootPath) {
+      _chrootPath = chrootPath;
+      return this;
+    }
+
+    public Builder setWtCachePaths(List<String> wtCachePaths) {
+      _wtCachePaths = wtCachePaths;
+      return this;
+    }
+
+    public Builder setZkCachePaths(List<String> zkCachePaths) {
+      _zkCachePaths = zkCachePaths;
+      return this;
+    }
+
+    /**
+     * Sets the ZkClientType. If this is set, ZkCacheBaseDataAccessor will be created on
+     * single-realm mode.
+     * @param zkClientType
+     * @return
+     */
+    public Builder setZkClientType(ZkBaseDataAccessor.ZkClientType zkClientType) {
+      _zkClientType = zkClientType;
+      return this;
+    }
+
+    public ZkCacheBaseDataAccessor build() {
+      validate();
+      return new ZkCacheBaseDataAccessor(this);
+    }
+
+    private void validate() {
+      // Resolve RealmMode based on other parameters
+      boolean isZkAddressSet = _zkAddress != null && !_zkAddress.isEmpty();
+      boolean isZkClientTypeSet = _zkClientType != null;
+
+      // If ZkClientType is set, RealmMode must either be single-realm or not set.
+      if (isZkClientTypeSet && _realmMode == RealmAwareZkClient.RealmMode.MULTI_REALM) {
+        throw new HelixException(
+            "ZkCacheBaseDataAccessor: you cannot set ZkClientType on multi-realm mode!");
+      }
+      // If ZkClientType is not set, default to SHARED
+      if (!isZkClientTypeSet) {
+        _zkClientType = ZkBaseDataAccessor.ZkClientType.SHARED;
+      }
+
+      if (_realmMode == RealmAwareZkClient.RealmMode.SINGLE_REALM && !isZkAddressSet) {
+        throw new HelixException(
+            "ZkCacheBaseDataAccessor: RealmMode cannot be single-realm without a valid ZkAddress set!");
+      }
+
+      if (_realmMode == RealmAwareZkClient.RealmMode.MULTI_REALM && isZkAddressSet) {
+        throw new HelixException(
+            "ZkCacheBaseDataAccessor: You cannot set the ZkAddress on multi-realm mode!");
+      }
+
+      if (_realmMode == RealmAwareZkClient.RealmMode.SINGLE_REALM
+          && _zkClientType == ZkBaseDataAccessor.ZkClientType.FEDERATED) {
+        throw new HelixException(
+            "ZkCacheBaseDataAccessor: You cannot use FederatedZkClient on single-realm mode!");
+      }
+
+      if (_realmMode == null) {
+        _realmMode = isZkAddressSet ? RealmAwareZkClient.RealmMode.SINGLE_REALM
+            : RealmAwareZkClient.RealmMode.MULTI_REALM;
+      }
+
+      // Resolve RealmAwareZkClientConfig
+      if (_realmAwareZkClientConfig == null) {
+        _realmAwareZkClientConfig = new RealmAwareZkClient.RealmAwareZkClientConfig();
+      }
+
+      // Resolve RealmAwareZkConnectionConfig
+      if (_realmAwareZkConnectionConfig == null) {
+        // If not set, create a default one
+        _realmAwareZkConnectionConfig =
+            new RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder().build();
+      }
+    }
+  }
 }


[helix] 49/49: Fix getClusters() in ZKHelixAdmin for multi-zk mode (#916)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit be20362fd3bb2a1259a89f1c993f9220ed0dbabd
Author: Hunter Lee <hu...@linkedin.com>
AuthorDate: Mon Mar 30 10:31:14 2020 -0700

    Fix getClusters() in ZKHelixAdmin for multi-zk mode (#916)
    
    This PR fixes the logic in getClusters() so that it works in a multi-zk environment. On multi-zk mode, the API will query for raw routing data from MSDS and produce a list of all clusters in the namespace. The behavior for single-zk mode remains the same for backward-compatibility.
---
 .../java/org/apache/helix/SystemPropertyKeys.java  |  7 +++++
 .../org/apache/helix/manager/zk/ZKHelixAdmin.java  | 36 +++++++++++++++++-----
 .../multizk/TestMultiZkHelixJavaApis.java          |  8 +++++
 .../helix/msdcommon/util/ZkValidationUtil.java     |  3 +-
 .../zookeeper/api/client/RealmAwareZkClient.java   |  8 +++++
 .../zookeeper/impl/client/DedicatedZkClient.java   | 14 +++++++++
 .../zookeeper/impl/client/FederatedZkClient.java   | 12 ++++++++
 .../zookeeper/impl/client/SharedZkClient.java      | 14 +++++++++
 8 files changed, 94 insertions(+), 8 deletions(-)

diff --git a/helix-common/src/main/java/org/apache/helix/SystemPropertyKeys.java b/helix-common/src/main/java/org/apache/helix/SystemPropertyKeys.java
index a40dbe9..9870cdd 100644
--- a/helix-common/src/main/java/org/apache/helix/SystemPropertyKeys.java
+++ b/helix-common/src/main/java/org/apache/helix/SystemPropertyKeys.java
@@ -19,6 +19,9 @@ package org.apache.helix;
  * under the License.
  */
 
+import org.apache.helix.msdcommon.constant.MetadataStoreRoutingConstants;
+
+
 public class SystemPropertyKeys {
   // Task Driver
   public static final String TASK_CONFIG_LIMITATION = "helixTask.configsLimitation";
@@ -63,4 +66,8 @@ public class SystemPropertyKeys {
 
   // Multi-ZK mode enable/disable flag
   public static final String MULTI_ZK_ENABLED = "helix.multiZkEnabled";
+
+  // System Property Metadata Store Directory Server endpoint key
+  public static final String MSDS_SERVER_ENDPOINT_KEY =
+      MetadataStoreRoutingConstants.MSDS_SERVER_ENDPOINT_KEY;
 }
diff --git a/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixAdmin.java b/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixAdmin.java
index 65e6ec0..fad5005 100644
--- a/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixAdmin.java
+++ b/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixAdmin.java
@@ -36,6 +36,7 @@ import java.util.Set;
 import java.util.TreeMap;
 import java.util.UUID;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 import org.apache.helix.AccessOption;
 import org.apache.helix.BaseDataAccessor;
@@ -83,6 +84,7 @@ import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.apache.helix.zookeeper.impl.client.FederatedZkClient;
 import org.apache.helix.zookeeper.impl.factory.SharedZkClientFactory;
+import org.apache.helix.zookeeper.util.HttpRoutingDataReader;
 import org.apache.helix.zookeeper.zkclient.DataUpdater;
 import org.apache.helix.zookeeper.zkclient.exception.ZkException;
 import org.apache.helix.zookeeper.zkclient.exception.ZkNoNodeException;
@@ -933,17 +935,37 @@ public class ZKHelixAdmin implements HelixAdmin {
 
   @Override
   public List<String> getClusters() {
+    List<String> zkToplevelPaths;
+
     if (Boolean.getBoolean(SystemPropertyKeys.MULTI_ZK_ENABLED)
         || _zkClient instanceof FederatedZkClient) {
-      String errMsg =
-          "getClusters() is not supported in multi-realm mode! Use Metadata Store Directory Service instead!";
-      LOG.error(errMsg);
-      throw new UnsupportedOperationException(errMsg);
+      // If on multi-zk mode, we retrieve cluster information from Metadata Store Directory Service.
+      Map<String, List<String>> realmToShardingKeys;
+      String msdsEndpoint = _zkClient.getRealmAwareZkConnectionConfig().getMsdsEndpoint();
+      try {
+        if (msdsEndpoint == null || msdsEndpoint.isEmpty()) {
+          realmToShardingKeys = HttpRoutingDataReader.getRawRoutingData();
+        } else {
+          realmToShardingKeys = HttpRoutingDataReader.getRawRoutingData(msdsEndpoint);
+        }
+      } catch (IOException e) {
+        throw new HelixException(
+            "ZKHelixAdmin: Failed to read raw routing data from Metadata Store Directory Service! MSDS endpoint used: "
+                + msdsEndpoint, e);
+      }
+      if (realmToShardingKeys == null || realmToShardingKeys.isEmpty()) {
+        return Collections.emptyList();
+      }
+      // Preceding "/"s are removed: e.g.) "/CLUSTER-SHARDING-KEY" -> "CLUSTER-SHARDING-KEY"
+      zkToplevelPaths = realmToShardingKeys.values().stream().flatMap(List::stream)
+          .map(shardingKey -> shardingKey.substring(1)).collect(Collectors.toList());
+    } else {
+      // single-zk mode
+      zkToplevelPaths = _zkClient.getChildren("/");
     }
 
-    List<String> zkToplevelPathes = _zkClient.getChildren("/");
-    List<String> result = new ArrayList<String>();
-    for (String pathName : zkToplevelPathes) {
+    List<String> result = new ArrayList<>();
+    for (String pathName : zkToplevelPaths) {
       if (ZKUtil.isClusterSetup(pathName, _zkClient)) {
         result.add(pathName);
       }
diff --git a/helix-core/src/test/java/org/apache/helix/integration/multizk/TestMultiZkHelixJavaApis.java b/helix-core/src/test/java/org/apache/helix/integration/multizk/TestMultiZkHelixJavaApis.java
index be8bb0b..d2a90a1 100644
--- a/helix-core/src/test/java/org/apache/helix/integration/multizk/TestMultiZkHelixJavaApis.java
+++ b/helix-core/src/test/java/org/apache/helix/integration/multizk/TestMultiZkHelixJavaApis.java
@@ -473,4 +473,12 @@ public class TestMultiZkHelixJavaApis {
       Assert.assertEquals(context.getWorkflowState(), wfStateFromTaskDriver);
     }
   }
+
+  /**
+   * This method tests that ZKHelixAdmin::getClusters() works in multi-zk environment.
+   */
+  @Test(dependsOnMethods = "testTaskFramework")
+  public void testGetAllClusters() {
+    Assert.assertEquals(new HashSet<>(_zkHelixAdmin.getClusters()), new HashSet<>(CLUSTER_LIST));
+  }
 }
diff --git a/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/util/ZkValidationUtil.java b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/util/ZkValidationUtil.java
index ab8258d..0b23431 100644
--- a/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/util/ZkValidationUtil.java
+++ b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/util/ZkValidationUtil.java
@@ -28,12 +28,13 @@ public class ZkValidationUtil {
    * /abc
    * /abc/abc/abc/abc
    * /abc/localhost:1234
+   * /abc/def.hil
    * Invalid matches:
    * null or empty string
    * /abc/
    * /abc/abc/abc/abc/
    **/
   public static boolean isPathValid(String path) {
-    return path.matches("^/|(/[\\w?:-]+)+$");
+    return path.matches("^/|(/[\\w?[$&+,:;=?@#|'<>.^*()%!-]-]+)+$");
   }
 }
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/RealmAwareZkClient.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/RealmAwareZkClient.java
index ee8c8e3..22f3678 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/RealmAwareZkClient.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/RealmAwareZkClient.java
@@ -266,6 +266,14 @@ public interface RealmAwareZkClient {
 
   PathBasedZkSerializer getZkSerializer();
 
+  default RealmAwareZkConnectionConfig getRealmAwareZkConnectionConfig() {
+    throw new UnsupportedOperationException("getRealmAwareZkClientConfig() is not supported!");
+  }
+
+  default RealmAwareZkClientConfig getRealmAwareZkClientConfig() {
+    throw new UnsupportedOperationException("getRealmAwareZkClientConfig() is not supported!");
+  }
+
   /**
    * A class that wraps a default implementation of
    * {@link IZkStateListener}, which means this listener
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/client/DedicatedZkClient.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/client/DedicatedZkClient.java
index beeb085..09b66fb 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/client/DedicatedZkClient.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/client/DedicatedZkClient.java
@@ -59,6 +59,8 @@ public class DedicatedZkClient implements RealmAwareZkClient {
   private final ZkClient _rawZkClient;
   private final MetadataStoreRoutingData _metadataStoreRoutingData;
   private final String _zkRealmShardingKey;
+  private final RealmAwareZkClient.RealmAwareZkConnectionConfig _connectionConfig;
+  private final RealmAwareZkClient.RealmAwareZkClientConfig _clientConfig;
 
   /**
    * DedicatedZkClient connects to a single ZK realm and supports full ZkClient functionalities
@@ -77,6 +79,8 @@ public class DedicatedZkClient implements RealmAwareZkClient {
     if (clientConfig == null) {
       throw new IllegalArgumentException("RealmAwareZkClientConfig cannot be null!");
     }
+    _connectionConfig = connectionConfig;
+    _clientConfig = clientConfig;
 
     // Get the routing data from a static Singleton HttpRoutingDataReader
     String msdsEndpoint = connectionConfig.getMsdsEndpoint();
@@ -468,6 +472,16 @@ public class DedicatedZkClient implements RealmAwareZkClient {
     return _rawZkClient.getZkSerializer();
   }
 
+  @Override
+  public RealmAwareZkConnectionConfig getRealmAwareZkConnectionConfig() {
+    return _connectionConfig;
+  }
+
+  @Override
+  public RealmAwareZkClientConfig getRealmAwareZkClientConfig() {
+    return _clientConfig;
+  }
+
   /**
    * Checks whether the given path belongs matches the ZK path sharding key this DedicatedZkClient is designated to at initialization.
    * @param path
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/client/FederatedZkClient.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/client/FederatedZkClient.java
index 1bfff66..1cebc90 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/client/FederatedZkClient.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/client/FederatedZkClient.java
@@ -74,6 +74,7 @@ public class FederatedZkClient implements RealmAwareZkClient {
       DedicatedZkClientFactory.class.getSimpleName();
 
   private final MetadataStoreRoutingData _metadataStoreRoutingData;
+  private final RealmAwareZkClient.RealmAwareZkConnectionConfig _connectionConfig;
   private final RealmAwareZkClient.RealmAwareZkClientConfig _clientConfig;
 
   // ZK realm -> ZkClient
@@ -102,6 +103,7 @@ public class FederatedZkClient implements RealmAwareZkClient {
     }
 
     _isClosed = false;
+    _connectionConfig = connectionConfig;
     _clientConfig = clientConfig;
     _pathBasedZkSerializer = clientConfig.getZkSerializer();
     _zkRealmToZkClientMap = new ConcurrentHashMap<>();
@@ -477,6 +479,16 @@ public class FederatedZkClient implements RealmAwareZkClient {
     return _pathBasedZkSerializer;
   }
 
+  @Override
+  public RealmAwareZkConnectionConfig getRealmAwareZkConnectionConfig() {
+    return _connectionConfig;
+  }
+
+  @Override
+  public RealmAwareZkClientConfig getRealmAwareZkClientConfig() {
+    return _clientConfig;
+  }
+
   private String create(final String path, final Object dataObject, final List<ACL> acl,
       final CreateMode mode, final String expectedSessionId) {
     if (mode.isEphemeral()) {
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/client/SharedZkClient.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/client/SharedZkClient.java
index dd78aba..0989136 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/client/SharedZkClient.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/client/SharedZkClient.java
@@ -59,6 +59,8 @@ public class SharedZkClient implements RealmAwareZkClient {
   private final MetadataStoreRoutingData _metadataStoreRoutingData;
   private final String _zkRealmShardingKey;
   private final String _zkRealmAddress;
+  private final RealmAwareZkClient.RealmAwareZkConnectionConfig _connectionConfig;
+  private final RealmAwareZkClient.RealmAwareZkClientConfig _clientConfig;
 
   public SharedZkClient(RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig,
       RealmAwareZkClient.RealmAwareZkClientConfig clientConfig)
@@ -69,6 +71,8 @@ public class SharedZkClient implements RealmAwareZkClient {
     if (clientConfig == null) {
       throw new IllegalArgumentException("RealmAwareZkClientConfig cannot be null!");
     }
+    _connectionConfig = connectionConfig;
+    _clientConfig = clientConfig;
 
     // Get the routing data from a static Singleton HttpRoutingDataReader
     String msdsEndpoint = connectionConfig.getMsdsEndpoint();
@@ -499,6 +503,16 @@ public class SharedZkClient implements RealmAwareZkClient {
   }
 
   @Override
+  public RealmAwareZkConnectionConfig getRealmAwareZkConnectionConfig() {
+    return _connectionConfig;
+  }
+
+  @Override
+  public RealmAwareZkClientConfig getRealmAwareZkClientConfig() {
+    return _clientConfig;
+  }
+
+  @Override
   public PathBasedZkSerializer getZkSerializer() {
     return _innerSharedZkClient.getZkSerializer();
   }


[helix] 33/49: Instrument ConfigAccessor's constructors (#856)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit 1ee271295402449802d6be758038bed620d5da6a
Author: Hunter Lee <hu...@linkedin.com>
AuthorDate: Wed Mar 4 12:24:34 2020 -0800

    Instrument ConfigAccessor's constructors (#856)
    
    This diff instruments ConfigAccessor's constructors to make it realm-aware. If ConfigAccessor is unable to start on multi-realm mode, then it falls back to starting on single-realm mode.
---
 .../main/java/org/apache/helix/ConfigAccessor.java | 23 +++++++++++++++++-----
 1 file changed, 18 insertions(+), 5 deletions(-)

diff --git a/helix-core/src/main/java/org/apache/helix/ConfigAccessor.java b/helix-core/src/main/java/org/apache/helix/ConfigAccessor.java
index 8e36f83..b0c1add 100644
--- a/helix-core/src/main/java/org/apache/helix/ConfigAccessor.java
+++ b/helix-core/src/main/java/org/apache/helix/ConfigAccessor.java
@@ -108,11 +108,11 @@ public class ConfigAccessor {
   /**
    * Initialize an accessor with a Zookeeper client
    * Note: it is recommended to use the other constructor instead to avoid having to create a
-   * HelixZkClient.
+   * RealmAwareZkClient.
    * @param zkClient
    */
   @Deprecated
-  public ConfigAccessor(HelixZkClient zkClient) {
+  public ConfigAccessor(RealmAwareZkClient zkClient) {
     _zkClient = zkClient;
     _usesExternalZkClient = true;
   }
@@ -124,9 +124,22 @@ public class ConfigAccessor {
    * @param zkAddress
    */
   public ConfigAccessor(String zkAddress) {
-    _zkClient = SharedZkClientFactory.getInstance()
-        .buildZkClient(new HelixZkClient.ZkConnectionConfig(zkAddress),
-            new HelixZkClient.ZkClientConfig().setZkSerializer(new ZNRecordSerializer()));
+    // First, attempt to connect on multi-realm mode using FederatedZkClient
+    RealmAwareZkClient zkClient;
+    try {
+      zkClient = new FederatedZkClient(
+          new RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder().build(),
+          new RealmAwareZkClient.RealmAwareZkClientConfig());
+    } catch (IOException | InvalidRoutingDataException | IllegalStateException e) {
+      // Connecting multi-realm failed - fall back to creating it on single-realm mode using the given ZK address
+      LOG.info(
+          "ConfigAccessor: not able to connect on multi-realm mode; connecting single-realm mode to ZK: {}",
+          zkAddress, e);
+      zkClient = SharedZkClientFactory.getInstance()
+          .buildZkClient(new HelixZkClient.ZkConnectionConfig(zkAddress),
+              new HelixZkClient.ZkClientConfig().setZkSerializer(new ZNRecordSerializer()));
+    }
+    _zkClient = zkClient;
     _usesExternalZkClient = false;
   }
 


[helix] 17/49: Fix tests in apache/zooscalability and rebase from apache/master (#787)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit fe9c576d1319fc509bb7f3310ad2f197fb6b4cc1
Author: Hunter Lee <hu...@linkedin.com>
AuthorDate: Thu Feb 20 13:52:28 2020 -0800

    Fix tests in apache/zooscalability and rebase from apache/master (#787)
    
    There are some failing tests due to environment issues. This PR fixes them by enforcing a deterministic order among tests.
    This PR also rebases apache/zooscalability from apache/master.
---
 .../accessor/TestZkRoutingDataWriter.java          |  1 +
 .../server/TestMetadataStoreDirectoryAccessor.java | 22 +++++++++++-----------
 2 files changed, 12 insertions(+), 11 deletions(-)

diff --git a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataWriter.java b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataWriter.java
index 8b7224c..1aba067 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataWriter.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataWriter.java
@@ -42,6 +42,7 @@ public class TestZkRoutingDataWriter extends AbstractTestClass {
 
   @BeforeClass
   public void beforeClass() {
+    _baseAccessor.remove(MetadataStoreRoutingConstants.ROUTING_DATA_PATH, AccessOption.PERSISTENT);
     _zkRoutingDataWriter = new ZkRoutingDataWriter(DUMMY_NAMESPACE, ZK_ADDR);
   }
 
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/server/TestMetadataStoreDirectoryAccessor.java b/helix-rest/src/test/java/org/apache/helix/rest/server/TestMetadataStoreDirectoryAccessor.java
index 02b3915..27e2b10 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/server/TestMetadataStoreDirectoryAccessor.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/server/TestMetadataStoreDirectoryAccessor.java
@@ -110,6 +110,13 @@ public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
     _metadataStoreDirectory = new ZkMetadataStoreDirectory(routingZkAddrMap);
   }
 
+  @AfterClass
+  public void afterClass()
+      throws Exception {
+    _metadataStoreDirectory.close();
+    deleteRoutingDataPath();
+  }
+
   @Test
   public void testGetAllMetadataStoreRealms()
       throws IOException {
@@ -133,7 +140,7 @@ public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
     Assert.assertEquals(queriedRealmsSet, expectedRealms);
   }
 
-  @Test
+  @Test(dependsOnMethods = "testGetAllMetadataStoreRealms")
   public void testAddMetadataStoreRealm() {
     Collection<String> previousRealms =
         _metadataStoreDirectory.getAllMetadataStoreRealms(TEST_NAMESPACE);
@@ -189,7 +196,7 @@ public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
   /*
    * Tests REST endpoints: "/sharding-keys"
    */
-  @Test
+  @Test(dependsOnMethods = "testDeleteMetadataStoreRealm")
   public void testGetShardingKeysInNamespace()
       throws IOException {
     get(NON_EXISTING_NAMESPACE_URI_PREFIX + "sharding-keys", null,
@@ -218,7 +225,7 @@ public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
   /*
    * Tests REST endpoint: "/sharding-keys?realm={realmName}"
    */
-  @Test
+  @Test(dependsOnMethods = "testGetShardingKeysInNamespace")
   public void testGetShardingKeysInRealm()
       throws IOException {
     // Test NOT_FOUND response for a non existed realm.
@@ -255,7 +262,7 @@ public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
     Assert.assertEquals(queriedShardingKeys, expectedShardingKeys);
   }
 
-  @Test
+  @Test(dependsOnMethods = "testGetShardingKeysInRealm")
   public void testAddShardingKey() {
     Set<String> expectedShardingKeysSet = new HashSet<>(
         _metadataStoreDirectory.getAllShardingKeysInRealm(TEST_NAMESPACE, TEST_REALM_1));
@@ -303,13 +310,6 @@ public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
 //    Assert.assertEquals(updatedShardingKeysSet, expectedShardingKeysSet);
   }
 
-  @AfterClass
-  public void afterClass()
-      throws Exception {
-    _metadataStoreDirectory.close();
-    deleteRoutingDataPath();
-  }
-
   private void deleteRoutingDataPath()
       throws Exception {
     Assert.assertTrue(TestHelper.verify(() -> {


[helix] 15/49: Add validation logic to MSD write operations (#759)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit 59ca671d9b2ce9642b8cc4ea7df0ba3fc8828fc8
Author: Neal Sun <ne...@gmail.com>
AuthorDate: Tue Feb 18 09:38:13 2020 -0800

    Add validation logic to MSD write operations (#759)
    
    This PR add routing data validation logic MSD writing operations to ensure the writes leave routing data in a valid state. TrieRoutingData logic is modified to reduce duplicate code.
---
 .../metadatastore/MetadataStoreRoutingData.java    |  19 ++++
 .../helix/rest/metadatastore/TrieRoutingData.java  | 126 ++++++++++++---------
 .../metadatastore/ZkMetadataStoreDirectory.java    |  12 +-
 .../resources/helix/PropertyStoreAccessor.java     |   3 +-
 .../resources/zookeeper/ZooKeeperAccessor.java     |  19 +---
 .../rest/metadatastore/TestTrieRoutingData.java    |  76 +++++++++++--
 .../helix/zookeeper/util/ZkValidationUtil.java     |  38 +++++++
 7 files changed, 206 insertions(+), 87 deletions(-)

diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/MetadataStoreRoutingData.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/MetadataStoreRoutingData.java
index 8d8b7e3..3bd9baa 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/MetadataStoreRoutingData.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/MetadataStoreRoutingData.java
@@ -45,4 +45,23 @@ public interface MetadataStoreRoutingData {
    * @throws NoSuchElementException - when the path doesn't contain a sharding key
    */
   String getMetadataStoreRealm(String path) throws IllegalArgumentException, NoSuchElementException;
+
+  /**
+   * Check if the provided sharding key can be inserted to the routing data. The insertion is
+   * invalid if: 1. the sharding key is a parent key to an existing sharding key; 2. the sharding
+   * key has a parent key that is an existing sharding key; 3. the sharding key already exists. In
+   * any of these cases, inserting the sharding key will cause ambiguity among 2 sharding keys,
+   * rendering the routing data invalid.
+   * @param shardingKey - the sharding key to be inserted
+   * @return true if the sharding key could be inserted, false otherwise
+   */
+  boolean isShardingKeyInsertionValid(String shardingKey);
+
+  /**
+   * Check if the provided sharding key and realm address pair exists in the routing data.
+   * @param shardingKey - the sharding key checked
+   * @param realmAddress - the realm address corresponding to the key
+   * @return true if the sharding key and realm address pair exist in the routing data
+   */
+  boolean containsKeyRealmPair(String shardingKey, String realmAddress);
 }
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/TrieRoutingData.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/TrieRoutingData.java
index 923f818..f82b718 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/TrieRoutingData.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/TrieRoutingData.java
@@ -26,7 +26,10 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
+
 import org.apache.helix.rest.metadatastore.exceptions.InvalidRoutingDataException;
+import org.apache.helix.zookeeper.util.ZkValidationUtil;
+
 
 /**
  * This is a class that uses a data structure similar to trie to represent metadata store routing
@@ -54,15 +57,12 @@ public class TrieRoutingData implements MetadataStoreRoutingData {
   }
 
   public Map<String, String> getAllMappingUnderPath(String path) throws IllegalArgumentException {
-    if (path.isEmpty() || !path.substring(0, 1).equals(DELIMITER)) {
-      throw new IllegalArgumentException("Provided path is empty or does not have a leading \""
-          + DELIMITER + "\" character: " + path);
+    if (!ZkValidationUtil.isPathValid(path)) {
+      throw new IllegalArgumentException("Provided path is not a valid Zookeeper path: " + path);
     }
 
-    TrieNode curNode;
-    try {
-      curNode = findTrieNode(path, false);
-    } catch (NoSuchElementException e) {
+    TrieNode curNode = getLongestPrefixNodeAlongPath(path);
+    if (!curNode.getPath().equals(path)) {
       return Collections.emptyMap();
     }
 
@@ -84,59 +84,73 @@ public class TrieRoutingData implements MetadataStoreRoutingData {
 
   public String getMetadataStoreRealm(String path)
       throws IllegalArgumentException, NoSuchElementException {
-    if (path.isEmpty() || !path.substring(0, 1).equals(DELIMITER)) {
-      throw new IllegalArgumentException("Provided path is empty or does not have a leading \""
-          + DELIMITER + "\" character: " + path);
+    if (!ZkValidationUtil.isPathValid(path)) {
+      throw new IllegalArgumentException("Provided path is not a valid Zookeeper path: " + path);
+    }
+
+    TrieNode node = getLongestPrefixNodeAlongPath(path);
+    if (!node.isShardingKey()) {
+      throw new NoSuchElementException(
+          "No sharding key found within the provided path. Path: " + path);
+    }
+    return node.getRealmAddress();
+  }
+
+  public boolean isShardingKeyInsertionValid(String shardingKey) {
+    if (!ZkValidationUtil.isPathValid(shardingKey)) {
+      throw new IllegalArgumentException(
+          "Provided shardingKey is not a valid Zookeeper path: " + shardingKey);
+    }
+
+    TrieNode node = getLongestPrefixNodeAlongPath(shardingKey);
+    return !node.isShardingKey() && !node.getPath().equals(shardingKey);
+  }
+
+  public boolean containsKeyRealmPair(String shardingKey, String realmAddress) {
+    if (!ZkValidationUtil.isPathValid(shardingKey)) {
+      throw new IllegalArgumentException(
+          "Provided shardingKey is not a valid Zookeeper path: " + shardingKey);
     }
 
-    TrieNode leafNode = findTrieNode(path, true);
-    return leafNode.getRealmAddress();
+    TrieNode node = getLongestPrefixNodeAlongPath(shardingKey);
+    return node.getPath().equals(shardingKey) && node.getRealmAddress().equals(realmAddress);
   }
 
-  /**
-   * If findLeafAlongPath is false, then starting from the root node, find the trie node that the
-   * given path is pointing to and return it; raise NoSuchElementException if the path does
-   * not point to any node. If findLeafAlongPath is true, then starting from the root node, find the
-   * leaf node along the provided path; raise NoSuchElementException if the path does not
-   * point to any node or if there is no leaf node along the path.
+  /*
+   * Given a path, find a trie node that represents the longest prefix of the path. For example,
+   * given "/a/b/c", the method starts at "/", and attempts to reach "/a", then attempts to reach
+   * "/a/b", then ends on "/a/b/c"; if any of the node doesn't exist, the traversal terminates and
+   * the last seen existing node is returned.
+   * Note:
+   * 1. When the returned TrieNode is a sharding key, it is the only sharding key along the
+   * provided path (the path points to this sharding key);
+   * 2. When the returned TrieNode is not a sharding key but it represents the provided path, the
+   * provided path is a prefix(parent) to a sharding key;
+   * 3. When the returned TrieNode is not a sharding key and it does not represent the provided
+   * path (meaning the traversal ended before the last node of the path is reached), the provided
+   * path is not associated with any sharding key and can be added as a sharding key without
+   * creating ambiguity cases among sharding keys.
    * @param path - the path where the search is conducted
-   * @param findLeafAlongPath - whether the search is for a leaf node on the path
-   * @return the node pointed by the path or a leaf node along the path
-   * @throws NoSuchElementException - when the path points to nothing or when no leaf node is
-   *           found
+   * @return a TrieNode that represents the longest prefix of the path
    */
-  private TrieNode findTrieNode(String path, boolean findLeafAlongPath)
-      throws NoSuchElementException {
+  private TrieNode getLongestPrefixNodeAlongPath(String path) {
     if (path.equals(DELIMITER)) {
-      if (findLeafAlongPath && !_rootNode.isShardingKey()) {
-        throw new NoSuchElementException("No leaf node found along the path. Path: " + path);
-      }
       return _rootNode;
     }
 
     TrieNode curNode = _rootNode;
-    if (findLeafAlongPath && curNode.isShardingKey()) {
-      return curNode;
-    }
-    Map<String, TrieNode> curChildren = curNode.getChildren();
+    TrieNode nextNode;
     for (String pathSection : path.substring(1).split(DELIMITER, 0)) {
-      curNode = curChildren.get(pathSection);
-      if (curNode == null) {
-        throw new NoSuchElementException(
-            "The provided path is missing from the trie. Path: " + path);
-      }
-      if (findLeafAlongPath && curNode.isShardingKey()) {
+      nextNode = curNode.getChildren().get(pathSection);
+      if (nextNode == null) {
         return curNode;
       }
-      curChildren = curNode.getChildren();
-    }
-    if (findLeafAlongPath) {
-      throw new NoSuchElementException("No leaf node found along the path. Path: " + path);
+      curNode = nextNode;
     }
     return curNode;
   }
 
-  /**
+  /*
    * Checks for the edge case when the only sharding key in provided routing data is the delimiter
    * or an empty string. When this is the case, the trie is valid and contains only one node, which
    * is the root node, and the root node is a leaf node with a realm address associated with it.
@@ -154,7 +168,7 @@ public class TrieRoutingData implements MetadataStoreRoutingData {
     return false;
   }
 
-  /**
+  /*
    * Constructs a trie based on the provided routing data. It loops through all sharding keys and
    * constructs the trie in a top down manner.
    * @param routingData- a mapping from "sharding keys" to "realm addresses" to be parsed into a
@@ -169,9 +183,9 @@ public class TrieRoutingData implements MetadataStoreRoutingData {
     for (Map.Entry<String, List<String>> entry : routingData.entrySet()) {
       for (String shardingKey : entry.getValue()) {
         // Missing leading delimiter is invalid
-        if (shardingKey.isEmpty() || !shardingKey.substring(0, 1).equals(DELIMITER)) {
-          throw new InvalidRoutingDataException("Sharding key does not have a leading \""
-              + DELIMITER + "\" character: " + shardingKey);
+        if (!ZkValidationUtil.isPathValid(shardingKey)) {
+          throw new InvalidRoutingDataException(
+              "Sharding key is not a valid Zookeeper path: " + shardingKey);
         }
 
         // Root can only be a sharding key if it's the only sharding key. Since this method is
@@ -195,12 +209,14 @@ public class TrieRoutingData implements MetadataStoreRoutingData {
           // If the node is already a leaf node, the current sharding key is invalid; if the node
           // doesn't exist, construct a node and continue
           if (nextNode != null && nextNode.isShardingKey()) {
-            throw new InvalidRoutingDataException(shardingKey + " cannot be a sharding key because "
-                + shardingKey.substring(0, nextDelimiterIndex)
-                + " is its parent key and is also a sharding key.");
+            throw new InvalidRoutingDataException(
+                shardingKey + " cannot be a sharding key because " + shardingKey
+                    .substring(0, nextDelimiterIndex)
+                    + " is its parent key and is also a sharding key.");
           } else if (nextNode == null) {
-            nextNode = new TrieNode(new HashMap<>(), shardingKey.substring(0, nextDelimiterIndex),
-                false, "");
+            nextNode =
+                new TrieNode(new HashMap<>(), shardingKey.substring(0, nextDelimiterIndex), false,
+                    "");
             curNode.addChild(keySection, nextNode);
           }
           prevDelimiterIndex = nextDelimiterIndex;
@@ -224,22 +240,22 @@ public class TrieRoutingData implements MetadataStoreRoutingData {
   }
 
   private static class TrieNode {
-    /**
+    /*
      * This field is a mapping between trie key and children nodes. For example, node "a" has
      * children "ab" and "ac", therefore the keys are "b" and "c" respectively.
      */
     private Map<String, TrieNode> _children;
-    /**
+    /*
      * This field states whether the path represented by the node is a sharding key
      */
     private final boolean _isShardingKey;
-    /**
+    /*
      * This field contains the complete path/prefix leading to the current node. For example, the
      * name of root node is "/", then the name of its child node
      * is "/a", and the name of the child's child node is "/a/b".
      */
     private final String _path;
-    /**
+    /*
      * This field represents the data contained in a node(which represents a path), and is only
      * available to the terminal nodes.
      */
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java
index 3be9b72..a57e08c 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java
@@ -37,6 +37,7 @@ import org.apache.helix.rest.metadatastore.exceptions.InvalidRoutingDataExceptio
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+
 /**
  * ZK-based MetadataStoreDirectory that listens on the routing data in routing ZKs with a update
  * callback.
@@ -157,10 +158,18 @@ public class ZkMetadataStoreDirectory implements MetadataStoreDirectory, Routing
 
   @Override
   public boolean addShardingKey(String namespace, String realm, String shardingKey) {
-    if (!_routingDataWriterMap.containsKey(namespace)) {
+    if (!_routingDataWriterMap.containsKey(namespace) || !_routingDataMap.containsKey(namespace)) {
       throw new IllegalArgumentException(
           "Failed to add sharding key: Namespace " + namespace + " is not found!");
     }
+    if (_routingDataMap.get(namespace).containsKeyRealmPair(shardingKey, realm)) {
+      return true;
+    }
+    if (!_routingDataMap.get(namespace).isShardingKeyInsertionValid(shardingKey)) {
+      throw new IllegalArgumentException(
+          "Failed to add sharding key: Adding sharding key " + shardingKey
+              + " makes routing data invalid!");
+    }
     return _routingDataWriterMap.get(namespace).addShardingKey(realm, shardingKey);
   }
 
@@ -210,7 +219,6 @@ public class ZkMetadataStoreDirectory implements MetadataStoreDirectory, Routing
     } catch (InvalidRoutingDataException e) {
       LOG.error("Failed to refresh cached routing data for namespace {}", namespace, e);
     }
-
   }
 
   @Override
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/server/resources/helix/PropertyStoreAccessor.java b/helix-rest/src/main/java/org/apache/helix/rest/server/resources/helix/PropertyStoreAccessor.java
index 4ccc610..e50d67e 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/server/resources/helix/PropertyStoreAccessor.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/server/resources/helix/PropertyStoreAccessor.java
@@ -31,6 +31,7 @@ import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.apache.helix.manager.zk.ZNRecordSerializer;
 import org.apache.helix.manager.zk.ZkBaseDataAccessor;
 import org.apache.helix.rest.server.resources.zookeeper.ZooKeeperAccessor;
+import org.apache.helix.zookeeper.util.ZkValidationUtil;
 import org.codehaus.jackson.node.ObjectNode;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -56,7 +57,7 @@ public class PropertyStoreAccessor extends AbstractHelixResource {
   public Response getPropertyByPath(@PathParam("clusterId") String clusterId,
       @PathParam("path") String path) {
     path = "/" + path;
-    if (!ZooKeeperAccessor.isPathValid(path)) {
+    if (!ZkValidationUtil.isPathValid(path)) {
       LOG.info("The propertyStore path {} is invalid for cluster {}", path, clusterId);
       return badRequest(
           "Invalid path string. Valid path strings use slash as the directory separator and names the location of ZNode");
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 0774bc7..bc2da05 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
@@ -35,6 +35,7 @@ import org.apache.helix.manager.zk.ZkBaseDataAccessor;
 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.helix.zookeeper.util.ZkValidationUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -72,7 +73,7 @@ public class ZooKeeperAccessor extends AbstractResource {
     path = "/" + path;
 
     // Check that the path supplied is valid
-    if (!isPathValid(path)) {
+    if (!ZkValidationUtil.isPathValid(path)) {
       String errMsg = "The given path is not a valid ZooKeeper path: " + path;
       LOG.info(errMsg);
       return badRequest(errMsg);
@@ -170,20 +171,4 @@ public class ZooKeeperAccessor extends AbstractResource {
   private ZooKeeperCommand getZooKeeperCommandIfPresent(String command) {
     return Enums.getIfPresent(ZooKeeperCommand.class, command).orNull();
   }
-
-  /**
-   * Validates whether a given path string is a valid ZK path.
-   *
-   * Valid matches:
-   * /
-   * /abc
-   * /abc/abc/abc/abc
-   * Invalid matches:
-   * null or empty string
-   * /abc/
-   * /abc/abc/abc/abc/
-   **/
-  public static boolean isPathValid(String path) {
-    return path.matches("^/|(/[\\w-]+)+$");
-  }
 }
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestTrieRoutingData.java b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestTrieRoutingData.java
index 4de68a6..dedde19 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestTrieRoutingData.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestTrieRoutingData.java
@@ -25,10 +25,12 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
+
 import org.apache.helix.rest.metadatastore.exceptions.InvalidRoutingDataException;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
+
 public class TestTrieRoutingData {
   private TrieRoutingData _trie;
 
@@ -76,8 +78,8 @@ public class TestTrieRoutingData {
       new TrieRoutingData(routingData);
       Assert.fail("Expecting InvalidRoutingDataException");
     } catch (InvalidRoutingDataException e) {
-      Assert.assertTrue(
-          e.getMessage().contains("Sharding key does not have a leading \"/\" character: b/c/d"));
+      Assert
+          .assertTrue(e.getMessage().contains("Sharding key is not a valid Zookeeper path: b/c/d"));
     }
   }
 
@@ -151,8 +153,7 @@ public class TestTrieRoutingData {
       _trie.getAllMappingUnderPath("");
       Assert.fail("Expecting IllegalArgumentException");
     } catch (IllegalArgumentException e) {
-      Assert.assertTrue(e.getMessage()
-          .contains("Provided path is empty or does not have a leading \"/\" character: "));
+      Assert.assertTrue(e.getMessage().contains("Provided path is not a valid Zookeeper path: "));
     }
   }
 
@@ -162,8 +163,8 @@ public class TestTrieRoutingData {
       _trie.getAllMappingUnderPath("test");
       Assert.fail("Expecting IllegalArgumentException");
     } catch (IllegalArgumentException e) {
-      Assert.assertTrue(e.getMessage()
-          .contains("Provided path is empty or does not have a leading \"/\" character: test"));
+      Assert
+          .assertTrue(e.getMessage().contains("Provided path is not a valid Zookeeper path: test"));
     }
   }
 
@@ -207,8 +208,7 @@ public class TestTrieRoutingData {
       Assert.assertEquals(_trie.getMetadataStoreRealm(""), "realmAddress2");
       Assert.fail("Expecting IllegalArgumentException");
     } catch (IllegalArgumentException e) {
-      Assert.assertTrue(e.getMessage()
-          .contains("Provided path is empty or does not have a leading \"/\" character: "));
+      Assert.assertTrue(e.getMessage().contains("Provided path is not a valid Zookeeper path: "));
     }
   }
 
@@ -218,8 +218,8 @@ public class TestTrieRoutingData {
       Assert.assertEquals(_trie.getMetadataStoreRealm("b/c/d/x/y/z"), "realmAddress2");
       Assert.fail("Expecting IllegalArgumentException");
     } catch (IllegalArgumentException e) {
-      Assert.assertTrue(e.getMessage().contains(
-          "Provided path is empty or does not have a leading \"/\" character: b/c/d/x/y/z"));
+      Assert.assertTrue(
+          e.getMessage().contains("Provided path is not a valid Zookeeper path: b/c/d/x/y/z"));
     }
   }
 
@@ -239,7 +239,7 @@ public class TestTrieRoutingData {
       Assert.fail("Expecting NoSuchElementException");
     } catch (NoSuchElementException e) {
       Assert.assertTrue(
-          e.getMessage().contains("The provided path is missing from the trie. Path: /x/y/z"));
+          e.getMessage().contains("No sharding key found within the provided path. Path: /x/y/z"));
     }
   }
 
@@ -249,7 +249,59 @@ public class TestTrieRoutingData {
       _trie.getMetadataStoreRealm("/b/c");
       Assert.fail("Expecting NoSuchElementException");
     } catch (NoSuchElementException e) {
-      Assert.assertTrue(e.getMessage().contains("No leaf node found along the path. Path: /b/c"));
+      Assert.assertTrue(
+          e.getMessage().contains("No sharding key found within the provided path. Path: /b/c"));
     }
   }
+
+  @Test(dependsOnMethods = "testConstructionNormal")
+  public void testIsShardingKeyInsertionValidNoSlash() {
+    try {
+      _trie.isShardingKeyInsertionValid("x/y/z");
+      Assert.fail("Expecting IllegalArgumentException");
+    } catch (IllegalArgumentException e) {
+      Assert.assertTrue(
+          e.getMessage().contains("Provided shardingKey is not a valid Zookeeper path: x/y/z"));
+    }
+  }
+
+  @Test(dependsOnMethods = "testConstructionNormal")
+  public void testIsShardingKeyInsertionValidSlashOnly() {
+    Assert.assertFalse(_trie.isShardingKeyInsertionValid("/"));
+  }
+
+  @Test(dependsOnMethods = "testConstructionNormal")
+  public void testIsShardingKeyInsertionValidNormal() {
+    Assert.assertTrue(_trie.isShardingKeyInsertionValid("/x/y/z"));
+  }
+
+  @Test(dependsOnMethods = "testConstructionNormal")
+  public void testIsShardingKeyInsertionValidParentKey() {
+    Assert.assertFalse(_trie.isShardingKeyInsertionValid("/b/c"));
+  }
+
+  @Test(dependsOnMethods = "testConstructionNormal")
+  public void testIsShardingKeyInsertionValidSameKey() {
+    Assert.assertFalse(_trie.isShardingKeyInsertionValid("/h/i"));
+  }
+
+  @Test(dependsOnMethods = "testConstructionNormal")
+  public void testIsShardingKeyInsertionValidChildKey() {
+    Assert.assertFalse(_trie.isShardingKeyInsertionValid("/h/i/k"));
+  }
+
+  @Test(dependsOnMethods = "testConstructionNormal")
+  public void testContainsKeyRealmPair() {
+    Assert.assertTrue(_trie.containsKeyRealmPair("/h/i", "realmAddress1"));
+  }
+
+  @Test(dependsOnMethods = "testConstructionNormal")
+  public void testContainsKeyRealmPairNoKey() {
+    Assert.assertFalse(_trie.containsKeyRealmPair("/h/i/k", "realmAddress1"));
+  }
+
+  @Test(dependsOnMethods = "testConstructionNormal")
+  public void testContainsKeyRealmPairNoRealm() {
+    Assert.assertFalse(_trie.containsKeyRealmPair("/h/i", "realmAddress0"));
+  }
 }
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/util/ZkValidationUtil.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/util/ZkValidationUtil.java
new file mode 100644
index 0000000..59070ac
--- /dev/null
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/util/ZkValidationUtil.java
@@ -0,0 +1,38 @@
+package org.apache.helix.zookeeper.util;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+public class ZkValidationUtil {
+  /**
+   * Validates whether a given path string is a valid ZK path.
+   *
+   * Valid matches:
+   * /
+   * /abc
+   * /abc/abc/abc/abc
+   * Invalid matches:
+   * null or empty string
+   * /abc/
+   * /abc/abc/abc/abc/
+   **/
+  public static boolean isPathValid(String path) {
+    return path.matches("^/|(/[\\w-]+)+$");
+  }
+}


[helix] 25/49: Update bump-up.command and ivy imports (#824)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit 6e5e3b3b0f721b4a7f15bb490c92cc97943e18bd
Author: Hunter Lee <hu...@linkedin.com>
AuthorDate: Wed Feb 26 22:45:48 2020 -0800

    Update bump-up.command and ivy imports (#824)
    
    We update the bump-up.command script here so that it includes newly added modules.
    .ivy change was needed for wrapper repositories could pick up on the right open-source libraries.
---
 bump-up.command | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/bump-up.command b/bump-up.command
index 7bf2858..eed4ba4 100755
--- a/bump-up.command
+++ b/bump-up.command
@@ -58,6 +58,25 @@ else
   echo "metrics-common/$ivy_file not exist"
 fi
 
+
+echo "bump up metadata-store-directory-common/pom.xml"
+sed -i "s/${version}/${new_version}/g" metadata-store-directory-common/pom.xml
+grep -C 1 "$new_version" metadata-store-directory-common/pom.xml
+# git diff metadata-store-directory-common/pom.xml
+
+ivy_file="metadata-store-directory-common-"$version".ivy"
+new_ivy_file="metadata-store-directory-common-"$new_version".ivy"
+# echo "$ivy_file"
+if [ -f metadata-store-directory-common/$ivy_file ]; then
+  echo "bump up metadata-store-directory-common/$ivy_file"
+  git mv "metadata-store-directory-common/$ivy_file" "metadata-store-directory-common/$new_ivy_file"
+  sed -i "s/${version}/${new_version}/g" "metadata-store-directory-common/$new_ivy_file"
+  grep -C 1 "$new_version" "metadata-store-directory-common/$new_ivy_file"
+else
+  echo "metadata-store-directory-common/$ivy_file not exist"
+fi
+
+
 echo "bump up zookeeper-api/pom.xml"
 sed -i "s/${version}/${new_version}/g" zookeeper-api/pom.xml
 grep -C 1 "$new_version" zookeeper-api/pom.xml


[helix] 07/49: Add MetadataStoreDirectory and ZkMetadataStoreDirectory (#720)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit 4906b4c971c23645e1da8e882296ac80b9888454
Author: Hunter Lee <hu...@linkedin.com>
AuthorDate: Wed Feb 5 11:12:50 2020 -0800

    Add MetadataStoreDirectory and ZkMetadataStoreDirectory (#720)
    
    MetadataStoreDirectory is an object that provides Metadata Store Directory APIs (routing APIs, CRUD of routing data, etc.). Helix REST will use this object to serve Metadata Store Directory Service REST endpoints.
    Also, it will make appropriate changes to the ZK access layer to listen on changes on the routing data.
    
    Changelist:
    1. Refactor AbstractTestClass to make multi-ZK setup work
    2. Add implementation of MetadataStoreDirectory
    3. Add TestZkMetadataStoreDirectory
---
 .../rest/metadatastore/MetadataStoreDirectory.java | 119 ++++++++++
 .../rest/metadatastore/RoutingDataListener.java    |  27 +++
 .../metadatastore/ZkMetadataStoreDirectory.java    | 193 +++++++++++++++++
 .../rest/metadatastore/ZkRoutingDataReader.java    | 143 ++++++++++--
 .../constant/MetadataStoreRoutingConstants.java    |  27 +++
 .../TestZkMetadataStoreDirectory.java              | 240 +++++++++++++++++++++
 .../metadatastore/TestZkRoutingDataReader.java     |  52 ++---
 .../helix/rest/server/AbstractTestClass.java       | 158 +++++++-------
 8 files changed, 843 insertions(+), 116 deletions(-)

diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/MetadataStoreDirectory.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/MetadataStoreDirectory.java
new file mode 100644
index 0000000..032362a
--- /dev/null
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/MetadataStoreDirectory.java
@@ -0,0 +1,119 @@
+package org.apache.helix.rest.metadatastore;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+
+/**
+ * MetadataStoreDirectory interface that provides methods that are used to route requests to appropriate metadata store realm.
+ *
+ * namespace: tied to a namespace used in Helix REST (Metadata Store Directory Service endpoints will be served by Helix REST deployables)
+ * realm: a metadata store deployable/ensemble. for example, if an application wishes to use 3 ZK quorums, then each ZK quorum would be considered a realm (ZK realm)
+ * metadata store path sharding key: assuming the metadata store uses a file system APIs, this sharding key denotes the key that maps to a particular metadata store realm. an example of a key is a cluster name mapping to a particular ZK realm (ZK address)
+ */
+public interface MetadataStoreDirectory extends AutoCloseable {
+
+  /**
+   * Retrieves all existing namespaces in the routing metadata store.
+   * @return
+   */
+  Collection<String> getAllNamespaces();
+
+  /**
+   * Returns all metadata store realms in the given namespace.
+   * @return
+   */
+  Collection<String> getAllMetadataStoreRealms(String namespace);
+
+  /**
+   * Returns all path-based sharding keys in the given namespace.
+   * @return
+   */
+  Collection<String> getAllShardingKeys(String namespace);
+
+  /**
+   * Returns all path-based sharding keys in the given namespace and the realm.
+   * @param namespace
+   * @param realm
+   * @return
+   */
+  Collection<String> getAllShardingKeysInRealm(String namespace, String realm);
+
+  /**
+   * Returns all sharding keys that have the given path as the prefix substring.
+   * E.g) Given that there are sharding keys: /a/b/c, /a/b/d, /a/e,
+   * getAllShardingKeysUnderPath(namespace, "/a/b") returns ["/a/b/c": "realm", "/a/b/d": "realm].
+   * @param namespace
+   * @param path
+   * @return
+   */
+  Map<String, String> getAllMappingUnderPath(String namespace, String path);
+
+  /**
+   * Returns the name of the metadata store realm based on the namespace and the sharding key given.
+   * @param namespace
+   * @param shardingKey
+   * @return
+   */
+  String getMetadataStoreRealm(String namespace, String shardingKey)
+      throws NoSuchElementException;
+
+  /**
+   * Creates a realm. If the namespace does not exist, it creates one.
+   * @param namespace
+   * @param realm
+   * @return true if successful or if the realm already exists. false otherwise.
+   */
+  boolean addMetadataStoreRealm(String namespace, String realm);
+
+  /**
+   * Deletes a realm.
+   * @param namespace
+   * @param realm
+   * @return true if successful or the realm or namespace does not exist. false otherwise.
+   */
+  boolean deleteMetadataStoreRealm(String namespace, String realm);
+
+  /**
+   * Creates a mapping between the sharding key to the realm in the given namespace.
+   * @param namespace
+   * @param realm
+   * @param shardingKey
+   * @return false if failed
+   */
+  boolean addShardingKey(String namespace, String realm, String shardingKey);
+
+  /**
+   * Deletes the mapping between the sharding key to the realm in the given namespace.
+   * @param namespace
+   * @param realm
+   * @param shardingKey
+   * @return false if failed; true if the deletion is successful or the key does not exist.
+   */
+  boolean deleteShardingKey(String namespace, String realm, String shardingKey);
+
+  /**
+   * Close MetadataStoreDirectory.
+   */
+  void close();
+}
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/RoutingDataListener.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/RoutingDataListener.java
new file mode 100644
index 0000000..a44fc17
--- /dev/null
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/RoutingDataListener.java
@@ -0,0 +1,27 @@
+package org.apache.helix.rest.metadatastore;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+public interface RoutingDataListener {
+  /**
+   * Callback for updating the internally-cached routing data.
+   */
+  void refreshRoutingData(String namespace);
+}
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java
new file mode 100644
index 0000000..85f8f4a
--- /dev/null
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java
@@ -0,0 +1,193 @@
+package org.apache.helix.rest.metadatastore;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.I0Itec.zkclient.IZkChildListener;
+import org.I0Itec.zkclient.IZkDataListener;
+import org.apache.helix.HelixManagerProperties;
+import org.apache.helix.manager.zk.ZNRecordSerializer;
+import org.apache.helix.manager.zk.client.DedicatedZkClientFactory;
+import org.apache.helix.manager.zk.client.HelixZkClient;
+import org.apache.helix.manager.zk.client.HelixZkClient.ZkClientConfig;
+import org.apache.helix.manager.zk.zookeeper.IZkStateListener;
+import org.apache.helix.rest.metadatastore.exceptions.InvalidRoutingDataException;
+import org.apache.zookeeper.Watcher;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * ZK-based MetadataStoreDirectory that listens on the routing data in routing ZKs with a update callback.
+ */
+public class ZkMetadataStoreDirectory implements MetadataStoreDirectory, RoutingDataListener {
+  private static final Logger LOG = LoggerFactory.getLogger(ZkMetadataStoreDirectory.class);
+
+  // TODO: enable the line below when implementation is complete
+  // The following maps' keys represent the namespace
+  private final Map<String, MetadataStoreRoutingDataReader> _routingDataReaderMap;
+  private final Map<String, MetadataStoreRoutingData> _routingDataMap;
+  private final Map<String, String> _routingZkAddressMap;
+  // <namespace, <realm, <list of sharding keys>> mappings
+  private final Map<String, Map<String, List<String>>> _realmToShardingKeysMap;
+
+  /**
+   * Creates a ZkMetadataStoreDirectory based on the given routing ZK addresses.
+   * @param routingZkAddressMap (namespace, routing ZK connect string)
+   * @throws InvalidRoutingDataException
+   */
+  public ZkMetadataStoreDirectory(Map<String, String> routingZkAddressMap)
+      throws InvalidRoutingDataException {
+    if (routingZkAddressMap == null || routingZkAddressMap.isEmpty()) {
+      throw new InvalidRoutingDataException("Routing ZK Addresses given are invalid!");
+    }
+    _routingDataReaderMap = new HashMap<>();
+    _routingZkAddressMap = routingZkAddressMap;
+    _realmToShardingKeysMap = new ConcurrentHashMap<>();
+    _routingDataMap = new ConcurrentHashMap<>();
+
+    // Create RoutingDataReaders
+    for (Map.Entry<String, String> routingEntry : _routingZkAddressMap.entrySet()) {
+      _routingDataReaderMap.put(routingEntry.getKey(),
+          new ZkRoutingDataReader(routingEntry.getKey(), routingEntry.getValue(), this));
+
+      // Populate realmToShardingKeys with ZkRoutingDataReader
+      _realmToShardingKeysMap.put(routingEntry.getKey(),
+          _routingDataReaderMap.get(routingEntry.getKey()).getRoutingData());
+    }
+  }
+
+  @Override
+  public Collection<String> getAllNamespaces() {
+    return Collections.unmodifiableCollection(_routingZkAddressMap.keySet());
+  }
+
+  @Override
+  public Collection<String> getAllMetadataStoreRealms(String namespace) {
+    if (!_realmToShardingKeysMap.containsKey(namespace)) {
+      throw new NoSuchElementException("Namespace " + namespace + " does not exist!");
+    }
+    return Collections.unmodifiableCollection(_realmToShardingKeysMap.get(namespace).keySet());
+  }
+
+  @Override
+  public Collection<String> getAllShardingKeys(String namespace) {
+    if (!_realmToShardingKeysMap.containsKey(namespace)) {
+      throw new NoSuchElementException("Namespace " + namespace + " does not exist!");
+    }
+    Set<String> allShardingKeys = new HashSet<>();
+    _realmToShardingKeysMap.get(namespace).values().forEach(keys -> allShardingKeys.addAll(keys));
+    return allShardingKeys;
+  }
+
+  @Override
+  public Collection<String> getAllShardingKeysInRealm(String namespace, String realm) {
+    if (!_realmToShardingKeysMap.containsKey(namespace)) {
+      throw new NoSuchElementException("Namespace " + namespace + " does not exist!");
+    }
+    if (!_realmToShardingKeysMap.get(namespace).containsKey(realm)) {
+      throw new NoSuchElementException(
+          "Realm " + realm + " does not exist in namespace " + namespace);
+    }
+    return Collections.unmodifiableCollection(_realmToShardingKeysMap.get(namespace).get(realm));
+  }
+
+  @Override
+  public Map<String, String> getAllMappingUnderPath(String namespace, String path) {
+    // TODO: get it from routingData
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public String getMetadataStoreRealm(String namespace, String shardingKey) {
+    // TODO: get it from routingData
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean addMetadataStoreRealm(String namespace, String realm) {
+    // TODO implement when MetadataStoreRoutingDataWriter is ready
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean deleteMetadataStoreRealm(String namespace, String realm) {
+    // TODO implement when MetadataStoreRoutingDataWriter is ready
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean addShardingKey(String namespace, String realm, String shardingKey) {
+    // TODO implement when MetadataStoreRoutingDataWriter is ready
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean deleteShardingKey(String namespace, String realm, String shardingKey) {
+    // TODO implement when MetadataStoreRoutingDataWriter is ready
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Callback for updating the cached routing data.
+   * Note: this method should not synchronize on the class or the map. We do not want namespaces blocking each other.
+   * Threadsafe map is used for _realmToShardingKeysMap.
+   * The global consistency of the in-memory routing data is not a requirement (eventual consistency is enough).
+   * @param namespace
+   */
+  @Override
+  public void refreshRoutingData(String namespace) {
+    // Safe to ignore the callback if routingDataMap is null.
+    // If routingDataMap is null, then it will be populated by the constructor anyway
+    // If routingDataMap is not null, then it's safe for the callback function to update it
+
+    // Check if namespace exists; otherwise, return as a NOP and log it
+    if (!_routingZkAddressMap.containsKey(namespace)) {
+      LOG.error("Failed to refresh internally-cached routing data! Namespace not found: " + namespace);
+    }
+
+    try {
+      _realmToShardingKeysMap.put(namespace, _routingDataReaderMap.get(namespace).getRoutingData());
+    } catch (InvalidRoutingDataException e) {
+      LOG.error("Failed to get routing data for namespace: " + namespace + "!");
+    }
+
+    if (_routingDataMap != null) {
+      MetadataStoreRoutingData newRoutingData =
+          new TrieRoutingData(new TrieRoutingData.TrieNode(null, null, false, null));
+      // TODO call constructRoutingData() here.
+      _routingDataMap.put(namespace, newRoutingData);
+    }
+  }
+
+  @Override
+  public synchronized void close() {
+    _routingDataReaderMap.values().forEach(MetadataStoreRoutingDataReader::close);
+  }
+}
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkRoutingDataReader.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkRoutingDataReader.java
index a4c7e1c..453180f 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkRoutingDataReader.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkRoutingDataReader.java
@@ -22,54 +22,163 @@ package org.apache.helix.rest.metadatastore;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+
+import org.I0Itec.zkclient.IZkChildListener;
+import org.I0Itec.zkclient.IZkDataListener;
 import org.I0Itec.zkclient.exception.ZkNoNodeException;
 import org.apache.helix.ZNRecord;
 import org.apache.helix.manager.zk.ZNRecordSerializer;
 import org.apache.helix.manager.zk.client.DedicatedZkClientFactory;
 import org.apache.helix.manager.zk.client.HelixZkClient;
+import org.apache.helix.manager.zk.zookeeper.IZkStateListener;
+import org.apache.helix.rest.metadatastore.constant.MetadataStoreRoutingConstants;
 import org.apache.helix.rest.metadatastore.exceptions.InvalidRoutingDataException;
+import org.apache.zookeeper.Watcher;
 
-public class ZkRoutingDataReader implements MetadataStoreRoutingDataReader {
-  static final String ROUTING_DATA_PATH = "/METADATA_STORE_ROUTING_DATA";
-  static final String ZNRECORD_LIST_FIELD_KEY = "ZK_PATH_SHARDING_KEYS";
 
+public class ZkRoutingDataReader implements MetadataStoreRoutingDataReader, IZkDataListener, IZkChildListener, IZkStateListener {
+  private final String _namespace;
   private final String _zkAddress;
   private final HelixZkClient _zkClient;
+  private final RoutingDataListener _routingDataListener;
+
+  public ZkRoutingDataReader(String namespace, String zkAddress) {
+    this(namespace, zkAddress, null);
+  }
 
-  public ZkRoutingDataReader(String zkAddress) {
+  public ZkRoutingDataReader(String namespace, String zkAddress,
+      RoutingDataListener routingDataListener) {
+    if (namespace == null || namespace.isEmpty()) {
+      throw new IllegalArgumentException("namespace cannot be null or empty!");
+    }
+    _namespace = namespace;
+    if (zkAddress == null || zkAddress.isEmpty()) {
+      throw new IllegalArgumentException("Zk address cannot be null or empty!");
+    }
     _zkAddress = zkAddress;
-    _zkClient = DedicatedZkClientFactory.getInstance().buildZkClient(
-        new HelixZkClient.ZkConnectionConfig(zkAddress),
-        new HelixZkClient.ZkClientConfig().setZkSerializer(new ZNRecordSerializer()));
+    _zkClient = DedicatedZkClientFactory.getInstance()
+        .buildZkClient(new HelixZkClient.ZkConnectionConfig(zkAddress),
+            new HelixZkClient.ZkClientConfig().setZkSerializer(new ZNRecordSerializer()));
+    _routingDataListener = routingDataListener;
+    if (_routingDataListener != null) {
+      // Subscribe child changes
+      _zkClient.subscribeChildChanges(MetadataStoreRoutingConstants.ROUTING_DATA_PATH, this);
+      // Subscribe data changes
+      for (String child : _zkClient.getChildren(MetadataStoreRoutingConstants.ROUTING_DATA_PATH)) {
+        _zkClient
+            .subscribeDataChanges(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + child,
+                this);
+      }
+    }
   }
 
-  public Map<String, List<String>> getRoutingData() throws InvalidRoutingDataException {
+  /**
+   * Returns (realm, list of ZK path sharding keys) mappings.
+   * @return
+   * @throws InvalidRoutingDataException
+   */
+  public Map<String, List<String>> getRoutingData()
+      throws InvalidRoutingDataException {
     Map<String, List<String>> routingData = new HashMap<>();
     List<String> children;
     try {
-      children = _zkClient.getChildren(ROUTING_DATA_PATH);
+      children = _zkClient.getChildren(MetadataStoreRoutingConstants.ROUTING_DATA_PATH);
     } catch (ZkNoNodeException e) {
-      throw new InvalidRoutingDataException("Routing data directory ZNode " + ROUTING_DATA_PATH
-          + " does not exist. Routing ZooKeeper address: " + _zkAddress);
+      throw new InvalidRoutingDataException(
+          "Routing data directory ZNode " + MetadataStoreRoutingConstants.ROUTING_DATA_PATH
+              + " does not exist. Routing ZooKeeper address: " + _zkAddress);
     }
     if (children == null || children.isEmpty()) {
       throw new InvalidRoutingDataException(
           "There are no metadata store realms defined. Routing ZooKeeper address: " + _zkAddress);
     }
     for (String child : children) {
-      ZNRecord record = _zkClient.readData(ROUTING_DATA_PATH + "/" + child);
-      List<String> shardingKeys = record.getListField(ZNRECORD_LIST_FIELD_KEY);
+      ZNRecord record =
+          _zkClient.readData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + child);
+      List<String> shardingKeys =
+          record.getListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY);
       if (shardingKeys == null || shardingKeys.isEmpty()) {
-        throw new InvalidRoutingDataException("Realm address ZNode " + ROUTING_DATA_PATH + "/"
-            + child + " does not have a value for key " + ZNRECORD_LIST_FIELD_KEY
-            + ". Routing ZooKeeper address: " + _zkAddress);
+        throw new InvalidRoutingDataException(
+            "Realm address ZNode " + MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + child
+                + " does not have a value for key "
+                + MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY
+                + ". Routing ZooKeeper address: " + _zkAddress);
       }
       routingData.put(child, shardingKeys);
     }
     return routingData;
   }
 
-  public void close() {
+  public synchronized void close() {
+    _zkClient.unsubscribeAll();
     _zkClient.close();
   }
+
+  @Override
+  public synchronized void handleDataChange(String s, Object o)
+      throws Exception {
+    if (_zkClient.isClosed()) {
+      return;
+    }
+    _routingDataListener.refreshRoutingData(_namespace);
+  }
+
+  @Override
+  public synchronized void handleDataDeleted(String s)
+      throws Exception {
+    if (_zkClient.isClosed()) {
+      return;
+    }
+
+    // Renew subscription
+    _zkClient.subscribeChildChanges(MetadataStoreRoutingConstants.ROUTING_DATA_PATH, this);
+    for (String child : _zkClient.getChildren(MetadataStoreRoutingConstants.ROUTING_DATA_PATH)) {
+      _zkClient.subscribeDataChanges(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + child,
+          this);
+    }
+    _routingDataListener.refreshRoutingData(_namespace);
+  }
+
+  @Override
+  public synchronized void handleChildChange(String s, List<String> list)
+      throws Exception {
+    if (_zkClient.isClosed()) {
+      return;
+    }
+
+    // Subscribe data changes again because some children might have been deleted or added
+    _zkClient.unsubscribeAll();
+    for (String child : _zkClient.getChildren(MetadataStoreRoutingConstants.ROUTING_DATA_PATH)) {
+      _zkClient.subscribeDataChanges(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + child,
+          this);
+    }
+    _routingDataListener.refreshRoutingData(_namespace);
+  }
+
+  @Override
+  public synchronized void handleStateChanged(Watcher.Event.KeeperState state)
+      throws Exception {
+    if (_zkClient.isClosed()) {
+      return;
+    }
+    _routingDataListener.refreshRoutingData(_namespace);
+  }
+
+  @Override
+  public synchronized void handleNewSession(String sessionId)
+      throws Exception {
+    if (_zkClient.isClosed()) {
+      return;
+    }
+    _routingDataListener.refreshRoutingData(_namespace);
+  }
+
+  @Override
+  public synchronized void handleSessionEstablishmentError(Throwable error)
+      throws Exception {
+    if (_zkClient.isClosed()) {
+      return;
+    }
+    _routingDataListener.refreshRoutingData(_namespace);
+  }
 }
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/constant/MetadataStoreRoutingConstants.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/constant/MetadataStoreRoutingConstants.java
new file mode 100644
index 0000000..fda355b
--- /dev/null
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/constant/MetadataStoreRoutingConstants.java
@@ -0,0 +1,27 @@
+package org.apache.helix.rest.metadatastore.constant;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+public class MetadataStoreRoutingConstants {
+  public static final String ROUTING_DATA_PATH = "/METADATA_STORE_ROUTING_DATA";
+
+  // For ZK only
+  public static final String ZNRECORD_LIST_FIELD_KEY = "ZK_PATH_SHARDING_KEYS";
+}
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java
new file mode 100644
index 0000000..7b0a4f0
--- /dev/null
+++ b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java
@@ -0,0 +1,240 @@
+package org.apache.helix.rest.metadatastore;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+import org.apache.helix.TestHelper;
+import org.apache.helix.ZNRecord;
+import org.apache.helix.manager.zk.ZNRecordSerializer;
+import org.apache.helix.rest.metadatastore.constant.MetadataStoreRoutingConstants;
+import org.apache.helix.rest.metadatastore.exceptions.InvalidRoutingDataException;
+import org.apache.helix.rest.server.AbstractTestClass;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+
+public class TestZkMetadataStoreDirectory extends AbstractTestClass {
+  /**
+   * The following are constants to be used for testing.
+   */
+  private static final String TEST_REALM_1 = "testRealm1";
+  private static final List<String> TEST_SHARDING_KEYS_1 =
+      Arrays.asList("/sharding/key/1/a", "/sharding/key/1/b", "/sharding/key/1/c");
+  private static final String TEST_REALM_2 = "testRealm2";
+  private static final List<String> TEST_SHARDING_KEYS_2 =
+      Arrays.asList("/sharding/key/1/d", "/sharding/key/1/e", "/sharding/key/1/f");
+  private static final String TEST_REALM_3 = "testRealm3";
+  private static final List<String> TEST_SHARDING_KEYS_3 =
+      Arrays.asList("/sharding/key/1/x", "/sharding/key/1/y", "/sharding/key/1/z");
+
+  // List of all ZK addresses, each of which corresponds to a namespace/routing ZK
+  private List<String> _zkList;
+  // <Namespace, ZkAddr> mapping
+  private Map<String, String> _routingZkAddrMap;
+  private MetadataStoreDirectory _metadataStoreDirectory;
+
+  @BeforeClass
+  public void beforeClass()
+      throws InvalidRoutingDataException {
+    _zkList = new ArrayList<>(ZK_SERVER_MAP.keySet());
+
+    // Populate routingZkAddrMap
+    _routingZkAddrMap = new LinkedHashMap<>();
+    int namespaceIndex = 0;
+    String namespacePrefix = "namespace_";
+    for (String zk : _zkList) {
+      _routingZkAddrMap.put(namespacePrefix + namespaceIndex, zk);
+    }
+
+    // Write dummy mappings in ZK
+    // Create a node that represents a realm address and add 3 sharding keys to it
+    ZNRecord znRecord = new ZNRecord("RoutingInfo");
+
+    _zkList.forEach(zk -> {
+      ZK_SERVER_MAP.get(zk).getZkClient().setZkSerializer(new ZNRecordSerializer());
+      // Write first realm and sharding keys pair
+      znRecord.setListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY,
+          TEST_SHARDING_KEYS_1);
+      ZK_SERVER_MAP.get(zk).getZkClient()
+          .createPersistent(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + TEST_REALM_1,
+              true);
+      ZK_SERVER_MAP.get(zk).getZkClient()
+          .writeData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + TEST_REALM_1,
+              znRecord);
+
+      // Create another realm and sharding keys pair
+      znRecord.setListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY,
+          TEST_SHARDING_KEYS_2);
+      ZK_SERVER_MAP.get(zk).getZkClient()
+          .createPersistent(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + TEST_REALM_2,
+              true);
+      ZK_SERVER_MAP.get(zk).getZkClient()
+          .writeData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + TEST_REALM_2,
+              znRecord);
+    });
+
+    // Create metadataStoreDirectory
+    _metadataStoreDirectory = new ZkMetadataStoreDirectory(_routingZkAddrMap);
+  }
+
+  @AfterClass
+  public void afterClass() {
+    _metadataStoreDirectory.close();
+    _zkList.forEach(zk -> ZK_SERVER_MAP.get(zk).getZkClient()
+        .deleteRecursive(MetadataStoreRoutingConstants.ROUTING_DATA_PATH));
+  }
+
+  @Test
+  public void testGetAllNamespaces() {
+    Assert.assertEquals(_metadataStoreDirectory.getAllNamespaces(), _routingZkAddrMap.keySet());
+  }
+
+  @Test(dependsOnMethods = "testGetAllNamespaces")
+  public void testGetAllMetadataStoreRealms() {
+    Set<String> realms = new HashSet<>();
+    realms.add(TEST_REALM_1);
+    realms.add(TEST_REALM_2);
+
+    for (String namespace : _routingZkAddrMap.keySet()) {
+      Assert.assertEquals(_metadataStoreDirectory.getAllMetadataStoreRealms(namespace), realms);
+    }
+  }
+
+  @Test(dependsOnMethods = "testGetAllMetadataStoreRealms")
+  public void testGetAllShardingKeys() {
+    Set<String> allShardingKeys = new HashSet<>();
+    allShardingKeys.addAll(TEST_SHARDING_KEYS_1);
+    allShardingKeys.addAll(TEST_SHARDING_KEYS_2);
+
+    for (String namespace : _routingZkAddrMap.keySet()) {
+      Assert.assertEquals(_metadataStoreDirectory.getAllShardingKeys(namespace), allShardingKeys);
+    }
+  }
+
+  @Test(dependsOnMethods = "testGetAllShardingKeys")
+  public void testGetAllShardingKeysInRealm() {
+    for (String namespace : _routingZkAddrMap.keySet()) {
+      // Test two realms independently
+      Assert
+          .assertEquals(_metadataStoreDirectory.getAllShardingKeysInRealm(namespace, TEST_REALM_1),
+              TEST_SHARDING_KEYS_1);
+      Assert
+          .assertEquals(_metadataStoreDirectory.getAllShardingKeysInRealm(namespace, TEST_REALM_2),
+              TEST_SHARDING_KEYS_2);
+    }
+  }
+
+  @Test(dependsOnMethods = "testGetAllShardingKeysInRealm")
+  public void testDataChangeCallback()
+      throws Exception {
+    // For all namespaces (Routing ZKs), add an extra sharding key to TEST_REALM_1
+    String newKey = "/a/b/c/d/e";
+    _zkList.forEach(zk -> {
+      ZNRecord znRecord = ZK_SERVER_MAP.get(zk).getZkClient()
+          .readData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + TEST_REALM_1);
+      znRecord.getListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY).add(newKey);
+      ZK_SERVER_MAP.get(zk).getZkClient()
+          .writeData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + TEST_REALM_1,
+              znRecord);
+    });
+
+    // Verify that the sharding keys field have been updated
+    Assert.assertTrue(TestHelper.verify(() -> {
+      for (String namespace : _routingZkAddrMap.keySet()) {
+        try {
+          return _metadataStoreDirectory.getAllShardingKeys(namespace).contains(newKey)
+              && _metadataStoreDirectory.getAllShardingKeysInRealm(namespace, TEST_REALM_1)
+              .contains(newKey);
+        } catch (NoSuchElementException e) {
+          // Pass - wait until callback is called
+        }
+      }
+      return false;
+    }, TestHelper.WAIT_DURATION));
+  }
+
+  @Test(dependsOnMethods = "testDataChangeCallback")
+  public void testChildChangeCallback()
+      throws Exception {
+    // For all namespaces (Routing ZKs), add a realm with a sharding key list
+    _zkList.forEach(zk -> {
+      ZK_SERVER_MAP.get(zk).getZkClient()
+          .createPersistent(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + TEST_REALM_3,
+              true);
+      ZNRecord znRecord = new ZNRecord("RoutingInfo");
+      znRecord.setListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY,
+          TEST_SHARDING_KEYS_3);
+      ZK_SERVER_MAP.get(zk).getZkClient()
+          .writeData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + TEST_REALM_3,
+              znRecord);
+    });
+
+    // Verify that the new realm and sharding keys have been updated in-memory via callback
+    Assert.assertTrue(TestHelper.verify(() -> {
+      for (String namespace : _routingZkAddrMap.keySet()) {
+        try {
+          return _metadataStoreDirectory.getAllMetadataStoreRealms(namespace).contains(TEST_REALM_3)
+              && _metadataStoreDirectory.getAllShardingKeysInRealm(namespace, TEST_REALM_3)
+              .containsAll(TEST_SHARDING_KEYS_3);
+        } catch (NoSuchElementException e) {
+          // Pass - wait until callback is called
+        }
+      }
+      return false;
+    }, TestHelper.WAIT_DURATION));
+
+    // Since there was a child change callback, make sure data change works on the new child (realm) as well by adding a key
+    // This tests removing all subscriptions and subscribing with new children list
+    // For all namespaces (Routing ZKs), add an extra sharding key to TEST_REALM_3
+    String newKey = "/a/b/c/d/e";
+    _zkList.forEach(zk -> {
+      ZNRecord znRecord = ZK_SERVER_MAP.get(zk).getZkClient()
+          .readData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + TEST_REALM_3);
+      znRecord.getListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY).add(newKey);
+      ZK_SERVER_MAP.get(zk).getZkClient()
+          .writeData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + TEST_REALM_3,
+              znRecord);
+    });
+
+    // Verify that the sharding keys field have been updated
+    Assert.assertTrue(TestHelper.verify(() -> {
+      for (String namespace : _routingZkAddrMap.keySet()) {
+        try {
+          return _metadataStoreDirectory.getAllShardingKeys(namespace).contains(newKey)
+              && _metadataStoreDirectory.getAllShardingKeysInRealm(namespace, TEST_REALM_3)
+              .contains(newKey);
+        } catch (NoSuchElementException e) {
+          // Pass - wait until callback is called
+        }
+      }
+      return false;
+    }, TestHelper.WAIT_DURATION));
+  }
+}
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkRoutingDataReader.java b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkRoutingDataReader.java
index d06c38d..4479f68 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkRoutingDataReader.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkRoutingDataReader.java
@@ -23,8 +23,10 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+
 import org.apache.helix.AccessOption;
 import org.apache.helix.ZNRecord;
+import org.apache.helix.rest.metadatastore.constant.MetadataStoreRoutingConstants;
 import org.apache.helix.rest.metadatastore.exceptions.InvalidRoutingDataException;
 import org.apache.helix.rest.server.AbstractTestClass;
 import org.testng.Assert;
@@ -33,12 +35,14 @@ import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
+
 public class TestZkRoutingDataReader extends AbstractTestClass {
+  private static final String DUMMY_NAMESPACE = "NAMESPACE";
   private MetadataStoreRoutingDataReader _zkRoutingDataReader;
 
   @BeforeClass
   public void beforeClass() {
-    _zkRoutingDataReader = new ZkRoutingDataReader(ZK_ADDR);
+    _zkRoutingDataReader = new ZkRoutingDataReader(DUMMY_NAMESPACE, ZK_ADDR, null);
   }
 
   @AfterClass
@@ -48,7 +52,7 @@ public class TestZkRoutingDataReader extends AbstractTestClass {
 
   @AfterMethod
   public void afterMethod() {
-    _baseAccessor.remove(ZkRoutingDataReader.ROUTING_DATA_PATH, AccessOption.PERSISTENT);
+    _baseAccessor.remove(MetadataStoreRoutingConstants.ROUTING_DATA_PATH, AccessOption.PERSISTENT);
   }
 
   @Test
@@ -57,23 +61,24 @@ public class TestZkRoutingDataReader extends AbstractTestClass {
     ZNRecord testZnRecord1 = new ZNRecord("testZnRecord1");
     List<String> testShardingKeys1 =
         Arrays.asList("/sharding/key/1/a", "/sharding/key/1/b", "/sharding/key/1/c");
-    testZnRecord1.setListField(ZkRoutingDataReader.ZNRECORD_LIST_FIELD_KEY, testShardingKeys1);
+    testZnRecord1
+        .setListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY, testShardingKeys1);
 
     // Create another node that represents a realm address and add 3 sharding keys to it
     ZNRecord testZnRecord2 = new ZNRecord("testZnRecord2");
-    List<String> testShardingKeys2 = Arrays.asList("/sharding/key/2/a", "/sharding/key/2/b",
-        "/sharding/key/2/c", "/sharding/key/2/d");
-    testZnRecord2.setListField(ZkRoutingDataReader.ZNRECORD_LIST_FIELD_KEY, testShardingKeys2);
+    List<String> testShardingKeys2 = Arrays
+        .asList("/sharding/key/2/a", "/sharding/key/2/b", "/sharding/key/2/c", "/sharding/key/2/d");
+    testZnRecord2
+        .setListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY, testShardingKeys2);
 
     // Add both nodes as children nodes to ZkRoutingDataReader.ROUTING_DATA_PATH
-    _baseAccessor.create(ZkRoutingDataReader.ROUTING_DATA_PATH + "/testRealmAddress1",
+    _baseAccessor.create(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/testRealmAddress1",
         testZnRecord1, AccessOption.PERSISTENT);
-    _baseAccessor.create(ZkRoutingDataReader.ROUTING_DATA_PATH + "/testRealmAddress2",
+    _baseAccessor.create(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/testRealmAddress2",
         testZnRecord2, AccessOption.PERSISTENT);
 
-    MetadataStoreRoutingDataReader zkRoutingDataReader = new ZkRoutingDataReader(ZK_ADDR);
     try {
-      Map<String, List<String>> routingData = zkRoutingDataReader.getRoutingData();
+      Map<String, List<String>> routingData = _zkRoutingDataReader.getRoutingData();
       Assert.assertEquals(routingData.size(), 2);
       Assert.assertEquals(routingData.get("testRealmAddress1"), testShardingKeys1);
       Assert.assertEquals(routingData.get("testRealmAddress2"), testShardingKeys2);
@@ -84,24 +89,22 @@ public class TestZkRoutingDataReader extends AbstractTestClass {
 
   @Test
   public void testGetRoutingDataMissingMSRD() {
-    MetadataStoreRoutingDataReader zkRoutingDataReader = new ZkRoutingDataReader(ZK_ADDR);
     try {
-      zkRoutingDataReader.getRoutingData();
+      _zkRoutingDataReader.getRoutingData();
       Assert.fail("Expecting InvalidRoutingDataException");
     } catch (InvalidRoutingDataException e) {
-      Assert.assertTrue(e.getMessage()
-          .contains("Routing data directory ZNode " + ZkRoutingDataReader.ROUTING_DATA_PATH
+      Assert.assertTrue(e.getMessage().contains(
+          "Routing data directory ZNode " + MetadataStoreRoutingConstants.ROUTING_DATA_PATH
               + " does not exist. Routing ZooKeeper address: " + ZK_ADDR));
     }
   }
 
   @Test
   public void testGetRoutingDataMissingMSRDChildren() {
-    _baseAccessor.create(ZkRoutingDataReader.ROUTING_DATA_PATH, new ZNRecord("test"),
+    _baseAccessor.create(MetadataStoreRoutingConstants.ROUTING_DATA_PATH, new ZNRecord("test"),
         AccessOption.PERSISTENT);
-    MetadataStoreRoutingDataReader zkRoutingDataReader = new ZkRoutingDataReader(ZK_ADDR);
     try {
-      zkRoutingDataReader.getRoutingData();
+      _zkRoutingDataReader.getRoutingData();
       Assert.fail("Expecting InvalidRoutingDataException");
     } catch (InvalidRoutingDataException e) {
       Assert.assertTrue(e.getMessage().contains(
@@ -112,20 +115,19 @@ public class TestZkRoutingDataReader extends AbstractTestClass {
   @Test
   public void testGetRoutingDataMSRDChildEmptyValue() {
     ZNRecord testZnRecord1 = new ZNRecord("testZnRecord1");
-    testZnRecord1.setListField(ZkRoutingDataReader.ZNRECORD_LIST_FIELD_KEY,
+    testZnRecord1.setListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY,
         Collections.emptyList());
-    _baseAccessor.create(ZkRoutingDataReader.ROUTING_DATA_PATH + "/testRealmAddress1",
+    _baseAccessor.create(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/testRealmAddress1",
         testZnRecord1, AccessOption.PERSISTENT);
-    MetadataStoreRoutingDataReader zkRoutingDataReader = new ZkRoutingDataReader(ZK_ADDR);
     try {
-      zkRoutingDataReader.getRoutingData();
+      _zkRoutingDataReader.getRoutingData();
       Assert.fail("Expecting InvalidRoutingDataException");
     } catch (InvalidRoutingDataException e) {
-      Assert.assertTrue(e.getMessage()
-          .contains("Realm address ZNode " + ZkRoutingDataReader.ROUTING_DATA_PATH
+      Assert.assertTrue(e.getMessage().contains(
+          "Realm address ZNode " + MetadataStoreRoutingConstants.ROUTING_DATA_PATH
               + "/testRealmAddress1 does not have a value for key "
-              + ZkRoutingDataReader.ZNRECORD_LIST_FIELD_KEY + ". Routing ZooKeeper address: "
-              + ZK_ADDR));
+              + MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY
+              + ". Routing ZooKeeper address: " + ZK_ADDR));
     }
   }
 }
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 0302758..e6ecb82 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
@@ -66,8 +66,6 @@ import org.apache.helix.store.HelixPropertyStore;
 import org.apache.helix.store.zk.ZkHelixPropertyStore;
 import org.apache.helix.task.JobConfig;
 import org.apache.helix.task.JobContext;
-import org.apache.helix.task.Task;
-import org.apache.helix.task.TaskCallbackContext;
 import org.apache.helix.task.TaskConstants;
 import org.apache.helix.task.TaskDriver;
 import org.apache.helix.task.TaskFactory;
@@ -92,13 +90,19 @@ import org.testng.Assert;
 import org.testng.annotations.AfterSuite;
 import org.testng.annotations.BeforeSuite;
 
+
 public class AbstractTestClass extends JerseyTestNg.ContainerPerClassTest {
+  /**
+   * Constants for multi-ZK environment.
+   */
   private static final String MULTI_ZK_PROPERTY_KEY = "multiZk";
   private static final String NUM_ZK_PROPERTY_KEY = "numZk";
-  private static final String ZK_PREFIX = "localhost:";
-  private static final int ZK_START_PORT = 2123;
-  protected Map<String, ZkServer> _zkServerMap;
+  protected static final String ZK_PREFIX = "localhost:";
+  protected static final int ZK_START_PORT = 2123;
+  // The following map must be a static map because it needs to be shared throughout tests
+  protected static final Map<String, ZkServer> ZK_SERVER_MAP = new HashMap<>();
 
+  // For a single-ZK/Helix environment
   protected static final String ZK_ADDR = "localhost:2123";
   protected static final String WORKFLOW_PREFIX = "Workflow_";
   protected static final String JOB_PREFIX = "Job_";
@@ -154,58 +158,19 @@ public class AbstractTestClass extends JerseyTestNg.ContainerPerClassTest {
 
   @Override
   protected Application configure() {
-    // start zk
-    _zkServerMap = new HashMap<>();
-    try {
-      if (_zkServer == null) {
-        _zkServer = TestHelper.startZkServer(ZK_ADDR);
-        Assert.assertNotNull(_zkServer);
-        _zkServerMap.put(ZK_ADDR, _zkServer);
-        ZKClientPool.reset();
-      }
-
-      if (_zkServerTestNS == null) {
-        _zkServerTestNS = TestHelper.startZkServer(_zkAddrTestNS);
-        Assert.assertNotNull(_zkServerTestNS);
-        _zkServerMap.put(_zkAddrTestNS, _zkServerTestNS);
-        ZKClientPool.reset();
-      }
-    } catch (Exception e) {
-      Assert.fail(String.format("Failed to start ZK server: %s", e.toString()));
-    }
-
-    // Start additional ZKs in a multi-ZK setup
-    String multiZkConfig = System.getProperty(MULTI_ZK_PROPERTY_KEY);
-    if (multiZkConfig != null && multiZkConfig.equalsIgnoreCase(Boolean.TRUE.toString())) {
-      String numZkFromConfig = System.getProperty(NUM_ZK_PROPERTY_KEY);
-      if (numZkFromConfig != null) {
-        try {
-          int numZkFromConfigInt = Integer.parseInt(numZkFromConfig);
-          // Start (numZkFromConfigInt - 2) ZooKeepers
-          for (int i = 2; i < numZkFromConfigInt; i++) {
-            String zkAddr = ZK_PREFIX + (ZK_START_PORT + i);
-            ZkServer zkServer = TestHelper.startZkServer(zkAddr);
-            Assert.assertNotNull(zkServer);
-            _zkServerMap.put(zkAddr, zkServer);
-          }
-        } catch (Exception e) {
-          Assert.fail("Failed to create multiple ZooKeepers!");
-        }
-      }
-    }
-
     // Configure server context
     ResourceConfig resourceConfig = new ResourceConfig();
     resourceConfig.packages(AbstractResource.class.getPackage().getName());
     ServerContext serverContext = new ServerContext(ZK_ADDR);
     resourceConfig.property(ContextPropertyKeys.SERVER_CONTEXT.name(), serverContext);
-    resourceConfig.register(new AuditLogFilter(Arrays.<AuditLogger>asList(new MockAuditLogger())));
+    resourceConfig.register(new AuditLogFilter(Collections.singletonList(new MockAuditLogger())));
 
     return resourceConfig;
   }
 
   @Override
-  protected TestContainerFactory getTestContainerFactory() throws TestContainerException {
+  protected TestContainerFactory getTestContainerFactory()
+      throws TestContainerException {
     return new TestContainerFactory() {
       @Override
       public TestContainer create(final URI baseUri, DeploymentContext deploymentContext) {
@@ -234,7 +199,7 @@ public class AbstractTestClass extends JerseyTestNg.ContainerPerClassTest {
               try {
                 _helixRestServer =
                     new HelixRestServer(namespaces, baseUri.getPort(), baseUri.getPath(),
-                        Arrays.<AuditLogger>asList(_auditLogger));
+                        Collections.singletonList(_auditLogger));
                 _helixRestServer.start();
               } catch (Exception ex) {
                 throw new TestContainerException(ex);
@@ -251,8 +216,11 @@ public class AbstractTestClass extends JerseyTestNg.ContainerPerClassTest {
   }
 
   @BeforeSuite
-  public void beforeSuite() throws Exception {
+  public void beforeSuite()
+      throws Exception {
     if (!_init) {
+      setupZooKeepers();
+
       // TODO: use logging.properties file to config java.util.logging.Logger levels
       java.util.logging.Logger topJavaLogger = java.util.logging.Logger.getLogger("");
       topJavaLogger.setLevel(Level.WARNING);
@@ -260,12 +228,12 @@ public class AbstractTestClass extends JerseyTestNg.ContainerPerClassTest {
       HelixZkClient.ZkClientConfig clientConfig = new HelixZkClient.ZkClientConfig();
 
       clientConfig.setZkSerializer(new ZNRecordSerializer());
-      _gZkClient = DedicatedZkClientFactory
-          .getInstance().buildZkClient(new HelixZkClient.ZkConnectionConfig(ZK_ADDR), clientConfig);
+      _gZkClient = DedicatedZkClientFactory.getInstance()
+          .buildZkClient(new HelixZkClient.ZkConnectionConfig(ZK_ADDR), clientConfig);
 
       clientConfig.setZkSerializer(new ZNRecordSerializer());
-      _gZkClientTestNS = DedicatedZkClientFactory
-          .getInstance().buildZkClient(new HelixZkClient.ZkConnectionConfig(_zkAddrTestNS), clientConfig);
+      _gZkClientTestNS = DedicatedZkClientFactory.getInstance()
+          .buildZkClient(new HelixZkClient.ZkConnectionConfig(_zkAddrTestNS), clientConfig);
 
       _gSetupTool = new ClusterSetup(_gZkClient);
       _configAccessor = new ConfigAccessor(_gZkClient);
@@ -274,14 +242,14 @@ public class AbstractTestClass extends JerseyTestNg.ContainerPerClassTest {
 
       // wait for the web service to start
       Thread.sleep(100);
-
-      setup();
+      setupHelixResources();
       _init = true;
     }
   }
 
   @AfterSuite
-  public void afterSuite() throws Exception {
+  public void afterSuite()
+      throws Exception {
     // tear down orphan-ed threads
     for (ClusterControllerManager cm : _clusterControllerManagers) {
       if (cm != null && cm.isConnected()) {
@@ -289,7 +257,7 @@ public class AbstractTestClass extends JerseyTestNg.ContainerPerClassTest {
       }
     }
 
-    for (MockParticipantManager mm: _mockParticipantManagers) {
+    for (MockParticipantManager mm : _mockParticipantManagers) {
       if (mm != null && mm.isConnected()) {
         mm.syncStop();
       }
@@ -315,16 +283,57 @@ public class AbstractTestClass extends JerseyTestNg.ContainerPerClassTest {
       _zkServerTestNS = null;
     }
 
-    // Stop all ZkServers
-    _zkServerMap.forEach((zkAddr, zkServer) -> TestHelper.stopZkServer(zkServer));
-
     if (_helixRestServer != null) {
       _helixRestServer.shutdown();
       _helixRestServer = null;
     }
+
+    // Stop all ZkServers
+    ZK_SERVER_MAP.forEach((zkAddr, zkServer) -> TestHelper.stopZkServer(zkServer));
+  }
+
+  private void setupZooKeepers() {
+    // start zk
+    try {
+      if (_zkServer == null) {
+        _zkServer = TestHelper.startZkServer(ZK_ADDR);
+        Assert.assertNotNull(_zkServer);
+        ZK_SERVER_MAP.put(ZK_ADDR, _zkServer);
+        ZKClientPool.reset();
+      }
+
+      if (_zkServerTestNS == null) {
+        _zkServerTestNS = TestHelper.startZkServer(_zkAddrTestNS);
+        Assert.assertNotNull(_zkServerTestNS);
+        ZK_SERVER_MAP.put(_zkAddrTestNS, _zkServerTestNS);
+        ZKClientPool.reset();
+      }
+    } catch (Exception e) {
+      Assert.fail(String.format("Failed to start ZK servers: %s", e.toString()));
+    }
+
+    // Start additional ZKs in a multi-ZK setup if applicable
+    String multiZkConfig = System.getProperty(MULTI_ZK_PROPERTY_KEY);
+    if (multiZkConfig != null && multiZkConfig.equalsIgnoreCase(Boolean.TRUE.toString())) {
+      String numZkFromConfig = System.getProperty(NUM_ZK_PROPERTY_KEY);
+      if (numZkFromConfig != null) {
+        try {
+          int numZkFromConfigInt = Integer.parseInt(numZkFromConfig);
+          // Start (numZkFromConfigInt - 2) ZooKeepers
+          for (int i = 2; i < numZkFromConfigInt; i++) {
+            String zkAddr = ZK_PREFIX + (ZK_START_PORT + i);
+            ZkServer zkServer = TestHelper.startZkServer(zkAddr);
+            Assert.assertNotNull(zkServer);
+            ZK_SERVER_MAP.put(zkAddr, zkServer);
+          }
+        } catch (Exception e) {
+          Assert.fail("Failed to create multiple ZooKeepers!");
+        }
+      }
+    }
   }
 
-  protected void setup() throws Exception {
+  protected void setupHelixResources() {
     _clusters = createClusters(3);
     _gSetupTool.addCluster(_superCluster, true);
     _gSetupTool.addCluster(TASK_TEST_CLUSTER, true);
@@ -347,7 +356,7 @@ public class AbstractTestClass extends JerseyTestNg.ContainerPerClassTest {
     preSetupForParallelInstancesStoppableTest(STOPPABLE_CLUSTER, STOPPABLE_INSTANCES);
   }
 
-  protected Set<String> createInstances(String cluster, int numInstances) throws Exception {
+  protected Set<String> createInstances(String cluster, int numInstances) {
     Set<String> instances = new HashSet<>();
     for (int i = 0; i < numInstances; i++) {
       String instanceName = cluster + "localhost_" + (12918 + i);
@@ -362,7 +371,8 @@ public class AbstractTestClass extends JerseyTestNg.ContainerPerClassTest {
     for (int i = 0; i < numResources; i++) {
       String resource = cluster + "_db_" + i;
       _gSetupTool.addResourceToCluster(cluster, resource, NUM_PARTITIONS, "MasterSlave");
-      IdealState idealState = _gSetupTool.getClusterManagementTool().getResourceIdealState(cluster, resource);
+      IdealState idealState =
+          _gSetupTool.getClusterManagementTool().getResourceIdealState(cluster, resource);
       idealState.setMinActiveReplicas(MIN_ACTIVE_REPLICA);
       _gSetupTool.getClusterManagementTool().setResourceIdealState(cluster, resource, idealState);
       _gSetupTool.rebalanceStorageCluster(cluster, resource, NUM_REPLICA);
@@ -390,12 +400,8 @@ public class AbstractTestClass extends JerseyTestNg.ContainerPerClassTest {
     int i = 0;
     for (String instance : instances) {
       MockParticipantManager participant = new MockParticipantManager(ZK_ADDR, cluster, instance);
-      Map<String, TaskFactory> taskFactoryReg = new HashMap<String, TaskFactory>();
-      taskFactoryReg.put(MockTask.TASK_COMMAND, new TaskFactory() {
-        @Override public Task createNewTask(TaskCallbackContext context) {
-          return new MockTask(context);
-        }
-      });
+      Map<String, TaskFactory> taskFactoryReg = new HashMap<>();
+      taskFactoryReg.put(MockTask.TASK_COMMAND, MockTask::new);
       StateMachineEngine stateMachineEngine = participant.getStateMachineEngine();
       stateMachineEngine.registerStateModelFactory("Task",
           new TaskStateModelFactory(participant, taskFactoryReg));
@@ -431,8 +437,9 @@ public class AbstractTestClass extends JerseyTestNg.ContainerPerClassTest {
 
   protected Map<String, Workflow> createWorkflows(String cluster, int numWorkflows) {
     Map<String, Workflow> workflows = new HashMap<>();
-    HelixPropertyStore<ZNRecord> propertyStore = new ZkHelixPropertyStore<>((ZkBaseDataAccessor<ZNRecord>) _baseAccessor,
-        PropertyPathBuilder.propertyStore(cluster), null);
+    HelixPropertyStore<ZNRecord> propertyStore =
+        new ZkHelixPropertyStore<>((ZkBaseDataAccessor<ZNRecord>) _baseAccessor,
+            PropertyPathBuilder.propertyStore(cluster), null);
 
     for (int i = 0; i < numWorkflows; i++) {
       Workflow.Builder workflow = new Workflow.Builder(WORKFLOW_PREFIX + i);
@@ -489,11 +496,13 @@ public class AbstractTestClass extends JerseyTestNg.ContainerPerClassTest {
     return jobCfgs;
   }
 
-  protected static ZNRecord toZNRecord(String data) throws IOException {
+  protected static ZNRecord toZNRecord(String data)
+      throws IOException {
     return OBJECT_MAPPER.reader(ZNRecord.class).readValue(data);
   }
 
-  protected String get(String uri, Map<String, String> queryParams, int expectedReturnStatus, boolean expectBodyReturned) {
+  protected String get(String uri, Map<String, String> queryParams, int expectedReturnStatus,
+      boolean expectBodyReturned) {
     WebTarget webTarget = target(uri);
     if (queryParams != null) {
       for (Map.Entry<String, String> entry : queryParams.entrySet()) {
@@ -552,7 +561,8 @@ public class AbstractTestClass extends JerseyTestNg.ContainerPerClassTest {
     return new TaskDriver(_gZkClient, clusterName);
   }
 
-  private void preSetupForParallelInstancesStoppableTest(String clusterName, List<String> instances) {
+  private void preSetupForParallelInstancesStoppableTest(String clusterName,
+      List<String> instances) {
     _gSetupTool.addCluster(clusterName, true);
     ClusterConfig clusterConfig = _configAccessor.getClusterConfig(clusterName);
     clusterConfig.setFaultZoneType("helixZoneId");


[helix] 31/49: Implement setRoutingData for MetadataStoreDirectoryService (#844)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit b788bf573f1a64a03dbc4b7f1416822e05526c41
Author: Neal Sun <ne...@gmail.com>
AuthorDate: Tue Mar 3 17:37:15 2020 -0800

    Implement setRoutingData for MetadataStoreDirectoryService (#844)
    
    Implement setRoutingData endpoint. Modify TrieRoutingData construction in MetadataStoreDirectory. Fix race conditions among writing operations in MetadataStoreDirectory.
---
 .../rest/metadatastore/MetadataStoreDirectory.java |   9 ++
 .../metadatastore/ZkMetadataStoreDirectory.java    | 100 ++++++++++++++-----
 .../accessor/MetadataStoreRoutingDataWriter.java   |   2 +-
 .../accessor/ZkRoutingDataReader.java              |   2 +-
 .../accessor/ZkRoutingDataWriter.java              |  43 +++++---
 .../MetadataStoreDirectoryAccessor.java            |  25 +++++
 .../TestZkMetadataStoreDirectory.java              |  69 ++++++++++++-
 .../accessor/TestZkRoutingDataReader.java          |  64 +++++-------
 .../accessor/TestZkRoutingDataWriter.java          | 109 +++++++++++++--------
 .../MetadataStoreDirectoryAccessorTestBase.java    |  30 ++++--
 .../rest/server/TestMSDAccessorLeaderElection.java |  57 ++++++++---
 .../server/TestMetadataStoreDirectoryAccessor.java |  38 +++++++
 .../helix/msdcommon/datamodel/TrieRoutingData.java |   4 +-
 13 files changed, 409 insertions(+), 143 deletions(-)

diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/MetadataStoreDirectory.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/MetadataStoreDirectory.java
index 4630d50..8371e07 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/MetadataStoreDirectory.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/MetadataStoreDirectory.java
@@ -61,6 +61,15 @@ public interface MetadataStoreDirectory extends AutoCloseable {
   Map<String, List<String>> getNamespaceRoutingData(String namespace);
 
   /**
+   * Sets and overwrites routing data in the given namespace.
+   *
+   * @param namespace namespace in metadata store directory.
+   * @param routingData Routing data map: realm -> List of sharding keys
+   * @return true if successful; false otherwise.
+   */
+  boolean setNamespaceRoutingData(String namespace, Map<String, List<String>> routingData);
+
+  /**
    * Returns all path-based sharding keys in the given namespace and the realm.
    * @param namespace
    * @param realm
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java
index 28a4afe..c83245f 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java
@@ -113,10 +113,14 @@ public class ZkMetadataStoreDirectory implements MetadataStoreDirectory, Routing
           _routingDataWriterMap.put(namespace, new ZkRoutingDataWriter(namespace, zkAddress));
 
           // Populate realmToShardingKeys with ZkRoutingDataReader
-          _realmToShardingKeysMap
-              .put(namespace, _routingDataReaderMap.get(namespace).getRoutingData());
-          _routingDataMap
-              .put(namespace, new TrieRoutingData(_realmToShardingKeysMap.get(namespace)));
+          Map<String, List<String>> rawRoutingData =
+              _routingDataReaderMap.get(namespace).getRoutingData();
+          _realmToShardingKeysMap.put(namespace, rawRoutingData);
+          try {
+            _routingDataMap.put(namespace, new TrieRoutingData(rawRoutingData));
+          } catch (InvalidRoutingDataException e) {
+            LOG.warn("TrieRoutingData is not created for namespace {}", namespace, e);
+          }
         }
       }
     }
@@ -156,6 +160,19 @@ public class ZkMetadataStoreDirectory implements MetadataStoreDirectory, Routing
   }
 
   @Override
+  public boolean setNamespaceRoutingData(String namespace, Map<String, List<String>> routingData) {
+    if (!_routingDataWriterMap.containsKey(namespace)) {
+      throw new IllegalArgumentException(
+          "Failed to set routing data: Namespace " + namespace + " is not found!");
+    }
+    synchronized (this) {
+      boolean result = _routingDataWriterMap.get(namespace).setRoutingData(routingData);
+      refreshRoutingData(namespace);
+      return result;
+    }
+  }
+
+  @Override
   public Collection<String> getAllShardingKeysInRealm(String namespace, String realm) {
     if (!_realmToShardingKeysMap.containsKey(namespace)) {
       throw new NoSuchElementException("Namespace " + namespace + " does not exist!");
@@ -169,19 +186,31 @@ public class ZkMetadataStoreDirectory implements MetadataStoreDirectory, Routing
 
   @Override
   public Map<String, String> getAllMappingUnderPath(String namespace, String path) {
-    if (!_routingDataMap.containsKey(namespace)) {
+    // Check _routingZkAddressMap first to see if namespace is included
+    if (!_routingZkAddressMap.containsKey(namespace)) {
       throw new NoSuchElementException(
           "Failed to get all mapping under path: Namespace " + namespace + " is not found!");
     }
+    // If namespace is included but not routing data, it means the routing data is invalid
+    if (!_routingDataMap.containsKey(namespace)) {
+      throw new IllegalStateException("Failed to get all mapping under path: Namespace " + namespace
+          + " contains either empty or invalid routing data!");
+    }
     return _routingDataMap.get(namespace).getAllMappingUnderPath(path);
   }
 
   @Override
   public String getMetadataStoreRealm(String namespace, String shardingKey) {
-    if (!_routingDataMap.containsKey(namespace)) {
+    // Check _routingZkAddressMap first to see if namespace is included
+    if (!_routingZkAddressMap.containsKey(namespace)) {
       throw new NoSuchElementException(
           "Failed to get metadata store realm: Namespace " + namespace + " is not found!");
     }
+    // If namespace is included but not routing data, it means the routing data is invalid
+    if (!_routingDataMap.containsKey(namespace)) {
+      throw new IllegalStateException("Failed to get metadata store realm: Namespace " + namespace
+          + " contains either empty or invalid routing data!");
+    }
     return _routingDataMap.get(namespace).getMetadataStoreRealm(shardingKey);
   }
 
@@ -193,7 +222,11 @@ public class ZkMetadataStoreDirectory implements MetadataStoreDirectory, Routing
       throw new NoSuchElementException(
           "Failed to add metadata store realm: Namespace " + namespace + " is not found!");
     }
-    return _routingDataWriterMap.get(namespace).addMetadataStoreRealm(realm);
+    synchronized (this) {
+      boolean result = _routingDataWriterMap.get(namespace).addMetadataStoreRealm(realm);
+      refreshRoutingData(namespace);
+      return result;
+    }
   }
 
   @Override
@@ -204,26 +237,36 @@ public class ZkMetadataStoreDirectory implements MetadataStoreDirectory, Routing
       throw new NoSuchElementException(
           "Failed to delete metadata store realm: Namespace " + namespace + " is not found!");
     }
-    return _routingDataWriterMap.get(namespace).deleteMetadataStoreRealm(realm);
+    synchronized (this) {
+      boolean result = _routingDataWriterMap.get(namespace).deleteMetadataStoreRealm(realm);
+      refreshRoutingData(namespace);
+      return result;
+    }
   }
 
   @Override
   public boolean addShardingKey(String namespace, String realm, String shardingKey) {
-    if (!_routingDataWriterMap.containsKey(namespace) || !_routingDataMap.containsKey(namespace)) {
+    if (!_routingDataWriterMap.containsKey(namespace)) {
       // throwing NoSuchElementException instead of IllegalArgumentException to differentiate the
       // status code in the Accessor level
       throw new NoSuchElementException(
           "Failed to add sharding key: Namespace " + namespace + " is not found!");
     }
-    if (_routingDataMap.get(namespace).containsKeyRealmPair(shardingKey, realm)) {
-      return true;
-    }
-    if (!_routingDataMap.get(namespace).isShardingKeyInsertionValid(shardingKey)) {
-      throw new IllegalArgumentException(
-          "Failed to add sharding key: Adding sharding key " + shardingKey
-              + " makes routing data invalid!");
+    synchronized (this) {
+      if (_routingDataMap.containsKey(namespace) && _routingDataMap.get(namespace)
+          .containsKeyRealmPair(shardingKey, realm)) {
+        return true;
+      }
+      if (_routingDataMap.containsKey(namespace) && !_routingDataMap.get(namespace)
+          .isShardingKeyInsertionValid(shardingKey)) {
+        throw new IllegalArgumentException(
+            "Failed to add sharding key: Adding sharding key " + shardingKey
+                + " makes routing data invalid!");
+      }
+      boolean result = _routingDataWriterMap.get(namespace).addShardingKey(realm, shardingKey);
+      refreshRoutingData(namespace);
+      return result;
     }
-    return _routingDataWriterMap.get(namespace).addShardingKey(realm, shardingKey);
   }
 
   @Override
@@ -234,7 +277,11 @@ public class ZkMetadataStoreDirectory implements MetadataStoreDirectory, Routing
       throw new NoSuchElementException(
           "Failed to delete sharding key: Namespace " + namespace + " is not found!");
     }
-    return _routingDataWriterMap.get(namespace).deleteShardingKey(realm, shardingKey);
+    synchronized (this) {
+      boolean result = _routingDataWriterMap.get(namespace).deleteShardingKey(realm, shardingKey);
+      refreshRoutingData(namespace);
+      return result;
+    }
   }
 
   /**
@@ -251,7 +298,7 @@ public class ZkMetadataStoreDirectory implements MetadataStoreDirectory, Routing
     // Safe to ignore the callback if any of the maps are null.
     // If routingDataMap is null, then it will be populated by the constructor anyway
     // If routingDataMap is not null, then it's safe for the callback function to update it
-    if (_routingZkAddressMap == null || _routingDataMap == null || _realmToShardingKeysMap == null
+    if (_routingZkAddressMap == null || _realmToShardingKeysMap == null
         || _routingDataReaderMap == null || _routingDataWriterMap == null) {
       LOG.warn(
           "refreshRoutingData callback called before ZKMetadataStoreDirectory was fully initialized. Skipping refresh!");
@@ -262,17 +309,22 @@ public class ZkMetadataStoreDirectory implements MetadataStoreDirectory, Routing
     if (!_routingZkAddressMap.containsKey(namespace)) {
       LOG.error(
           "Failed to refresh internally-cached routing data! Namespace not found: " + namespace);
+      return;
     }
 
+    Map<String, List<String>> rawRoutingData;
     try {
-      Map<String, List<String>> rawRoutingData =
-          _routingDataReaderMap.get(namespace).getRoutingData();
+      rawRoutingData = _routingDataReaderMap.get(namespace).getRoutingData();
       _realmToShardingKeysMap.put(namespace, rawRoutingData);
-
-      MetadataStoreRoutingData routingData = new TrieRoutingData(rawRoutingData);
-      _routingDataMap.put(namespace, routingData);
     } catch (InvalidRoutingDataException e) {
       LOG.error("Failed to refresh cached routing data for namespace {}", namespace, e);
+      return;
+    }
+
+    try {
+      _routingDataMap.put(namespace, new TrieRoutingData(rawRoutingData));
+    } catch (InvalidRoutingDataException e) {
+      LOG.warn("TrieRoutingData is not created for namespace {}", namespace, e);
     }
   }
 
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/MetadataStoreRoutingDataWriter.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/MetadataStoreRoutingDataWriter.java
index 349bbd0..02ce60b 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/MetadataStoreRoutingDataWriter.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/MetadataStoreRoutingDataWriter.java
@@ -63,7 +63,7 @@ public interface MetadataStoreRoutingDataWriter {
    * Sets (overwrites) the routing data with the given <realm, list of sharding keys> mapping.
    * WARNING: This overwrites all existing routing data. Use with care!
    * @param routingData
-   * @return
+   * @return true if successful; false otherwise.
    */
   boolean setRoutingData(Map<String, List<String>> routingData);
 
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataReader.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataReader.java
index 9251571..6c75618 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataReader.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataReader.java
@@ -99,7 +99,7 @@ public class ZkRoutingDataReader implements MetadataStoreRoutingDataReader, IZkD
     if (allRealmAddresses != null) {
       for (String realmAddress : allRealmAddresses) {
         ZNRecord record = _zkClient
-            .readData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + realmAddress);
+            .readData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + realmAddress, true);
         if (record != null) {
           List<String> shardingKeys =
               record.getListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY);
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataWriter.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataWriter.java
index 74cc14c..fddd9ee 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataWriter.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataWriter.java
@@ -41,9 +41,14 @@ import org.apache.http.client.config.RequestConfig;
 import org.apache.http.client.methods.HttpDelete;
 import org.apache.http.client.methods.HttpPut;
 import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClientBuilder;
 import org.apache.http.util.EntityUtils;
+import org.codehaus.jackson.JsonGenerationException;
+import org.codehaus.jackson.map.JsonMappingException;
+import org.codehaus.jackson.map.ObjectMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -52,6 +57,7 @@ public class ZkRoutingDataWriter implements MetadataStoreRoutingDataWriter {
   // Time out for http requests that are forwarded to leader instances measured in milliseconds
   private static final int HTTP_REQUEST_FORWARDING_TIMEOUT = 60 * 1000;
   private static final Logger LOG = LoggerFactory.getLogger(ZkRoutingDataWriter.class);
+  private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
 
   private final String _namespace;
   private final HelixZkClient _zkClient;
@@ -113,7 +119,7 @@ public class ZkRoutingDataWriter implements MetadataStoreRoutingDataWriter {
 
     String urlSuffix =
         constructUrlSuffix(MetadataStoreRoutingConstants.MSDS_GET_ALL_REALMS_ENDPOINT, realm);
-    return forwardRequestToLeader(urlSuffix, HttpConstants.RestVerbs.PUT,
+    return buildAndSendRequestToLeader(urlSuffix, HttpConstants.RestVerbs.PUT,
         Response.Status.CREATED.getStatusCode());
   }
 
@@ -128,7 +134,7 @@ public class ZkRoutingDataWriter implements MetadataStoreRoutingDataWriter {
 
     String urlSuffix =
         constructUrlSuffix(MetadataStoreRoutingConstants.MSDS_GET_ALL_REALMS_ENDPOINT, realm);
-    return forwardRequestToLeader(urlSuffix, HttpConstants.RestVerbs.DELETE,
+    return buildAndSendRequestToLeader(urlSuffix, HttpConstants.RestVerbs.DELETE,
         Response.Status.OK.getStatusCode());
   }
 
@@ -144,7 +150,7 @@ public class ZkRoutingDataWriter implements MetadataStoreRoutingDataWriter {
     String urlSuffix =
         constructUrlSuffix(MetadataStoreRoutingConstants.MSDS_GET_ALL_REALMS_ENDPOINT, realm,
             MetadataStoreRoutingConstants.MSDS_GET_ALL_SHARDING_KEYS_ENDPOINT, shardingKey);
-    return forwardRequestToLeader(urlSuffix, HttpConstants.RestVerbs.PUT,
+    return buildAndSendRequestToLeader(urlSuffix, HttpConstants.RestVerbs.PUT,
         Response.Status.CREATED.getStatusCode());
   }
 
@@ -160,7 +166,7 @@ public class ZkRoutingDataWriter implements MetadataStoreRoutingDataWriter {
     String urlSuffix =
         constructUrlSuffix(MetadataStoreRoutingConstants.MSDS_GET_ALL_REALMS_ENDPOINT, realm,
             MetadataStoreRoutingConstants.MSDS_GET_ALL_SHARDING_KEYS_ENDPOINT, shardingKey);
-    return forwardRequestToLeader(urlSuffix, HttpConstants.RestVerbs.DELETE,
+    return buildAndSendRequestToLeader(urlSuffix, HttpConstants.RestVerbs.DELETE,
         Response.Status.OK.getStatusCode());
   }
 
@@ -209,8 +215,23 @@ public class ZkRoutingDataWriter implements MetadataStoreRoutingDataWriter {
       return true;
     }
 
-    // TODO: Forward the request to leader
-    return true;
+    String leaderHostName = _leaderElection.getCurrentLeaderInfo().getId();
+    String url = leaderHostName + constructUrlSuffix(
+        MetadataStoreRoutingConstants.MSDS_GET_ALL_ROUTING_DATA_ENDPOINT);
+    HttpPut httpPut = new HttpPut(url);
+    String routingDataJsonString;
+    try {
+      routingDataJsonString = OBJECT_MAPPER.writeValueAsString(routingData);
+    } catch (JsonGenerationException | JsonMappingException e) {
+      throw new IllegalArgumentException(e.getMessage());
+    } catch (IOException e) {
+      LOG.error(
+          "setRoutingData failed before forwarding the request to leader: an exception happened while routingData is converted to json. routingData: {}",
+          routingData, e);
+      return false;
+    }
+    httpPut.setEntity(new StringEntity(routingDataJsonString, ContentType.APPLICATION_JSON));
+    return sendRequestToLeader(httpPut, Response.Status.CREATED.getStatusCode(), leaderHostName);
   }
 
   @Override
@@ -332,12 +353,13 @@ public class ZkRoutingDataWriter implements MetadataStoreRoutingDataWriter {
     return String.join("", allUrlParameters);
   }
 
-  private boolean forwardRequestToLeader(String urlSuffix, HttpConstants.RestVerbs request_method,
-      int expectedResponseCode) throws IllegalArgumentException {
+  private boolean buildAndSendRequestToLeader(String urlSuffix,
+      HttpConstants.RestVerbs requestMethod, int expectedResponseCode)
+      throws IllegalArgumentException {
     String leaderHostName = _leaderElection.getCurrentLeaderInfo().getId();
     String url = leaderHostName + urlSuffix;
     HttpUriRequest request;
-    switch (request_method) {
+    switch (requestMethod) {
       case PUT:
         request = new HttpPut(url);
         break;
@@ -345,8 +367,7 @@ public class ZkRoutingDataWriter implements MetadataStoreRoutingDataWriter {
         request = new HttpDelete(url);
         break;
       default:
-        LOG.error("Unsupported request_method: " + request_method.name());
-        return false;
+        throw new IllegalArgumentException("Unsupported requestMethod: " + requestMethod.name());
     }
 
     return sendRequestToLeader(request, expectedResponseCode, leaderHostName);
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/server/resources/metadatastore/MetadataStoreDirectoryAccessor.java b/helix-rest/src/main/java/org/apache/helix/rest/server/resources/metadatastore/MetadataStoreDirectoryAccessor.java
index 0763ec1..f20fd9a 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/server/resources/metadatastore/MetadataStoreDirectoryAccessor.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/server/resources/metadatastore/MetadataStoreDirectoryAccessor.java
@@ -19,18 +19,22 @@ package org.apache.helix.rest.server.resources.metadatastore;
  * under the License.
  */
 
+import java.io.IOException;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.stream.Collectors;
 import javax.annotation.PostConstruct;
+import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
 import javax.ws.rs.GET;
 import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 
 import com.google.common.collect.ImmutableMap;
@@ -44,6 +48,9 @@ import org.apache.helix.rest.metadatastore.ZkMetadataStoreDirectory;
 import org.apache.helix.rest.metadatastore.datamodel.MetadataStoreShardingKey;
 import org.apache.helix.rest.metadatastore.datamodel.MetadataStoreShardingKeysByRealm;
 import org.apache.helix.rest.server.resources.AbstractResource;
+import org.codehaus.jackson.JsonParseException;
+import org.codehaus.jackson.map.JsonMappingException;
+import org.codehaus.jackson.type.TypeReference;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -220,6 +227,24 @@ public class MetadataStoreDirectoryAccessor extends AbstractResource {
     return JSONRepresentation(responseMap);
   }
 
+  @PUT
+  @Path("/routing-data")
+  @Consumes(MediaType.APPLICATION_JSON)
+  public Response setRoutingData(String jsonContent) {
+    try {
+      Map<String, List<String>> routingData =
+          OBJECT_MAPPER.readValue(jsonContent, new TypeReference<HashMap<String, List<String>>>() {
+          });
+      _metadataStoreDirectory.setNamespaceRoutingData(_namespace, routingData);
+    } catch (JsonMappingException | JsonParseException | IllegalArgumentException e) {
+      return badRequest(e.getMessage());
+    } catch (IOException e) {
+      return serverError(e);
+    }
+
+    return created();
+  }
+
   /**
    * Gets all path-based sharding keys for a queried realm at endpoint:
    * "GET /metadata-store-realms/{realm}/sharding-keys"
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java
index 8eddcea..df84754 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java
@@ -35,6 +35,7 @@ import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
 import org.apache.helix.rest.server.AbstractTestClass;
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
+import org.apache.helix.zookeeper.zkclient.ZkClient;
 import org.testng.Assert;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
@@ -62,9 +63,11 @@ public class TestZkMetadataStoreDirectory extends AbstractTestClass {
   private MetadataStoreDirectory _metadataStoreDirectory;
 
   @BeforeClass
-  public void beforeClass() throws InvalidRoutingDataException {
+  public void beforeClass() throws Exception {
     _zkList = new ArrayList<>(ZK_SERVER_MAP.keySet());
 
+    clearRoutingData();
+
     // Populate routingZkAddrMap
     _routingZkAddrMap = new LinkedHashMap<>();
     int namespaceIndex = 0;
@@ -111,10 +114,9 @@ public class TestZkMetadataStoreDirectory extends AbstractTestClass {
   }
 
   @AfterClass
-  public void afterClass() {
+  public void afterClass() throws Exception {
     _metadataStoreDirectory.close();
-    _zkList.forEach(zk -> ZK_SERVER_MAP.get(zk).getZkClient()
-        .deleteRecursive(MetadataStoreRoutingConstants.ROUTING_DATA_PATH));
+    clearRoutingData();
     System.clearProperty(MetadataStoreRoutingConstants.MSDS_SERVER_HOSTNAME_KEY);
   }
 
@@ -147,6 +149,42 @@ public class TestZkMetadataStoreDirectory extends AbstractTestClass {
   }
 
   @Test(dependsOnMethods = "testGetAllShardingKeys")
+  public void testGetNamespaceRoutingData() {
+    Map<String, List<String>> routingDataMap = new HashMap<>();
+    routingDataMap.put(TEST_REALM_1, TEST_SHARDING_KEYS_1);
+    routingDataMap.put(TEST_REALM_2, TEST_SHARDING_KEYS_2);
+
+    for (String namespace : _routingZkAddrMap.keySet()) {
+      Assert
+          .assertEquals(_metadataStoreDirectory.getNamespaceRoutingData(namespace), routingDataMap);
+    }
+  }
+
+  @Test(dependsOnMethods = "testGetNamespaceRoutingData")
+  public void testSetNamespaceRoutingData() {
+    Map<String, List<String>> routingDataMap = new HashMap<>();
+    routingDataMap.put(TEST_REALM_1, TEST_SHARDING_KEYS_2);
+    routingDataMap.put(TEST_REALM_2, TEST_SHARDING_KEYS_1);
+
+    for (String namespace : _routingZkAddrMap.keySet()) {
+      _metadataStoreDirectory.setNamespaceRoutingData(namespace, routingDataMap);
+      Assert
+          .assertEquals(_metadataStoreDirectory.getNamespaceRoutingData(namespace), routingDataMap);
+    }
+
+    // Revert it back to the original state
+    Map<String, List<String>> originalRoutingDataMap = new HashMap<>();
+    originalRoutingDataMap.put(TEST_REALM_1, TEST_SHARDING_KEYS_1);
+    originalRoutingDataMap.put(TEST_REALM_2, TEST_SHARDING_KEYS_2);
+
+    for (String namespace : _routingZkAddrMap.keySet()) {
+      _metadataStoreDirectory.setNamespaceRoutingData(namespace, originalRoutingDataMap);
+      Assert.assertEquals(_metadataStoreDirectory.getNamespaceRoutingData(namespace),
+          originalRoutingDataMap);
+    }
+  }
+
+  @Test(dependsOnMethods = "testGetNamespaceRoutingData")
   public void testGetAllShardingKeysInRealm() {
     for (String namespace : _routingZkAddrMap.keySet()) {
       // Test two realms independently
@@ -277,4 +315,27 @@ public class TestZkMetadataStoreDirectory extends AbstractTestClass {
       return false;
     }, TestHelper.WAIT_DURATION));
   }
+
+  private void clearRoutingData() throws Exception {
+    Assert.assertTrue(TestHelper.verify(() -> {
+      for (String zk : _zkList) {
+        ZkClient zkClient = ZK_SERVER_MAP.get(zk).getZkClient();
+        if (zkClient.exists(MetadataStoreRoutingConstants.ROUTING_DATA_PATH)) {
+          for (String zkRealm : zkClient
+              .getChildren(MetadataStoreRoutingConstants.ROUTING_DATA_PATH)) {
+            zkClient.delete(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + zkRealm);
+          }
+        }
+      }
+
+      for (String zk : _zkList) {
+        ZkClient zkClient = ZK_SERVER_MAP.get(zk).getZkClient();
+        if (zkClient.exists(MetadataStoreRoutingConstants.ROUTING_DATA_PATH) && !zkClient
+            .getChildren(MetadataStoreRoutingConstants.ROUTING_DATA_PATH).isEmpty()) {
+          return false;
+        }
+      }
+      return true;
+    }, TestHelper.WAIT_DURATION), "Routing data path should be deleted after the tests.");
+  }
 }
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataReader.java b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataReader.java
index 63a013b..4c95445 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataReader.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataReader.java
@@ -24,12 +24,12 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
-import org.apache.helix.AccessOption;
 import org.apache.helix.TestHelper;
 import org.apache.helix.msdcommon.constant.MetadataStoreRoutingConstants;
 import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
 import org.apache.helix.rest.server.AbstractTestClass;
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
+import org.apache.helix.zookeeper.zkclient.ZkClient;
 import org.testng.Assert;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.AfterMethod;
@@ -38,24 +38,25 @@ import org.testng.annotations.Test;
 
 
 public class TestZkRoutingDataReader extends AbstractTestClass {
-  private static final String DUMMY_NAMESPACE = "NAMESPACE";
   private MetadataStoreRoutingDataReader _zkRoutingDataReader;
+  private ZkClient _zkClient;
 
   @BeforeClass
   public void beforeClass() throws Exception {
-    deleteRoutingDataPath();
-    _zkRoutingDataReader = new ZkRoutingDataReader(DUMMY_NAMESPACE, ZK_ADDR, null);
+    _zkClient = ZK_SERVER_MAP.get(_zkAddrTestNS).getZkClient();
+    _zkRoutingDataReader = new ZkRoutingDataReader(TEST_NAMESPACE, _zkAddrTestNS, null);
+    clearRoutingDataPath();
   }
 
   @AfterClass
   public void afterClass() throws Exception {
     _zkRoutingDataReader.close();
-    deleteRoutingDataPath();
+    clearRoutingDataPath();
   }
 
   @AfterMethod
-  public void afterMethod() {
-    _baseAccessor.remove(MetadataStoreRoutingConstants.ROUTING_DATA_PATH, AccessOption.PERSISTENT);
+  public void afterMethod() throws Exception {
+    clearRoutingDataPath();
   }
 
   @Test
@@ -75,10 +76,14 @@ public class TestZkRoutingDataReader extends AbstractTestClass {
         .setListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY, testShardingKeys2);
 
     // Add both nodes as children nodes to ZkRoutingDataReader.ROUTING_DATA_PATH
-    _baseAccessor.create(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/testRealmAddress1",
-        testZnRecord1, AccessOption.PERSISTENT);
-    _baseAccessor.create(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/testRealmAddress2",
-        testZnRecord2, AccessOption.PERSISTENT);
+    _zkClient
+        .createPersistent(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/testRealmAddress1");
+    _zkClient.writeData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/testRealmAddress1",
+        testZnRecord1);
+    _zkClient
+        .createPersistent(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/testRealmAddress2");
+    _zkClient.writeData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/testRealmAddress2",
+        testZnRecord2);
 
     try {
       Map<String, List<String>> routingData = _zkRoutingDataReader.getRoutingData();
@@ -90,22 +95,8 @@ public class TestZkRoutingDataReader extends AbstractTestClass {
     }
   }
 
-  @Test
-  public void testGetRoutingDataMissingMSRD() {
-    try {
-      _zkRoutingDataReader.getRoutingData();
-      Assert.fail("Expecting InvalidRoutingDataException");
-    } catch (InvalidRoutingDataException e) {
-      Assert.assertTrue(e.getMessage().contains(
-          "Routing data directory ZNode " + MetadataStoreRoutingConstants.ROUTING_DATA_PATH
-              + " does not exist. Routing ZooKeeper address: " + ZK_ADDR));
-    }
-  }
-
-  @Test
+  @Test(dependsOnMethods = "testGetRoutingData")
   public void testGetRoutingDataMissingMSRDChildren() {
-    _baseAccessor.create(MetadataStoreRoutingConstants.ROUTING_DATA_PATH, new ZNRecord("test"),
-        AccessOption.PERSISTENT);
     try {
       Map<String, List<String>> routingData = _zkRoutingDataReader.getRoutingData();
       Assert.assertEquals(routingData.size(), 0);
@@ -114,13 +105,15 @@ public class TestZkRoutingDataReader extends AbstractTestClass {
     }
   }
 
-  @Test
+  @Test(dependsOnMethods = "testGetRoutingData")
   public void testGetRoutingDataMSRDChildEmptyValue() {
     ZNRecord testZnRecord1 = new ZNRecord("testZnRecord1");
     testZnRecord1.setListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY,
         Collections.emptyList());
-    _baseAccessor.create(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/testRealmAddress1",
-        testZnRecord1, AccessOption.PERSISTENT);
+    _zkClient
+        .createPersistent(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/testRealmAddress1");
+    _zkClient.writeData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/testRealmAddress1",
+        testZnRecord1);
     try {
       Map<String, List<String>> routingData = _zkRoutingDataReader.getRoutingData();
       Assert.assertEquals(routingData.size(), 1);
@@ -130,17 +123,14 @@ public class TestZkRoutingDataReader extends AbstractTestClass {
     }
   }
 
-  private void deleteRoutingDataPath() throws Exception {
+  private void clearRoutingDataPath() throws Exception {
     Assert.assertTrue(TestHelper.verify(() -> {
-      ZK_SERVER_MAP.get(ZK_ADDR).getZkClient()
-          .deleteRecursively(MetadataStoreRoutingConstants.ROUTING_DATA_PATH);
-
-      if (ZK_SERVER_MAP.get(ZK_ADDR).getZkClient()
-          .exists(MetadataStoreRoutingConstants.ROUTING_DATA_PATH)) {
-        return false;
+      for (String zkRealm : _zkClient
+          .getChildren(MetadataStoreRoutingConstants.ROUTING_DATA_PATH)) {
+        _zkClient.delete(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + zkRealm);
       }
 
-      return true;
+      return _zkClient.getChildren(MetadataStoreRoutingConstants.ROUTING_DATA_PATH).isEmpty();
     }, TestHelper.WAIT_DURATION), "Routing data path should be deleted after the tests.");
   }
 }
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataWriter.java b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataWriter.java
index 069931f..31db291 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataWriter.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataWriter.java
@@ -25,22 +25,25 @@ import java.util.List;
 import java.util.Map;
 
 import com.google.common.collect.ImmutableMap;
-import org.apache.helix.AccessOption;
+import org.apache.helix.TestHelper;
 import org.apache.helix.msdcommon.constant.MetadataStoreRoutingConstants;
+import org.apache.helix.rest.common.HttpConstants;
 import org.apache.helix.rest.server.AbstractTestClass;
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
+import org.apache.helix.zookeeper.zkclient.ZkClient;
 import org.apache.http.client.methods.HttpUriRequest;
-import org.junit.Assert;
+import org.testng.Assert;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
 
 public class TestZkRoutingDataWriter extends AbstractTestClass {
-  private static final String DUMMY_NAMESPACE = "NAMESPACE";
   private static final String DUMMY_REALM = "REALM";
-  private static final String DUMMY_SHARDING_KEY = "SHARDING_KEY";
+  private static final String DUMMY_SHARDING_KEY = "/DUMMY/SHARDING/KEY";
+
   private MetadataStoreRoutingDataWriter _zkRoutingDataWriter;
+  private ZkClient _zkClient;
 
   // MockWriter is used for testing request forwarding features in non-leader situations
   class MockWriter extends ZkRoutingDataWriter {
@@ -60,43 +63,41 @@ public class TestZkRoutingDataWriter extends AbstractTestClass {
   }
 
   @BeforeClass
-  public void beforeClass() {
-    _baseAccessor.remove(MetadataStoreRoutingConstants.ROUTING_DATA_PATH, AccessOption.PERSISTENT);
+  public void beforeClass() throws Exception {
+    _zkClient = ZK_SERVER_MAP.get(_zkAddrTestNS).getZkClient();
     System.setProperty(MetadataStoreRoutingConstants.MSDS_SERVER_HOSTNAME_KEY,
         getBaseUri().getHost() + ":" + getBaseUri().getPort());
-    _zkRoutingDataWriter = new ZkRoutingDataWriter(DUMMY_NAMESPACE, ZK_ADDR);
+    _zkRoutingDataWriter = new ZkRoutingDataWriter(TEST_NAMESPACE, _zkAddrTestNS);
+    clearRoutingDataPath();
   }
 
   @AfterClass
-  public void afterClass() {
-    _baseAccessor.remove(MetadataStoreRoutingConstants.ROUTING_DATA_PATH, AccessOption.PERSISTENT);
+  public void afterClass() throws Exception {
     System.clearProperty(MetadataStoreRoutingConstants.MSDS_SERVER_HOSTNAME_KEY);
     _zkRoutingDataWriter.close();
+    clearRoutingDataPath();
   }
 
   @Test
   public void testAddMetadataStoreRealm() {
     _zkRoutingDataWriter.addMetadataStoreRealm(DUMMY_REALM);
-    ZNRecord znRecord = _baseAccessor
-        .get(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + DUMMY_REALM, null,
-            AccessOption.PERSISTENT);
+    ZNRecord znRecord =
+        _zkClient.readData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + DUMMY_REALM);
     Assert.assertNotNull(znRecord);
   }
 
   @Test(dependsOnMethods = "testAddMetadataStoreRealm")
   public void testDeleteMetadataStoreRealm() {
     _zkRoutingDataWriter.deleteMetadataStoreRealm(DUMMY_REALM);
-    Assert.assertFalse(_baseAccessor
-        .exists(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + DUMMY_REALM,
-            AccessOption.PERSISTENT));
+    Assert.assertFalse(
+        _zkClient.exists(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + DUMMY_REALM));
   }
 
   @Test(dependsOnMethods = "testDeleteMetadataStoreRealm")
   public void testAddShardingKey() {
     _zkRoutingDataWriter.addShardingKey(DUMMY_REALM, DUMMY_SHARDING_KEY);
-    ZNRecord znRecord = _baseAccessor
-        .get(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + DUMMY_REALM, null,
-            AccessOption.PERSISTENT);
+    ZNRecord znRecord =
+        _zkClient.readData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + DUMMY_REALM);
     Assert.assertNotNull(znRecord);
     Assert.assertTrue(znRecord.getListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY)
         .contains(DUMMY_SHARDING_KEY));
@@ -105,9 +106,8 @@ public class TestZkRoutingDataWriter extends AbstractTestClass {
   @Test(dependsOnMethods = "testAddShardingKey")
   public void testDeleteShardingKey() {
     _zkRoutingDataWriter.deleteShardingKey(DUMMY_REALM, DUMMY_SHARDING_KEY);
-    ZNRecord znRecord = _baseAccessor
-        .get(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + DUMMY_REALM, null,
-            AccessOption.PERSISTENT);
+    ZNRecord znRecord =
+        _zkClient.readData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + DUMMY_REALM);
     Assert.assertNotNull(znRecord);
     Assert.assertFalse(znRecord.getListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY)
         .contains(DUMMY_SHARDING_KEY));
@@ -118,9 +118,8 @@ public class TestZkRoutingDataWriter extends AbstractTestClass {
     Map<String, List<String>> testRoutingDataMap =
         ImmutableMap.of(DUMMY_REALM, Collections.singletonList(DUMMY_SHARDING_KEY));
     _zkRoutingDataWriter.setRoutingData(testRoutingDataMap);
-    ZNRecord znRecord = _baseAccessor
-        .get(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + DUMMY_REALM, null,
-            AccessOption.PERSISTENT);
+    ZNRecord znRecord =
+        _zkClient.readData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + DUMMY_REALM);
     Assert.assertNotNull(znRecord);
     Assert.assertEquals(
         znRecord.getListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY).size(), 1);
@@ -130,63 +129,93 @@ public class TestZkRoutingDataWriter extends AbstractTestClass {
 
   @Test(dependsOnMethods = "testSetRoutingData")
   public void testAddMetadataStoreRealmNonLeader() {
-    MockWriter mockWriter = new MockWriter(DUMMY_NAMESPACE, ZK_ADDR);
+    MockWriter mockWriter = new MockWriter(TEST_NAMESPACE, _zkAddrTestNS);
     mockWriter.addMetadataStoreRealm(DUMMY_REALM);
-    Assert.assertEquals("PUT", mockWriter.calledRequest.getMethod());
+    Assert.assertEquals(mockWriter.calledRequest.getMethod(), HttpConstants.RestVerbs.PUT.name());
     List<String> expectedUrlParams = Arrays
-        .asList(MetadataStoreRoutingConstants.MSDS_NAMESPACES_URL_PREFIX, DUMMY_NAMESPACE,
+        .asList(MetadataStoreRoutingConstants.MSDS_NAMESPACES_URL_PREFIX, TEST_NAMESPACE,
             MetadataStoreRoutingConstants.MSDS_GET_ALL_REALMS_ENDPOINT, DUMMY_REALM);
     String expectedUrl =
         getBaseUri().toString() + String.join("/", expectedUrlParams).replaceAll("//", "/")
             .substring(1);
-    Assert.assertEquals(expectedUrl, mockWriter.calledRequest.getURI().toString());
+    Assert.assertEquals(mockWriter.calledRequest.getURI().toString(), expectedUrl);
     mockWriter.close();
   }
 
   @Test(dependsOnMethods = "testAddMetadataStoreRealmNonLeader")
   public void testDeleteMetadataStoreRealmNonLeader() {
-    MockWriter mockWriter = new MockWriter(DUMMY_NAMESPACE, ZK_ADDR);
+    MockWriter mockWriter = new MockWriter(TEST_NAMESPACE, _zkAddrTestNS);
     mockWriter.deleteMetadataStoreRealm(DUMMY_REALM);
-    Assert.assertEquals("DELETE", mockWriter.calledRequest.getMethod());
+    Assert
+        .assertEquals(mockWriter.calledRequest.getMethod(), HttpConstants.RestVerbs.DELETE.name());
     List<String> expectedUrlParams = Arrays
-        .asList(MetadataStoreRoutingConstants.MSDS_NAMESPACES_URL_PREFIX, DUMMY_NAMESPACE,
+        .asList(MetadataStoreRoutingConstants.MSDS_NAMESPACES_URL_PREFIX, TEST_NAMESPACE,
             MetadataStoreRoutingConstants.MSDS_GET_ALL_REALMS_ENDPOINT, DUMMY_REALM);
     String expectedUrl =
         getBaseUri().toString() + String.join("/", expectedUrlParams).replaceAll("//", "/")
             .substring(1);
-    Assert.assertEquals(expectedUrl, mockWriter.calledRequest.getURI().toString());
+    Assert.assertEquals(mockWriter.calledRequest.getURI().toString(), expectedUrl);
     mockWriter.close();
   }
 
   @Test(dependsOnMethods = "testDeleteMetadataStoreRealmNonLeader")
   public void testAddShardingKeyNonLeader() {
-    MockWriter mockWriter = new MockWriter(DUMMY_NAMESPACE, ZK_ADDR);
+    MockWriter mockWriter = new MockWriter(TEST_NAMESPACE, _zkAddrTestNS);
     mockWriter.addShardingKey(DUMMY_REALM, DUMMY_SHARDING_KEY);
-    Assert.assertEquals("PUT", mockWriter.calledRequest.getMethod());
+    Assert.assertEquals(mockWriter.calledRequest.getMethod(), HttpConstants.RestVerbs.PUT.name());
     List<String> expectedUrlParams = Arrays
-        .asList(MetadataStoreRoutingConstants.MSDS_NAMESPACES_URL_PREFIX, DUMMY_NAMESPACE,
+        .asList(MetadataStoreRoutingConstants.MSDS_NAMESPACES_URL_PREFIX, TEST_NAMESPACE,
             MetadataStoreRoutingConstants.MSDS_GET_ALL_REALMS_ENDPOINT, DUMMY_REALM,
             MetadataStoreRoutingConstants.MSDS_GET_ALL_SHARDING_KEYS_ENDPOINT, DUMMY_SHARDING_KEY);
     String expectedUrl =
         getBaseUri().toString() + String.join("/", expectedUrlParams).replaceAll("//", "/")
             .substring(1);
-    Assert.assertEquals(expectedUrl, mockWriter.calledRequest.getURI().toString());
+    Assert.assertEquals(mockWriter.calledRequest.getURI().toString(), expectedUrl);
     mockWriter.close();
   }
 
   @Test(dependsOnMethods = "testAddShardingKeyNonLeader")
   public void testDeleteShardingKeyNonLeader() {
-    MockWriter mockWriter = new MockWriter(DUMMY_NAMESPACE, ZK_ADDR);
+    MockWriter mockWriter = new MockWriter(TEST_NAMESPACE, _zkAddrTestNS);
     mockWriter.deleteShardingKey(DUMMY_REALM, DUMMY_SHARDING_KEY);
-    Assert.assertEquals("DELETE", mockWriter.calledRequest.getMethod());
+    Assert
+        .assertEquals(mockWriter.calledRequest.getMethod(), HttpConstants.RestVerbs.DELETE.name());
     List<String> expectedUrlParams = Arrays
-        .asList(MetadataStoreRoutingConstants.MSDS_NAMESPACES_URL_PREFIX, DUMMY_NAMESPACE,
+        .asList(MetadataStoreRoutingConstants.MSDS_NAMESPACES_URL_PREFIX, TEST_NAMESPACE,
             MetadataStoreRoutingConstants.MSDS_GET_ALL_REALMS_ENDPOINT, DUMMY_REALM,
             MetadataStoreRoutingConstants.MSDS_GET_ALL_SHARDING_KEYS_ENDPOINT, DUMMY_SHARDING_KEY);
     String expectedUrl =
         getBaseUri().toString() + String.join("/", expectedUrlParams).replaceAll("//", "/")
             .substring(1);
-    Assert.assertEquals(expectedUrl, mockWriter.calledRequest.getURI().toString());
+    Assert.assertEquals(mockWriter.calledRequest.getURI().toString(), expectedUrl);
     mockWriter.close();
   }
+
+  @Test(dependsOnMethods = "testDeleteShardingKeyNonLeader")
+  public void testSetRoutingDataNonLeader() {
+    MockWriter mockWriter = new MockWriter(TEST_NAMESPACE, _zkAddrTestNS);
+    Map<String, List<String>> testRoutingDataMap =
+        ImmutableMap.of(DUMMY_REALM, Collections.singletonList(DUMMY_SHARDING_KEY));
+    mockWriter.setRoutingData(testRoutingDataMap);
+    Assert.assertEquals(mockWriter.calledRequest.getMethod(), HttpConstants.RestVerbs.PUT.name());
+    List<String> expectedUrlParams = Arrays
+        .asList(MetadataStoreRoutingConstants.MSDS_NAMESPACES_URL_PREFIX, TEST_NAMESPACE,
+            MetadataStoreRoutingConstants.MSDS_GET_ALL_ROUTING_DATA_ENDPOINT);
+    String expectedUrl =
+        getBaseUri().toString() + String.join("/", expectedUrlParams).replaceAll("//", "/")
+            .substring(1);
+    Assert.assertEquals(mockWriter.calledRequest.getURI().toString(), expectedUrl);
+    mockWriter.close();
+  }
+
+  private void clearRoutingDataPath() throws Exception {
+    Assert.assertTrue(TestHelper.verify(() -> {
+      for (String zkRealm : _zkClient
+          .getChildren(MetadataStoreRoutingConstants.ROUTING_DATA_PATH)) {
+        _zkClient.delete(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + zkRealm);
+      }
+
+      return _zkClient.getChildren(MetadataStoreRoutingConstants.ROUTING_DATA_PATH).isEmpty();
+    }, TestHelper.WAIT_DURATION), "Routing data path should be deleted after the tests.");
+  }
 }
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/server/MetadataStoreDirectoryAccessorTestBase.java b/helix-rest/src/test/java/org/apache/helix/rest/server/MetadataStoreDirectoryAccessorTestBase.java
index 7cebbf3..f2ed433 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/server/MetadataStoreDirectoryAccessorTestBase.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/server/MetadataStoreDirectoryAccessorTestBase.java
@@ -23,6 +23,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import org.apache.helix.TestHelper;
@@ -32,6 +33,7 @@ import org.apache.helix.rest.metadatastore.accessor.MetadataStoreRoutingDataRead
 import org.apache.helix.rest.metadatastore.accessor.ZkRoutingDataReader;
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
+import org.apache.helix.zookeeper.zkclient.ZkClient;
 import org.testng.Assert;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
@@ -62,7 +64,7 @@ public class MetadataStoreDirectoryAccessorTestBase extends AbstractTestClass {
   public void beforeClass() throws Exception {
     _zkList = new ArrayList<>(ZK_SERVER_MAP.keySet());
 
-    deleteRoutingDataPath();
+    clearRoutingData();
 
     // Write dummy mappings in ZK
     // Create a node that represents a realm address and add 3 sharding keys to it
@@ -100,21 +102,29 @@ public class MetadataStoreDirectoryAccessorTestBase extends AbstractTestClass {
   @AfterClass
   public void afterClass() throws Exception {
     System.clearProperty(MetadataStoreRoutingConstants.MSDS_SERVER_HOSTNAME_KEY);
-    deleteRoutingDataPath();
+    _routingDataReader.close();
+    clearRoutingData();
   }
 
-  protected void deleteRoutingDataPath() throws Exception {
+  protected void clearRoutingData() throws Exception {
     Assert.assertTrue(TestHelper.verify(() -> {
-      _zkList.forEach(zk -> ZK_SERVER_MAP.get(zk).getZkClient()
-          .deleteRecursively(MetadataStoreRoutingConstants.ROUTING_DATA_PATH));
+      for (String zk : _zkList) {
+        ZkClient zkClient = ZK_SERVER_MAP.get(zk).getZkClient();
+        if (zkClient.exists(MetadataStoreRoutingConstants.ROUTING_DATA_PATH)) {
+          for (String zkRealm : zkClient
+              .getChildren(MetadataStoreRoutingConstants.ROUTING_DATA_PATH)) {
+            zkClient.delete(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + zkRealm);
+          }
+        }
+      }
 
       for (String zk : _zkList) {
-        if (ZK_SERVER_MAP.get(zk).getZkClient()
-            .exists(MetadataStoreRoutingConstants.ROUTING_DATA_PATH)) {
+        ZkClient zkClient = ZK_SERVER_MAP.get(zk).getZkClient();
+        if (zkClient.exists(MetadataStoreRoutingConstants.ROUTING_DATA_PATH) && !zkClient
+            .getChildren(MetadataStoreRoutingConstants.ROUTING_DATA_PATH).isEmpty()) {
           return false;
         }
       }
-
       return true;
     }, TestHelper.WAIT_DURATION), "Routing data path should be deleted after the tests.");
   }
@@ -129,4 +139,8 @@ public class MetadataStoreDirectoryAccessorTestBase extends AbstractTestClass {
   protected Set<String> getAllShardingKeysInTestRealm1() throws InvalidRoutingDataException {
     return new HashSet<>(_routingDataReader.getRoutingData().get(TEST_REALM_1));
   }
+
+  protected Map<String, List<String>> getRawRoutingData() throws InvalidRoutingDataException {
+    return _routingDataReader.getRoutingData();
+  }
 }
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/server/TestMSDAccessorLeaderElection.java b/helix-rest/src/test/java/org/apache/helix/rest/server/TestMSDAccessorLeaderElection.java
index 951015b..2a3f0be 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/server/TestMSDAccessorLeaderElection.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/server/TestMSDAccessorLeaderElection.java
@@ -24,7 +24,9 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import javax.ws.rs.core.Response;
 
@@ -45,8 +47,11 @@ import org.apache.http.HttpResponse;
 import org.apache.http.client.methods.HttpDelete;
 import org.apache.http.client.methods.HttpPut;
 import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClients;
+import org.codehaus.jackson.map.ObjectMapper;
 import org.glassfish.jersey.server.ResourceConfig;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -120,8 +125,9 @@ public class TestMSDAccessorLeaderElection extends MetadataStoreDirectoryAccesso
     Set<String> expectedRealmsSet = getAllRealms();
     Assert.assertFalse(expectedRealmsSet.contains(TEST_REALM_3),
         "Metadata store directory should not have realm: " + TEST_REALM_3);
-    sendRequestAndValidate("/metadata-store-realms/" + TEST_REALM_3, HttpConstants.RestVerbs.PUT,
-        Response.Status.CREATED.getStatusCode());
+    HttpUriRequest request =
+        buildRequest("/metadata-store-realms/" + TEST_REALM_3, HttpConstants.RestVerbs.PUT, "");
+    sendRequestAndValidate(request, Response.Status.CREATED.getStatusCode());
     expectedRealmsSet.add(TEST_REALM_3);
     Assert.assertEquals(getAllRealms(), expectedRealmsSet);
     MockMetadataStoreDirectoryAccessor._mockMSDInstance.close();
@@ -131,8 +137,9 @@ public class TestMSDAccessorLeaderElection extends MetadataStoreDirectoryAccesso
   public void testDeleteMetadataStoreRealmRequestForwarding()
       throws InvalidRoutingDataException, IOException {
     Set<String> expectedRealmsSet = getAllRealms();
-    sendRequestAndValidate("/metadata-store-realms/" + TEST_REALM_3, HttpConstants.RestVerbs.DELETE,
-        Response.Status.OK.getStatusCode());
+    HttpUriRequest request =
+        buildRequest("/metadata-store-realms/" + TEST_REALM_3, HttpConstants.RestVerbs.DELETE, "");
+    sendRequestAndValidate(request, Response.Status.OK.getStatusCode());
     expectedRealmsSet.remove(TEST_REALM_3);
     Assert.assertEquals(getAllRealms(), expectedRealmsSet);
     MockMetadataStoreDirectoryAccessor._mockMSDInstance.close();
@@ -144,9 +151,10 @@ public class TestMSDAccessorLeaderElection extends MetadataStoreDirectoryAccesso
     Set<String> expectedShardingKeysSet = getAllShardingKeysInTestRealm1();
     Assert.assertFalse(expectedShardingKeysSet.contains(TEST_SHARDING_KEY),
         "Realm does not have sharding key: " + TEST_SHARDING_KEY);
-    sendRequestAndValidate(
+    HttpUriRequest request = buildRequest(
         "/metadata-store-realms/" + TEST_REALM_1 + "/sharding-keys/" + TEST_SHARDING_KEY,
-        HttpConstants.RestVerbs.PUT, Response.Status.CREATED.getStatusCode());
+        HttpConstants.RestVerbs.PUT, "");
+    sendRequestAndValidate(request, Response.Status.CREATED.getStatusCode());
     expectedShardingKeysSet.add(TEST_SHARDING_KEY);
     Assert.assertEquals(getAllShardingKeysInTestRealm1(), expectedShardingKeysSet);
     MockMetadataStoreDirectoryAccessor._mockMSDInstance.close();
@@ -156,28 +164,47 @@ public class TestMSDAccessorLeaderElection extends MetadataStoreDirectoryAccesso
   public void testDeleteShardingKeyRequestForwarding()
       throws InvalidRoutingDataException, IOException {
     Set<String> expectedShardingKeysSet = getAllShardingKeysInTestRealm1();
-    sendRequestAndValidate(
+    HttpUriRequest request = buildRequest(
         "/metadata-store-realms/" + TEST_REALM_1 + "/sharding-keys/" + TEST_SHARDING_KEY,
-        HttpConstants.RestVerbs.DELETE, Response.Status.OK.getStatusCode());
+        HttpConstants.RestVerbs.DELETE, "");
+    sendRequestAndValidate(request, Response.Status.OK.getStatusCode());
     expectedShardingKeysSet.remove(TEST_SHARDING_KEY);
     Assert.assertEquals(getAllShardingKeysInTestRealm1(), expectedShardingKeysSet);
     MockMetadataStoreDirectoryAccessor._mockMSDInstance.close();
   }
 
-  private void sendRequestAndValidate(String urlSuffix, HttpConstants.RestVerbs requestMethod,
-      int expectedResponseCode) throws IllegalArgumentException, IOException {
+  @Test(dependsOnMethods = "testDeleteShardingKeyRequestForwarding")
+  public void testSetRoutingDataRequestForwarding()
+      throws InvalidRoutingDataException, IOException {
+    Map<String, List<String>> routingData = new HashMap<>();
+    routingData.put(TEST_REALM_1, TEST_SHARDING_KEYS_2);
+    routingData.put(TEST_REALM_2, TEST_SHARDING_KEYS_1);
+    String routingDataString = new ObjectMapper().writeValueAsString(routingData);
+    HttpUriRequest request =
+        buildRequest(MetadataStoreRoutingConstants.MSDS_GET_ALL_ROUTING_DATA_ENDPOINT,
+            HttpConstants.RestVerbs.PUT, routingDataString);
+    sendRequestAndValidate(request, Response.Status.CREATED.getStatusCode());
+    Assert.assertEquals(getRawRoutingData(), routingData);
+    MockMetadataStoreDirectoryAccessor._mockMSDInstance.close();
+  }
+
+  private HttpUriRequest buildRequest(String urlSuffix, HttpConstants.RestVerbs requestMethod,
+      String jsonEntity) {
     String url = _mockBaseUri + TEST_NAMESPACE_URI_PREFIX + MOCK_URL_PREFIX + urlSuffix;
-    HttpUriRequest request;
     switch (requestMethod) {
       case PUT:
-        request = new HttpPut(url);
-        break;
+        HttpPut httpPut = new HttpPut(url);
+        httpPut.setEntity(new StringEntity(jsonEntity, ContentType.APPLICATION_JSON));
+        return httpPut;
       case DELETE:
-        request = new HttpDelete(url);
-        break;
+        return new HttpDelete(url);
       default:
         throw new IllegalArgumentException("Unsupported requestMethod: " + requestMethod);
     }
+  }
+
+  private void sendRequestAndValidate(HttpUriRequest request, int expectedResponseCode)
+      throws IllegalArgumentException, IOException {
     HttpResponse response = _httpClient.execute(request);
     Assert.assertEquals(response.getStatusLine().getStatusCode(), expectedResponseCode);
 
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/server/TestMetadataStoreDirectoryAccessor.java b/helix-rest/src/test/java/org/apache/helix/rest/server/TestMetadataStoreDirectoryAccessor.java
index 94641ff..570ab90 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/server/TestMetadataStoreDirectoryAccessor.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/server/TestMetadataStoreDirectoryAccessor.java
@@ -21,6 +21,7 @@ package org.apache.helix.rest.server;
 
 import java.io.IOException;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -452,6 +453,43 @@ public class TestMetadataStoreDirectoryAccessor extends MetadataStoreDirectoryAc
     Assert.assertEquals(getAllShardingKeysInTestRealm1(), expectedShardingKeysSet);
   }
 
+  @Test(dependsOnMethods = "testDeleteShardingKey")
+  public void testSetRoutingData() throws InvalidRoutingDataException, IOException {
+    Map<String, List<String>> routingData = new HashMap<>();
+    routingData.put(TEST_REALM_1, TEST_SHARDING_KEYS_2);
+    routingData.put(TEST_REALM_2, TEST_SHARDING_KEYS_1);
+    String routingDataString = OBJECT_MAPPER.writeValueAsString(routingData);
+
+    Map<String, String> badFormatRoutingData = new HashMap<>();
+    badFormatRoutingData.put(TEST_REALM_1, TEST_REALM_2);
+    badFormatRoutingData.put(TEST_REALM_2, TEST_REALM_1);
+    String badFormatRoutingDataString = OBJECT_MAPPER.writeValueAsString(badFormatRoutingData);
+
+    // Request that gets not found response.
+    put("/namespaces/non-existing-namespace"
+            + MetadataStoreRoutingConstants.MSDS_GET_ALL_ROUTING_DATA_ENDPOINT, null,
+        Entity.entity(routingDataString, MediaType.APPLICATION_JSON_TYPE),
+        Response.Status.NOT_FOUND.getStatusCode());
+
+    put(TEST_NAMESPACE_URI_PREFIX
+            + MetadataStoreRoutingConstants.MSDS_GET_ALL_ROUTING_DATA_ENDPOINT, null,
+        Entity.entity("?", MediaType.APPLICATION_JSON_TYPE),
+        Response.Status.BAD_REQUEST.getStatusCode());
+
+    put(TEST_NAMESPACE_URI_PREFIX
+            + MetadataStoreRoutingConstants.MSDS_GET_ALL_ROUTING_DATA_ENDPOINT, null,
+        Entity.entity(badFormatRoutingDataString, MediaType.APPLICATION_JSON_TYPE),
+        Response.Status.BAD_REQUEST.getStatusCode());
+
+    // Successful request.
+    put(TEST_NAMESPACE_URI_PREFIX
+            + MetadataStoreRoutingConstants.MSDS_GET_ALL_ROUTING_DATA_ENDPOINT, null,
+        Entity.entity(routingDataString, MediaType.APPLICATION_JSON_TYPE),
+        Response.Status.CREATED.getStatusCode());
+
+    Assert.assertEquals(getRawRoutingData(), routingData);
+  }
+
   private void verifyRealmShardingKeys(String responseBody) throws IOException {
     // It is safe to cast the object and suppress warnings.
     @SuppressWarnings("unchecked")
diff --git a/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/datamodel/TrieRoutingData.java b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/datamodel/TrieRoutingData.java
index 0f53c23..94bb331 100644
--- a/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/datamodel/TrieRoutingData.java
+++ b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/datamodel/TrieRoutingData.java
@@ -167,8 +167,8 @@ public class TrieRoutingData implements MetadataStoreRoutingData {
   }
 
   /*
-   * Checks for the edge case when the only sharding key in provided routing data is the delimiter
-   * or an empty string. When this is the case, the trie is valid and contains only one node, which
+   * Checks for the edge case when the only sharding key in provided routing data is the delimiter.
+   * When this is the case, the trie is valid and contains only one node, which
    * is the root node, and the root node is a leaf node with a realm address associated with it.
    * @param routingData - a mapping from "sharding keys" to "realm addresses" to be parsed into a
    *          trie


[helix] 45/49: Fix setRoutingData boolean handling; fix leader forwarding url construction (#902)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit a1f58f43eadf0b974b6b75e4536c25ad557cedfc
Author: Neal Sun <ne...@gmail.com>
AuthorDate: Wed Mar 18 10:01:48 2020 -0700

    Fix setRoutingData boolean handling; fix leader forwarding url construction (#902)
    
    This PR makes SetRoutingData respect the return value of the underlying function; it also fixes request forwarding urls, allowing ports and endpoint prefixes to be added.
---
 .../accessor/ZkRoutingDataWriter.java              | 67 ++++++++++++++++------
 .../MetadataStoreDirectoryAccessor.java            |  4 +-
 .../TestZkMetadataStoreDirectory.java              |  5 +-
 .../accessor/TestZkRoutingDataWriter.java          |  8 ++-
 .../MetadataStoreDirectoryAccessorTestBase.java    |  5 +-
 .../rest/server/TestMSDAccessorLeaderElection.java | 12 +++-
 .../constant/MetadataStoreRoutingConstants.java    | 11 +++-
 7 files changed, 84 insertions(+), 28 deletions(-)

diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataWriter.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataWriter.java
index fddd9ee..32b7681 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataWriter.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataWriter.java
@@ -22,6 +22,7 @@ package org.apache.helix.rest.metadatastore.accessor;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
@@ -59,6 +60,10 @@ public class ZkRoutingDataWriter implements MetadataStoreRoutingDataWriter {
   private static final Logger LOG = LoggerFactory.getLogger(ZkRoutingDataWriter.class);
   private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
 
+  private static final String SIMPLE_FIELD_KEY_HOSTNAME = "hostname";
+  private static final String SIMPLE_FIELD_KEY_PORT = "port";
+  private static final String SIMPLE_FIELD_KEY_CONTEXT_URL_PREFIX = "contextUrlPrefix";
+
   private final String _namespace;
   private final HelixZkClient _zkClient;
   private final ZkDistributedLeaderElection _leaderElection;
@@ -89,15 +94,27 @@ public class ZkRoutingDataWriter implements MetadataStoreRoutingDataWriter {
     String hostName = System.getProperty(MetadataStoreRoutingConstants.MSDS_SERVER_HOSTNAME_KEY);
     if (hostName == null || hostName.isEmpty()) {
       throw new IllegalStateException(
-          "Unable to get the hostname of this server instance. System.getProperty fails to fetch "
+          "Hostname is not set or is empty. System.getProperty fails to fetch "
               + MetadataStoreRoutingConstants.MSDS_SERVER_HOSTNAME_KEY + ".");
     }
-    // remove trailing slash
-    if (hostName.charAt(hostName.length() - 1) == '/') {
-      hostName = hostName.substring(0, hostName.length() - 1);
-    }
     _myHostName = HttpConstants.HTTP_PROTOCOL_PREFIX + hostName;
-    ZNRecord myServerInfo = new ZNRecord(_myHostName);
+
+    ZNRecord myServerInfo = new ZNRecord(hostName);
+    myServerInfo.setSimpleField(SIMPLE_FIELD_KEY_HOSTNAME, hostName);
+
+    String port = System.getProperty(MetadataStoreRoutingConstants.MSDS_SERVER_PORT_KEY);
+    if (port != null && !port.isEmpty()) {
+      myServerInfo.setSimpleField(SIMPLE_FIELD_KEY_PORT, port);
+    }
+
+    // One example of context url prefix is "/admin/v2". With the prefix specified, we want to
+    // make sure the final url is "/admin/v2/namespaces/NAMESPACE/some/endpoint"; without it
+    // being specified, we will skip it and go with "/namespaces/NAMESPACE/some/endpoint".
+    String contextUrlPrefix =
+        System.getProperty(MetadataStoreRoutingConstants.MSDS_CONTEXT_URL_PREFIX_KEY);
+    if (contextUrlPrefix != null && !contextUrlPrefix.isEmpty()) {
+      myServerInfo.setSimpleField(SIMPLE_FIELD_KEY_CONTEXT_URL_PREFIX, contextUrlPrefix);
+    }
 
     _leaderElection = new ZkDistributedLeaderElection(_zkClient,
         MetadataStoreRoutingConstants.LEADER_ELECTION_ZNODE, myServerInfo);
@@ -108,6 +125,22 @@ public class ZkRoutingDataWriter implements MetadataStoreRoutingDataWriter {
     _forwardHttpClient = HttpClientBuilder.create().setDefaultRequestConfig(config).build();
   }
 
+  public static String buildEndpointFromLeaderElectionNode(ZNRecord znRecord) {
+    List<String> urlComponents =
+        new ArrayList<>(Collections.singletonList(HttpConstants.HTTP_PROTOCOL_PREFIX));
+    urlComponents.add(znRecord.getSimpleField(SIMPLE_FIELD_KEY_HOSTNAME));
+    String port = znRecord.getSimpleField(SIMPLE_FIELD_KEY_PORT);
+    if (port != null && !port.isEmpty()) {
+      urlComponents.add(":");
+      urlComponents.add(port);
+    }
+    String contextUrlPrefix = znRecord.getSimpleField(SIMPLE_FIELD_KEY_CONTEXT_URL_PREFIX);
+    if (contextUrlPrefix != null && !contextUrlPrefix.isEmpty()) {
+      urlComponents.add(contextUrlPrefix);
+    }
+    return String.join("", urlComponents);
+  }
+
   @Override
   public synchronized boolean addMetadataStoreRealm(String realm) {
     if (_leaderElection.isLeader()) {
@@ -215,9 +248,8 @@ public class ZkRoutingDataWriter implements MetadataStoreRoutingDataWriter {
       return true;
     }
 
-    String leaderHostName = _leaderElection.getCurrentLeaderInfo().getId();
-    String url = leaderHostName + constructUrlSuffix(
-        MetadataStoreRoutingConstants.MSDS_GET_ALL_ROUTING_DATA_ENDPOINT);
+    String url = buildEndpointFromLeaderElectionNode(_leaderElection.getCurrentLeaderInfo())
+        + constructUrlSuffix(MetadataStoreRoutingConstants.MSDS_GET_ALL_ROUTING_DATA_ENDPOINT);
     HttpPut httpPut = new HttpPut(url);
     String routingDataJsonString;
     try {
@@ -231,7 +263,7 @@ public class ZkRoutingDataWriter implements MetadataStoreRoutingDataWriter {
       return false;
     }
     httpPut.setEntity(new StringEntity(routingDataJsonString, ContentType.APPLICATION_JSON));
-    return sendRequestToLeader(httpPut, Response.Status.CREATED.getStatusCode(), leaderHostName);
+    return sendRequestToLeader(httpPut, Response.Status.CREATED.getStatusCode());
   }
 
   @Override
@@ -356,8 +388,8 @@ public class ZkRoutingDataWriter implements MetadataStoreRoutingDataWriter {
   private boolean buildAndSendRequestToLeader(String urlSuffix,
       HttpConstants.RestVerbs requestMethod, int expectedResponseCode)
       throws IllegalArgumentException {
-    String leaderHostName = _leaderElection.getCurrentLeaderInfo().getId();
-    String url = leaderHostName + urlSuffix;
+    String url =
+        buildEndpointFromLeaderElectionNode(_leaderElection.getCurrentLeaderInfo()) + urlSuffix;
     HttpUriRequest request;
     switch (requestMethod) {
       case PUT:
@@ -370,19 +402,18 @@ public class ZkRoutingDataWriter implements MetadataStoreRoutingDataWriter {
         throw new IllegalArgumentException("Unsupported requestMethod: " + requestMethod.name());
     }
 
-    return sendRequestToLeader(request, expectedResponseCode, leaderHostName);
+    return sendRequestToLeader(request, expectedResponseCode);
   }
 
   // Set to be protected for testing purposes
-  protected boolean sendRequestToLeader(HttpUriRequest request, int expectedResponseCode,
-      String leaderHostName) {
+  protected boolean sendRequestToLeader(HttpUriRequest request, int expectedResponseCode) {
     try {
       HttpResponse response = _forwardHttpClient.execute(request);
       if (response.getStatusLine().getStatusCode() != expectedResponseCode) {
         HttpEntity respEntity = response.getEntity();
         String errorLog = "The forwarded request to leader has failed. Uri: " + request.getURI()
             + ". Error code: " + response.getStatusLine().getStatusCode() + " Current hostname: "
-            + _myHostName + " Leader hostname: " + leaderHostName;
+            + _myHostName;
         if (respEntity != null) {
           errorLog += " Response: " + EntityUtils.toString(respEntity);
         }
@@ -391,8 +422,8 @@ public class ZkRoutingDataWriter implements MetadataStoreRoutingDataWriter {
       }
     } catch (IOException e) {
       LOG.error(
-          "The forwarded request to leader raised an exception. Uri: {} Current hostname: {} Leader hostname: {}",
-          request.getURI(), _myHostName, leaderHostName, e);
+          "The forwarded request to leader raised an exception. Uri: {} Current hostname: {} ",
+          request.getURI(), _myHostName, e);
       return false;
     }
     return true;
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/server/resources/metadatastore/MetadataStoreDirectoryAccessor.java b/helix-rest/src/main/java/org/apache/helix/rest/server/resources/metadatastore/MetadataStoreDirectoryAccessor.java
index f20fd9a..fcaf327 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/server/resources/metadatastore/MetadataStoreDirectoryAccessor.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/server/resources/metadatastore/MetadataStoreDirectoryAccessor.java
@@ -235,7 +235,9 @@ public class MetadataStoreDirectoryAccessor extends AbstractResource {
       Map<String, List<String>> routingData =
           OBJECT_MAPPER.readValue(jsonContent, new TypeReference<HashMap<String, List<String>>>() {
           });
-      _metadataStoreDirectory.setNamespaceRoutingData(_namespace, routingData);
+      if (!_metadataStoreDirectory.setNamespaceRoutingData(_namespace, routingData)) {
+        return serverError();
+      }
     } catch (JsonMappingException | JsonParseException | IllegalArgumentException e) {
       return badRequest(e.getMessage());
     } catch (IOException e) {
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java
index df84754..47d6fba 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java
@@ -104,7 +104,9 @@ public class TestZkMetadataStoreDirectory extends AbstractTestClass {
     });
 
     System.setProperty(MetadataStoreRoutingConstants.MSDS_SERVER_HOSTNAME_KEY,
-        getBaseUri().getHost() + ":" + getBaseUri().getPort());
+        getBaseUri().getHost());
+    System.setProperty(MetadataStoreRoutingConstants.MSDS_SERVER_PORT_KEY,
+        Integer.toString(getBaseUri().getPort()));
 
     // Create metadataStoreDirectory
     for (Map.Entry<String, String> entry : _routingZkAddrMap.entrySet()) {
@@ -118,6 +120,7 @@ public class TestZkMetadataStoreDirectory extends AbstractTestClass {
     _metadataStoreDirectory.close();
     clearRoutingData();
     System.clearProperty(MetadataStoreRoutingConstants.MSDS_SERVER_HOSTNAME_KEY);
+    System.clearProperty(MetadataStoreRoutingConstants.MSDS_SERVER_PORT_KEY);
   }
 
   @Test
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataWriter.java b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataWriter.java
index 31db291..217b13e 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataWriter.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataWriter.java
@@ -55,8 +55,7 @@ public class TestZkRoutingDataWriter extends AbstractTestClass {
 
     // This method does not call super() because the http call should not be actually made
     @Override
-    protected boolean sendRequestToLeader(HttpUriRequest request, int expectedResponseCode,
-        String leaderHostName) {
+    protected boolean sendRequestToLeader(HttpUriRequest request, int expectedResponseCode) {
       calledRequest = request;
       return false;
     }
@@ -66,7 +65,9 @@ public class TestZkRoutingDataWriter extends AbstractTestClass {
   public void beforeClass() throws Exception {
     _zkClient = ZK_SERVER_MAP.get(_zkAddrTestNS).getZkClient();
     System.setProperty(MetadataStoreRoutingConstants.MSDS_SERVER_HOSTNAME_KEY,
-        getBaseUri().getHost() + ":" + getBaseUri().getPort());
+        getBaseUri().getHost());
+    System.setProperty(MetadataStoreRoutingConstants.MSDS_SERVER_PORT_KEY,
+        Integer.toString(getBaseUri().getPort()));
     _zkRoutingDataWriter = new ZkRoutingDataWriter(TEST_NAMESPACE, _zkAddrTestNS);
     clearRoutingDataPath();
   }
@@ -74,6 +75,7 @@ public class TestZkRoutingDataWriter extends AbstractTestClass {
   @AfterClass
   public void afterClass() throws Exception {
     System.clearProperty(MetadataStoreRoutingConstants.MSDS_SERVER_HOSTNAME_KEY);
+    System.clearProperty(MetadataStoreRoutingConstants.MSDS_SERVER_PORT_KEY);
     _zkRoutingDataWriter.close();
     clearRoutingDataPath();
   }
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/server/MetadataStoreDirectoryAccessorTestBase.java b/helix-rest/src/test/java/org/apache/helix/rest/server/MetadataStoreDirectoryAccessorTestBase.java
index f2ed433..c8c416f 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/server/MetadataStoreDirectoryAccessorTestBase.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/server/MetadataStoreDirectoryAccessorTestBase.java
@@ -96,12 +96,15 @@ public class MetadataStoreDirectoryAccessorTestBase extends AbstractTestClass {
     _routingDataReader = new ZkRoutingDataReader(TEST_NAMESPACE, _zkAddrTestNS, null);
 
     System.setProperty(MetadataStoreRoutingConstants.MSDS_SERVER_HOSTNAME_KEY,
-        getBaseUri().getHost() + ":" + getBaseUri().getPort());
+        getBaseUri().getHost());
+    System.setProperty(MetadataStoreRoutingConstants.MSDS_SERVER_PORT_KEY,
+        Integer.toString(getBaseUri().getPort()));
   }
 
   @AfterClass
   public void afterClass() throws Exception {
     System.clearProperty(MetadataStoreRoutingConstants.MSDS_SERVER_HOSTNAME_KEY);
+    System.clearProperty(MetadataStoreRoutingConstants.MSDS_SERVER_PORT_KEY);
     _routingDataReader.close();
     clearRoutingData();
   }
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/server/TestMSDAccessorLeaderElection.java b/helix-rest/src/test/java/org/apache/helix/rest/server/TestMSDAccessorLeaderElection.java
index 2a3f0be..b42a101 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/server/TestMSDAccessorLeaderElection.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/server/TestMSDAccessorLeaderElection.java
@@ -36,6 +36,7 @@ import org.apache.helix.rest.common.ContextPropertyKeys;
 import org.apache.helix.rest.common.HelixRestNamespace;
 import org.apache.helix.rest.common.HttpConstants;
 import org.apache.helix.rest.common.ServletType;
+import org.apache.helix.rest.metadatastore.accessor.ZkRoutingDataWriter;
 import org.apache.helix.rest.server.auditlog.AuditLogger;
 import org.apache.helix.rest.server.filters.CORSFilter;
 import org.apache.helix.rest.server.mock.MockMetadataStoreDirectoryAccessor;
@@ -99,7 +100,9 @@ public class TestMSDAccessorLeaderElection extends MetadataStoreDirectoryAccesso
 
     // Set the new uri to be used in leader election
     System.setProperty(MetadataStoreRoutingConstants.MSDS_SERVER_HOSTNAME_KEY,
-        getBaseUri().getHost() + ":" + newPort);
+        getBaseUri().getHost());
+    System
+        .setProperty(MetadataStoreRoutingConstants.MSDS_SERVER_PORT_KEY, Integer.toString(newPort));
 
     // Start http client for testing
     _httpClient = HttpClients.createDefault();
@@ -217,8 +220,11 @@ public class TestMSDAccessorLeaderElection extends MetadataStoreDirectoryAccesso
         MetadataStoreRoutingConstants.LEADER_ELECTION_ZNODE + "/" + leaderSelectionNodes.get(0));
     ZNRecord secondEphemeralNode = _zkClient.readData(
         MetadataStoreRoutingConstants.LEADER_ELECTION_ZNODE + "/" + leaderSelectionNodes.get(1));
-    Assert.assertEquals(firstEphemeralNode.getId(), _leaderBaseUri);
-    Assert.assertEquals(secondEphemeralNode.getId(), _mockBaseUri);
+    Assert.assertEquals(ZkRoutingDataWriter.buildEndpointFromLeaderElectionNode(firstEphemeralNode),
+        _leaderBaseUri);
+    Assert
+        .assertEquals(ZkRoutingDataWriter.buildEndpointFromLeaderElectionNode(secondEphemeralNode),
+            _mockBaseUri);
 
     // Make sure the operation is not done by the follower instance
     Assert.assertFalse(MockMetadataStoreDirectoryAccessor.operatedOnZk);
diff --git a/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/constant/MetadataStoreRoutingConstants.java b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/constant/MetadataStoreRoutingConstants.java
index 766f98a..41d5011 100644
--- a/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/constant/MetadataStoreRoutingConstants.java
+++ b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/constant/MetadataStoreRoutingConstants.java
@@ -78,7 +78,16 @@ public class MetadataStoreRoutingConstants {
   // MSDS resource get all sharding keys endpoint string
   public static final String MSDS_GET_ALL_SHARDING_KEYS_ENDPOINT = "/sharding-keys";
 
-  // The key for system properties that contains the hostname of of the
+  // The key for system properties that contains the hostname of the
   // MetadataStoreDirectoryService server instance
   public static final String MSDS_SERVER_HOSTNAME_KEY = "msds_hostname";
+
+  // The key for system properties that contains the port of the
+  // MetadataStoreDirectoryService server instance
+  public static final String MSDS_SERVER_PORT_KEY = "msds_port";
+
+  // This is added for helix-rest 2.0. For example, without this value, the url will be
+  // "localhost:9998"; with this value, the url will be "localhost:9998/admin/v2" if this
+  // value is "/admin/v2".
+  public static final String MSDS_CONTEXT_URL_PREFIX_KEY = "msds_context_url_prefix";
 }


[helix] 34/49: Add rerunFailingTestsCount config to surefire-plugin (#865)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit 379682720c71dcbeea15d1d806113f9f045d6581
Author: Hunter Lee <hu...@linkedin.com>
AuthorDate: Thu Mar 5 18:55:27 2020 -0800

    Add rerunFailingTestsCount config to surefire-plugin (#865)
    
    It was observed that if build fails (if there is a test failure), then not all of the test goals are executed. There are currently two goals: default-test (single ZK) and multi-zk. If default-test has any test failures, we won't ever see multi-zk get executed, and this is a problem because we don't get to run the test suite in a multi-zk setup.
    
    This config change is a workaround for this - we allow failing tests to be retried up to 3 times to make them pass. If they still fail, we will consider them as flaky tests and have to fix them moving forward.
    
    This PR also address minor comments and fixes a test.
---
 .../helix/integration/TestEnableCompression.java   | 53 +++++++++++++---------
 pom.xml                                            |  6 +++
 .../zookeeper/util/HttpRoutingDataReader.java      |  6 +--
 3 files changed, 40 insertions(+), 25 deletions(-)

diff --git a/helix-core/src/test/java/org/apache/helix/integration/TestEnableCompression.java b/helix-core/src/test/java/org/apache/helix/integration/TestEnableCompression.java
index 39e412c..f1da2e6 100644
--- a/helix-core/src/test/java/org/apache/helix/integration/TestEnableCompression.java
+++ b/helix-core/src/test/java/org/apache/helix/integration/TestEnableCompression.java
@@ -1,25 +1,5 @@
 package org.apache.helix.integration;
 
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-
-import org.apache.helix.PropertyPathBuilder;
-import org.apache.helix.TestHelper;
-import org.apache.helix.common.ZkTestBase;
-import org.apache.helix.integration.manager.ClusterControllerManager;
-import org.apache.helix.integration.manager.MockParticipantManager;
-import org.apache.helix.zookeeper.api.client.HelixZkClient;
-import org.apache.helix.zookeeper.impl.factory.SharedZkClientFactory;
-import org.apache.helix.model.IdealState;
-import org.apache.helix.model.builder.CustomModeISBuilder;
-import org.apache.helix.tools.ClusterStateVerifier;
-import org.apache.helix.tools.ClusterStateVerifier.BestPossAndExtViewZkVerifier;
-import org.apache.helix.util.GZipCompressionUtil;
-import org.apache.helix.zookeeper.zkclient.serialize.BytesPushThroughSerializer;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
@@ -38,6 +18,29 @@ import org.testng.annotations.Test;
  * specific language governing permissions and limitations
  * under the License.
  */
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.helix.PropertyPathBuilder;
+import org.apache.helix.TestHelper;
+import org.apache.helix.common.ZkTestBase;
+import org.apache.helix.integration.manager.ClusterControllerManager;
+import org.apache.helix.integration.manager.MockParticipantManager;
+import org.apache.helix.model.IdealState;
+import org.apache.helix.model.builder.CustomModeISBuilder;
+import org.apache.helix.tools.ClusterVerifiers.BestPossibleExternalViewVerifier;
+import org.apache.helix.util.GZipCompressionUtil;
+import org.apache.helix.zookeeper.api.client.HelixZkClient;
+import org.apache.helix.zookeeper.impl.factory.SharedZkClientFactory;
+import org.apache.helix.zookeeper.zkclient.serialize.BytesPushThroughSerializer;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+
 /**
  * Test controller, spectator and participant roles when compression is enabled.
  * Compression can be enabled for a specific resource by setting enableCompression=true in the
@@ -66,6 +69,8 @@ public class TestEnableCompression extends ZkTestBase {
     List<String> instancesInCluster =
         _gSetupTool.getClusterManagementTool().getInstancesInCluster(clusterName);
     String resourceName = "TestResource";
+    Set<String> expectedResources = new HashSet<>();
+    expectedResources.add(resourceName);
     CustomModeISBuilder customModeISBuilder = new CustomModeISBuilder(resourceName);
 
     int numPartitions = 10000;
@@ -96,15 +101,19 @@ public class TestEnableCompression extends ZkTestBase {
         new ClusterControllerManager(ZK_ADDR, clusterName, "controller_0");
     controller.syncStart();
 
+    Set<String> expectedLiveInstances = new HashSet<>();
     // start participants
     for (int i = 0; i < 5; i++) {
       String instanceName = "localhost_" + (12918 + i);
       participants[i] = new MockParticipantManager(ZK_ADDR, clusterName, instanceName);
       participants[i].syncStart();
+      expectedLiveInstances.add(instanceName);
     }
 
-    boolean result = ClusterStateVerifier
-        .verifyByPolling(new BestPossAndExtViewZkVerifier(ZK_ADDR, clusterName), 120000L);
+    BestPossibleExternalViewVerifier verifier =
+        new BestPossibleExternalViewVerifier.Builder(clusterName).setZkAddr(ZK_ADDR)
+            .setExpectLiveInstances(expectedLiveInstances).setResources(expectedResources).build();
+    boolean result = verifier.verify(120000L);
     Assert.assertTrue(result);
 
     List<String> compressedPaths = new ArrayList<>();
diff --git a/pom.xml b/pom.xml
index 1681850..e8c9bd3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -643,6 +643,10 @@ under the License.
               </goals>
               <id>default-test</id>
               <phase>test</phase>
+              <configuration>
+                <rerunFailingTestsCount>3</rerunFailingTestsCount>
+                <skipAfterFailureCount>10</skipAfterFailureCount>
+              </configuration>
             </execution>
             <execution>
               <goals>
@@ -655,6 +659,8 @@ under the License.
                   <multiZk>true</multiZk>
                   <numZk>3</numZk>
                 </systemPropertyVariables>
+                <rerunFailingTestsCount>3</rerunFailingTestsCount>
+                <skipAfterFailureCount>10</skipAfterFailureCount>
               </configuration>
             </execution>
           </executions>
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/util/HttpRoutingDataReader.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/util/HttpRoutingDataReader.java
index b4c1f9c..f2f907a 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/util/HttpRoutingDataReader.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/util/HttpRoutingDataReader.java
@@ -21,9 +21,9 @@ package org.apache.helix.zookeeper.util;
 
 import java.io.IOException;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
@@ -49,10 +49,10 @@ public class HttpRoutingDataReader {
   /** Double-checked locking requires that the following fields be volatile */
   // The following map stands for (MSDS endpoint, Raw Routing Data)
   private static volatile Map<String, Map<String, List<String>>> _rawRoutingDataMap =
-      new HashMap<>();
+      new ConcurrentHashMap<>();
   // The following map stands for (MSDS endpoint, MetadataStoreRoutingData)
   private static volatile Map<String, MetadataStoreRoutingData> _metadataStoreRoutingDataMap =
-      new HashMap<>();
+      new ConcurrentHashMap<>();
 
   /**
    * This class is a Singleton.


[helix] 11/49: Rebase ZooScalability from upstream master

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit fb579ba1131751a9393ca277139874d7ed563260
Author: Hunter Lee <hu...@linkedin.com>
AuthorDate: Tue Feb 11 18:48:46 2020 -0800

    Rebase ZooScalability from upstream master
---
 .../metadatastore/accessor/ZkRoutingDataReader.java    | 18 +++++++++---------
 .../metadatastore/accessor/ZkRoutingDataWriter.java    | 15 +++++++--------
 .../concurrency/ZkDistributedLeaderElection.java       | 12 ++++++------
 .../metadatastore/TestZkMetadataStoreDirectory.java    |  4 ++--
 .../accessor/TestZkRoutingDataReader.java              |  2 +-
 .../accessor/TestZkRoutingDataWriter.java              |  2 +-
 6 files changed, 26 insertions(+), 27 deletions(-)

diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataReader.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataReader.java
index ea8c290..9decf23 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataReader.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataReader.java
@@ -23,17 +23,17 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-import org.I0Itec.zkclient.IZkChildListener;
-import org.I0Itec.zkclient.IZkDataListener;
-import org.I0Itec.zkclient.exception.ZkNoNodeException;
-import org.apache.helix.ZNRecord;
-import org.apache.helix.manager.zk.ZNRecordSerializer;
-import org.apache.helix.manager.zk.client.DedicatedZkClientFactory;
-import org.apache.helix.manager.zk.client.HelixZkClient;
-import org.apache.helix.manager.zk.zookeeper.IZkStateListener;
 import org.apache.helix.rest.metadatastore.RoutingDataListener;
 import org.apache.helix.rest.metadatastore.constant.MetadataStoreRoutingConstants;
 import org.apache.helix.rest.metadatastore.exceptions.InvalidRoutingDataException;
+import org.apache.helix.zookeeper.api.client.HelixZkClient;
+import org.apache.helix.zookeeper.datamodel.ZNRecord;
+import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
+import org.apache.helix.zookeeper.impl.factory.DedicatedZkClientFactory;
+import org.apache.helix.zookeeper.zkclient.IZkChildListener;
+import org.apache.helix.zookeeper.zkclient.IZkDataListener;
+import org.apache.helix.zookeeper.zkclient.IZkStateListener;
+import org.apache.helix.zookeeper.zkclient.exception.ZkNoNodeException;
 import org.apache.zookeeper.Watcher;
 
 
@@ -71,7 +71,7 @@ public class ZkRoutingDataReader implements MetadataStoreRoutingDataReader, IZkD
 
   /**
    * Returns (realm, list of ZK path sharding keys) mappings.
-   * @return
+   * @return Map <realm, list of ZK path sharding keys>
    * @throws InvalidRoutingDataException
    */
   public Map<String, List<String>> getRoutingData()
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataWriter.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataWriter.java
index 3e43202..c8da80e 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataWriter.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataWriter.java
@@ -23,20 +23,19 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
-import org.I0Itec.zkclient.exception.ZkNodeExistsException;
-import org.apache.helix.ZNRecord;
-import org.apache.helix.manager.zk.ZNRecordSerializer;
-import org.apache.helix.manager.zk.ZkBaseDataAccessor;
-import org.apache.helix.manager.zk.client.DedicatedZkClientFactory;
-import org.apache.helix.manager.zk.client.HelixZkClient;
 import org.apache.helix.rest.metadatastore.concurrency.ZkDistributedLeaderElection;
 import org.apache.helix.rest.metadatastore.constant.MetadataStoreRoutingConstants;
+import org.apache.helix.zookeeper.api.client.HelixZkClient;
+import org.apache.helix.zookeeper.datamodel.ZNRecord;
+import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
+import org.apache.helix.zookeeper.impl.factory.DedicatedZkClientFactory;
+import org.apache.helix.zookeeper.zkclient.exception.ZkNodeExistsException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 
 public class ZkRoutingDataWriter implements MetadataStoreRoutingDataWriter {
-  private static final Logger LOG = LoggerFactory.getLogger(ZkBaseDataAccessor.class);
+  private static final Logger LOG = LoggerFactory.getLogger(ZkRoutingDataWriter.class);
 
   private final String _namespace;
   private final HelixZkClient _zkClient;
@@ -244,7 +243,7 @@ public class ZkRoutingDataWriter implements MetadataStoreRoutingDataWriter {
       _zkClient.writeData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + realm,
           new ZNRecord(realm));
     } catch (Exception e) {
-      LOG.error("Failed to create ZkRealm: {}, Namespace: ", realm, _namespace);
+      LOG.error("Failed to create ZkRealm: {}, Namespace: {}", realm, _namespace, e);
       return false;
     }
 
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/concurrency/ZkDistributedLeaderElection.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/concurrency/ZkDistributedLeaderElection.java
index c9b6bb2..330611f 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/concurrency/ZkDistributedLeaderElection.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/concurrency/ZkDistributedLeaderElection.java
@@ -22,12 +22,12 @@ package org.apache.helix.rest.metadatastore.concurrency;
 import java.util.Collections;
 import java.util.List;
 
-import org.I0Itec.zkclient.IZkDataListener;
-import org.I0Itec.zkclient.exception.ZkNodeExistsException;
-import org.apache.helix.ZNRecord;
-import org.apache.helix.manager.zk.ZNRecordSerializer;
-import org.apache.helix.manager.zk.client.HelixZkClient;
-import org.apache.helix.manager.zk.zookeeper.IZkStateListener;
+import org.apache.helix.zookeeper.api.client.HelixZkClient;
+import org.apache.helix.zookeeper.datamodel.ZNRecord;
+import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
+import org.apache.helix.zookeeper.zkclient.IZkDataListener;
+import org.apache.helix.zookeeper.zkclient.IZkStateListener;
+import org.apache.helix.zookeeper.zkclient.exception.ZkNodeExistsException;
 import org.apache.zookeeper.CreateMode;
 import org.apache.zookeeper.Watcher;
 import org.slf4j.Logger;
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java
index 7b0a4f0..c0741ee 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java
@@ -29,11 +29,11 @@ import java.util.NoSuchElementException;
 import java.util.Set;
 
 import org.apache.helix.TestHelper;
-import org.apache.helix.ZNRecord;
-import org.apache.helix.manager.zk.ZNRecordSerializer;
 import org.apache.helix.rest.metadatastore.constant.MetadataStoreRoutingConstants;
 import org.apache.helix.rest.metadatastore.exceptions.InvalidRoutingDataException;
 import org.apache.helix.rest.server.AbstractTestClass;
+import org.apache.helix.zookeeper.datamodel.ZNRecord;
+import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
 import org.testng.Assert;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataReader.java b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataReader.java
index 77eb5eb..aa46429 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataReader.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataReader.java
@@ -25,10 +25,10 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.helix.AccessOption;
-import org.apache.helix.ZNRecord;
 import org.apache.helix.rest.metadatastore.constant.MetadataStoreRoutingConstants;
 import org.apache.helix.rest.metadatastore.exceptions.InvalidRoutingDataException;
 import org.apache.helix.rest.server.AbstractTestClass;
+import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.testng.Assert;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.AfterMethod;
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataWriter.java b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataWriter.java
index 441bf65..29b7e36 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataWriter.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataWriter.java
@@ -25,9 +25,9 @@ import java.util.Map;
 
 import com.google.common.collect.ImmutableMap;
 import org.apache.helix.AccessOption;
-import org.apache.helix.ZNRecord;
 import org.apache.helix.rest.metadatastore.constant.MetadataStoreRoutingConstants;
 import org.apache.helix.rest.server.AbstractTestClass;
+import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.junit.Assert;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;


[helix] 06/49: Add MetadataStoreRoutingDataReader interface and ZkRoutingDataReader class to helix-rest (#714)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit dda53d10f89b4b44b301a10773559c030dec204a
Author: Neal Sun <ne...@gmail.com>
AuthorDate: Tue Feb 4 17:53:16 2020 -0800

    Add MetadataStoreRoutingDataReader interface and ZkRoutingDataReader class to helix-rest (#714)
    
    This PR adds an interface and an implementation for metadata store routing data accessing. The interface works directly with MetadataStoreRoutingData, acting as an abstract layer that allows the data to be accessed from different sources. The implementation focuses on accessing data from ZooKeeper.
---
 .../MetadataStoreRoutingDataReader.java            |  46 ++++++++
 .../rest/metadatastore/ZkRoutingDataReader.java    |  75 ++++++++++++
 .../exceptions/InvalidRoutingDataException.java    |  30 +++++
 .../metadatastore/TestZkRoutingDataReader.java     | 131 +++++++++++++++++++++
 4 files changed, 282 insertions(+)

diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/MetadataStoreRoutingDataReader.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/MetadataStoreRoutingDataReader.java
new file mode 100644
index 0000000..3cc9a06
--- /dev/null
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/MetadataStoreRoutingDataReader.java
@@ -0,0 +1,46 @@
+package org.apache.helix.rest.metadatastore;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.List;
+import java.util.Map;
+import org.apache.helix.rest.metadatastore.exceptions.InvalidRoutingDataException;
+
+/**
+ * An interface for a DAO that fetches routing data from a source and return a key-value mapping
+ * that represent the said routing data.
+ */
+public interface MetadataStoreRoutingDataReader {
+
+  /**
+   * Fetches routing data from the data source.
+   * @return a mapping from "metadata store realm addresses" to lists of "metadata store sharding
+   *         keys", where the sharding keys in a value list all route to the realm address in the
+   *         key
+   * @throws InvalidRoutingDataException - when the routing data is malformed in any way that
+   *           disallows a meaningful mapping to be returned
+   */
+  Map<String, List<String>> getRoutingData() throws InvalidRoutingDataException;
+
+  /**
+   * Closes any stateful resources such as connections or threads.
+   */
+  void close();
+}
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkRoutingDataReader.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkRoutingDataReader.java
new file mode 100644
index 0000000..a4c7e1c
--- /dev/null
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkRoutingDataReader.java
@@ -0,0 +1,75 @@
+package org.apache.helix.rest.metadatastore;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.I0Itec.zkclient.exception.ZkNoNodeException;
+import org.apache.helix.ZNRecord;
+import org.apache.helix.manager.zk.ZNRecordSerializer;
+import org.apache.helix.manager.zk.client.DedicatedZkClientFactory;
+import org.apache.helix.manager.zk.client.HelixZkClient;
+import org.apache.helix.rest.metadatastore.exceptions.InvalidRoutingDataException;
+
+public class ZkRoutingDataReader implements MetadataStoreRoutingDataReader {
+  static final String ROUTING_DATA_PATH = "/METADATA_STORE_ROUTING_DATA";
+  static final String ZNRECORD_LIST_FIELD_KEY = "ZK_PATH_SHARDING_KEYS";
+
+  private final String _zkAddress;
+  private final HelixZkClient _zkClient;
+
+  public ZkRoutingDataReader(String zkAddress) {
+    _zkAddress = zkAddress;
+    _zkClient = DedicatedZkClientFactory.getInstance().buildZkClient(
+        new HelixZkClient.ZkConnectionConfig(zkAddress),
+        new HelixZkClient.ZkClientConfig().setZkSerializer(new ZNRecordSerializer()));
+  }
+
+  public Map<String, List<String>> getRoutingData() throws InvalidRoutingDataException {
+    Map<String, List<String>> routingData = new HashMap<>();
+    List<String> children;
+    try {
+      children = _zkClient.getChildren(ROUTING_DATA_PATH);
+    } catch (ZkNoNodeException e) {
+      throw new InvalidRoutingDataException("Routing data directory ZNode " + ROUTING_DATA_PATH
+          + " does not exist. Routing ZooKeeper address: " + _zkAddress);
+    }
+    if (children == null || children.isEmpty()) {
+      throw new InvalidRoutingDataException(
+          "There are no metadata store realms defined. Routing ZooKeeper address: " + _zkAddress);
+    }
+    for (String child : children) {
+      ZNRecord record = _zkClient.readData(ROUTING_DATA_PATH + "/" + child);
+      List<String> shardingKeys = record.getListField(ZNRECORD_LIST_FIELD_KEY);
+      if (shardingKeys == null || shardingKeys.isEmpty()) {
+        throw new InvalidRoutingDataException("Realm address ZNode " + ROUTING_DATA_PATH + "/"
+            + child + " does not have a value for key " + ZNRECORD_LIST_FIELD_KEY
+            + ". Routing ZooKeeper address: " + _zkAddress);
+      }
+      routingData.put(child, shardingKeys);
+    }
+    return routingData;
+  }
+
+  public void close() {
+    _zkClient.close();
+  }
+}
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/exceptions/InvalidRoutingDataException.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/exceptions/InvalidRoutingDataException.java
new file mode 100644
index 0000000..267aadc
--- /dev/null
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/exceptions/InvalidRoutingDataException.java
@@ -0,0 +1,30 @@
+package org.apache.helix.rest.metadatastore.exceptions;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * This exception is thrown by MetadataStoreRoutingDataAccessor when the routing data it's trying to
+ * access is malformed and is there invalid.
+ */
+public class InvalidRoutingDataException extends Exception {
+  public InvalidRoutingDataException(String info) {
+    super(info);
+  }
+}
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkRoutingDataReader.java b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkRoutingDataReader.java
new file mode 100644
index 0000000..d06c38d
--- /dev/null
+++ b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkRoutingDataReader.java
@@ -0,0 +1,131 @@
+package org.apache.helix.rest.metadatastore;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import org.apache.helix.AccessOption;
+import org.apache.helix.ZNRecord;
+import org.apache.helix.rest.metadatastore.exceptions.InvalidRoutingDataException;
+import org.apache.helix.rest.server.AbstractTestClass;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class TestZkRoutingDataReader extends AbstractTestClass {
+  private MetadataStoreRoutingDataReader _zkRoutingDataReader;
+
+  @BeforeClass
+  public void beforeClass() {
+    _zkRoutingDataReader = new ZkRoutingDataReader(ZK_ADDR);
+  }
+
+  @AfterClass
+  public void afterClass() {
+    _zkRoutingDataReader.close();
+  }
+
+  @AfterMethod
+  public void afterMethod() {
+    _baseAccessor.remove(ZkRoutingDataReader.ROUTING_DATA_PATH, AccessOption.PERSISTENT);
+  }
+
+  @Test
+  public void testGetRoutingData() {
+    // Create a node that represents a realm address and add 3 sharding keys to it
+    ZNRecord testZnRecord1 = new ZNRecord("testZnRecord1");
+    List<String> testShardingKeys1 =
+        Arrays.asList("/sharding/key/1/a", "/sharding/key/1/b", "/sharding/key/1/c");
+    testZnRecord1.setListField(ZkRoutingDataReader.ZNRECORD_LIST_FIELD_KEY, testShardingKeys1);
+
+    // Create another node that represents a realm address and add 3 sharding keys to it
+    ZNRecord testZnRecord2 = new ZNRecord("testZnRecord2");
+    List<String> testShardingKeys2 = Arrays.asList("/sharding/key/2/a", "/sharding/key/2/b",
+        "/sharding/key/2/c", "/sharding/key/2/d");
+    testZnRecord2.setListField(ZkRoutingDataReader.ZNRECORD_LIST_FIELD_KEY, testShardingKeys2);
+
+    // Add both nodes as children nodes to ZkRoutingDataReader.ROUTING_DATA_PATH
+    _baseAccessor.create(ZkRoutingDataReader.ROUTING_DATA_PATH + "/testRealmAddress1",
+        testZnRecord1, AccessOption.PERSISTENT);
+    _baseAccessor.create(ZkRoutingDataReader.ROUTING_DATA_PATH + "/testRealmAddress2",
+        testZnRecord2, AccessOption.PERSISTENT);
+
+    MetadataStoreRoutingDataReader zkRoutingDataReader = new ZkRoutingDataReader(ZK_ADDR);
+    try {
+      Map<String, List<String>> routingData = zkRoutingDataReader.getRoutingData();
+      Assert.assertEquals(routingData.size(), 2);
+      Assert.assertEquals(routingData.get("testRealmAddress1"), testShardingKeys1);
+      Assert.assertEquals(routingData.get("testRealmAddress2"), testShardingKeys2);
+    } catch (InvalidRoutingDataException e) {
+      Assert.fail("Not expecting InvalidRoutingDataException");
+    }
+  }
+
+  @Test
+  public void testGetRoutingDataMissingMSRD() {
+    MetadataStoreRoutingDataReader zkRoutingDataReader = new ZkRoutingDataReader(ZK_ADDR);
+    try {
+      zkRoutingDataReader.getRoutingData();
+      Assert.fail("Expecting InvalidRoutingDataException");
+    } catch (InvalidRoutingDataException e) {
+      Assert.assertTrue(e.getMessage()
+          .contains("Routing data directory ZNode " + ZkRoutingDataReader.ROUTING_DATA_PATH
+              + " does not exist. Routing ZooKeeper address: " + ZK_ADDR));
+    }
+  }
+
+  @Test
+  public void testGetRoutingDataMissingMSRDChildren() {
+    _baseAccessor.create(ZkRoutingDataReader.ROUTING_DATA_PATH, new ZNRecord("test"),
+        AccessOption.PERSISTENT);
+    MetadataStoreRoutingDataReader zkRoutingDataReader = new ZkRoutingDataReader(ZK_ADDR);
+    try {
+      zkRoutingDataReader.getRoutingData();
+      Assert.fail("Expecting InvalidRoutingDataException");
+    } catch (InvalidRoutingDataException e) {
+      Assert.assertTrue(e.getMessage().contains(
+          "There are no metadata store realms defined. Routing ZooKeeper address: " + ZK_ADDR));
+    }
+  }
+
+  @Test
+  public void testGetRoutingDataMSRDChildEmptyValue() {
+    ZNRecord testZnRecord1 = new ZNRecord("testZnRecord1");
+    testZnRecord1.setListField(ZkRoutingDataReader.ZNRECORD_LIST_FIELD_KEY,
+        Collections.emptyList());
+    _baseAccessor.create(ZkRoutingDataReader.ROUTING_DATA_PATH + "/testRealmAddress1",
+        testZnRecord1, AccessOption.PERSISTENT);
+    MetadataStoreRoutingDataReader zkRoutingDataReader = new ZkRoutingDataReader(ZK_ADDR);
+    try {
+      zkRoutingDataReader.getRoutingData();
+      Assert.fail("Expecting InvalidRoutingDataException");
+    } catch (InvalidRoutingDataException e) {
+      Assert.assertTrue(e.getMessage()
+          .contains("Realm address ZNode " + ZkRoutingDataReader.ROUTING_DATA_PATH
+              + "/testRealmAddress1 does not have a value for key "
+              + ZkRoutingDataReader.ZNRECORD_LIST_FIELD_KEY + ". Routing ZooKeeper address: "
+              + ZK_ADDR));
+    }
+  }
+}


[helix] 04/49: Upgrade ZkTestBase with multi-ZK support in helix-core (#712)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit eda7677a3d608e5b9a2c0d89bb1454e3341ee53d
Author: Hunter Lee <hu...@linkedin.com>
AuthorDate: Tue Feb 4 12:10:51 2020 -0800

    Upgrade ZkTestBase with multi-ZK support in helix-core (#712)
    
    Prior to instrumenting Helix APIs and components so that they would be aware of multiple ZKs for horizontal scalability, we need to have a way to run all integration tests involving ZooKeeper in different environments: one with a single ZK and another with multiple ZKs.
    
    Changelist:
    1. Implement the logic in ZkTestBase so that in conjunction with maven-surefire-plugin configs, there will be two executions of the test suite
    2. Remove system property variable from default-test since it's unnecessary
---
 .../java/org/apache/helix/common/ZkTestBase.java   | 74 +++++++++++++++++++---
 pom.xml                                            |  6 +-
 2 files changed, 66 insertions(+), 14 deletions(-)

diff --git a/helix-core/src/test/java/org/apache/helix/common/ZkTestBase.java b/helix-core/src/test/java/org/apache/helix/common/ZkTestBase.java
index b9c08a9..f1fdf53 100644
--- a/helix-core/src/test/java/org/apache/helix/common/ZkTestBase.java
+++ b/helix-core/src/test/java/org/apache/helix/common/ZkTestBase.java
@@ -98,6 +98,8 @@ import org.testng.annotations.BeforeSuite;
 
 public class ZkTestBase {
   private static Logger LOG = LoggerFactory.getLogger(ZkTestBase.class);
+  private static final String MULTI_ZK_PROPERTY_KEY = "multiZk";
+  private static final String NUM_ZK_PROPERTY_KEY = "numZk";
 
   protected static ZkServer _zkServer;
   protected static HelixZkClient _gZkClient;
@@ -107,13 +109,24 @@ public class ZkTestBase {
 
   private Map<String, Map<String, HelixZkClient>> _liveInstanceOwners = new HashMap<>();
 
-  public static final String ZK_ADDR = "localhost:2183";
+  private static final String ZK_PREFIX = "localhost:";
+  private static final int ZK_START_PORT = 2183;
+  public static final String ZK_ADDR = ZK_PREFIX + ZK_START_PORT;
   protected static final String CLUSTER_PREFIX = "CLUSTER";
   protected static final String CONTROLLER_CLUSTER_PREFIX = "CONTROLLER_CLUSTER";
   protected final String CONTROLLER_PREFIX = "controller";
   protected final String PARTICIPANT_PREFIX = "localhost";
   private static final long MANUAL_GC_PAUSE = 4000L;
 
+  /*
+   * Multiple ZK references
+   */
+  // The following maps hold ZK connect string as keys
+  protected Map<String, ZkServer> _zkServerMap = new HashMap<>();
+  protected Map<String, HelixZkClient> _helixZkClientMap = new HashMap<>();
+  protected Map<String, ClusterSetup> _clusterSetupMap = new HashMap<>();
+  protected Map<String, BaseDataAccessor> _baseDataAccessorMap = new HashMap<>();
+
   @BeforeSuite
   public void beforeSuite() throws Exception {
     // TODO: use logging.properties file to config java.util.logging.Logger levels
@@ -124,8 +137,32 @@ public class ZkTestBase {
     System.setProperty("zookeeper.4lw.commands.whitelist", "*");
     System.setProperty(SystemPropertyKeys.CONTROLLER_MESSAGE_PURGE_DELAY, "3000");
 
-    _zkServer = TestHelper.startZkServer(ZK_ADDR);
-    AssertJUnit.assertNotNull(_zkServer);
+    // Start in-memory ZooKeepers
+    // If multi-ZooKeeper is enabled, start more ZKs. Otherwise, just set up one ZK
+    int numZkToStart = 1;
+    String multiZkConfig = System.getProperty(MULTI_ZK_PROPERTY_KEY);
+    if (multiZkConfig != null && multiZkConfig.equalsIgnoreCase(Boolean.TRUE.toString())) {
+      String numZkFromConfig = System.getProperty(NUM_ZK_PROPERTY_KEY);
+      if (numZkFromConfig != null) {
+        try {
+          numZkToStart = Math.max(Integer.parseInt(numZkFromConfig), numZkToStart);
+        } catch (Exception e) {
+          Assert.fail("Failed to parse the number of ZKs from config!");
+        }
+      }
+      Assert.fail("multiZk config is set but numZk config is missing!");
+    }
+
+    // Start "numZkFromConfigInt" ZooKeepers
+    for (int i = 0; i < numZkToStart; i++) {
+      startZooKeeper(i);
+    }
+
+    // Set the references for backward-compatibility with a single ZK environment
+    _zkServer = _zkServerMap.get(ZK_ADDR);
+    _gZkClient = _helixZkClientMap.get(ZK_ADDR);
+    _gSetupTool = _clusterSetupMap.get(ZK_ADDR);
+    _baseAccessor = _baseDataAccessorMap.get(ZK_ADDR);
 
     // Clean up all JMX objects
     for (ObjectName mbean : _server.queryNames(null, null)) {
@@ -135,13 +172,29 @@ public class ZkTestBase {
         // OK
       }
     }
+  }
 
+  /**
+   * Starts an additional in-memory ZooKeeper for testing.
+   * @param i index to be added to the ZK port to avoid conflicts
+   * @throws Exception
+   */
+  private void startZooKeeper(int i)
+      throws Exception {
+    String zkAddress = ZK_PREFIX + (ZK_START_PORT + i);
+    ZkServer zkServer = TestHelper.startZkServer(zkAddress);
+    AssertJUnit.assertNotNull(zkServer);
     HelixZkClient.ZkClientConfig clientConfig = new HelixZkClient.ZkClientConfig();
     clientConfig.setZkSerializer(new ZNRecordSerializer());
-    _gZkClient = DedicatedZkClientFactory.getInstance()
-        .buildZkClient(new HelixZkClient.ZkConnectionConfig(ZK_ADDR), clientConfig);
-    _gSetupTool = new ClusterSetup(_gZkClient);
-    _baseAccessor = new ZkBaseDataAccessor<>(_gZkClient);
+    HelixZkClient zkClient = DedicatedZkClientFactory.getInstance()
+        .buildZkClient(new HelixZkClient.ZkConnectionConfig(zkAddress), clientConfig);
+    ClusterSetup gSetupTool = new ClusterSetup(zkClient);
+    BaseDataAccessor baseDataAccessor = new ZkBaseDataAccessor<>(zkClient);
+
+    _zkServerMap.put(zkAddress, zkServer);
+    _helixZkClientMap.put(zkAddress, zkClient);
+    _clusterSetupMap.put(zkAddress, gSetupTool);
+    _baseDataAccessorMap.put(zkAddress, baseDataAccessor);
   }
 
   @AfterSuite
@@ -155,8 +208,11 @@ public class ZkTestBase {
       }
     }
 
-    _gZkClient.close();
-    TestHelper.stopZkServer(_zkServer);
+    // Close all ZK resources
+    _baseDataAccessorMap.values().forEach(BaseDataAccessor::close);
+    _clusterSetupMap.values().forEach(ClusterSetup::close);
+    _helixZkClientMap.values().forEach(HelixZkClient::close);
+    _zkServerMap.values().forEach(TestHelper::stopZkServer);
   }
 
   @BeforeClass
diff --git a/pom.xml b/pom.xml
index e773d07..6a51c9c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -642,11 +642,6 @@ under the License.
               </goals>
               <id>default-test</id>
               <phase>test</phase>
-              <configuration>
-                <systemPropertyVariables>
-                  <multiZk>false</multiZk>
-                </systemPropertyVariables>
-              </configuration>
             </execution>
             <execution>
               <goals>
@@ -657,6 +652,7 @@ under the License.
               <configuration>
                 <systemPropertyVariables>
                   <multiZk>true</multiZk>
+                  <numZk>3</numZk>
                 </systemPropertyVariables>
               </configuration>
             </execution>


[helix] 08/49: Add TrieRoutingData constructor (#731)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit 9bf22694dfca070bc25d2b077b3f6a83c63ddf04
Author: Neal Sun <ne...@gmail.com>
AuthorDate: Mon Feb 10 16:41:11 2020 -0800

    Add TrieRoutingData constructor (#731)
    
    This PR adds a constructor to TrieRoutingData. The constructor takes in a mapping of sharding keys to realm addresses and parses it into a trie. Also, TrieRoutingData now treats sharding keys without leading slashes as invalid. Related changes are made due to the addition of the constructor: TrieNode is made private; missing logic is added for refreshRoutingData in ZkMetadataStoreDirectory.
---
 .../metadatastore/MetadataStoreRoutingData.java    |   7 +-
 .../helix/rest/metadatastore/TrieRoutingData.java  | 201 ++++++++++++---
 .../metadatastore/ZkMetadataStoreDirectory.java    |  36 ++-
 .../rest/metadatastore/TestTrieRoutingData.java    | 282 ++++++++++++++-------
 4 files changed, 380 insertions(+), 146 deletions(-)

diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/MetadataStoreRoutingData.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/MetadataStoreRoutingData.java
index 237e9b6..8d8b7e3 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/MetadataStoreRoutingData.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/MetadataStoreRoutingData.java
@@ -22,7 +22,6 @@ package org.apache.helix.rest.metadatastore;
 import java.util.Map;
 import java.util.NoSuchElementException;
 
-
 public interface MetadataStoreRoutingData {
   /**
    * Given a path, return all the "metadata store sharding key-metadata store realm address" pairs
@@ -33,15 +32,17 @@ public interface MetadataStoreRoutingData {
    * @param path - the path where the search is conducted
    * @return all "sharding key-realm address" pairs where the sharding keys contain the given
    *         path if the path is valid; empty mapping otherwise
+   * @throws IllegalArgumentException - when the path is invalid
    */
-  Map<String, String> getAllMappingUnderPath(String path);
+  Map<String, String> getAllMappingUnderPath(String path) throws IllegalArgumentException;
 
   /**
    * Given a path, return the realm address corresponding to the sharding key contained in the
    * path. If the path doesn't contain a sharding key, throw NoSuchElementException.
    * @param path - the path where the search is conducted
    * @return the realm address corresponding to the sharding key contained in the path
+   * @throws IllegalArgumentException - when the path is invalid
    * @throws NoSuchElementException - when the path doesn't contain a sharding key
    */
-  String getMetadataStoreRealm(String path) throws NoSuchElementException;
+  String getMetadataStoreRealm(String path) throws IllegalArgumentException, NoSuchElementException;
 }
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/TrieRoutingData.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/TrieRoutingData.java
index b89a5f9..28add4c 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/TrieRoutingData.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/TrieRoutingData.java
@@ -23,8 +23,10 @@ import java.util.ArrayDeque;
 import java.util.Collections;
 import java.util.Deque;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
+import org.apache.helix.rest.metadatastore.exceptions.InvalidRoutingDataException;
 
 /**
  * This is a class that uses a data structure similar to trie to represent metadata store routing
@@ -37,15 +39,26 @@ public class TrieRoutingData implements MetadataStoreRoutingData {
 
   private final TrieNode _rootNode;
 
-  // TODO: THIS IS A TEMPORARY PLACEHOLDER. A proper constructor will be created, which will not
-  // take in a TrieNode; it instead initializes the rootNode and creates a trie based on
-  // some input data. The constructor is blocked by the implementation of RoutingDataAccessor, and
-  // will therefore be implemented later.
-  public TrieRoutingData(TrieNode rootNode) {
-    _rootNode = rootNode;
+  public TrieRoutingData(Map<String, List<String>> routingData) throws InvalidRoutingDataException {
+    if (routingData == null || routingData.isEmpty()) {
+      throw new InvalidRoutingDataException("routingData cannot be null or empty");
+    }
+
+    if (isRootShardingKey(routingData)) {
+      Map.Entry<String, List<String>> entry = routingData.entrySet().iterator().next();
+      _rootNode = new TrieNode(Collections.emptyMap(), "/", true, entry.getKey());
+    } else {
+      _rootNode = new TrieNode(new HashMap<>(), "/", false, "");
+      constructTrie(routingData);
+    }
   }
 
-  public Map<String, String> getAllMappingUnderPath(String path) {
+  public Map<String, String> getAllMappingUnderPath(String path) throws IllegalArgumentException {
+    if (path.isEmpty() || !path.substring(0, 1).equals(DELIMITER)) {
+      throw new IllegalArgumentException("Provided path is empty or does not have a leading \""
+          + DELIMITER + "\" character: " + path);
+    }
+
     TrieNode curNode;
     try {
       curNode = findTrieNode(path, false);
@@ -58,10 +71,10 @@ public class TrieRoutingData implements MetadataStoreRoutingData {
     nodeStack.push(curNode);
     while (!nodeStack.isEmpty()) {
       curNode = nodeStack.pop();
-      if (curNode._isLeaf) {
-        resultMap.put(curNode._name, curNode._realmAddress);
+      if (curNode.isShardingKey()) {
+        resultMap.put(curNode.getPath(), curNode.getRealmAddress());
       } else {
-        for (TrieNode child : curNode._children.values()) {
+        for (TrieNode child : curNode.getChildren().values()) {
           nodeStack.push(child);
         }
       }
@@ -69,9 +82,15 @@ public class TrieRoutingData implements MetadataStoreRoutingData {
     return resultMap;
   }
 
-  public String getMetadataStoreRealm(String path) throws NoSuchElementException {
+  public String getMetadataStoreRealm(String path)
+      throws IllegalArgumentException, NoSuchElementException {
+    if (path.isEmpty() || !path.substring(0, 1).equals(DELIMITER)) {
+      throw new IllegalArgumentException("Provided path is empty or does not have a leading \""
+          + DELIMITER + "\" character: " + path);
+    }
+
     TrieNode leafNode = findTrieNode(path, true);
-    return leafNode._realmAddress;
+    return leafNode.getRealmAddress();
   }
 
   /**
@@ -88,35 +107,28 @@ public class TrieRoutingData implements MetadataStoreRoutingData {
    */
   private TrieNode findTrieNode(String path, boolean findLeafAlongPath)
       throws NoSuchElementException {
-    if (path.equals(DELIMITER) || path.equals("")) {
-      if (findLeafAlongPath && !_rootNode._isLeaf) {
+    if (path.equals(DELIMITER)) {
+      if (findLeafAlongPath && !_rootNode.isShardingKey()) {
         throw new NoSuchElementException("No leaf node found along the path. Path: " + path);
       }
       return _rootNode;
     }
 
-    String[] splitPath;
-    if (path.substring(0, 1).equals(DELIMITER)) {
-      splitPath = path.substring(1).split(DELIMITER, 0);
-    } else {
-      splitPath = path.split(DELIMITER, 0);
-    }
-
     TrieNode curNode = _rootNode;
-    if (findLeafAlongPath && curNode._isLeaf) {
+    if (findLeafAlongPath && curNode.isShardingKey()) {
       return curNode;
     }
-    Map<String, TrieNode> curChildren = curNode._children;
-    for (String pathSection : splitPath) {
+    Map<String, TrieNode> curChildren = curNode.getChildren();
+    for (String pathSection : path.substring(1).split(DELIMITER, 0)) {
       curNode = curChildren.get(pathSection);
       if (curNode == null) {
         throw new NoSuchElementException(
             "The provided path is missing from the trie. Path: " + path);
       }
-      if (findLeafAlongPath && curNode._isLeaf) {
+      if (findLeafAlongPath && curNode.isShardingKey()) {
         return curNode;
       }
-      curChildren = curNode._children;
+      curChildren = curNode.getChildren();
     }
     if (findLeafAlongPath) {
       throw new NoSuchElementException("No leaf node found along the path. Path: " + path);
@@ -124,36 +136,145 @@ public class TrieRoutingData implements MetadataStoreRoutingData {
     return curNode;
   }
 
-  // TODO: THE CLASS WILL BE CHANGED TO PRIVATE ONCE THE CONSTRUCTOR IS CREATED.
-  static class TrieNode {
+  /**
+   * Checks for the edge case when the only sharding key in provided routing data is the delimiter
+   * or an empty string. When this is the case, the trie is valid and contains only one node, which
+   * is the root node, and the root node is a leaf node with a realm address associated with it.
+   * @param routingData - a mapping from "sharding keys" to "realm addresses" to be parsed into a
+   *          trie
+   * @return whether the edge case is true
+   */
+  private boolean isRootShardingKey(Map<String, List<String>> routingData) {
+    if (routingData.size() == 1) {
+      for (List<String> shardingKeys : routingData.values()) {
+        return shardingKeys.size() == 1 && shardingKeys.get(0).equals(DELIMITER);
+      }
+    }
+
+    return false;
+  }
+
+  /**
+   * Constructs a trie based on the provided routing data. It loops through all sharding keys and
+   * constructs the trie in a top down manner.
+   * @param routingData- a mapping from "sharding keys" to "realm addresses" to be parsed into a
+   *          trie
+   * @throws InvalidRoutingDataException - when there is an empty sharding key (edge case that
+   *           always renders the routing data invalid); when there is a sharding key which already
+   *           contains a sharding key (invalid); when there is a sharding key that is a part of
+   *           another sharding key (invalid)
+   */
+  private void constructTrie(Map<String, List<String>> routingData)
+      throws InvalidRoutingDataException {
+    for (Map.Entry<String, List<String>> entry : routingData.entrySet()) {
+      if (entry.getValue().isEmpty()) {
+        throw new InvalidRoutingDataException(
+            "Realm address does not have associating sharding keys: " + entry.getKey());
+      }
+      for (String shardingKey : entry.getValue()) {
+        // Missing leading delimiter is invalid
+        if (shardingKey.isEmpty() || !shardingKey.substring(0, 1).equals(DELIMITER)) {
+          throw new InvalidRoutingDataException("Sharding key does not have a leading \""
+              + DELIMITER + "\" character: " + shardingKey);
+        }
+
+        // Root can only be a sharding key if it's the only sharding key. Since this method is
+        // running, the special case has already been checked, therefore it's definitely invalid
+        if (shardingKey.equals(DELIMITER)) {
+          throw new InvalidRoutingDataException(
+              "There exist other sharding keys. Root cannot be a sharding key.");
+        }
+
+        // Locate the next delimiter
+        int nextDelimiterIndex = shardingKey.indexOf(DELIMITER, 1);
+        int prevDelimiterIndex = 0;
+        String keySection = shardingKey.substring(prevDelimiterIndex + 1,
+            nextDelimiterIndex > 0 ? nextDelimiterIndex : shardingKey.length());
+        TrieNode curNode = _rootNode;
+        TrieNode nextNode = curNode.getChildren().get(keySection);
+
+        // If the key section is not the last section yet, go in the loop; if the key section is the
+        // last section, exit
+        while (nextDelimiterIndex > 0) {
+          // If the node is already a leaf node, the current sharding key is invalid; if the node
+          // doesn't exist, construct a node and continue
+          if (nextNode != null && nextNode.isShardingKey()) {
+            throw new InvalidRoutingDataException(shardingKey + " cannot be a sharding key because "
+                + shardingKey.substring(0, nextDelimiterIndex)
+                + " is its parent key and is also a sharding key.");
+          } else if (nextNode == null) {
+            nextNode = new TrieNode(new HashMap<>(), shardingKey.substring(0, nextDelimiterIndex),
+                false, "");
+            curNode.addChild(keySection, nextNode);
+          }
+          prevDelimiterIndex = nextDelimiterIndex;
+          nextDelimiterIndex = shardingKey.indexOf(DELIMITER, prevDelimiterIndex + 1);
+          keySection = shardingKey.substring(prevDelimiterIndex + 1,
+              nextDelimiterIndex > 0 ? nextDelimiterIndex : shardingKey.length());
+          curNode = nextNode;
+          nextNode = curNode.getChildren().get(keySection);
+        }
+
+        // If the last node already exists, it's a part of another sharding key, making the current
+        // sharding key invalid
+        if (nextNode != null) {
+          throw new InvalidRoutingDataException(shardingKey
+              + " cannot be a sharding key because it is a parent key to another sharding key.");
+        }
+        nextNode = new TrieNode(new HashMap<>(), shardingKey, true, entry.getKey());
+        curNode.addChild(keySection, nextNode);
+      }
+    }
+  }
+
+  private static class TrieNode {
     /**
      * This field is a mapping between trie key and children nodes. For example, node "a" has
      * children "ab" and "ac", therefore the keys are "b" and "c" respectively.
      */
-    Map<String, TrieNode> _children;
+    private Map<String, TrieNode> _children;
     /**
-     * This field means if the node is a terminal node in the tree sense, not the trie sense. Any
-     * node that has children cannot possibly be a leaf node because only the node without children
-     * can store information. If a node is leaf, then it shouldn't have any children.
+     * This field states whether the path represented by the node is a sharding key
      */
-    final boolean _isLeaf;
+    private final boolean _isShardingKey;
     /**
-     * This field aligns the traditional trie design: it entails the complete path/prefix leading to
-     * the current node. For example, the name of root node is "/", then the name of its child node
+     * This field contains the complete path/prefix leading to the current node. For example, the
+     * name of root node is "/", then the name of its child node
      * is "/a", and the name of the child's child node is "/a/b".
      */
-    final String _name;
+    private final String _path;
     /**
      * This field represents the data contained in a node(which represents a path), and is only
      * available to the terminal nodes.
      */
-    final String _realmAddress;
+    private final String _realmAddress;
 
-    TrieNode(Map<String, TrieNode> children, String name, boolean isLeaf, String realmAddress) {
+    TrieNode(Map<String, TrieNode> children, String path, boolean isShardingKey,
+        String realmAddress) {
       _children = children;
-      _isLeaf = isLeaf;
-      _name = name;
+      _isShardingKey = isShardingKey;
+      _path = path;
       _realmAddress = realmAddress;
     }
+
+    public Map<String, TrieNode> getChildren() {
+      return _children;
+    }
+
+    public boolean isShardingKey() {
+      return _isShardingKey;
+    }
+
+    public String getPath() {
+      return _path;
+    }
+
+    public String getRealmAddress() {
+      return _realmAddress;
+    }
+
+    public void addChild(String key, TrieNode node) {
+      _children.put(key, node);
+    }
   }
 }
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java
index 85f8f4a..5a88ca9 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java
@@ -42,9 +42,9 @@ import org.apache.zookeeper.Watcher;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-
 /**
- * ZK-based MetadataStoreDirectory that listens on the routing data in routing ZKs with a update callback.
+ * ZK-based MetadataStoreDirectory that listens on the routing data in routing ZKs with a update
+ * callback.
  */
 public class ZkMetadataStoreDirectory implements MetadataStoreDirectory, RoutingDataListener {
   private static final Logger LOG = LoggerFactory.getLogger(ZkMetadataStoreDirectory.class);
@@ -156,34 +156,42 @@ public class ZkMetadataStoreDirectory implements MetadataStoreDirectory, Routing
 
   /**
    * Callback for updating the cached routing data.
-   * Note: this method should not synchronize on the class or the map. We do not want namespaces blocking each other.
+   * Note: this method should not synchronize on the class or the map. We do not want namespaces
+   * blocking each other.
    * Threadsafe map is used for _realmToShardingKeysMap.
-   * The global consistency of the in-memory routing data is not a requirement (eventual consistency is enough).
+   * The global consistency of the in-memory routing data is not a requirement (eventual consistency
+   * is enough).
    * @param namespace
    */
   @Override
   public void refreshRoutingData(String namespace) {
-    // Safe to ignore the callback if routingDataMap is null.
+    // Safe to ignore the callback if any of the mapping is null.
     // If routingDataMap is null, then it will be populated by the constructor anyway
     // If routingDataMap is not null, then it's safe for the callback function to update it
+    if (_routingZkAddressMap == null || _routingDataMap == null
+        || _realmToShardingKeysMap == null) {
+      LOG.error("Construction is not completed! ");
+      return;
+    }
 
     // Check if namespace exists; otherwise, return as a NOP and log it
     if (!_routingZkAddressMap.containsKey(namespace)) {
-      LOG.error("Failed to refresh internally-cached routing data! Namespace not found: " + namespace);
+      LOG.error("Failed to refresh internally-cached routing data! Namespace not found: {}",
+          namespace);
+      return;
     }
 
     try {
-      _realmToShardingKeysMap.put(namespace, _routingDataReaderMap.get(namespace).getRoutingData());
+      Map<String, List<String>> rawRoutingData =
+          _routingDataReaderMap.get(namespace).getRoutingData();
+      _realmToShardingKeysMap.put(namespace, rawRoutingData);
+
+      MetadataStoreRoutingData routingData = new TrieRoutingData(rawRoutingData);
+      _routingDataMap.put(namespace, routingData);
     } catch (InvalidRoutingDataException e) {
-      LOG.error("Failed to get routing data for namespace: " + namespace + "!");
+      LOG.error("Failed to refresh cached routing data for namespace {}", namespace, e);
     }
 
-    if (_routingDataMap != null) {
-      MetadataStoreRoutingData newRoutingData =
-          new TrieRoutingData(new TrieRoutingData.TrieNode(null, null, false, null));
-      // TODO call constructRoutingData() here.
-      _routingDataMap.put(namespace, newRoutingData);
-    }
   }
 
   @Override
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestTrieRoutingData.java b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestTrieRoutingData.java
index 1b1754d..bf71456 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestTrieRoutingData.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestTrieRoutingData.java
@@ -19,146 +19,250 @@ package org.apache.helix.rest.metadatastore;
  * under the License.
  */
 
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
+import org.apache.helix.rest.metadatastore.exceptions.InvalidRoutingDataException;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
 public class TestTrieRoutingData {
-  // TODO: add constructor related tests after constructor is finished
+  private TrieRoutingData _trie;
 
   @Test
-  public void testGetAllMappingUnderPathFromRoot() {
-    TrieRoutingData trie = constructTestTrie();
-    Map<String, String> result = trie.getAllMappingUnderPath("/");
-    Assert.assertEquals(result.size(), 4);
-    Assert.assertEquals(result.get("/b/c/d"), "realmAddressD");
-    Assert.assertEquals(result.get("/b/c/e"), "realmAddressE");
-    Assert.assertEquals(result.get("/b/f"), "realmAddressF");
-    Assert.assertEquals(result.get("/g"), "realmAddressG");
+  public void testConstructionMissingRoutingData() {
+    try {
+      new TrieRoutingData(null);
+      Assert.fail("Expecting InvalidRoutingDataException");
+    } catch (InvalidRoutingDataException e) {
+      Assert.assertTrue(e.getMessage().contains("routingData cannot be null or empty"));
+    }
+    try {
+      new TrieRoutingData(Collections.emptyMap());
+      Assert.fail("Expecting InvalidRoutingDataException");
+    } catch (InvalidRoutingDataException e) {
+      Assert.assertTrue(e.getMessage().contains("routingData cannot be null or empty"));
+    }
+  }
+
+  /**
+   * This test case is for the situation when there's only one sharding key and it's root.
+   */
+  @Test
+  public void testConstructionSpecialCase() {
+    Map<String, List<String>> routingData = new HashMap<>();
+    routingData.put("realmAddress", Collections.singletonList("/"));
+    TrieRoutingData trie;
+    try {
+      trie = new TrieRoutingData(routingData);
+      Map<String, String> result = trie.getAllMappingUnderPath("/");
+      Assert.assertEquals(result.size(), 1);
+      Assert.assertEquals(result.get("/"), "realmAddress");
+    } catch (InvalidRoutingDataException e) {
+      Assert.fail("Not expecting InvalidRoutingDataException");
+    }
+  }
+
+  @Test
+  public void testConstructionEmptyShardingKeys() {
+    Map<String, List<String>> routingData = new HashMap<>();
+    routingData.put("realmAddress1", Collections.emptyList());
+    try {
+      new TrieRoutingData(routingData);
+      Assert.fail("Expecting InvalidRoutingDataException");
+    } catch (InvalidRoutingDataException e) {
+      Assert.assertTrue(e.getMessage()
+          .contains("Realm address does not have associating sharding keys: realmAddress1"));
+    }
+  }
+
+  @Test
+  public void testConstructionShardingKeyNoLeadingSlash() {
+    Map<String, List<String>> routingData = new HashMap<>();
+    routingData.put("realmAddress1", Arrays.asList("/g", "/h/i", "/h/j"));
+    routingData.put("realmAddress2", Arrays.asList("b/c/d", "/b/f"));
+    routingData.put("realmAddress3", Collections.singletonList("/b/c/e"));
+    try {
+      new TrieRoutingData(routingData);
+      Assert.fail("Expecting InvalidRoutingDataException");
+    } catch (InvalidRoutingDataException e) {
+      Assert.assertTrue(
+          e.getMessage().contains("Sharding key does not have a leading \"/\" character: b/c/d"));
+    }
+  }
+
+  @Test
+  public void testConstructionRootAsShardingKeyInvalid() {
+    Map<String, List<String>> routingData = new HashMap<>();
+    routingData.put("realmAddress1", Arrays.asList("/a/b", "/"));
+    try {
+      new TrieRoutingData(routingData);
+      Assert.fail("Expecting InvalidRoutingDataException");
+    } catch (InvalidRoutingDataException e) {
+      Assert.assertTrue(e.getMessage()
+          .contains("There exist other sharding keys. Root cannot be a sharding key."));
+    }
   }
 
   @Test
-  public void testGetAllMappingUnderPathFromRootEmptyPath() {
-    TrieRoutingData trie = constructTestTrie();
-    Map<String, String> result = trie.getAllMappingUnderPath("");
-    Assert.assertEquals(result.size(), 4);
-    Assert.assertEquals(result.get("/b/c/d"), "realmAddressD");
-    Assert.assertEquals(result.get("/b/c/e"), "realmAddressE");
-    Assert.assertEquals(result.get("/b/f"), "realmAddressF");
-    Assert.assertEquals(result.get("/g"), "realmAddressG");
+  public void testConstructionShardingKeyContainsAnother() {
+    Map<String, List<String>> routingData = new HashMap<>();
+    routingData.put("realmAddress1", Arrays.asList("/a/b", "/a/b/c"));
+    try {
+      new TrieRoutingData(routingData);
+      Assert.fail("Expecting InvalidRoutingDataException");
+    } catch (InvalidRoutingDataException e) {
+      Assert.assertTrue(e.getMessage().contains(
+          "/a/b/c cannot be a sharding key because /a/b is its parent key and is also a sharding key."));
+    }
   }
 
   @Test
+  public void testConstructionShardingKeyIsAPartOfAnother() {
+    Map<String, List<String>> routingData = new HashMap<>();
+    routingData.put("realmAddress1", Arrays.asList("/a/b/c", "/a/b"));
+    try {
+      new TrieRoutingData(routingData);
+      Assert.fail("Expecting InvalidRoutingDataException");
+    } catch (InvalidRoutingDataException e) {
+      Assert.assertTrue(e.getMessage().contains(
+          "/a/b cannot be a sharding key because it is a parent key to another sharding key."));
+    }
+  }
+
+  /**
+   * Constructing a trie that will also be reused for other tests
+   * -----<empty>
+   * ------/-|--\
+   * -----b--g--h
+   * ----/-\---/-\
+   * ---c--f--i--j
+   * --/-\
+   * -d--e
+   * Note: "g", "i", "j" lead to "realmAddress1"; "d", "f" lead to "realmAddress2"; "e" leads to
+   * "realmAddress3"
+   */
+  @Test
+  public void testConstructionNormal() {
+    Map<String, List<String>> routingData = new HashMap<>();
+    routingData.put("realmAddress1", Arrays.asList("/g", "/h/i", "/h/j"));
+    routingData.put("realmAddress2", Arrays.asList("/b/c/d", "/b/f"));
+    routingData.put("realmAddress3", Collections.singletonList("/b/c/e"));
+    try {
+      _trie = new TrieRoutingData(routingData);
+    } catch (InvalidRoutingDataException e) {
+      Assert.fail("Not expecting InvalidRoutingDataException");
+    }
+  }
+
+  @Test(dependsOnMethods = "testConstructionNormal")
+  public void testGetAllMappingUnderPathEmptyPath() {
+    try {
+      _trie.getAllMappingUnderPath("");
+      Assert.fail("Expecting IllegalArgumentException");
+    } catch (IllegalArgumentException e) {
+      Assert.assertTrue(e.getMessage()
+          .contains("Provided path is empty or does not have a leading \"/\" character: "));
+    }
+  }
+
+  @Test(dependsOnMethods = "testConstructionNormal")
+  public void testGetAllMappingUnderPathNoLeadingSlash() {
+    try {
+      _trie.getAllMappingUnderPath("test");
+      Assert.fail("Expecting IllegalArgumentException");
+    } catch (IllegalArgumentException e) {
+      Assert.assertTrue(e.getMessage()
+          .contains("Provided path is empty or does not have a leading \"/\" character: test"));
+    }
+  }
+
+  @Test(dependsOnMethods = "testConstructionNormal")
+  public void testGetAllMappingUnderPathFromRoot() {
+    Map<String, String> result = _trie.getAllMappingUnderPath("/");
+    Assert.assertEquals(result.size(), 6);
+    Assert.assertEquals(result.get("/b/c/d"), "realmAddress2");
+    Assert.assertEquals(result.get("/b/c/e"), "realmAddress3");
+    Assert.assertEquals(result.get("/b/f"), "realmAddress2");
+    Assert.assertEquals(result.get("/g"), "realmAddress1");
+    Assert.assertEquals(result.get("/h/i"), "realmAddress1");
+    Assert.assertEquals(result.get("/h/j"), "realmAddress1");
+  }
+
+  @Test(dependsOnMethods = "testConstructionNormal")
   public void testGetAllMappingUnderPathFromSecondLevel() {
-    TrieRoutingData trie = constructTestTrie();
-    Map<String, String> result = trie.getAllMappingUnderPath("/b");
+    Map<String, String> result = _trie.getAllMappingUnderPath("/b");
     Assert.assertEquals(result.size(), 3);
-    Assert.assertEquals(result.get("/b/c/d"), "realmAddressD");
-    Assert.assertEquals(result.get("/b/c/e"), "realmAddressE");
-    Assert.assertEquals(result.get("/b/f"), "realmAddressF");
+    Assert.assertEquals(result.get("/b/c/d"), "realmAddress2");
+    Assert.assertEquals(result.get("/b/c/e"), "realmAddress3");
+    Assert.assertEquals(result.get("/b/f"), "realmAddress2");
   }
 
-  @Test
+  @Test(dependsOnMethods = "testConstructionNormal")
   public void testGetAllMappingUnderPathFromLeaf() {
-    TrieRoutingData trie = constructTestTrie();
-    Map<String, String> result = trie.getAllMappingUnderPath("/b/c/d");
+    Map<String, String> result = _trie.getAllMappingUnderPath("/b/c/d");
     Assert.assertEquals(result.size(), 1);
-    Assert.assertEquals(result.get("/b/c/d"), "realmAddressD");
+    Assert.assertEquals(result.get("/b/c/d"), "realmAddress2");
   }
 
-  @Test
+  @Test(dependsOnMethods = "testConstructionNormal")
   public void testGetAllMappingUnderPathWrongPath() {
-    TrieRoutingData trie = constructTestTrie();
-    Map<String, String> result = trie.getAllMappingUnderPath("/b/c/d/g");
+    Map<String, String> result = _trie.getAllMappingUnderPath("/b/c/d/g");
     Assert.assertEquals(result.size(), 0);
   }
 
-  @Test
-  public void testGetMetadataStoreRealm() {
-    TrieRoutingData trie = constructTestTrie();
+  @Test(dependsOnMethods = "testConstructionNormal")
+  public void testGetMetadataStoreRealmEmptyPath() {
     try {
-      Assert.assertEquals(trie.getMetadataStoreRealm("/b/c/d/x/y/z"), "realmAddressD");
-    } catch (NoSuchElementException e) {
-      Assert.fail("Not expecting NoSuchElementException");
+      Assert.assertEquals(_trie.getMetadataStoreRealm(""), "realmAddress2");
+      Assert.fail("Expecting IllegalArgumentException");
+    } catch (IllegalArgumentException e) {
+      Assert.assertTrue(e.getMessage()
+          .contains("Provided path is empty or does not have a leading \"/\" character: "));
     }
   }
 
-  @Test
+  @Test(dependsOnMethods = "testConstructionNormal")
   public void testGetMetadataStoreRealmNoSlash() {
-    TrieRoutingData trie = constructTestTrie();
     try {
-      Assert.assertEquals(trie.getMetadataStoreRealm("b/c/d/x/y/z"), "realmAddressD");
+      Assert.assertEquals(_trie.getMetadataStoreRealm("b/c/d/x/y/z"), "realmAddress2");
+      Assert.fail("Expecting IllegalArgumentException");
+    } catch (IllegalArgumentException e) {
+      Assert.assertTrue(e.getMessage().contains(
+          "Provided path is empty or does not have a leading \"/\" character: b/c/d/x/y/z"));
+    }
+  }
+
+  @Test(dependsOnMethods = "testConstructionNormal")
+  public void testGetMetadataStoreRealm() {
+    try {
+      Assert.assertEquals(_trie.getMetadataStoreRealm("/b/c/d/x/y/z"), "realmAddress2");
     } catch (NoSuchElementException e) {
       Assert.fail("Not expecting NoSuchElementException");
     }
   }
 
-  @Test
+  @Test(dependsOnMethods = "testConstructionNormal")
   public void testGetMetadataStoreRealmWrongPath() {
-    TrieRoutingData trie = constructTestTrie();
     try {
-      trie.getMetadataStoreRealm("/x/y/z");
+      _trie.getMetadataStoreRealm("/x/y/z");
       Assert.fail("Expecting NoSuchElementException");
     } catch (NoSuchElementException e) {
-      Assert.assertTrue(e.getMessage().contains("The provided path is missing from the trie. Path: /x/y/z"));
+      Assert.assertTrue(
+          e.getMessage().contains("The provided path is missing from the trie. Path: /x/y/z"));
     }
   }
 
-  @Test
+  @Test(dependsOnMethods = "testConstructionNormal")
   public void testGetMetadataStoreRealmNoLeaf() {
-    TrieRoutingData trie = constructTestTrie();
     try {
-      trie.getMetadataStoreRealm("/b/c");
+      _trie.getMetadataStoreRealm("/b/c");
       Assert.fail("Expecting NoSuchElementException");
     } catch (NoSuchElementException e) {
       Assert.assertTrue(e.getMessage().contains("No leaf node found along the path. Path: /b/c"));
     }
   }
-
-  /**
-   * Constructing a trie for testing purposes
-   * -----<empty>
-   * ------/--\
-   * -----b---g
-   * ----/-\
-   * ---c--f
-   * --/-\
-   * -d--e
-   */
-  private TrieRoutingData constructTestTrie() {
-    TrieRoutingData.TrieNode nodeD =
-        new TrieRoutingData.TrieNode(Collections.emptyMap(), "/b/c/d", true, "realmAddressD");
-    TrieRoutingData.TrieNode nodeE =
-        new TrieRoutingData.TrieNode(Collections.emptyMap(), "/b/c/e", true, "realmAddressE");
-    TrieRoutingData.TrieNode nodeF =
-        new TrieRoutingData.TrieNode(Collections.emptyMap(), "/b/f", true, "realmAddressF");
-    TrieRoutingData.TrieNode nodeG =
-        new TrieRoutingData.TrieNode(Collections.emptyMap(), "/g", true, "realmAddressG");
-    TrieRoutingData.TrieNode nodeC =
-        new TrieRoutingData.TrieNode(new HashMap<String, TrieRoutingData.TrieNode>() {
-          {
-            put("d", nodeD);
-            put("e", nodeE);
-          }
-        }, "c", false, "");
-    TrieRoutingData.TrieNode nodeB =
-        new TrieRoutingData.TrieNode(new HashMap<String, TrieRoutingData.TrieNode>() {
-          {
-            put("c", nodeC);
-            put("f", nodeF);
-          }
-        }, "b", false, "");
-    TrieRoutingData.TrieNode root =
-        new TrieRoutingData.TrieNode(new HashMap<String, TrieRoutingData.TrieNode>() {
-          {
-            put("b", nodeB);
-            put("g", nodeG);
-          }
-        }, "", false, "");
-
-    return new TrieRoutingData(root);
-  }
 }


[helix] 18/49: Add DedicatedZkClient and update DedicatedZkClientFactory (#765)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit c132ace4e95cb56ff63a018fb748f7b1d217be96
Author: Hunter Lee <hu...@linkedin.com>
AuthorDate: Thu Feb 20 16:17:03 2020 -0800

    Add DedicatedZkClient and update DedicatedZkClientFactory (#765)
    
    As part of ZkClient API enhancement, we wish to add DedicatedZkClient, which is a wrapper of the raw ZkClient, that provides realm-aware access to ZooKeeper.
    
    Realm-aware in that it only performs requests whose path's Zk path sharding key belongs to the ZK realm it's connected to.
    
    Also, we need to modify DedicatedZkClientFactory so that users could use this factory to generate instances of DedicatedZkClient.
---
 zookeeper-api/pom.xml                              |  11 +
 .../api/factory/RealmAwareZkClientFactory.java     |  16 +-
 .../zookeeper/impl/client/DedicatedZkClient.java   | 473 +++++++++++++++++++++
 .../impl/factory/DedicatedZkClientFactory.java     |  19 +-
 .../impl/factory/SharedZkClientFactory.java        |   7 +-
 .../apache/helix/zookeeper/impl/ZkTestBase.java    | 148 +++++++
 .../impl/client/RealmAwareZkClientTestBase.java    | 163 +++++++
 .../impl/client/TestDedicatedZkClient.java         |  35 ++
 8 files changed, 854 insertions(+), 18 deletions(-)

diff --git a/zookeeper-api/pom.xml b/zookeeper-api/pom.xml
index 5ec1e56..91b448f 100644
--- a/zookeeper-api/pom.xml
+++ b/zookeeper-api/pom.xml
@@ -44,6 +44,11 @@ under the License.
       <version>${project.version}</version>
     </dependency>
     <dependency>
+      <groupId>org.apache.helix</groupId>
+      <artifactId>metadata-store-directory-common</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
       <groupId>org.apache.zookeeper</groupId>
       <artifactId>zookeeper</artifactId>
       <version>3.4.13</version>
@@ -79,6 +84,12 @@ under the License.
       <artifactId>testng</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>commons-io</groupId>
+      <artifactId>commons-io</artifactId>
+      <version>2.6</version>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
   <build>
     <resources>
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/factory/RealmAwareZkClientFactory.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/factory/RealmAwareZkClientFactory.java
index f68ffe4..8c1f7a3 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/factory/RealmAwareZkClientFactory.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/factory/RealmAwareZkClientFactory.java
@@ -19,6 +19,7 @@ package org.apache.helix.zookeeper.api.factory;
  * under the License.
  */
 
+import org.apache.helix.msdcommon.datamodel.MetadataStoreRoutingData;
 import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 
 
@@ -30,16 +31,25 @@ public interface RealmAwareZkClientFactory {
    * Build a RealmAwareZkClient using specified connection config and client config.
    * @param connectionConfig
    * @param clientConfig
+   * @param metadataStoreRoutingData
    * @return HelixZkClient
    */
+  // TODO: remove MetadataStoreRoutingData
   RealmAwareZkClient buildZkClient(RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig,
-      RealmAwareZkClient.RealmAwareZkClientConfig clientConfig);
+      RealmAwareZkClient.RealmAwareZkClientConfig clientConfig,
+      MetadataStoreRoutingData metadataStoreRoutingData);
 
   /**
    * Builds a RealmAwareZkClient using specified connection config and default client config.
    * @param connectionConfig
+   * @param metadataStoreRoutingData
    * @return RealmAwareZkClient
    */
-  RealmAwareZkClient buildZkClient(
-      RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig);
+  // TODO: remove MetadataStoreRoutingData
+  default RealmAwareZkClient buildZkClient(
+      RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig,
+      MetadataStoreRoutingData metadataStoreRoutingData) {
+    return buildZkClient(connectionConfig, new RealmAwareZkClient.RealmAwareZkClientConfig(),
+        metadataStoreRoutingData);
+  }
 }
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/client/DedicatedZkClient.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/client/DedicatedZkClient.java
new file mode 100644
index 0000000..8352b2b
--- /dev/null
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/client/DedicatedZkClient.java
@@ -0,0 +1,473 @@
+package org.apache.helix.zookeeper.impl.client;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.helix.msdcommon.datamodel.MetadataStoreRoutingData;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
+import org.apache.helix.zookeeper.zkclient.DataUpdater;
+import org.apache.helix.zookeeper.zkclient.IZkChildListener;
+import org.apache.helix.zookeeper.zkclient.IZkConnection;
+import org.apache.helix.zookeeper.zkclient.IZkDataListener;
+import org.apache.helix.zookeeper.zkclient.ZkConnection;
+import org.apache.helix.zookeeper.zkclient.callback.ZkAsyncCallbacks;
+import org.apache.helix.zookeeper.zkclient.deprecated.IZkStateListener;
+import org.apache.helix.zookeeper.zkclient.serialize.PathBasedZkSerializer;
+import org.apache.helix.zookeeper.zkclient.serialize.ZkSerializer;
+import org.apache.zookeeper.CreateMode;
+import org.apache.zookeeper.Op;
+import org.apache.zookeeper.OpResult;
+import org.apache.zookeeper.ZooDefs;
+import org.apache.zookeeper.data.ACL;
+import org.apache.zookeeper.data.Stat;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * NOTE: DO NOT USE THIS CLASS DIRECTLY. Use DedicatedZkClientFactory to create instances of DedicatedZkClient.
+ *
+ * An implementation of the RealmAwareZkClient interface.
+ * Supports CRUD, data change subscription, and ephemeral mode operations.
+ */
+public class DedicatedZkClient implements RealmAwareZkClient {
+  private static Logger LOG = LoggerFactory.getLogger(DedicatedZkClient.class);
+
+  private final ZkClient _rawZkClient;
+  private final MetadataStoreRoutingData _metadataStoreRoutingData;
+  private final String _zkRealmShardingKey;
+  private final String _zkRealmAddress;
+
+  // TODO: Remove MetadataStoreRoutingData from constructor
+  public DedicatedZkClient(RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig,
+      RealmAwareZkClient.RealmAwareZkClientConfig clientConfig,
+      MetadataStoreRoutingData metadataStoreRoutingData) {
+
+    if (connectionConfig == null) {
+      throw new IllegalArgumentException("RealmAwareZkConnectionConfig cannot be null!");
+    }
+    _zkRealmShardingKey = connectionConfig.getZkRealmShardingKey();
+
+    if (metadataStoreRoutingData == null) {
+      throw new IllegalArgumentException("MetadataStoreRoutingData cannot be null!");
+    }
+    _metadataStoreRoutingData = metadataStoreRoutingData;
+
+    // TODO: Get it from static map/singleton (HttpRoutingDataReader)
+    // Get the ZkRealm address based on the ZK path sharding key
+    String zkRealmAddress = _metadataStoreRoutingData.getMetadataStoreRealm(_zkRealmShardingKey);
+    if (zkRealmAddress == null || zkRealmAddress.isEmpty()) {
+      throw new IllegalArgumentException(
+          "ZK realm address for the given ZK realm sharding key is invalid! ZK realm address: "
+              + zkRealmAddress + " ZK realm sharding key: " + _zkRealmShardingKey);
+    }
+    _zkRealmAddress = zkRealmAddress;
+
+    // Create a ZK connection
+    IZkConnection zkConnection =
+        new ZkConnection(zkRealmAddress, connectionConfig.getSessionTimeout());
+
+    // Create a ZkClient
+    _rawZkClient = new ZkClient(zkConnection, (int) clientConfig.getConnectInitTimeout(),
+        clientConfig.getOperationRetryTimeout(), clientConfig.getZkSerializer(),
+        clientConfig.getMonitorType(), clientConfig.getMonitorKey(),
+        clientConfig.getMonitorInstanceName(), clientConfig.isMonitorRootPathOnly());
+  }
+
+  @Override
+  public List<String> subscribeChildChanges(String path, IZkChildListener listener) {
+    checkIfPathContainsShardingKey(path);
+    return _rawZkClient.subscribeChildChanges(path, listener);
+  }
+
+  @Override
+  public void unsubscribeChildChanges(String path, IZkChildListener listener) {
+    checkIfPathContainsShardingKey(path);
+    _rawZkClient.unsubscribeChildChanges(path, listener);
+  }
+
+  @Override
+  public void subscribeDataChanges(String path, IZkDataListener listener) {
+    checkIfPathContainsShardingKey(path);
+    _rawZkClient.subscribeDataChanges(path, listener);
+  }
+
+  @Override
+  public void unsubscribeDataChanges(String path, IZkDataListener listener) {
+    checkIfPathContainsShardingKey(path);
+    _rawZkClient.unsubscribeDataChanges(path, listener);
+  }
+
+  @Override
+  public void subscribeStateChanges(IZkStateListener listener) {
+    _rawZkClient.subscribeStateChanges(listener);
+  }
+
+  @Override
+  public void unsubscribeStateChanges(IZkStateListener listener) {
+    _rawZkClient.unsubscribeStateChanges(listener);
+  }
+
+  @Override
+  public void unsubscribeAll() {
+    _rawZkClient.unsubscribeAll();
+  }
+
+  @Override
+  public void createPersistent(String path) {
+    createPersistent(path, false);
+  }
+
+  @Override
+  public void createPersistent(String path, boolean createParents) {
+    createPersistent(path, createParents, ZooDefs.Ids.OPEN_ACL_UNSAFE);
+  }
+
+  @Override
+  public void createPersistent(String path, boolean createParents, List<ACL> acl) {
+    checkIfPathContainsShardingKey(path);
+    _rawZkClient.createPersistent(path, createParents, acl);
+  }
+
+  @Override
+  public void createPersistent(String path, Object data) {
+    create(path, data, CreateMode.PERSISTENT);
+  }
+
+  @Override
+  public void createPersistent(String path, Object data, List<ACL> acl) {
+    create(path, data, acl, CreateMode.PERSISTENT);
+  }
+
+  @Override
+  public String createPersistentSequential(String path, Object data) {
+    return create(path, data, CreateMode.PERSISTENT_SEQUENTIAL);
+  }
+
+  @Override
+  public String createPersistentSequential(String path, Object data, List<ACL> acl) {
+    return create(path, data, acl, CreateMode.PERSISTENT_SEQUENTIAL);
+  }
+
+  @Override
+  public void createEphemeral(String path) {
+    create(path, null, CreateMode.EPHEMERAL);
+  }
+
+  @Override
+  public void createEphemeral(String path, String sessionId) {
+    createEphemeral(path, null, sessionId);
+  }
+
+  @Override
+  public void createEphemeral(String path, List<ACL> acl) {
+    create(path, null, acl, CreateMode.EPHEMERAL);
+  }
+
+  @Override
+  public void createEphemeral(String path, List<ACL> acl, String sessionId) {
+    checkIfPathContainsShardingKey(path);
+    _rawZkClient.createEphemeral(path, acl, sessionId);
+  }
+
+  @Override
+  public String create(String path, Object data, CreateMode mode) {
+    return create(path, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, mode);
+  }
+
+  @Override
+  public String create(String path, Object datat, List<ACL> acl, CreateMode mode) {
+    checkIfPathContainsShardingKey(path);
+    return _rawZkClient.create(path, datat, acl, mode);
+  }
+
+  @Override
+  public void createEphemeral(String path, Object data) {
+    create(path, data, CreateMode.EPHEMERAL);
+  }
+
+  @Override
+  public void createEphemeral(String path, Object data, String sessionId) {
+    checkIfPathContainsShardingKey(path);
+    _rawZkClient.createEphemeral(path, data, sessionId);
+  }
+
+  @Override
+  public void createEphemeral(String path, Object data, List<ACL> acl) {
+    create(path, data, acl, CreateMode.EPHEMERAL);
+  }
+
+  @Override
+  public void createEphemeral(String path, Object data, List<ACL> acl, String sessionId) {
+    checkIfPathContainsShardingKey(path);
+    _rawZkClient.createEphemeral(path, data, acl, sessionId);
+  }
+
+  @Override
+  public String createEphemeralSequential(String path, Object data) {
+    return create(path, data, CreateMode.EPHEMERAL_SEQUENTIAL);
+  }
+
+  @Override
+  public String createEphemeralSequential(String path, Object data, List<ACL> acl) {
+    return create(path, data, acl, CreateMode.EPHEMERAL_SEQUENTIAL);
+  }
+
+  @Override
+  public String createEphemeralSequential(String path, Object data, String sessionId) {
+    checkIfPathContainsShardingKey(path);
+    return _rawZkClient.createEphemeralSequential(path, data, sessionId);
+  }
+
+  @Override
+  public String createEphemeralSequential(String path, Object data, List<ACL> acl,
+      String sessionId) {
+    checkIfPathContainsShardingKey(path);
+    return _rawZkClient.createEphemeralSequential(path, data, acl, sessionId);
+  }
+
+  @Override
+  public List<String> getChildren(String path) {
+    checkIfPathContainsShardingKey(path);
+    return _rawZkClient.getChildren(path);
+  }
+
+  @Override
+  public int countChildren(String path) {
+    checkIfPathContainsShardingKey(path);
+    return _rawZkClient.countChildren(path);
+  }
+
+  @Override
+  public boolean exists(String path) {
+    checkIfPathContainsShardingKey(path);
+    return _rawZkClient.exists(path);
+  }
+
+  @Override
+  public Stat getStat(String path) {
+    checkIfPathContainsShardingKey(path);
+    return _rawZkClient.getStat(path);
+  }
+
+  @Override
+  public boolean waitUntilExists(String path, TimeUnit timeUnit, long time) {
+    checkIfPathContainsShardingKey(path);
+    return _rawZkClient.waitUntilExists(path, timeUnit, time);
+  }
+
+  @Override
+  public void deleteRecursively(String path) {
+    checkIfPathContainsShardingKey(path);
+    _rawZkClient.deleteRecursively(path);
+  }
+
+  @Override
+  public boolean delete(String path) {
+    checkIfPathContainsShardingKey(path);
+    return _rawZkClient.delete(path);
+  }
+
+  @Override
+  public <T> T readData(String path) {
+    return readData(path, false);
+  }
+
+  @Override
+  public <T> T readData(String path, boolean returnNullIfPathNotExists) {
+    checkIfPathContainsShardingKey(path);
+    return _rawZkClient.readData(path, returnNullIfPathNotExists);
+  }
+
+  @Override
+  public <T> T readData(String path, Stat stat) {
+    checkIfPathContainsShardingKey(path);
+    return _rawZkClient.readData(path, stat);
+  }
+
+  @Override
+  public <T> T readData(String path, Stat stat, boolean watch) {
+    checkIfPathContainsShardingKey(path);
+    return _rawZkClient.readData(path, stat, watch);
+  }
+
+  @Override
+  public <T> T readDataAndStat(String path, Stat stat, boolean returnNullIfPathNotExists) {
+    checkIfPathContainsShardingKey(path);
+    return _rawZkClient.readDataAndStat(path, stat, returnNullIfPathNotExists);
+  }
+
+  @Override
+  public void writeData(String path, Object object) {
+    writeData(path, object, -1);
+  }
+
+  @Override
+  public <T> void updateDataSerialized(String path, DataUpdater<T> updater) {
+    checkIfPathContainsShardingKey(path);
+    _rawZkClient.updateDataSerialized(path, updater);
+  }
+
+  @Override
+  public void writeData(String path, Object datat, int expectedVersion) {
+    writeDataReturnStat(path, datat, expectedVersion);
+  }
+
+  @Override
+  public Stat writeDataReturnStat(String path, Object datat, int expectedVersion) {
+    checkIfPathContainsShardingKey(path);
+    return _rawZkClient.writeDataReturnStat(path, datat, expectedVersion);
+  }
+
+  @Override
+  public Stat writeDataGetStat(String path, Object datat, int expectedVersion) {
+    return writeDataReturnStat(path, datat, expectedVersion);
+  }
+
+  @Override
+  public void asyncCreate(String path, Object datat, CreateMode mode,
+      ZkAsyncCallbacks.CreateCallbackHandler cb) {
+    checkIfPathContainsShardingKey(path);
+    _rawZkClient.asyncCreate(path, datat, mode, cb);
+  }
+
+  @Override
+  public void asyncSetData(String path, Object datat, int version,
+      ZkAsyncCallbacks.SetDataCallbackHandler cb) {
+    checkIfPathContainsShardingKey(path);
+    _rawZkClient.asyncSetData(path, datat, version, cb);
+  }
+
+  @Override
+  public void asyncGetData(String path, ZkAsyncCallbacks.GetDataCallbackHandler cb) {
+    checkIfPathContainsShardingKey(path);
+    _rawZkClient.asyncGetData(path, cb);
+  }
+
+  @Override
+  public void asyncExists(String path, ZkAsyncCallbacks.ExistsCallbackHandler cb) {
+    checkIfPathContainsShardingKey(path);
+    _rawZkClient.asyncExists(path, cb);
+  }
+
+  @Override
+  public void asyncDelete(String path, ZkAsyncCallbacks.DeleteCallbackHandler cb) {
+    checkIfPathContainsShardingKey(path);
+    _rawZkClient.asyncDelete(path, cb);
+  }
+
+  @Override
+  public void watchForData(String path) {
+    checkIfPathContainsShardingKey(path);
+    _rawZkClient.watchForData(path);
+  }
+
+  @Override
+  public List<String> watchForChilds(String path) {
+    checkIfPathContainsShardingKey(path);
+    return _rawZkClient.watchForChilds(path);
+  }
+
+  @Override
+  public long getCreationTime(String path) {
+    checkIfPathContainsShardingKey(path);
+    return _rawZkClient.getCreationTime(path);
+  }
+
+  @Override
+  public List<OpResult> multi(Iterable<Op> ops) {
+    return _rawZkClient.multi(ops);
+  }
+
+  @Override
+  public boolean waitUntilConnected(long time, TimeUnit timeUnit) {
+    return _rawZkClient.waitUntilConnected(time, timeUnit);
+  }
+
+  @Override
+  public String getServers() {
+    return _rawZkClient.getServers();
+  }
+
+  @Override
+  public long getSessionId() {
+    return _rawZkClient.getSessionId();
+  }
+
+  @Override
+  public void close() {
+    _rawZkClient.close();
+  }
+
+  @Override
+  public boolean isClosed() {
+    return _rawZkClient.isClosed();
+  }
+
+  @Override
+  public byte[] serialize(Object data, String path) {
+    checkIfPathContainsShardingKey(path);
+    return _rawZkClient.serialize(data, path);
+  }
+
+  @Override
+  public <T> T deserialize(byte[] data, String path) {
+    checkIfPathContainsShardingKey(path);
+    return _rawZkClient.deserialize(data, path);
+  }
+
+  @Override
+  public void setZkSerializer(ZkSerializer zkSerializer) {
+    _rawZkClient.setZkSerializer(zkSerializer);
+  }
+
+  @Override
+  public void setZkSerializer(PathBasedZkSerializer zkSerializer) {
+    _rawZkClient.setZkSerializer(zkSerializer);
+  }
+
+  @Override
+  public PathBasedZkSerializer getZkSerializer() {
+    return _rawZkClient.getZkSerializer();
+  }
+
+  /**
+   * Checks whether the given path belongs matches the ZK path sharding key this DedicatedZkClient is designated to at initialization.
+   * @param path
+   * @return
+   */
+  private void checkIfPathContainsShardingKey(String path) {
+    // TODO: replace with the singleton MetadataStoreRoutingData
+    try {
+      String zkRealmForPath = _metadataStoreRoutingData.getMetadataStoreRealm(path);
+      if (!_zkRealmAddress.equals(zkRealmForPath)) {
+        throw new IllegalArgumentException("Given path: " + path + "'s ZK realm: " + zkRealmForPath
+            + " does not match the ZK realm: " + _zkRealmAddress + " and sharding key: "
+            + _zkRealmShardingKey + " for this DedicatedZkClient!");
+      }
+    } catch (NoSuchElementException e) {
+      throw new IllegalArgumentException(
+          "Given path: " + path + " does not have a valid sharding key!");
+    }
+  }
+}
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/DedicatedZkClientFactory.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/DedicatedZkClientFactory.java
index 2695a5d..6694497 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/DedicatedZkClientFactory.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/DedicatedZkClientFactory.java
@@ -19,8 +19,10 @@ package org.apache.helix.zookeeper.impl.factory;
  * under the License.
  */
 
+import org.apache.helix.msdcommon.datamodel.MetadataStoreRoutingData;
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
 import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
+import org.apache.helix.zookeeper.impl.client.DedicatedZkClient;
 import org.apache.helix.zookeeper.impl.client.ZkClient;
 
 
@@ -35,17 +37,9 @@ public class DedicatedZkClientFactory extends HelixZkClientFactory {
   @Override
   public RealmAwareZkClient buildZkClient(
       RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig,
-      RealmAwareZkClient.RealmAwareZkClientConfig clientConfig) {
-    // TODO: Implement the logic
-    // Return an instance of DedicatedZkClient
-    return null;
-  }
-
-  @Override
-  public RealmAwareZkClient buildZkClient(
-      RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig) {
-    // TODO: Implement the logic
-    return null;
+      RealmAwareZkClient.RealmAwareZkClientConfig clientConfig,
+      MetadataStoreRoutingData metadataStoreRoutingData) {
+    return new DedicatedZkClient(connectionConfig, clientConfig, metadataStoreRoutingData);
   }
 
   private static class SingletonHelper {
@@ -57,8 +51,7 @@ public class DedicatedZkClientFactory extends HelixZkClientFactory {
   }
 
   /**
-   * Build a Dedicated ZkClient based on connection config and client config
-   *
+   * Build a Dedicated ZkClient based on connection config and client config.
    * @param connectionConfig
    * @param clientConfig
    * @return
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/SharedZkClientFactory.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/SharedZkClientFactory.java
index a9b8e33..1801614 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/SharedZkClientFactory.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/SharedZkClientFactory.java
@@ -21,6 +21,7 @@ package org.apache.helix.zookeeper.impl.factory;
 
 import java.util.HashMap;
 
+import org.apache.helix.msdcommon.datamodel.MetadataStoreRoutingData;
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
 import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 import org.apache.helix.zookeeper.exception.ZkClientException;
@@ -44,7 +45,8 @@ public class SharedZkClientFactory extends HelixZkClientFactory {
   @Override
   public RealmAwareZkClient buildZkClient(
       RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig,
-      RealmAwareZkClient.RealmAwareZkClientConfig clientConfig) {
+      RealmAwareZkClient.RealmAwareZkClientConfig clientConfig,
+      MetadataStoreRoutingData metadataStoreRoutingData) {
     // TODO: Implement the logic
     // Return an instance of SharedZkClient
     return null;
@@ -52,7 +54,8 @@ public class SharedZkClientFactory extends HelixZkClientFactory {
 
   @Override
   public RealmAwareZkClient buildZkClient(
-      RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig) {
+      RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig,
+      MetadataStoreRoutingData metadataStoreRoutingData) {
     // TODO: Implement the logic
     return null;
   }
diff --git a/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/ZkTestBase.java b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/ZkTestBase.java
new file mode 100644
index 0000000..10edaf4
--- /dev/null
+++ b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/ZkTestBase.java
@@ -0,0 +1,148 @@
+package org.apache.helix.zookeeper.impl;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.management.MBeanServerConnection;
+import javax.management.ObjectName;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.helix.zookeeper.zkclient.IDefaultNameSpace;
+import org.apache.helix.zookeeper.zkclient.ZkServer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.AfterSuite;
+import org.testng.annotations.BeforeSuite;
+
+
+/**
+ * Test base class for various integration tests with an in-memory ZooKeeper.
+ */
+public class ZkTestBase {
+  private static final Logger LOG = LoggerFactory.getLogger(ZkTestBase.class);
+  private static final MBeanServerConnection MBEAN_SERVER =
+      ManagementFactory.getPlatformMBeanServer();
+
+  // maven surefire-plugin's multiple ZK config keys
+  private static final String MULTI_ZK_PROPERTY_KEY = "multiZk";
+  private static final String NUM_ZK_PROPERTY_KEY = "numZk";
+
+  protected static final String ZK_PREFIX = "localhost:";
+  protected static final int ZK_START_PORT = 2127;
+
+  /*
+   * Multiple ZK references
+   */
+  // The following maps hold ZK connect string as keys
+  protected Map<String, ZkServer> _zkServerMap = new HashMap<>();
+  protected int _numZk = 1; // Initial value
+
+  @BeforeSuite
+  public void beforeSuite()
+      throws IOException {
+    // Due to ZOOKEEPER-2693 fix, we need to specify whitelist for execute zk commends
+    System.setProperty("zookeeper.4lw.commands.whitelist", "*");
+
+    // Set up in-memory ZooKeepers
+    setupZooKeepers();
+
+    // Clean up all JMX objects
+    for (ObjectName mbean : MBEAN_SERVER.queryNames(null, null)) {
+      try {
+        MBEAN_SERVER.unregisterMBean(mbean);
+      } catch (Exception e) {
+        // OK
+      }
+    }
+  }
+
+  @AfterSuite
+  public void afterSuite()
+      throws IOException {
+    // Clean up all JMX objects
+    for (ObjectName mbean : MBEAN_SERVER.queryNames(null, null)) {
+      try {
+        MBEAN_SERVER.unregisterMBean(mbean);
+      } catch (Exception e) {
+        // OK
+      }
+    }
+
+    // Shut down all ZkServers
+    _zkServerMap.values().forEach(ZkServer::shutdown);
+  }
+
+  private void setupZooKeepers() {
+    // If multi-ZooKeeper is enabled, start more ZKs. Otherwise, just set up one ZK
+    String multiZkConfig = System.getProperty(MULTI_ZK_PROPERTY_KEY);
+    if (multiZkConfig != null && multiZkConfig.equalsIgnoreCase(Boolean.TRUE.toString())) {
+      String numZkFromConfig = System.getProperty(NUM_ZK_PROPERTY_KEY);
+      if (numZkFromConfig != null) {
+        try {
+          _numZk = Math.max(Integer.parseInt(numZkFromConfig), _numZk);
+        } catch (Exception e) {
+          Assert.fail("Failed to parse the number of ZKs from config!");
+        }
+      } else {
+        Assert.fail("multiZk config is set but numZk config is missing!");
+      }
+    }
+
+    // Start "numZkFromConfigInt" ZooKeepers
+    for (int i = 0; i < _numZk; i++) {
+      String zkAddress = ZK_PREFIX + (ZK_START_PORT + i);
+      ZkServer zkServer = startZkServer(zkAddress);
+      _zkServerMap.put(zkAddress, zkServer);
+    }
+  }
+
+  /**
+   * Creates an in-memory ZK at the given ZK address.
+   * @param zkAddress
+   * @return
+   */
+  private ZkServer startZkServer(final String zkAddress) {
+    String zkDir = zkAddress.replace(':', '_');
+    final String logDir = "/tmp/" + zkDir + "/logs";
+    final String dataDir = "/tmp/" + zkDir + "/dataDir";
+
+    // Clean up local directory
+    try {
+      FileUtils.deleteDirectory(new File(dataDir));
+      FileUtils.deleteDirectory(new File(logDir));
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+
+    IDefaultNameSpace defaultNameSpace = zkClient -> {
+    };
+
+    int port = Integer.parseInt(zkAddress.substring(zkAddress.lastIndexOf(':') + 1));
+    ZkServer zkServer = new ZkServer(dataDir, logDir, defaultNameSpace, port);
+    zkServer.start();
+    return zkServer;
+  }
+}
diff --git a/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/RealmAwareZkClientTestBase.java b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/RealmAwareZkClientTestBase.java
new file mode 100644
index 0000000..cd74975
--- /dev/null
+++ b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/RealmAwareZkClientTestBase.java
@@ -0,0 +1,163 @@
+package org.apache.helix.zookeeper.impl.client;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.helix.msdcommon.datamodel.MetadataStoreRoutingData;
+import org.apache.helix.msdcommon.datamodel.TrieRoutingData;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
+import org.apache.helix.zookeeper.api.factory.RealmAwareZkClientFactory;
+import org.apache.helix.zookeeper.datamodel.ZNRecord;
+import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
+import org.apache.helix.zookeeper.impl.ZkTestBase;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+
+public abstract class RealmAwareZkClientTestBase extends ZkTestBase {
+  private static final String ZK_SHARDING_KEY_PREFIX = "/TEST_SHARDING_KEY";
+  private static final String TEST_VALID_PATH = ZK_SHARDING_KEY_PREFIX + "_" + 0 + "/a/b/c";
+  private static final String TEST_INVALID_PATH = ZK_SHARDING_KEY_PREFIX + "_invalid" + "/a/b/c";
+
+  // <Realm, List of sharding keys> Mapping
+  private static final Map<String, List<String>> RAW_ROUTING_DATA = new HashMap<>();
+
+  // The following RealmAwareZkClientFactory is to be defined in subclasses
+  protected RealmAwareZkClientFactory _realmAwareZkClientFactory;
+  private RealmAwareZkClient _realmAwareZkClient;
+  private MetadataStoreRoutingData _metadataStoreRoutingData;
+
+  @BeforeClass
+  public void beforeClass() throws Exception {
+    // Populate RAW_ROUTING_DATA
+    for (int i = 0; i < _numZk; i++) {
+      List<String> shardingKeyList = new ArrayList<>();
+      shardingKeyList.add(ZK_SHARDING_KEY_PREFIX + "_" + i);
+      String realmName = ZK_PREFIX + (ZK_START_PORT + i);
+      RAW_ROUTING_DATA.put(realmName, shardingKeyList);
+    }
+
+    // Feed the raw routing data into TrieRoutingData to construct an in-memory representation of routing information
+    _metadataStoreRoutingData = new TrieRoutingData(RAW_ROUTING_DATA);
+  }
+
+  @AfterClass
+  public void afterClass() {
+    if (_realmAwareZkClient != null && !_realmAwareZkClient.isClosed()) {
+      _realmAwareZkClient.close();
+    }
+  }
+
+  /**
+   * 1. Create a RealmAwareZkClient with a non-existing sharding key (for which there is no valid ZK realm)
+   * -> This should fail with an exception
+   * 2. Create a RealmAwareZkClient with a valid sharding key
+   * -> This should pass
+   */
+  @Test
+  public void testRealmAwareZkClientCreation() {
+    // Create a RealmAwareZkClient
+    String invalidShardingKey = "InvalidShardingKey";
+    RealmAwareZkClient.RealmAwareZkClientConfig clientConfig =
+        new RealmAwareZkClient.RealmAwareZkClientConfig();
+
+    // Create a connection config with the invalid sharding key
+    RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig =
+        new RealmAwareZkClient.RealmAwareZkConnectionConfig(invalidShardingKey);
+
+    try {
+      _realmAwareZkClient = _realmAwareZkClientFactory
+          .buildZkClient(connectionConfig, clientConfig, _metadataStoreRoutingData);
+      Assert.fail("Should not succeed with an invalid sharding key!");
+    } catch (IllegalArgumentException e) {
+      // Expected
+    }
+
+    // Use a valid sharding key this time around
+    String validShardingKey = ZK_SHARDING_KEY_PREFIX + "_" + 0; // Use TEST_SHARDING_KEY_0
+    connectionConfig = new RealmAwareZkClient.RealmAwareZkConnectionConfig(validShardingKey);
+    _realmAwareZkClient = _realmAwareZkClientFactory
+        .buildZkClient(connectionConfig, clientConfig, _metadataStoreRoutingData);
+  }
+
+  /**
+   * Test the persistent create() call against a valid path and an invalid path.
+   * Valid path is one that belongs to the realm designated by the sharding key.
+   * Invalid path is one that does not belong to the realm designated by the sharding key.
+   */
+  @Test(dependsOnMethods = "testRealmAwareZkClientCreation")
+  public void testRealmAwareZkClientCreatePersistent() {
+    _realmAwareZkClient.setZkSerializer(new ZNRecordSerializer());
+
+    // Create a dummy ZNRecord
+    ZNRecord znRecord = new ZNRecord("DummyRecord");
+    znRecord.setSimpleField("Dummy", "Value");
+
+    // Test writing and reading against the validPath
+    _realmAwareZkClient.createPersistent(TEST_VALID_PATH, true);
+    _realmAwareZkClient.writeData(TEST_VALID_PATH, znRecord);
+    Assert.assertEquals(_realmAwareZkClient.readData(TEST_VALID_PATH), znRecord);
+
+    // Test writing and reading against the invalid path
+    try {
+      _realmAwareZkClient.createPersistent(TEST_INVALID_PATH, true);
+      Assert.fail("Create() should not succeed on an invalid path!");
+    } catch (IllegalArgumentException e) {
+      // Okay - expected
+    }
+  }
+
+  /**
+   * Test that exists() works on valid path and fails on invalid path.
+   */
+  @Test(dependsOnMethods = "testRealmAwareZkClientCreatePersistent")
+  public void testExists() {
+    Assert.assertTrue(_realmAwareZkClient.exists(TEST_VALID_PATH));
+
+    try {
+      _realmAwareZkClient.exists(TEST_INVALID_PATH);
+      Assert.fail("Exists() should not succeed on an invalid path!");
+    } catch (IllegalArgumentException e) {
+      // Okay - expected
+    }
+  }
+
+  /**
+   * Test that delete() works on valid path and fails on invalid path.
+   */
+  @Test(dependsOnMethods = "testExists")
+  public void testDelete() {
+    try {
+      _realmAwareZkClient.delete(TEST_INVALID_PATH);
+      Assert.fail("Exists() should not succeed on an invalid path!");
+    } catch (IllegalArgumentException e) {
+      // Okay - expected
+    }
+
+    Assert.assertTrue(_realmAwareZkClient.delete(TEST_VALID_PATH));
+    Assert.assertFalse(_realmAwareZkClient.exists(TEST_VALID_PATH));
+  }
+}
\ No newline at end of file
diff --git a/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/TestDedicatedZkClient.java b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/TestDedicatedZkClient.java
new file mode 100644
index 0000000..8cf3f85
--- /dev/null
+++ b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/TestDedicatedZkClient.java
@@ -0,0 +1,35 @@
+package org.apache.helix.zookeeper.impl.client;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.helix.zookeeper.impl.factory.DedicatedZkClientFactory;
+import org.testng.annotations.BeforeClass;
+
+
+public class TestDedicatedZkClient extends RealmAwareZkClientTestBase {
+
+  @BeforeClass
+  public void beforeClass()
+      throws Exception {
+    super.beforeClass();
+    // Set the factory to DedicatedZkClientFactory
+    _realmAwareZkClientFactory = DedicatedZkClientFactory.getInstance();
+  }
+}


[helix] 01/49: Add MetadataStoreRoutingData interface and TrieRoutingData class to helix-rest

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit 3b8b6217235905e997d4483d9ea1121b4d0942a6
Author: Neal Sun <ne...@gmail.com>
AuthorDate: Fri Jan 31 11:00:51 2020 -0800

    Add MetadataStoreRoutingData interface and TrieRoutingData class to helix-rest
    
    The TrieRoutingData class constructs a trie-like structure for "metadata store sharding keys - metadata store realm addressses" pairs to allow faster lookup operations. The MetadataStoreRoutingData interface allows generalized access to routing data.
---
 .../metadatastore/MetadataStoreRoutingData.java    |  47 ++++++
 .../helix/rest/metadatastore/TrieRoutingData.java  | 159 ++++++++++++++++++++
 .../rest/metadatastore/TestTrieRoutingData.java    | 164 +++++++++++++++++++++
 3 files changed, 370 insertions(+)

diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/MetadataStoreRoutingData.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/MetadataStoreRoutingData.java
new file mode 100644
index 0000000..237e9b6
--- /dev/null
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/MetadataStoreRoutingData.java
@@ -0,0 +1,47 @@
+package org.apache.helix.rest.metadatastore;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+
+public interface MetadataStoreRoutingData {
+  /**
+   * Given a path, return all the "metadata store sharding key-metadata store realm address" pairs
+   * where the sharding keys contain the given path. For example, given "/a/b", return {"/a/b/c":
+   * "realm.address.c.com:1234", "/a/b/d": "realm.address.d.com:1234"} where "a/b/c" and "a/b/d" are
+   * sharding keys and the urls are realm addresses. If the path is invalid, returns an empty
+   * mapping.
+   * @param path - the path where the search is conducted
+   * @return all "sharding key-realm address" pairs where the sharding keys contain the given
+   *         path if the path is valid; empty mapping otherwise
+   */
+  Map<String, String> getAllMappingUnderPath(String path);
+
+  /**
+   * Given a path, return the realm address corresponding to the sharding key contained in the
+   * path. If the path doesn't contain a sharding key, throw NoSuchElementException.
+   * @param path - the path where the search is conducted
+   * @return the realm address corresponding to the sharding key contained in the path
+   * @throws NoSuchElementException - when the path doesn't contain a sharding key
+   */
+  String getMetadataStoreRealm(String path) throws NoSuchElementException;
+}
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/TrieRoutingData.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/TrieRoutingData.java
new file mode 100644
index 0000000..b89a5f9
--- /dev/null
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/TrieRoutingData.java
@@ -0,0 +1,159 @@
+package org.apache.helix.rest.metadatastore;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.ArrayDeque;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+/**
+ * This is a class that uses a data structure similar to trie to represent metadata store routing
+ * data. It is not exactly a trie because it in essence stores a mapping (from sharding keys to
+ * realm addresses) instead of pure text information; also, only the terminal nodes store meaningful
+ * information (realm addresses).
+ */
+public class TrieRoutingData implements MetadataStoreRoutingData {
+  private static final String DELIMITER = "/";
+
+  private final TrieNode _rootNode;
+
+  // TODO: THIS IS A TEMPORARY PLACEHOLDER. A proper constructor will be created, which will not
+  // take in a TrieNode; it instead initializes the rootNode and creates a trie based on
+  // some input data. The constructor is blocked by the implementation of RoutingDataAccessor, and
+  // will therefore be implemented later.
+  public TrieRoutingData(TrieNode rootNode) {
+    _rootNode = rootNode;
+  }
+
+  public Map<String, String> getAllMappingUnderPath(String path) {
+    TrieNode curNode;
+    try {
+      curNode = findTrieNode(path, false);
+    } catch (NoSuchElementException e) {
+      return Collections.emptyMap();
+    }
+
+    Map<String, String> resultMap = new HashMap<>();
+    Deque<TrieNode> nodeStack = new ArrayDeque<>();
+    nodeStack.push(curNode);
+    while (!nodeStack.isEmpty()) {
+      curNode = nodeStack.pop();
+      if (curNode._isLeaf) {
+        resultMap.put(curNode._name, curNode._realmAddress);
+      } else {
+        for (TrieNode child : curNode._children.values()) {
+          nodeStack.push(child);
+        }
+      }
+    }
+    return resultMap;
+  }
+
+  public String getMetadataStoreRealm(String path) throws NoSuchElementException {
+    TrieNode leafNode = findTrieNode(path, true);
+    return leafNode._realmAddress;
+  }
+
+  /**
+   * If findLeafAlongPath is false, then starting from the root node, find the trie node that the
+   * given path is pointing to and return it; raise NoSuchElementException if the path does
+   * not point to any node. If findLeafAlongPath is true, then starting from the root node, find the
+   * leaf node along the provided path; raise NoSuchElementException if the path does not
+   * point to any node or if there is no leaf node along the path.
+   * @param path - the path where the search is conducted
+   * @param findLeafAlongPath - whether the search is for a leaf node on the path
+   * @return the node pointed by the path or a leaf node along the path
+   * @throws NoSuchElementException - when the path points to nothing or when no leaf node is
+   *           found
+   */
+  private TrieNode findTrieNode(String path, boolean findLeafAlongPath)
+      throws NoSuchElementException {
+    if (path.equals(DELIMITER) || path.equals("")) {
+      if (findLeafAlongPath && !_rootNode._isLeaf) {
+        throw new NoSuchElementException("No leaf node found along the path. Path: " + path);
+      }
+      return _rootNode;
+    }
+
+    String[] splitPath;
+    if (path.substring(0, 1).equals(DELIMITER)) {
+      splitPath = path.substring(1).split(DELIMITER, 0);
+    } else {
+      splitPath = path.split(DELIMITER, 0);
+    }
+
+    TrieNode curNode = _rootNode;
+    if (findLeafAlongPath && curNode._isLeaf) {
+      return curNode;
+    }
+    Map<String, TrieNode> curChildren = curNode._children;
+    for (String pathSection : splitPath) {
+      curNode = curChildren.get(pathSection);
+      if (curNode == null) {
+        throw new NoSuchElementException(
+            "The provided path is missing from the trie. Path: " + path);
+      }
+      if (findLeafAlongPath && curNode._isLeaf) {
+        return curNode;
+      }
+      curChildren = curNode._children;
+    }
+    if (findLeafAlongPath) {
+      throw new NoSuchElementException("No leaf node found along the path. Path: " + path);
+    }
+    return curNode;
+  }
+
+  // TODO: THE CLASS WILL BE CHANGED TO PRIVATE ONCE THE CONSTRUCTOR IS CREATED.
+  static class TrieNode {
+    /**
+     * This field is a mapping between trie key and children nodes. For example, node "a" has
+     * children "ab" and "ac", therefore the keys are "b" and "c" respectively.
+     */
+    Map<String, TrieNode> _children;
+    /**
+     * This field means if the node is a terminal node in the tree sense, not the trie sense. Any
+     * node that has children cannot possibly be a leaf node because only the node without children
+     * can store information. If a node is leaf, then it shouldn't have any children.
+     */
+    final boolean _isLeaf;
+    /**
+     * This field aligns the traditional trie design: it entails the complete path/prefix leading to
+     * the current node. For example, the name of root node is "/", then the name of its child node
+     * is "/a", and the name of the child's child node is "/a/b".
+     */
+    final String _name;
+    /**
+     * This field represents the data contained in a node(which represents a path), and is only
+     * available to the terminal nodes.
+     */
+    final String _realmAddress;
+
+    TrieNode(Map<String, TrieNode> children, String name, boolean isLeaf, String realmAddress) {
+      _children = children;
+      _isLeaf = isLeaf;
+      _name = name;
+      _realmAddress = realmAddress;
+    }
+  }
+}
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestTrieRoutingData.java b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestTrieRoutingData.java
new file mode 100644
index 0000000..1b1754d
--- /dev/null
+++ b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestTrieRoutingData.java
@@ -0,0 +1,164 @@
+package org.apache.helix.rest.metadatastore;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class TestTrieRoutingData {
+  // TODO: add constructor related tests after constructor is finished
+
+  @Test
+  public void testGetAllMappingUnderPathFromRoot() {
+    TrieRoutingData trie = constructTestTrie();
+    Map<String, String> result = trie.getAllMappingUnderPath("/");
+    Assert.assertEquals(result.size(), 4);
+    Assert.assertEquals(result.get("/b/c/d"), "realmAddressD");
+    Assert.assertEquals(result.get("/b/c/e"), "realmAddressE");
+    Assert.assertEquals(result.get("/b/f"), "realmAddressF");
+    Assert.assertEquals(result.get("/g"), "realmAddressG");
+  }
+
+  @Test
+  public void testGetAllMappingUnderPathFromRootEmptyPath() {
+    TrieRoutingData trie = constructTestTrie();
+    Map<String, String> result = trie.getAllMappingUnderPath("");
+    Assert.assertEquals(result.size(), 4);
+    Assert.assertEquals(result.get("/b/c/d"), "realmAddressD");
+    Assert.assertEquals(result.get("/b/c/e"), "realmAddressE");
+    Assert.assertEquals(result.get("/b/f"), "realmAddressF");
+    Assert.assertEquals(result.get("/g"), "realmAddressG");
+  }
+
+  @Test
+  public void testGetAllMappingUnderPathFromSecondLevel() {
+    TrieRoutingData trie = constructTestTrie();
+    Map<String, String> result = trie.getAllMappingUnderPath("/b");
+    Assert.assertEquals(result.size(), 3);
+    Assert.assertEquals(result.get("/b/c/d"), "realmAddressD");
+    Assert.assertEquals(result.get("/b/c/e"), "realmAddressE");
+    Assert.assertEquals(result.get("/b/f"), "realmAddressF");
+  }
+
+  @Test
+  public void testGetAllMappingUnderPathFromLeaf() {
+    TrieRoutingData trie = constructTestTrie();
+    Map<String, String> result = trie.getAllMappingUnderPath("/b/c/d");
+    Assert.assertEquals(result.size(), 1);
+    Assert.assertEquals(result.get("/b/c/d"), "realmAddressD");
+  }
+
+  @Test
+  public void testGetAllMappingUnderPathWrongPath() {
+    TrieRoutingData trie = constructTestTrie();
+    Map<String, String> result = trie.getAllMappingUnderPath("/b/c/d/g");
+    Assert.assertEquals(result.size(), 0);
+  }
+
+  @Test
+  public void testGetMetadataStoreRealm() {
+    TrieRoutingData trie = constructTestTrie();
+    try {
+      Assert.assertEquals(trie.getMetadataStoreRealm("/b/c/d/x/y/z"), "realmAddressD");
+    } catch (NoSuchElementException e) {
+      Assert.fail("Not expecting NoSuchElementException");
+    }
+  }
+
+  @Test
+  public void testGetMetadataStoreRealmNoSlash() {
+    TrieRoutingData trie = constructTestTrie();
+    try {
+      Assert.assertEquals(trie.getMetadataStoreRealm("b/c/d/x/y/z"), "realmAddressD");
+    } catch (NoSuchElementException e) {
+      Assert.fail("Not expecting NoSuchElementException");
+    }
+  }
+
+  @Test
+  public void testGetMetadataStoreRealmWrongPath() {
+    TrieRoutingData trie = constructTestTrie();
+    try {
+      trie.getMetadataStoreRealm("/x/y/z");
+      Assert.fail("Expecting NoSuchElementException");
+    } catch (NoSuchElementException e) {
+      Assert.assertTrue(e.getMessage().contains("The provided path is missing from the trie. Path: /x/y/z"));
+    }
+  }
+
+  @Test
+  public void testGetMetadataStoreRealmNoLeaf() {
+    TrieRoutingData trie = constructTestTrie();
+    try {
+      trie.getMetadataStoreRealm("/b/c");
+      Assert.fail("Expecting NoSuchElementException");
+    } catch (NoSuchElementException e) {
+      Assert.assertTrue(e.getMessage().contains("No leaf node found along the path. Path: /b/c"));
+    }
+  }
+
+  /**
+   * Constructing a trie for testing purposes
+   * -----<empty>
+   * ------/--\
+   * -----b---g
+   * ----/-\
+   * ---c--f
+   * --/-\
+   * -d--e
+   */
+  private TrieRoutingData constructTestTrie() {
+    TrieRoutingData.TrieNode nodeD =
+        new TrieRoutingData.TrieNode(Collections.emptyMap(), "/b/c/d", true, "realmAddressD");
+    TrieRoutingData.TrieNode nodeE =
+        new TrieRoutingData.TrieNode(Collections.emptyMap(), "/b/c/e", true, "realmAddressE");
+    TrieRoutingData.TrieNode nodeF =
+        new TrieRoutingData.TrieNode(Collections.emptyMap(), "/b/f", true, "realmAddressF");
+    TrieRoutingData.TrieNode nodeG =
+        new TrieRoutingData.TrieNode(Collections.emptyMap(), "/g", true, "realmAddressG");
+    TrieRoutingData.TrieNode nodeC =
+        new TrieRoutingData.TrieNode(new HashMap<String, TrieRoutingData.TrieNode>() {
+          {
+            put("d", nodeD);
+            put("e", nodeE);
+          }
+        }, "c", false, "");
+    TrieRoutingData.TrieNode nodeB =
+        new TrieRoutingData.TrieNode(new HashMap<String, TrieRoutingData.TrieNode>() {
+          {
+            put("c", nodeC);
+            put("f", nodeF);
+          }
+        }, "b", false, "");
+    TrieRoutingData.TrieNode root =
+        new TrieRoutingData.TrieNode(new HashMap<String, TrieRoutingData.TrieNode>() {
+          {
+            put("b", nodeB);
+            put("g", nodeG);
+          }
+        }, "", false, "");
+
+    return new TrieRoutingData(root);
+  }
+}


[helix] 47/49: Make Helix REST realm-aware (#908)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit c7cdf986d9303ace339ae6497e9aa7c884562fb7
Author: Hunter Lee <hu...@linkedin.com>
AuthorDate: Wed Mar 25 22:20:10 2020 -0700

    Make Helix REST realm-aware (#908)
    
    Helix REST needs to start using a realm-aware ZkClient on multi-zk mode. Also it needs to become a listener on routing data because we don't want to restart the HelixRestServer every time we update the routing data.
    
    Changelist:
    
    Make ServerContext listen on routing data paths if run on multi-zk mode
    Make HelixRestServer use RealmAwareZkClient (FederatedZkClient) on multi-zk mode
---
 .../java/org/apache/helix/task/TaskDriver.java     |   6 +-
 .../helix/rest/common/HelixRestNamespace.java      |  24 +-
 .../metadatastore/ZkMetadataStoreDirectory.java    |  55 +++-
 .../accessor/ZkRoutingDataReader.java              |  37 +--
 .../accessor/ZkRoutingDataWriter.java              |  17 +-
 .../apache/helix/rest/server/HelixRestMain.java    |   6 +-
 .../apache/helix/rest/server/HelixRestServer.java  |   2 +-
 .../apache/helix/rest/server/ServerContext.java    | 289 +++++++++++++++++----
 .../resources/helix/AbstractHelixResource.java     |  14 +-
 .../server/resources/helix/ClusterAccessor.java    |  11 +-
 .../server/resources/helix/ResourceAccessor.java   |  10 +-
 .../integration/TestRoutingDataUpdate.java         | 176 +++++++++++++
 .../helix/rest/server/AbstractTestClass.java       |  40 +--
 .../constant/MetadataStoreRoutingConstants.java    |   2 +
 .../helix/msdcommon/util/ZkValidationUtil.java     |   3 +-
 pom.xml                                            |  34 ---
 .../zookeeper/api/client/RealmAwareZkClient.java   |  16 ++
 .../zookeeper/util/HttpRoutingDataReader.java      |  14 +-
 .../impl/client/RealmAwareZkClientTestBase.java    |   2 +-
 19 files changed, 570 insertions(+), 188 deletions(-)

diff --git a/helix-core/src/main/java/org/apache/helix/task/TaskDriver.java b/helix-core/src/main/java/org/apache/helix/task/TaskDriver.java
index 987cc44..506a06b 100644
--- a/helix-core/src/main/java/org/apache/helix/task/TaskDriver.java
+++ b/helix-core/src/main/java/org/apache/helix/task/TaskDriver.java
@@ -47,7 +47,7 @@ import org.apache.helix.model.builder.CustomModeISBuilder;
 import org.apache.helix.store.HelixPropertyStore;
 import org.apache.helix.store.zk.ZkHelixPropertyStore;
 import org.apache.helix.util.HelixUtil;
-import org.apache.helix.zookeeper.api.client.HelixZkClient;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.apache.helix.zookeeper.zkclient.DataUpdater;
 import org.slf4j.Logger;
@@ -98,12 +98,12 @@ public class TaskDriver {
   }
 
   @Deprecated
-  public TaskDriver(HelixZkClient client, String clusterName) {
+  public TaskDriver(RealmAwareZkClient client, String clusterName) {
     this(client, new ZkBaseDataAccessor<>(client), clusterName);
   }
 
   @Deprecated
-  public TaskDriver(HelixZkClient client, ZkBaseDataAccessor<ZNRecord> baseAccessor,
+  public TaskDriver(RealmAwareZkClient client, ZkBaseDataAccessor<ZNRecord> baseAccessor,
       String clusterName) {
     this(new ZKHelixAdmin(client), new ZKHelixDataAccessor(clusterName, baseAccessor),
         new ZkHelixPropertyStore<>(baseAccessor, PropertyPathBuilder.propertyStore(clusterName),
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/common/HelixRestNamespace.java b/helix-rest/src/main/java/org/apache/helix/rest/common/HelixRestNamespace.java
index a2fb52c..0632f36 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/common/HelixRestNamespace.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/common/HelixRestNamespace.java
@@ -34,7 +34,8 @@ public class HelixRestNamespace {
     NAME,
     METADATA_STORE_TYPE,
     METADATA_STORE_ADDRESS,
-    IS_DEFAULT
+    IS_DEFAULT,
+    MSDS_ENDPOINT
   }
 
   /**
@@ -55,7 +56,7 @@ public class HelixRestNamespace {
   private HelixMetadataStoreType _metadataStoreType;
 
   /**
-   * Address of metadata store. Should be informat of
+   * Address of metadata store. Should be in the format of
    * "[ip-address]:[port]" or "[dns-name]:[port]"
    */
   private String _metadataStoreAddress;
@@ -65,16 +66,27 @@ public class HelixRestNamespace {
    */
   private boolean _isDefault;
 
+  /**
+   * Endpoint for accessing MSDS for this namespace.
+   */
+  private String _msdsEndpoint;
+
   public HelixRestNamespace(String metadataStoreAddress) throws IllegalArgumentException {
     this(DEFAULT_NAMESPACE_NAME, HelixMetadataStoreType.ZOOKEEPER, metadataStoreAddress, true);
   }
 
-  public HelixRestNamespace(String name, HelixMetadataStoreType metadataStoreType, String metadataStoreAddress, boolean isDefault)
-      throws IllegalArgumentException {
+  public HelixRestNamespace(String name, HelixMetadataStoreType metadataStoreType,
+      String metadataStoreAddress, boolean isDefault) throws IllegalArgumentException {
+    this(name, metadataStoreType, metadataStoreAddress, isDefault, null);
+  }
+
+  public HelixRestNamespace(String name, HelixMetadataStoreType metadataStoreType,
+      String metadataStoreAddress, boolean isDefault, String msdsEndpoint) {
     _name = name;
     _metadataStoreAddress = metadataStoreAddress;
     _metadataStoreType = metadataStoreType;
     _isDefault = isDefault;
+    _msdsEndpoint = msdsEndpoint;
     validate();
   }
 
@@ -109,4 +121,8 @@ public class HelixRestNamespace {
     ret.put(HelixRestNamespaceProperty.IS_DEFAULT.name(), String.valueOf(_isDefault));
     return ret;
   }
+
+  public String getMsdsEndpoint() {
+    return _msdsEndpoint;
+  }
 }
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java
index c83245f..42b2b17 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java
@@ -39,6 +39,7 @@ import org.apache.helix.rest.metadatastore.accessor.MetadataStoreRoutingDataWrit
 import org.apache.helix.rest.metadatastore.accessor.ZkRoutingDataReader;
 import org.apache.helix.rest.metadatastore.accessor.ZkRoutingDataWriter;
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
+import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
 import org.apache.helix.zookeeper.impl.factory.DedicatedZkClientFactory;
 import org.apache.helix.zookeeper.zkclient.exception.ZkNodeExistsException;
@@ -98,20 +99,27 @@ public class ZkMetadataStoreDirectory implements MetadataStoreDirectory, Routing
     if (!_routingZkAddressMap.containsKey(namespace)) {
       synchronized (_routingZkAddressMap) {
         if (!_routingZkAddressMap.containsKey(namespace)) {
-          // Ensure that ROUTING_DATA_PATH exists in ZK.
-          HelixZkClient zkClient = DedicatedZkClientFactory.getInstance()
-              .buildZkClient(new HelixZkClient.ZkConnectionConfig(zkAddress),
-                  new HelixZkClient.ZkClientConfig().setZkSerializer(new ZNRecordSerializer()));
+          HelixZkClient zkClient = null;
           try {
-            zkClient.createPersistent(MetadataStoreRoutingConstants.ROUTING_DATA_PATH, true);
-          } catch (ZkNodeExistsException e) {
-            // The node already exists and it's okay
+            // Ensure that ROUTING_DATA_PATH exists in ZK.
+            zkClient = DedicatedZkClientFactory.getInstance()
+                .buildZkClient(new HelixZkClient.ZkConnectionConfig(zkAddress),
+                    new HelixZkClient.ZkClientConfig().setZkSerializer(new ZNRecordSerializer()));
+            createRoutingDataPath(zkClient, zkAddress);
+          } finally {
+            if (zkClient != null && !zkClient.isClosed()) {
+              zkClient.close();
+            }
+          }
+          try {
+            _routingZkAddressMap.put(namespace, zkAddress);
+            _routingDataReaderMap
+                .put(namespace, new ZkRoutingDataReader(namespace, zkAddress, this));
+            _routingDataWriterMap.put(namespace, new ZkRoutingDataWriter(namespace, zkAddress));
+          } catch (IllegalArgumentException | IllegalStateException e) {
+            LOG.error("ZkMetadataStoreDirectory: initializing ZkRoutingDataReader/Writer failed!",
+                e);
           }
-
-          _routingZkAddressMap.put(namespace, zkAddress);
-          _routingDataReaderMap.put(namespace, new ZkRoutingDataReader(namespace, zkAddress, this));
-          _routingDataWriterMap.put(namespace, new ZkRoutingDataWriter(namespace, zkAddress));
-
           // Populate realmToShardingKeys with ZkRoutingDataReader
           Map<String, List<String>> rawRoutingData =
               _routingDataReaderMap.get(namespace).getRoutingData();
@@ -119,7 +127,8 @@ public class ZkMetadataStoreDirectory implements MetadataStoreDirectory, Routing
           try {
             _routingDataMap.put(namespace, new TrieRoutingData(rawRoutingData));
           } catch (InvalidRoutingDataException e) {
-            LOG.warn("TrieRoutingData is not created for namespace {}", namespace, e);
+            LOG.warn("ZkMetadataStoreDirectory: TrieRoutingData is not created for namespace {}",
+                namespace, e);
           }
         }
       }
@@ -145,7 +154,7 @@ public class ZkMetadataStoreDirectory implements MetadataStoreDirectory, Routing
       throw new NoSuchElementException("Namespace " + namespace + " does not exist!");
     }
     Set<String> allShardingKeys = new HashSet<>();
-    _realmToShardingKeysMap.get(namespace).values().forEach(keys -> allShardingKeys.addAll(keys));
+    _realmToShardingKeysMap.get(namespace).values().forEach(allShardingKeys::addAll);
     return allShardingKeys;
   }
 
@@ -339,4 +348,22 @@ public class ZkMetadataStoreDirectory implements MetadataStoreDirectory, Routing
     _routingDataMap.clear();
     _zkMetadataStoreDirectoryInstance = null;
   }
+
+  /**
+   * Make sure the root routing data path exists. Also, register the routing ZK address.
+   * @param zkClient
+   */
+  public static void createRoutingDataPath(HelixZkClient zkClient, String zkAddress) {
+    try {
+      zkClient.createPersistent(MetadataStoreRoutingConstants.ROUTING_DATA_PATH, true);
+    } catch (ZkNodeExistsException e) {
+      // The node already exists and it's okay
+    }
+    // Make sure ROUTING_DATA_PATH is mapped to the routing ZK so that FederatedZkClient used
+    // in Helix REST can subscribe to the routing data path
+    ZNRecord znRecord = new ZNRecord(MetadataStoreRoutingConstants.ROUTING_DATA_PATH.substring(1));
+    znRecord.setListField(MetadataStoreRoutingConstants.ROUTING_ZK_ADDRESS_KEY,
+        Collections.singletonList(zkAddress));
+    zkClient.writeData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH, znRecord);
+  }
 }
\ No newline at end of file
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataReader.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataReader.java
index 6c75618..cfe6eb5 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataReader.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataReader.java
@@ -27,7 +27,9 @@ import java.util.Map;
 import org.apache.helix.msdcommon.callback.RoutingDataListener;
 import org.apache.helix.msdcommon.constant.MetadataStoreRoutingConstants;
 import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
+import org.apache.helix.rest.metadatastore.ZkMetadataStoreDirectory;
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
 import org.apache.helix.zookeeper.impl.factory.DedicatedZkClientFactory;
@@ -35,7 +37,6 @@ import org.apache.helix.zookeeper.zkclient.IZkChildListener;
 import org.apache.helix.zookeeper.zkclient.IZkDataListener;
 import org.apache.helix.zookeeper.zkclient.IZkStateListener;
 import org.apache.helix.zookeeper.zkclient.exception.ZkNoNodeException;
-import org.apache.helix.zookeeper.zkclient.exception.ZkNodeExistsException;
 import org.apache.zookeeper.Watcher;
 
 
@@ -59,24 +60,11 @@ public class ZkRoutingDataReader implements MetadataStoreRoutingDataReader, IZkD
         .buildZkClient(new HelixZkClient.ZkConnectionConfig(zkAddress),
             new HelixZkClient.ZkClientConfig().setZkSerializer(new ZNRecordSerializer()));
 
-    // Ensure that ROUTING_DATA_PATH exists in ZK. If not, create
-    // create() semantic will fail if it already exists
-    try {
-      _zkClient.createPersistent(MetadataStoreRoutingConstants.ROUTING_DATA_PATH, true);
-    } catch (ZkNodeExistsException e) {
-      // This is okay
-    }
+    ZkMetadataStoreDirectory.createRoutingDataPath(_zkClient, _zkAddress);
 
     _routingDataListener = routingDataListener;
     if (_routingDataListener != null) {
-      // Subscribe child changes
-      _zkClient.subscribeChildChanges(MetadataStoreRoutingConstants.ROUTING_DATA_PATH, this);
-      // Subscribe data changes
-      for (String child : _zkClient.getChildren(MetadataStoreRoutingConstants.ROUTING_DATA_PATH)) {
-        _zkClient
-            .subscribeDataChanges(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + child,
-                this);
-      }
+      _zkClient.subscribeRoutingDataChanges(this, this);
     }
   }
 
@@ -118,7 +106,7 @@ public class ZkRoutingDataReader implements MetadataStoreRoutingDataReader, IZkD
 
   @Override
   public synchronized void handleDataChange(String s, Object o) {
-    if (_zkClient.isClosed()) {
+    if (_zkClient == null || _zkClient.isClosed()) {
       return;
     }
     _routingDataListener.refreshRoutingData(_namespace);
@@ -138,7 +126,7 @@ public class ZkRoutingDataReader implements MetadataStoreRoutingDataReader, IZkD
 
   @Override
   public synchronized void handleStateChanged(Watcher.Event.KeeperState state) {
-    if (_zkClient.isClosed()) {
+    if (_zkClient == null || _zkClient.isClosed()) {
       return;
     }
     _routingDataListener.refreshRoutingData(_namespace);
@@ -146,7 +134,7 @@ public class ZkRoutingDataReader implements MetadataStoreRoutingDataReader, IZkD
 
   @Override
   public synchronized void handleNewSession(String sessionId) {
-    if (_zkClient.isClosed()) {
+    if (_zkClient == null || _zkClient.isClosed()) {
       return;
     }
     _routingDataListener.refreshRoutingData(_namespace);
@@ -154,24 +142,19 @@ public class ZkRoutingDataReader implements MetadataStoreRoutingDataReader, IZkD
 
   @Override
   public synchronized void handleSessionEstablishmentError(Throwable error) {
-    if (_zkClient.isClosed()) {
+    if (_zkClient == null || _zkClient.isClosed()) {
       return;
     }
     _routingDataListener.refreshRoutingData(_namespace);
   }
 
   private void handleResubscription() {
-    if (_zkClient.isClosed()) {
+    if (_zkClient == null || _zkClient.isClosed()) {
       return;
     }
-
     // Renew subscription
     _zkClient.unsubscribeAll();
-    _zkClient.subscribeChildChanges(MetadataStoreRoutingConstants.ROUTING_DATA_PATH, this);
-    for (String child : _zkClient.getChildren(MetadataStoreRoutingConstants.ROUTING_DATA_PATH)) {
-      _zkClient.subscribeDataChanges(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + child,
-          this);
-    }
+    _zkClient.subscribeRoutingDataChanges(this, this);
     _routingDataListener.refreshRoutingData(_namespace);
   }
 }
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataWriter.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataWriter.java
index 32b7681..791d9bb 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataWriter.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataWriter.java
@@ -30,6 +30,7 @@ import javax.ws.rs.core.Response;
 
 import org.apache.helix.msdcommon.constant.MetadataStoreRoutingConstants;
 import org.apache.helix.rest.common.HttpConstants;
+import org.apache.helix.rest.metadatastore.ZkMetadataStoreDirectory;
 import org.apache.helix.rest.metadatastore.concurrency.ZkDistributedLeaderElection;
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
@@ -82,20 +83,16 @@ public class ZkRoutingDataWriter implements MetadataStoreRoutingDataWriter {
         .buildZkClient(new HelixZkClient.ZkConnectionConfig(zkAddress),
             new HelixZkClient.ZkClientConfig().setZkSerializer(new ZNRecordSerializer()));
 
-    // Ensure that ROUTING_DATA_PATH exists in ZK. If not, create
-    // create() semantic will fail if it already exists
-    try {
-      _zkClient.createPersistent(MetadataStoreRoutingConstants.ROUTING_DATA_PATH, true);
-    } catch (ZkNodeExistsException e) {
-      // This is okay
-    }
+    ZkMetadataStoreDirectory.createRoutingDataPath(_zkClient, zkAddress);
 
     // Get the hostname (REST endpoint) from System property
     String hostName = System.getProperty(MetadataStoreRoutingConstants.MSDS_SERVER_HOSTNAME_KEY);
     if (hostName == null || hostName.isEmpty()) {
-      throw new IllegalStateException(
-          "Hostname is not set or is empty. System.getProperty fails to fetch "
-              + MetadataStoreRoutingConstants.MSDS_SERVER_HOSTNAME_KEY + ".");
+      String errMsg =
+          "ZkRoutingDataWriter: Hostname is not set or is empty. System.getProperty fails to fetch "
+              + MetadataStoreRoutingConstants.MSDS_SERVER_HOSTNAME_KEY;
+      LOG.error(errMsg);
+      throw new IllegalStateException(errMsg);
     }
     _myHostName = HttpConstants.HTTP_PROTOCOL_PREFIX + hostName;
 
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/server/HelixRestMain.java b/helix-rest/src/main/java/org/apache/helix/rest/server/HelixRestMain.java
index b28f227..49940c3 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/server/HelixRestMain.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/server/HelixRestMain.java
@@ -146,10 +146,12 @@ public class HelixRestMain {
       // Currently we don't support adding default namespace through yaml manifest so all
       // namespaces created here will not be default
       // TODO: support specifying default namespace from config file
-      namespaces.add(new HelixRestNamespace(config.get(HelixRestNamespace.HelixRestNamespaceProperty.NAME.name()),
+      namespaces.add(new HelixRestNamespace(
+          config.get(HelixRestNamespace.HelixRestNamespaceProperty.NAME.name()),
           HelixRestNamespace.HelixMetadataStoreType.valueOf(
               config.get(HelixRestNamespace.HelixRestNamespaceProperty.METADATA_STORE_TYPE.name())),
-          config.get(HelixRestNamespace.HelixRestNamespaceProperty.METADATA_STORE_ADDRESS.name()), false));
+          config.get(HelixRestNamespace.HelixRestNamespaceProperty.METADATA_STORE_ADDRESS.name()),
+          false, config.get(HelixRestNamespace.HelixRestNamespaceProperty.MSDS_ENDPOINT.name())));
     }
   }
 
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/server/HelixRestServer.java b/helix-rest/src/main/java/org/apache/helix/rest/server/HelixRestServer.java
index 64c1139..e6b5b34 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/server/HelixRestServer.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/server/HelixRestServer.java
@@ -149,7 +149,7 @@ public class HelixRestServer {
     // Enable the default statistical monitoring MBean for Jersey server
     cfg.property(ServerProperties.MONITORING_STATISTICS_MBEANS_ENABLED, true);
     cfg.property(ContextPropertyKeys.SERVER_CONTEXT.name(),
-        new ServerContext(namespace.getMetadataStoreAddress()));
+        new ServerContext(namespace.getMetadataStoreAddress(), namespace.getMsdsEndpoint()));
     if (type == ServletType.DEFAULT_SERVLET) {
       cfg.property(ContextPropertyKeys.ALL_NAMESPACES.name(), _helixNamespaces);
     } else {
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/server/ServerContext.java b/helix-rest/src/main/java/org/apache/helix/rest/server/ServerContext.java
index b845356..52f1738 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/server/ServerContext.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/server/ServerContext.java
@@ -20,34 +20,53 @@ package org.apache.helix.rest.server;
  * under the License.
  */
 
-import java.util.HashMap;
+import java.io.IOException;
+import java.util.List;
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
 import org.apache.helix.ConfigAccessor;
 import org.apache.helix.HelixAdmin;
 import org.apache.helix.HelixDataAccessor;
+import org.apache.helix.HelixException;
 import org.apache.helix.InstanceType;
-import org.apache.helix.rest.metadatastore.ZkMetadataStoreDirectory;
-import org.apache.helix.zookeeper.datamodel.ZNRecord;
+import org.apache.helix.SystemPropertyKeys;
 import org.apache.helix.manager.zk.ZKHelixAdmin;
 import org.apache.helix.manager.zk.ZKHelixDataAccessor;
-import org.apache.helix.manager.zk.ZNRecordSerializer;
 import org.apache.helix.manager.zk.ZkBaseDataAccessor;
-import org.apache.helix.zookeeper.impl.client.ZkClient;
-import org.apache.helix.zookeeper.api.client.HelixZkClient;
-import org.apache.helix.zookeeper.impl.factory.SharedZkClientFactory;
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
+import org.apache.helix.rest.metadatastore.ZkMetadataStoreDirectory;
 import org.apache.helix.task.TaskDriver;
 import org.apache.helix.tools.ClusterSetup;
+import org.apache.helix.zookeeper.api.client.HelixZkClient;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
+import org.apache.helix.zookeeper.datamodel.ZNRecord;
+import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
+import org.apache.helix.zookeeper.impl.client.FederatedZkClient;
+import org.apache.helix.zookeeper.impl.client.ZkClient;
+import org.apache.helix.zookeeper.impl.factory.DedicatedZkClientFactory;
+import org.apache.helix.zookeeper.impl.factory.SharedZkClientFactory;
+import org.apache.helix.zookeeper.util.HttpRoutingDataReader;
+import org.apache.helix.zookeeper.zkclient.IZkChildListener;
+import org.apache.helix.zookeeper.zkclient.IZkDataListener;
+import org.apache.helix.zookeeper.zkclient.IZkStateListener;
 import org.apache.helix.zookeeper.zkclient.exception.ZkMarshallingError;
 import org.apache.helix.zookeeper.zkclient.serialize.ZkSerializer;
+import org.apache.zookeeper.Watcher;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 
+public class ServerContext implements IZkDataListener, IZkChildListener, IZkStateListener {
+  private static final Logger LOG = LoggerFactory.getLogger(ServerContext.class);
 
-public class ServerContext {
   private final String _zkAddr;
-  private HelixZkClient _zkClient;
-  private ZKHelixAdmin _zkHelixAdmin;
-  private ClusterSetup _clusterSetup;
-  private ConfigAccessor _configAccessor;
+  private final String _msdsEndpoint;
+  private volatile RealmAwareZkClient _zkClient;
+
+  private volatile ZKHelixAdmin _zkHelixAdmin;
+  private volatile ClusterSetup _clusterSetup;
+  private volatile ConfigAccessor _configAccessor;
   // A lazily-initialized base data accessor that reads/writes byte array to ZK
   // TODO: Only read (deserialize) is supported at this time. This baseDataAccessor should support write (serialize) as needs arise
   private volatile ZkBaseDataAccessor<byte[]> _byteArrayZkBaseDataAccessor;
@@ -55,76 +74,149 @@ public class ServerContext {
   private final Map<String, HelixDataAccessor> _helixDataAccessorPool;
   // 1 Cluster name will correspond to 1 task driver
   private final Map<String, TaskDriver> _taskDriverPool;
+
+  /**
+   * Multi-ZK support
+   */
   private ZkMetadataStoreDirectory _zkMetadataStoreDirectory;
+  // Create a dedicated ZkClient for listening to data changes in routing data
+  private RealmAwareZkClient _zkClientForListener;
 
   public ServerContext(String zkAddr) {
+    this(zkAddr, null);
+  }
+
+  /**
+   * Initializes a ServerContext for this namespace.
+   * @param zkAddr routing ZK address (on multi-zk mode)
+   * @param msdsEndpoint if given, this server context will try to read routing data from this MSDS.
+   */
+  public ServerContext(String zkAddr, String msdsEndpoint) {
     _zkAddr = zkAddr;
+    _msdsEndpoint = msdsEndpoint; // only applicable on multi-zk mode
 
     // We should NOT initiate _zkClient and anything that depends on _zkClient in
     // constructor, as it is reasonable to start up HelixRestServer first and then
     // ZooKeeper. In this case, initializing _zkClient will fail and HelixRestServer
     // cannot be started correctly.
-    _helixDataAccessorPool = new HashMap<>();
-    _taskDriverPool = new HashMap<>();
+    _helixDataAccessorPool = new ConcurrentHashMap<>();
+    _taskDriverPool = new ConcurrentHashMap<>();
+
     // Initialize the singleton ZkMetadataStoreDirectory instance to allow it to be closed later
     _zkMetadataStoreDirectory = ZkMetadataStoreDirectory.getInstance();
   }
 
-  public HelixZkClient getHelixZkClient() {
+  public RealmAwareZkClient getRealmAwareZkClient() {
     if (_zkClient == null) {
-      HelixZkClient.ZkClientConfig clientConfig = new HelixZkClient.ZkClientConfig();
-      clientConfig.setZkSerializer(new ZNRecordSerializer());
-      _zkClient = SharedZkClientFactory.getInstance()
-          .buildZkClient(new HelixZkClient.ZkConnectionConfig(_zkAddr), clientConfig);
+      synchronized (this) {
+        if (_zkClient == null) {
+          // If the multi ZK config is enabled, use FederatedZkClient on multi-realm mode
+          if (Boolean.parseBoolean(System.getProperty(SystemPropertyKeys.MULTI_ZK_ENABLED))) {
+            LOG.info("ServerContext: initializing FederatedZkClient with routing ZK at {}!",
+                _zkAddr);
+            try {
+              RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder connectionConfigBuilder =
+                  new RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder();
+              // If MSDS endpoint is set for this namespace, use that instead.
+              if (_msdsEndpoint != null && !_msdsEndpoint.isEmpty()) {
+                connectionConfigBuilder.setMsdsEndpoint(_msdsEndpoint);
+              }
+              _zkClient = new FederatedZkClient(connectionConfigBuilder.build(),
+                  new RealmAwareZkClient.RealmAwareZkClientConfig()
+                      .setZkSerializer(new ZNRecordSerializer()));
+
+              // Make sure the ServerContext is subscribed to routing data change so that it knows
+              // when to reset ZkClient and Helix APIs
+              if (_zkClientForListener == null) {
+                _zkClientForListener = DedicatedZkClientFactory.getInstance()
+                    .buildZkClient(new HelixZkClient.ZkConnectionConfig(_zkAddr),
+                        new HelixZkClient.ZkClientConfig()
+                            .setZkSerializer(new ZNRecordSerializer()));
+              }
+              // Refresh data subscription
+              _zkClientForListener.unsubscribeAll();
+              _zkClientForListener.subscribeRoutingDataChanges(this, this);
+            } catch (IOException | InvalidRoutingDataException | IllegalStateException e) {
+              throw new HelixException("Failed to create FederatedZkClient!", e);
+            }
+          } else {
+            // If multi ZK config is not set, just connect to the ZK address given
+            HelixZkClient.ZkClientConfig clientConfig = new HelixZkClient.ZkClientConfig();
+            clientConfig.setZkSerializer(new ZNRecordSerializer());
+            _zkClient = SharedZkClientFactory.getInstance()
+                .buildZkClient(new HelixZkClient.ZkConnectionConfig(_zkAddr), clientConfig);
+          }
+        }
+      }
     }
     return _zkClient;
   }
 
   @Deprecated
   public ZkClient getZkClient() {
-    return (ZkClient) getHelixZkClient();
+    return (ZkClient) getRealmAwareZkClient();
   }
 
   public HelixAdmin getHelixAdmin() {
     if (_zkHelixAdmin == null) {
-      _zkHelixAdmin = new ZKHelixAdmin(getHelixZkClient());
+      synchronized (this) {
+        if (_zkHelixAdmin == null) {
+          _zkHelixAdmin = new ZKHelixAdmin(getRealmAwareZkClient());
+        }
+      }
     }
     return _zkHelixAdmin;
   }
 
   public ClusterSetup getClusterSetup() {
     if (_clusterSetup == null) {
-      _clusterSetup = new ClusterSetup(getHelixZkClient(), getHelixAdmin());
+      synchronized (this) {
+        if (_clusterSetup == null) {
+          _clusterSetup = new ClusterSetup(getRealmAwareZkClient(), getHelixAdmin());
+        }
+      }
     }
     return _clusterSetup;
   }
 
   public TaskDriver getTaskDriver(String clusterName) {
-    synchronized (_taskDriverPool) {
-      if (!_taskDriverPool.containsKey(clusterName)) {
-        _taskDriverPool.put(clusterName, new TaskDriver(getHelixZkClient(), clusterName));
+    TaskDriver taskDriver = _taskDriverPool.get(clusterName);
+    if (taskDriver == null) {
+      synchronized (this) {
+        if (!_taskDriverPool.containsKey(clusterName)) {
+          _taskDriverPool.put(clusterName, new TaskDriver(getRealmAwareZkClient(), clusterName));
+        }
+        taskDriver = _taskDriverPool.get(clusterName);
       }
-      return _taskDriverPool.get(clusterName);
     }
+    return taskDriver;
   }
 
   public ConfigAccessor getConfigAccessor() {
     if (_configAccessor == null) {
-      _configAccessor = new ConfigAccessor(getHelixZkClient());
+      synchronized (this) {
+        if (_configAccessor == null) {
+          _configAccessor = new ConfigAccessor(getRealmAwareZkClient());
+        }
+      }
     }
     return _configAccessor;
   }
 
-  public HelixDataAccessor getDataAccssor(String clusterName) {
-    synchronized (_helixDataAccessorPool) {
-      if (!_helixDataAccessorPool.containsKey(clusterName)) {
-        ZkBaseDataAccessor<ZNRecord> baseDataAccessor =
-            new ZkBaseDataAccessor<>(getHelixZkClient());
-        _helixDataAccessorPool.put(clusterName,
-            new ZKHelixDataAccessor(clusterName, InstanceType.ADMINISTRATOR, baseDataAccessor));
+  public HelixDataAccessor getDataAccessor(String clusterName) {
+    HelixDataAccessor dataAccessor = _helixDataAccessorPool.get(clusterName);
+    if (dataAccessor == null) {
+      synchronized (this) {
+        if (!_helixDataAccessorPool.containsKey(clusterName)) {
+          ZkBaseDataAccessor<ZNRecord> baseDataAccessor =
+              new ZkBaseDataAccessor<>(getRealmAwareZkClient());
+          _helixDataAccessorPool.put(clusterName,
+              new ZKHelixDataAccessor(clusterName, InstanceType.ADMINISTRATOR, baseDataAccessor));
+        }
+        dataAccessor = _helixDataAccessorPool.get(clusterName);
       }
-      return _helixDataAccessorPool.get(clusterName);
     }
+    return dataAccessor;
   }
 
   /**
@@ -132,30 +224,26 @@ public class ServerContext {
    * @return
    */
   public ZkBaseDataAccessor<byte[]> getByteArrayZkBaseDataAccessor() {
-    ZkBaseDataAccessor<byte[]> byteArrayZkBaseDataAccessor = _byteArrayZkBaseDataAccessor;
-    if (byteArrayZkBaseDataAccessor != null) { // First check (no locking)
-      return byteArrayZkBaseDataAccessor;
-    }
+    if (_byteArrayZkBaseDataAccessor == null) {
+      synchronized (this) {
+        if (_byteArrayZkBaseDataAccessor == null) {
 
-    synchronized (this) {
-      if (_byteArrayZkBaseDataAccessor == null) { // Second check (with locking)
-        _byteArrayZkBaseDataAccessor = new ZkBaseDataAccessor<>(_zkAddr, new ZkSerializer() {
-          @Override
-          public byte[] serialize(Object o)
-              throws ZkMarshallingError {
-            // TODO: Support serialize for write methods if necessary
-            throw new UnsupportedOperationException("serialize() is not supported.");
-          }
+          _byteArrayZkBaseDataAccessor = new ZkBaseDataAccessor<>(_zkAddr, new ZkSerializer() {
+            @Override
+            public byte[] serialize(Object o) throws ZkMarshallingError {
+              // TODO: Support serialize for write methods if necessary
+              throw new UnsupportedOperationException("serialize() is not supported.");
+            }
 
-          @Override
-          public Object deserialize(byte[] bytes)
-              throws ZkMarshallingError {
-            return bytes;
-          }
-        });
+            @Override
+            public Object deserialize(byte[] bytes) throws ZkMarshallingError {
+              return bytes;
+            }
+          });
+        }
       }
-      return _byteArrayZkBaseDataAccessor;
     }
+    return _byteArrayZkBaseDataAccessor;
   }
 
   public void close() {
@@ -165,5 +253,96 @@ public class ServerContext {
     if (_zkMetadataStoreDirectory != null) {
       _zkMetadataStoreDirectory.close();
     }
+    if (_zkClientForListener != null) {
+      _zkClientForListener.close();
+    }
+  }
+
+  @Override
+  public void handleChildChange(String parentPath, List<String> currentChilds) {
+    if (_zkClientForListener == null || _zkClientForListener.isClosed()) {
+      return;
+    }
+    // Resubscribe
+    _zkClientForListener.unsubscribeAll();
+    _zkClientForListener.subscribeRoutingDataChanges(this, this);
+    resetZkResources();
+  }
+
+  @Override
+  public void handleDataChange(String dataPath, Object data) {
+    if (_zkClientForListener == null || _zkClientForListener.isClosed()) {
+      return;
+    }
+    resetZkResources();
+  }
+
+  @Override
+  public void handleDataDeleted(String dataPath) {
+    // NOP because this is covered by handleChildChange()
+  }
+
+  @Override
+  public void handleStateChanged(Watcher.Event.KeeperState state) {
+    if (_zkClientForListener == null || _zkClientForListener.isClosed()) {
+      return;
+    }
+    // Resubscribe
+    _zkClientForListener.unsubscribeAll();
+    _zkClientForListener.subscribeRoutingDataChanges(this, this);
+    resetZkResources();
+  }
+
+  @Override
+  public void handleNewSession(String sessionId) {
+    if (_zkClientForListener == null || _zkClientForListener.isClosed()) {
+      return;
+    }
+    // Resubscribe
+    _zkClientForListener.unsubscribeAll();
+    _zkClientForListener.subscribeRoutingDataChanges(this, this);
+    resetZkResources();
+  }
+
+  @Override
+  public void handleSessionEstablishmentError(Throwable error) {
+    if (_zkClientForListener == null || _zkClientForListener.isClosed()) {
+      return;
+    }
+    // Resubscribe
+    _zkClientForListener.unsubscribeAll();
+    _zkClientForListener.subscribeRoutingDataChanges(this, this);
+    resetZkResources();
+  }
+
+  /**
+   * Resets all internally cached routing data by closing and nullifying the ZkClient and Helix APIs.
+   * This is okay because routing data update should be infrequent.
+   */
+  private void resetZkResources() {
+    synchronized (this) {
+      LOG.info("ServerContext: Resetting ZK resources due to routing data change! Routing ZK: {}",
+          _zkAddr);
+      try {
+        // Reset HttpRoutingDataReader's cache
+        HttpRoutingDataReader.reset();
+        // All Helix APIs will be closed implicitly because ZkClient is closed
+        if (_zkClient != null && !_zkClient.isClosed()) {
+          _zkClient.close();
+        }
+        if (_byteArrayZkBaseDataAccessor != null) {
+          _byteArrayZkBaseDataAccessor.close();
+        }
+        _zkClient = null;
+        _zkHelixAdmin = null;
+        _clusterSetup = null;
+        _configAccessor = null;
+        _byteArrayZkBaseDataAccessor = null;
+        _helixDataAccessorPool.clear();
+        _taskDriverPool.clear();
+      } catch (Exception e) {
+        LOG.error("Failed to reset ZkClient and Helix APIs in ServerContext!", e);
+      }
+    }
   }
 }
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/server/resources/helix/AbstractHelixResource.java b/helix-rest/src/main/java/org/apache/helix/rest/server/resources/helix/AbstractHelixResource.java
index f1bb583..487316b 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/server/resources/helix/AbstractHelixResource.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/server/resources/helix/AbstractHelixResource.java
@@ -24,15 +24,15 @@ import java.io.IOException;
 import org.apache.helix.ConfigAccessor;
 import org.apache.helix.HelixAdmin;
 import org.apache.helix.HelixDataAccessor;
-import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.apache.helix.manager.zk.ZkBaseDataAccessor;
-import org.apache.helix.zookeeper.impl.client.ZkClient;
-import org.apache.helix.zookeeper.api.client.HelixZkClient;
 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.helix.task.TaskDriver;
 import org.apache.helix.tools.ClusterSetup;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
+import org.apache.helix.zookeeper.datamodel.ZNRecord;
+import org.apache.helix.zookeeper.impl.client.ZkClient;
 
 
 /**
@@ -42,14 +42,14 @@ import org.apache.helix.tools.ClusterSetup;
  */
 public class AbstractHelixResource extends AbstractResource {
 
-  public HelixZkClient getHelixZkClient() {
+  public RealmAwareZkClient getRealmAwareZkClient() {
     ServerContext serverContext = getServerContext();
-    return serverContext.getHelixZkClient();
+    return serverContext.getRealmAwareZkClient();
   }
 
   @Deprecated
   public ZkClient getZkClient() {
-    return (ZkClient) getHelixZkClient();
+    return (ZkClient) getRealmAwareZkClient();
   }
 
   public HelixAdmin getHelixAdmin() {
@@ -74,7 +74,7 @@ public class AbstractHelixResource extends AbstractResource {
 
   public HelixDataAccessor getDataAccssor(String clusterName) {
     ServerContext serverContext = getServerContext();
-    return serverContext.getDataAccssor(clusterName);
+    return serverContext.getDataAccessor(clusterName);
   }
 
   protected ZkBaseDataAccessor<byte[]> getByteArrayDataAccessor() {
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 762210a..fe824b1 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
@@ -44,22 +44,22 @@ import org.apache.helix.HelixDataAccessor;
 import org.apache.helix.HelixException;
 import org.apache.helix.PropertyKey;
 import org.apache.helix.PropertyPathBuilder;
-import org.apache.helix.model.RESTConfig;
-import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.apache.helix.manager.zk.ZKUtil;
-import org.apache.helix.zookeeper.api.client.HelixZkClient;
 import org.apache.helix.model.ClusterConfig;
 import org.apache.helix.model.ControllerHistory;
 import org.apache.helix.model.HelixConfigScope;
 import org.apache.helix.model.LiveInstance;
 import org.apache.helix.model.MaintenanceSignal;
 import org.apache.helix.model.Message;
+import org.apache.helix.model.RESTConfig;
 import org.apache.helix.model.StateModelDefinition;
 import org.apache.helix.model.builder.HelixConfigScopeBuilder;
 import org.apache.helix.rest.server.json.cluster.ClusterTopology;
 import org.apache.helix.rest.server.service.ClusterService;
 import org.apache.helix.rest.server.service.ClusterServiceImpl;
 import org.apache.helix.tools.ClusterSetup;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
+import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.codehaus.jackson.type.TypeReference;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -137,7 +137,6 @@ public class ClusterAccessor extends AbstractHelixResource {
       @DefaultValue("false") @QueryParam("recreate") String recreate) {
     boolean recreateIfExists = Boolean.valueOf(recreate);
     ClusterSetup clusterSetup = getClusterSetup();
-
     try {
       clusterSetup.addCluster(clusterId, recreateIfExists);
     } catch (Exception ex) {
@@ -454,7 +453,7 @@ public class ClusterAccessor extends AbstractHelixResource {
       LOG.error("Failed to deserialize user's input {}. Exception: {}.", content, e);
       return badRequest("Input is not a valid ZNRecord!");
     }
-    HelixZkClient zkClient = getHelixZkClient();
+    RealmAwareZkClient zkClient = getRealmAwareZkClient();
     String path = PropertyPathBuilder.stateModelDef(clusterId);
     try {
       ZKUtil.createChildren(zkClient, path, record);
@@ -638,7 +637,7 @@ public class ClusterAccessor extends AbstractHelixResource {
   }
 
   private boolean doesClusterExist(String cluster) {
-    HelixZkClient zkClient = getHelixZkClient();
+    RealmAwareZkClient zkClient = getRealmAwareZkClient();
     return ZKUtil.isClusterSetup(cluster, zkClient);
   }
 
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/server/resources/helix/ResourceAccessor.java b/helix-rest/src/main/java/org/apache/helix/rest/server/resources/helix/ResourceAccessor.java
index ca2189e..b8c7c38 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/server/resources/helix/ResourceAccessor.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/server/resources/helix/ResourceAccessor.java
@@ -41,18 +41,18 @@ import org.apache.helix.ConfigAccessor;
 import org.apache.helix.HelixAdmin;
 import org.apache.helix.HelixException;
 import org.apache.helix.PropertyPathBuilder;
-import org.apache.helix.zookeeper.datamodel.ZNRecord;
-import org.apache.helix.zookeeper.api.client.HelixZkClient;
 import org.apache.helix.model.ExternalView;
 import org.apache.helix.model.HelixConfigScope;
 import org.apache.helix.model.IdealState;
 import org.apache.helix.model.ResourceConfig;
 import org.apache.helix.model.StateModelDefinition;
 import org.apache.helix.model.builder.HelixConfigScopeBuilder;
-import org.codehaus.jackson.type.TypeReference;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
+import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.codehaus.jackson.node.ArrayNode;
 import org.codehaus.jackson.node.JsonNodeFactory;
 import org.codehaus.jackson.node.ObjectNode;
+import org.codehaus.jackson.type.TypeReference;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -79,7 +79,7 @@ public class ResourceAccessor extends AbstractHelixResource {
     ObjectNode root = JsonNodeFactory.instance.objectNode();
     root.put(Properties.id.name(), JsonNodeFactory.instance.textNode(clusterId));
 
-    HelixZkClient zkClient = getHelixZkClient();
+    RealmAwareZkClient zkClient = getRealmAwareZkClient();
 
     ArrayNode idealStatesNode = root.putArray(ResourceProperties.idealStates.name());
     ArrayNode externalViewsNode = root.putArray(ResourceProperties.externalViews.name());
@@ -109,7 +109,7 @@ public class ResourceAccessor extends AbstractHelixResource {
   @Path("health")
   public Response getResourceHealth(@PathParam("clusterId") String clusterId) {
 
-    HelixZkClient zkClient = getHelixZkClient();
+    RealmAwareZkClient zkClient = getRealmAwareZkClient();
 
     List<String> resourcesInIdealState =
         zkClient.getChildren(PropertyPathBuilder.idealState(clusterId));
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/integration/TestRoutingDataUpdate.java b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/integration/TestRoutingDataUpdate.java
new file mode 100644
index 0000000..0babf05
--- /dev/null
+++ b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/integration/TestRoutingDataUpdate.java
@@ -0,0 +1,176 @@
+package org.apache.helix.rest.metadatastore.integration;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.apache.helix.SystemPropertyKeys;
+import org.apache.helix.msdcommon.constant.MetadataStoreRoutingConstants;
+import org.apache.helix.rest.server.AbstractTestClass;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+
+/**
+ * TestRoutingDataUpdate tests that Helix REST server's ServerContext gets a proper update whenever
+ * there is change in the routing data.
+ */
+public class TestRoutingDataUpdate extends AbstractTestClass {
+  private static final String CLUSTER_0_SHARDING_KEY = "/TestRoutingDataUpdate-cluster-0";
+  private static final String CLUSTER_1_SHARDING_KEY = "/TestRoutingDataUpdate-cluster-1";
+  private final Map<String, List<String>> _routingData = new HashMap<>();
+
+  @BeforeClass
+  public void beforeClass() {
+    System.setProperty(MetadataStoreRoutingConstants.MSDS_SERVER_HOSTNAME_KEY,
+        getBaseUri().getHost());
+    System.setProperty(MetadataStoreRoutingConstants.MSDS_SERVER_PORT_KEY,
+        Integer.toString(getBaseUri().getPort()));
+
+    // Set the multi-zk config
+    System.setProperty(SystemPropertyKeys.MULTI_ZK_ENABLED, "true");
+    // Set the MSDS address
+    String msdsEndpoint = getBaseUri().toString();
+    System.setProperty(MetadataStoreRoutingConstants.MSDS_SERVER_ENDPOINT_KEY, msdsEndpoint);
+
+    // Restart Helix Rest server to get a fresh ServerContext created
+    restartRestServer();
+  }
+
+  @AfterClass
+  public void afterClass() {
+    // Clear all property
+    System.clearProperty(MetadataStoreRoutingConstants.MSDS_SERVER_HOSTNAME_KEY);
+    System.clearProperty(MetadataStoreRoutingConstants.MSDS_SERVER_PORT_KEY);
+    System.clearProperty(SystemPropertyKeys.MULTI_ZK_ENABLED);
+
+    restartRestServer();
+  }
+
+  @Test
+  public void testRoutingDataUpdate() throws Exception {
+    // Set up routing data
+    _routingData.put(ZK_ADDR, Arrays.asList(CLUSTER_0_SHARDING_KEY, CLUSTER_1_SHARDING_KEY));
+    _routingData.put(_zkAddrTestNS, new ArrayList<>());
+    String routingDataString = OBJECT_MAPPER.writeValueAsString(_routingData);
+    put(MetadataStoreRoutingConstants.MSDS_GET_ALL_ROUTING_DATA_ENDPOINT, null,
+        Entity.entity(routingDataString, MediaType.APPLICATION_JSON_TYPE),
+        Response.Status.CREATED.getStatusCode());
+
+    // Need to wait so that ServerContext processes the callback
+    // TODO: Think of another way to wait -
+    // this is only used because of the nature of the testing environment
+    // in production, the server might return a 500 if a http call comes in before callbacks get
+    // processed fully
+    Thread.sleep(500L);
+
+    // Create the first cluster using Helix REST API via ClusterAccessor
+    put("/clusters" + CLUSTER_0_SHARDING_KEY, null,
+        Entity.entity("", MediaType.APPLICATION_JSON_TYPE),
+        Response.Status.CREATED.getStatusCode());
+    // Check that the first cluster is created in the first ZK as designated by routing data
+    Assert.assertTrue(_gZkClient.exists(CLUSTER_0_SHARDING_KEY));
+    Assert.assertFalse(_gZkClientTestNS.exists(CLUSTER_0_SHARDING_KEY));
+
+    // Change the routing data mapping so that CLUSTER_1 points to the second ZK
+    _routingData.clear();
+    _routingData.put(ZK_ADDR, Collections.singletonList(CLUSTER_0_SHARDING_KEY));
+    _routingData.put(_zkAddrTestNS, Collections.singletonList(CLUSTER_1_SHARDING_KEY));
+    routingDataString = OBJECT_MAPPER.writeValueAsString(_routingData);
+    put(MetadataStoreRoutingConstants.MSDS_GET_ALL_ROUTING_DATA_ENDPOINT, null,
+        Entity.entity(routingDataString, MediaType.APPLICATION_JSON_TYPE),
+        Response.Status.CREATED.getStatusCode());
+
+    // Need to wait so that ServerContext processes the callback
+    // TODO: Think of another way to wait -
+    // this is only used because of the nature of the testing environment
+    // in production, the server might return a 500 if a http call comes in before callbacks get
+    // processed fully
+    Thread.sleep(500L);
+
+    // Create the second cluster using Helix REST API via ClusterAccessor
+    put("/clusters" + CLUSTER_1_SHARDING_KEY, null,
+        Entity.entity("", MediaType.APPLICATION_JSON_TYPE),
+        Response.Status.CREATED.getStatusCode());
+    // Check that the second cluster is created in the second ZK as designated by routing data
+    Assert.assertTrue(_gZkClientTestNS.exists(CLUSTER_1_SHARDING_KEY));
+    Assert.assertFalse(_gZkClient.exists(CLUSTER_1_SHARDING_KEY));
+
+    // Remove all routing data
+    put(MetadataStoreRoutingConstants.MSDS_GET_ALL_ROUTING_DATA_ENDPOINT, null, Entity
+        .entity(OBJECT_MAPPER.writeValueAsString(Collections.emptyMap()),
+            MediaType.APPLICATION_JSON_TYPE), Response.Status.CREATED.getStatusCode());
+
+    // Need to wait so that ServerContext processes the callback
+    // TODO: Think of another way to wait -
+    // this is only used because of the nature of the testing environment
+    // in production, the server might return a 500 if a http call comes in before callbacks get
+    // processed fully
+    Thread.sleep(500L);
+
+    // Delete clusters - both should fail because routing data don't have these clusters
+    delete("/clusters" + CLUSTER_0_SHARDING_KEY,
+        Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
+    delete("/clusters" + CLUSTER_1_SHARDING_KEY,
+        Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
+
+    // Set the routing data again
+    put(MetadataStoreRoutingConstants.MSDS_GET_ALL_ROUTING_DATA_ENDPOINT, null,
+        Entity.entity(routingDataString, MediaType.APPLICATION_JSON_TYPE),
+        Response.Status.CREATED.getStatusCode());
+
+    // Need to wait so that ServerContext processes the callback
+    // TODO: Think of another way to wait -
+    // this is only used because of the nature of the testing environment
+    // in production, the server might return a 500 if a http call comes in before callbacks get
+    // processed fully
+    Thread.sleep(500L);
+
+    // Attempt deletion again - now they should succeed
+    delete("/clusters" + CLUSTER_0_SHARDING_KEY, Response.Status.OK.getStatusCode());
+    delete("/clusters" + CLUSTER_1_SHARDING_KEY, Response.Status.OK.getStatusCode());
+
+    // Double-verify using ZkClients
+    Assert.assertFalse(_gZkClientTestNS.exists(CLUSTER_1_SHARDING_KEY));
+    Assert.assertFalse(_gZkClient.exists(CLUSTER_0_SHARDING_KEY));
+
+    // Remove all routing data
+    put(MetadataStoreRoutingConstants.MSDS_GET_ALL_ROUTING_DATA_ENDPOINT, null, Entity
+        .entity(OBJECT_MAPPER.writeValueAsString(Collections.emptyMap()),
+            MediaType.APPLICATION_JSON_TYPE), Response.Status.CREATED.getStatusCode());
+  }
+
+  private void restartRestServer() {
+    if (_helixRestServer != null) {
+      _helixRestServer.shutdown();
+    }
+    _helixRestServer = startRestServer();
+  }
+}
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 c5ffd41..0fee10d 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
@@ -189,21 +189,7 @@ public class AbstractTestClass extends JerseyTestNg.ContainerPerClassTest {
           @Override
           public void start() {
             if (_helixRestServer == null) {
-              // Create namespace manifest map
-              List<HelixRestNamespace> namespaces = new ArrayList<>();
-              // Add test namespace
-              namespaces.add(new HelixRestNamespace(TEST_NAMESPACE,
-                  HelixRestNamespace.HelixMetadataStoreType.ZOOKEEPER, _zkAddrTestNS, false));
-              // Add default namesapce
-              namespaces.add(new HelixRestNamespace(ZK_ADDR));
-              try {
-                _helixRestServer =
-                    new HelixRestServer(namespaces, baseUri.getPort(), baseUri.getPath(),
-                        Collections.singletonList(_auditLogger));
-                _helixRestServer.start();
-              } catch (Exception ex) {
-                throw new TestContainerException(ex);
-              }
+              _helixRestServer = startRestServer();
             }
           }
 
@@ -584,4 +570,28 @@ public class AbstractTestClass extends JerseyTestNg.ContainerPerClassTest {
     _clusters.add(STOPPABLE_CLUSTER);
     _workflowMap.put(STOPPABLE_CLUSTER, createWorkflows(STOPPABLE_CLUSTER, 3));
   }
+
+  /**
+   * Starts a HelixRestServer for the test suite.
+   * @return
+   */
+  protected HelixRestServer startRestServer() {
+    // Create namespace manifest map
+    List<HelixRestNamespace> namespaces = new ArrayList<>();
+    // Add test namespace
+    namespaces.add(new HelixRestNamespace(TEST_NAMESPACE,
+        HelixRestNamespace.HelixMetadataStoreType.ZOOKEEPER, _zkAddrTestNS, false));
+    // Add default namesapce
+    namespaces.add(new HelixRestNamespace(ZK_ADDR));
+    HelixRestServer server;
+    try {
+      server =
+          new HelixRestServer(namespaces, getBaseUri().getPort(), getBaseUri().getPath(),
+              Collections.singletonList(_auditLogger));
+      server.start();
+    } catch (Exception ex) {
+      throw new TestContainerException(ex);
+    }
+    return server;
+  }
 }
diff --git a/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/constant/MetadataStoreRoutingConstants.java b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/constant/MetadataStoreRoutingConstants.java
index 41d5011..6cceb50 100644
--- a/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/constant/MetadataStoreRoutingConstants.java
+++ b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/constant/MetadataStoreRoutingConstants.java
@@ -22,6 +22,8 @@ package org.apache.helix.msdcommon.constant;
 public class MetadataStoreRoutingConstants {
   public static final String ROUTING_DATA_PATH = "/METADATA_STORE_ROUTING_DATA";
 
+  public static final String ROUTING_ZK_ADDRESS_KEY = "ROUTING_ZK_ADDRESS";
+
   // For ZK only
   public static final String ZNRECORD_LIST_FIELD_KEY = "ZK_PATH_SHARDING_KEYS";
 
diff --git a/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/util/ZkValidationUtil.java b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/util/ZkValidationUtil.java
index 472b3d9..ab8258d 100644
--- a/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/util/ZkValidationUtil.java
+++ b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/util/ZkValidationUtil.java
@@ -27,12 +27,13 @@ public class ZkValidationUtil {
    * /
    * /abc
    * /abc/abc/abc/abc
+   * /abc/localhost:1234
    * Invalid matches:
    * null or empty string
    * /abc/
    * /abc/abc/abc/abc/
    **/
   public static boolean isPathValid(String path) {
-    return path.matches("^/|(/[\\w-]+)+$");
+    return path.matches("^/|(/[\\w?:-]+)+$");
   }
 }
diff --git a/pom.xml b/pom.xml
index e8c9bd3..60f8d87 100644
--- a/pom.xml
+++ b/pom.xml
@@ -630,40 +630,6 @@ under the License.
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-surefire-plugin</artifactId>
           <version>3.0.0-M3</version>
-          <executions>
-            <!--
-            "executions" enables multiple runs of integration test suites. This is to enable two
-            runs: 1. run in a single-ZK environment, 2. run in a multi-ZK environment.
-            "multiZk" is the config accessible via Systems.Properties so that the two runs could be
-            differentiated.
-            -->
-            <execution>
-              <goals>
-                <goal>test</goal>
-              </goals>
-              <id>default-test</id>
-              <phase>test</phase>
-              <configuration>
-                <rerunFailingTestsCount>3</rerunFailingTestsCount>
-                <skipAfterFailureCount>10</skipAfterFailureCount>
-              </configuration>
-            </execution>
-            <execution>
-              <goals>
-                <goal>test</goal>
-              </goals>
-              <id>multi-zk</id>
-              <phase>test</phase>
-              <configuration>
-                <systemPropertyVariables>
-                  <multiZk>true</multiZk>
-                  <numZk>3</numZk>
-                </systemPropertyVariables>
-                <rerunFailingTestsCount>3</rerunFailingTestsCount>
-                <skipAfterFailureCount>10</skipAfterFailureCount>
-              </configuration>
-            </execution>
-          </executions>
         </plugin>
         <plugin>
           <groupId>org.apache.rat</groupId>
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/RealmAwareZkClient.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/RealmAwareZkClient.java
index 0e461b7..ee8c8e3 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/RealmAwareZkClient.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/RealmAwareZkClient.java
@@ -23,6 +23,7 @@ import java.io.IOException;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
+import org.apache.helix.msdcommon.constant.MetadataStoreRoutingConstants;
 import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
 import org.apache.helix.zookeeper.exception.ZkClientException;
 import org.apache.helix.zookeeper.util.HttpRoutingDataReader;
@@ -579,4 +580,19 @@ public interface RealmAwareZkClient {
           .setConnectInitTimeout(_connectInitTimeout);
     }
   }
+
+  /**
+   * Subscribes to the routing data paths using the provided ZkClient.
+   * Note: this method assumes that the routing data path has already been created.
+   * @param childListener
+   * @param dataListener
+   */
+  default void subscribeRoutingDataChanges(IZkChildListener childListener,
+      IZkDataListener dataListener) {
+    subscribeChildChanges(MetadataStoreRoutingConstants.ROUTING_DATA_PATH, childListener);
+    for (String child : getChildren(MetadataStoreRoutingConstants.ROUTING_DATA_PATH)) {
+      subscribeDataChanges(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + child,
+          dataListener);
+    }
+  }
 }
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/util/HttpRoutingDataReader.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/util/HttpRoutingDataReader.java
index f2f907a..b214cc4 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/util/HttpRoutingDataReader.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/util/HttpRoutingDataReader.java
@@ -87,7 +87,7 @@ public class HttpRoutingDataReader {
       synchronized (HttpRoutingDataReader.class) {
         rawRoutingData = _rawRoutingDataMap.get(msdsEndpoint);
         if (rawRoutingData == null) {
-          String routingDataJson = getAllRoutingData();
+          String routingDataJson = getAllRoutingData(msdsEndpoint);
           // Update the reference if reading routingData over HTTP is successful
           rawRoutingData = parseRoutingData(routingDataJson);
           _rawRoutingDataMap.put(msdsEndpoint, rawRoutingData);
@@ -136,16 +136,24 @@ public class HttpRoutingDataReader {
   }
 
   /**
+   * Clears the statically-cached routing data in HttpRoutingDataReader.
+   */
+  public static void reset() {
+    _rawRoutingDataMap.clear();
+    _metadataStoreRoutingDataMap.clear();
+  }
+
+  /**
    * Makes an HTTP call to fetch all routing data.
    * @return
    * @throws IOException
    */
-  private static String getAllRoutingData() throws IOException {
+  private static String getAllRoutingData(String msdsEndpoint) throws IOException {
     // Note that MSDS_ENDPOINT should provide high-availability - it risks becoming a single point
     // of failure if it's backed by a single IP address/host
     // Retry count is 3 by default.
     HttpGet requestAllData = new HttpGet(
-        SYSTEM_MSDS_ENDPOINT + MetadataStoreRoutingConstants.MSDS_GET_ALL_ROUTING_DATA_ENDPOINT);
+        msdsEndpoint + MetadataStoreRoutingConstants.MSDS_GET_ALL_ROUTING_DATA_ENDPOINT);
 
     // Define timeout configs
     RequestConfig config = RequestConfig.custom().setConnectTimeout(HTTP_TIMEOUT_IN_MS)
diff --git a/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/RealmAwareZkClientTestBase.java b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/RealmAwareZkClientTestBase.java
index 900c79f..acb2299 100644
--- a/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/RealmAwareZkClientTestBase.java
+++ b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/RealmAwareZkClientTestBase.java
@@ -43,7 +43,7 @@ public abstract class RealmAwareZkClientTestBase extends ZkTestBase {
 
   @BeforeClass
   public void beforeClass() throws IOException, InvalidRoutingDataException {
-    // Create a mock MSDS so that HttpRoudingDataReader could fetch the routing data
+    // Create a mock MSDS so that HttpRoutingDataReader could fetch the routing data
     if (_msdsServer == null) {
       // Do not create again if Mock MSDS server has already been created by other tests
       _msdsServer = new MockMetadataStoreDirectoryServer(MSDS_HOSTNAME, MSDS_PORT, MSDS_NAMESPACE,


[helix] 16/49: Create metadata-store-directory-common module (#771)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit b633ddd2e5889ecad089dcdf2a0514848939b982
Author: Hunter Lee <hu...@linkedin.com>
AuthorDate: Thu Feb 20 10:09:22 2020 -0800

    Create metadata-store-directory-common module (#771)
    
    metadata-store-directory-common module was created to avoid circular dependency because the RoutingData classes will be used in zookeeper-api.
---
 helix-rest/pom.xml                                 |   5 +
 .../metadatastore/ZkMetadataStoreDirectory.java    |   7 +-
 .../accessor/MetadataStoreRoutingDataReader.java   |   7 +-
 .../accessor/ZkRoutingDataReader.java              |   9 +-
 .../accessor/ZkRoutingDataWriter.java              |   2 +-
 .../resources/helix/PropertyStoreAccessor.java     |   5 +-
 .../MetadataStoreDirectoryAccessor.java            |   4 +-
 .../resources/zookeeper/ZooKeeperAccessor.java     |   2 +-
 .../TestZkMetadataStoreDirectory.java              |   4 +-
 .../accessor/TestZkRoutingDataReader.java          |   4 +-
 .../accessor/TestZkRoutingDataWriter.java          |   2 +-
 .../TestMetadataStoreDirectoryAccessor.java        |  23 +-
 metadata-store-directory-common/LICENSE            | 273 +++++++++++++++++++++
 metadata-store-directory-common/NOTICE             |  37 +++
 ...adata-store-directory-common-0.9.2-SNAPSHOT.ivy |  54 ++++
 metadata-store-directory-common/pom.xml            | 124 ++++++++++
 .../src/assemble/assembly.xml                      |  60 +++++
 .../src/main/config/log4j.properties               |  32 +++
 .../msdcommon/callback}/RoutingDataListener.java   |   2 +-
 .../constant/MetadataStoreRoutingConstants.java    |   2 +-
 .../datamodel}/MetadataStoreRoutingData.java       |   4 +-
 .../msdcommon/datamodel}/TrieRoutingData.java      |  14 +-
 .../exception}/InvalidRoutingDataException.java    |   2 +-
 .../helix/msdcommon}/util/ZkValidationUtil.java    |   2 +-
 .../src/test/conf/testng.xml                       |  27 ++
 .../msdcommon/datamodel}/TestTrieRoutingData.java  |   6 +-
 .../src/test/resources/log4j.properties            |  41 ++++
 pom.xml                                            |   1 +
 28 files changed, 711 insertions(+), 44 deletions(-)

diff --git a/helix-rest/pom.xml b/helix-rest/pom.xml
index e3421d5..018d1dd 100644
--- a/helix-rest/pom.xml
+++ b/helix-rest/pom.xml
@@ -45,6 +45,11 @@ under the License.
 
   <dependencies>
     <dependency>
+      <groupId>org.apache.helix</groupId>
+      <artifactId>metadata-store-directory-common</artifactId>
+      <version>0.9.2-SNAPSHOT</version>
+    </dependency>
+    <dependency>
       <groupId>org.yaml</groupId>
       <artifactId>snakeyaml</artifactId>
       <version>1.17</version>
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java
index a57e08c..7e972ab 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java
@@ -29,11 +29,14 @@ import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
+import org.apache.helix.msdcommon.callback.RoutingDataListener;
+import org.apache.helix.msdcommon.datamodel.MetadataStoreRoutingData;
+import org.apache.helix.msdcommon.datamodel.TrieRoutingData;
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
 import org.apache.helix.rest.metadatastore.accessor.MetadataStoreRoutingDataReader;
 import org.apache.helix.rest.metadatastore.accessor.MetadataStoreRoutingDataWriter;
 import org.apache.helix.rest.metadatastore.accessor.ZkRoutingDataReader;
 import org.apache.helix.rest.metadatastore.accessor.ZkRoutingDataWriter;
-import org.apache.helix.rest.metadatastore.exceptions.InvalidRoutingDataException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -226,4 +229,4 @@ public class ZkMetadataStoreDirectory implements MetadataStoreDirectory, Routing
     _routingDataReaderMap.values().forEach(MetadataStoreRoutingDataReader::close);
     _routingDataWriterMap.values().forEach(MetadataStoreRoutingDataWriter::close);
   }
-}
+}
\ No newline at end of file
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/MetadataStoreRoutingDataReader.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/MetadataStoreRoutingDataReader.java
index f19e8ff..b56716e 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/MetadataStoreRoutingDataReader.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/MetadataStoreRoutingDataReader.java
@@ -21,7 +21,9 @@ package org.apache.helix.rest.metadatastore.accessor;
 
 import java.util.List;
 import java.util.Map;
-import org.apache.helix.rest.metadatastore.exceptions.InvalidRoutingDataException;
+
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
+
 
 /**
  * An interface for a DAO that fetches routing data from a source and return a key-value mapping
@@ -38,7 +40,8 @@ public interface MetadataStoreRoutingDataReader {
    * @throws InvalidRoutingDataException - when the routing data is malformed in any way that
    *           disallows a meaningful mapping to be returned
    */
-  Map<String, List<String>> getRoutingData() throws InvalidRoutingDataException;
+  Map<String, List<String>> getRoutingData()
+      throws InvalidRoutingDataException;
 
   /**
    * Closes any stateful resources such as connections or threads.
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataReader.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataReader.java
index 44ce110..12de9e2 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataReader.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataReader.java
@@ -23,9 +23,9 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-import org.apache.helix.rest.metadatastore.RoutingDataListener;
-import org.apache.helix.rest.metadatastore.constant.MetadataStoreRoutingConstants;
-import org.apache.helix.rest.metadatastore.exceptions.InvalidRoutingDataException;
+import org.apache.helix.msdcommon.callback.RoutingDataListener;
+import org.apache.helix.msdcommon.constant.MetadataStoreRoutingConstants;
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
@@ -75,7 +75,8 @@ public class ZkRoutingDataReader implements MetadataStoreRoutingDataReader, IZkD
    * @throws InvalidRoutingDataException - when the node on
    *           MetadataStoreRoutingConstants.ROUTING_DATA_PATH is missing
    */
-  public Map<String, List<String>> getRoutingData() throws InvalidRoutingDataException {
+  public Map<String, List<String>> getRoutingData()
+      throws InvalidRoutingDataException {
     Map<String, List<String>> routingData = new HashMap<>();
     List<String> allRealmAddresses;
     try {
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataWriter.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataWriter.java
index c8da80e..48aeb13 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataWriter.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataWriter.java
@@ -23,8 +23,8 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
+import org.apache.helix.msdcommon.constant.MetadataStoreRoutingConstants;
 import org.apache.helix.rest.metadatastore.concurrency.ZkDistributedLeaderElection;
-import org.apache.helix.rest.metadatastore.constant.MetadataStoreRoutingConstants;
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/server/resources/helix/PropertyStoreAccessor.java b/helix-rest/src/main/java/org/apache/helix/rest/server/resources/helix/PropertyStoreAccessor.java
index e50d67e..11226e5 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/server/resources/helix/PropertyStoreAccessor.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/server/resources/helix/PropertyStoreAccessor.java
@@ -27,11 +27,10 @@ import javax.ws.rs.core.Response;
 
 import org.apache.helix.AccessOption;
 import org.apache.helix.PropertyPathBuilder;
-import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.apache.helix.manager.zk.ZNRecordSerializer;
 import org.apache.helix.manager.zk.ZkBaseDataAccessor;
-import org.apache.helix.rest.server.resources.zookeeper.ZooKeeperAccessor;
-import org.apache.helix.zookeeper.util.ZkValidationUtil;
+import org.apache.helix.zookeeper.datamodel.ZNRecord;
+import org.apache.helix.msdcommon.util.ZkValidationUtil;
 import org.codehaus.jackson.node.ObjectNode;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/server/resources/metadatastore/MetadataStoreDirectoryAccessor.java b/helix-rest/src/main/java/org/apache/helix/rest/server/resources/metadatastore/MetadataStoreDirectoryAccessor.java
index e731b28..5d84d8a 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/server/resources/metadatastore/MetadataStoreDirectoryAccessor.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/server/resources/metadatastore/MetadataStoreDirectoryAccessor.java
@@ -34,13 +34,13 @@ import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.Response;
 
 import com.google.common.collect.ImmutableMap;
+import org.apache.helix.msdcommon.constant.MetadataStoreRoutingConstants;
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
 import org.apache.helix.rest.common.ContextPropertyKeys;
 import org.apache.helix.rest.common.HelixRestNamespace;
 import org.apache.helix.rest.common.HelixRestUtils;
 import org.apache.helix.rest.metadatastore.MetadataStoreDirectory;
 import org.apache.helix.rest.metadatastore.ZkMetadataStoreDirectory;
-import org.apache.helix.rest.metadatastore.constant.MetadataStoreRoutingConstants;
-import org.apache.helix.rest.metadatastore.exceptions.InvalidRoutingDataException;
 import org.apache.helix.rest.server.resources.AbstractResource;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
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 bc2da05..7b30482 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
@@ -35,7 +35,7 @@ import org.apache.helix.manager.zk.ZkBaseDataAccessor;
 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.helix.zookeeper.util.ZkValidationUtil;
+import org.apache.helix.msdcommon.util.ZkValidationUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java
index 68b93b3..604d331 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java
@@ -30,8 +30,8 @@ import java.util.NoSuchElementException;
 import java.util.Set;
 
 import org.apache.helix.TestHelper;
-import org.apache.helix.rest.metadatastore.constant.MetadataStoreRoutingConstants;
-import org.apache.helix.rest.metadatastore.exceptions.InvalidRoutingDataException;
+import org.apache.helix.msdcommon.constant.MetadataStoreRoutingConstants;
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
 import org.apache.helix.rest.server.AbstractTestClass;
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataReader.java b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataReader.java
index c188840..63a013b 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataReader.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataReader.java
@@ -26,8 +26,8 @@ import java.util.Map;
 
 import org.apache.helix.AccessOption;
 import org.apache.helix.TestHelper;
-import org.apache.helix.rest.metadatastore.constant.MetadataStoreRoutingConstants;
-import org.apache.helix.rest.metadatastore.exceptions.InvalidRoutingDataException;
+import org.apache.helix.msdcommon.constant.MetadataStoreRoutingConstants;
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
 import org.apache.helix.rest.server.AbstractTestClass;
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.testng.Assert;
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataWriter.java b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataWriter.java
index 29b7e36..8b7224c 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataWriter.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataWriter.java
@@ -25,7 +25,7 @@ import java.util.Map;
 
 import com.google.common.collect.ImmutableMap;
 import org.apache.helix.AccessOption;
-import org.apache.helix.rest.metadatastore.constant.MetadataStoreRoutingConstants;
+import org.apache.helix.msdcommon.constant.MetadataStoreRoutingConstants;
 import org.apache.helix.rest.server.AbstractTestClass;
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.junit.Assert;
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/server/resources/zookeeper/TestMetadataStoreDirectoryAccessor.java b/helix-rest/src/test/java/org/apache/helix/rest/server/TestMetadataStoreDirectoryAccessor.java
similarity index 96%
rename from helix-rest/src/test/java/org/apache/helix/rest/server/resources/zookeeper/TestMetadataStoreDirectoryAccessor.java
rename to helix-rest/src/test/java/org/apache/helix/rest/server/TestMetadataStoreDirectoryAccessor.java
index c08d845..02b3915 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/server/resources/zookeeper/TestMetadataStoreDirectoryAccessor.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/server/TestMetadataStoreDirectoryAccessor.java
@@ -1,4 +1,4 @@
-package org.apache.helix.rest.server.resources.zookeeper;
+package org.apache.helix.rest.server;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -34,11 +34,10 @@ import javax.ws.rs.core.Response;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import org.apache.helix.TestHelper;
+import org.apache.helix.msdcommon.constant.MetadataStoreRoutingConstants;
 import org.apache.helix.rest.common.HelixRestNamespace;
 import org.apache.helix.rest.metadatastore.MetadataStoreDirectory;
 import org.apache.helix.rest.metadatastore.ZkMetadataStoreDirectory;
-import org.apache.helix.rest.metadatastore.constant.MetadataStoreRoutingConstants;
-import org.apache.helix.rest.server.AbstractTestClass;
 import org.apache.helix.rest.server.util.JerseyUriRequestBuilder;
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
@@ -69,7 +68,8 @@ public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
   private MetadataStoreDirectory _metadataStoreDirectory;
 
   @BeforeClass
-  public void beforeClass() throws Exception {
+  public void beforeClass()
+      throws Exception {
     _zkList = new ArrayList<>(ZK_SERVER_MAP.keySet());
 
     deleteRoutingDataPath();
@@ -111,7 +111,8 @@ public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
   }
 
   @Test
-  public void testGetAllMetadataStoreRealms() throws IOException {
+  public void testGetAllMetadataStoreRealms()
+      throws IOException {
     get(NON_EXISTING_NAMESPACE_URI_PREFIX + "metadata-store-realms", null,
         Response.Status.NOT_FOUND.getStatusCode(), false);
 
@@ -189,7 +190,8 @@ public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
    * Tests REST endpoints: "/sharding-keys"
    */
   @Test
-  public void testGetShardingKeysInNamespace() throws IOException {
+  public void testGetShardingKeysInNamespace()
+      throws IOException {
     get(NON_EXISTING_NAMESPACE_URI_PREFIX + "sharding-keys", null,
         Response.Status.NOT_FOUND.getStatusCode(), true);
 
@@ -217,7 +219,8 @@ public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
    * Tests REST endpoint: "/sharding-keys?realm={realmName}"
    */
   @Test
-  public void testGetShardingKeysInRealm() throws IOException {
+  public void testGetShardingKeysInRealm()
+      throws IOException {
     // Test NOT_FOUND response for a non existed realm.
     new JerseyUriRequestBuilder(TEST_NAMESPACE_URI_PREFIX + "/sharding-keys?realm=nonExistedRealm")
         .expectedReturnStatusCode(Response.Status.NOT_FOUND.getStatusCode()).get(this);
@@ -301,12 +304,14 @@ public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
   }
 
   @AfterClass
-  public void afterClass() throws Exception {
+  public void afterClass()
+      throws Exception {
     _metadataStoreDirectory.close();
     deleteRoutingDataPath();
   }
 
-  private void deleteRoutingDataPath() throws Exception {
+  private void deleteRoutingDataPath()
+      throws Exception {
     Assert.assertTrue(TestHelper.verify(() -> {
       _zkList.forEach(zk -> ZK_SERVER_MAP.get(zk).getZkClient()
           .deleteRecursively(MetadataStoreRoutingConstants.ROUTING_DATA_PATH));
diff --git a/metadata-store-directory-common/LICENSE b/metadata-store-directory-common/LICENSE
new file mode 100644
index 0000000..413913f
--- /dev/null
+++ b/metadata-store-directory-common/LICENSE
@@ -0,0 +1,273 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+
+For xstream:
+
+Copyright (c) 2003-2006, Joe Walnes
+Copyright (c) 2006-2009, 2011 XStream Committers
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this list of
+conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice, this list of
+conditions and the following disclaimer in the documentation and/or other materials provided
+with the distribution.
+
+3. Neither the name of XStream nor the names of its contributors may be used to endorse
+or promote products derived from this software without specific prior written
+permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGE.
+
+for jline:
+
+Copyright (c) 2002-2006, Marc Prud'hommeaux <mw...@cornell.edu>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or
+without modification, are permitted provided that the following
+conditions are met:
+
+Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with
+the distribution.
+
+Neither the name of JLine nor the names of its contributors
+may be used to endorse or promote products derived from this
+software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+
diff --git a/metadata-store-directory-common/NOTICE b/metadata-store-directory-common/NOTICE
new file mode 100644
index 0000000..9dc340b
--- /dev/null
+++ b/metadata-store-directory-common/NOTICE
@@ -0,0 +1,37 @@
+Apache Helix
+Copyright 2014 The Apache Software Foundation
+
+
+I. Included Software
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+Licensed under the Apache License 2.0.
+
+This product includes software developed at
+Codehaus (http://www.codehaus.org/).
+Licensed under the BSD License.
+
+This product includes software developed at
+jline (http://jline.sourceforge.net/).
+Licensed under the BSD License.
+
+This product includes software developed at
+restlet (http://www.restlet.org/about/legal).
+Licensed under the Apache License 2.0.
+
+This product includes software developed at
+Google (http://www.google.com/).
+Licensed under the Apache License 2.0.
+
+This product includes software developed at
+snakeyaml (http://www.snakeyaml.org/).
+Licensed under the Apache License 2.0.
+
+This product includes software developed at
+zkclient (https://github.com/sgroschupf/zkclient).
+Licensed under the Apache License 2.0.
+
+II. License Summary
+- Apache License 2.0
+- BSD License
diff --git a/metadata-store-directory-common/metadata-store-directory-common-0.9.2-SNAPSHOT.ivy b/metadata-store-directory-common/metadata-store-directory-common-0.9.2-SNAPSHOT.ivy
new file mode 100644
index 0000000..a8fd16d
--- /dev/null
+++ b/metadata-store-directory-common/metadata-store-directory-common-0.9.2-SNAPSHOT.ivy
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<ivy-module version="1.0">
+	<info organisation="org.apache.helix"
+		module="metadata-store-directory-common"
+		revision="0.9.2-SNAPSHOT"
+		status="integration"
+		publication="20170128141623"
+	/>
+	<configurations>
+		<conf name="default" visibility="public" description="runtime dependencies and master artifact can be used with this conf" extends="runtime,master"/>
+		<conf name="master" visibility="public" description="contains only the artifact published by this module itself, with no transitive dependencies"/>
+		<conf name="compile" visibility="public" description="this is the default scope, used if none is specified. Compile dependencies are available in all classpaths."/>
+		<conf name="provided" visibility="public" description="this is much like compile, but indicates you expect the JDK or a container to provide it. It is only available on the compilation classpath, and is not transitive."/>
+		<conf name="runtime" visibility="public" description="this scope indicates that the dependency is not required for compilation, but is for execution. It is in the runtime and test classpaths, but not the compile classpath." extends="compile"/>
+		<conf name="test" visibility="private" description="this scope indicates that the dependency is not required for normal use of the application, and is only available for the test compilation and execution phases."/>
+		<conf name="system" visibility="public" description="this scope is similar to provided except that you have to provide the JAR which contains it explicitly. The artifact is always available and is not looked up in a repository."/>
+	</configurations>
+	<publications>
+		<artifact name="metadata-store-directory-common" type="jar" ext="jar" conf="master"/>
+	</publications>
+	<dependencies>
+	  <dependency org="org.slf4j" name="slf4j-api" rev="1.7.25" force="true" conf="compile->compile(*),master(*);runtime->runtime(*)">
+        <artifact name="slf4j-api" ext="jar"/>
+    </dependency>
+    <dependency org="org.slf4j" name="slf4j-log4j12" rev="1.7.14" force="true" conf="compile->compile(*),master(*);runtime->runtime(*)">
+        <artifact name="slf4j-log4j12" ext="jar"/>
+    </dependency>
+    <dependency org="org.yaml" name="snakeyaml" rev="1.17">
+        <artifact name="snakeyaml" m:classifier="sources" ext="jar"/>
+    </dependency>
+		<dependency org="org.apache.helix" name="helix-core" rev="0.9.2-SNAPSHOT" force="true" conf="compile->compile(*),master(*);runtime->runtime(*)"/>
+		<dependency org="com.fasterxml.jackson.core" name="jackson-databind" rev="2.10.2" force="true" conf="compile->compile(*),master(*);runtime->runtime(*)"/>
+		<dependency org="commons-cli" name="commons-cli" rev="1.2" force="true" conf="compile->compile(*),master(*);runtime->runtime(*)"/>
+		<dependency org="commons-io" name="commons-io" rev="1.4" conf="compile->compile(default);runtime->runtime(default);default->default"/>
+	</dependencies>
+</ivy-module>
diff --git a/metadata-store-directory-common/pom.xml b/metadata-store-directory-common/pom.xml
new file mode 100644
index 0000000..cc81a33
--- /dev/null
+++ b/metadata-store-directory-common/pom.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <parent>
+    <groupId>org.apache.helix</groupId>
+    <artifactId>helix</artifactId>
+    <version>0.9.2-SNAPSHOT</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+
+  <artifactId>metadata-store-directory-common</artifactId>
+  <packaging>bundle</packaging>
+  <name>Apache Helix :: Metadata Store Directory Common</name>
+
+  <properties>
+    <osgi.import>
+      org.apache.commons.cli*,
+      org.apache.commons.io*;version="[1.4,2)",
+      org.slf4j*;version="[1.6,2)",
+      *
+    </osgi.import>
+    <osgi.export>org.apache.helix.msdcommon*;version="${project.version};-noimport:=true
+    </osgi.export>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.fasterxml.jackson.core</groupId>
+      <artifactId>jackson-databind</artifactId>
+      <version>2.10.2</version>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+      <version>1.7.25</version>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-log4j12</artifactId>
+      <version>1.7.14</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-lang3</artifactId>
+      <version>3.8.1</version>
+    </dependency>
+    <dependency>
+      <groupId>commons-cli</groupId>
+      <artifactId>commons-cli</artifactId>
+      <version>1.2</version>
+    </dependency>
+    <dependency>
+      <groupId>org.testng</groupId>
+      <artifactId>testng</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava</artifactId>
+      <version>20.0</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.yaml</groupId>
+      <artifactId>snakeyaml</artifactId>
+      <version>1.17</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>commons-io</groupId>
+      <artifactId>commons-io</artifactId>
+      <version>1.4</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+  <build>
+    <resources>
+      <resource>
+        <directory>${basedir}</directory>
+        <includes>
+          <include>DISCLAIMER</include>
+        </includes>
+      </resource>
+    </resources>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <configuration>
+          <descriptors>
+            <descriptor>src/assemble/assembly.xml</descriptor>
+          </descriptors>
+        </configuration>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/metadata-store-directory-common/src/assemble/assembly.xml b/metadata-store-directory-common/src/assemble/assembly.xml
new file mode 100644
index 0000000..3e17941
--- /dev/null
+++ b/metadata-store-directory-common/src/assemble/assembly.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<assembly>
+  <id>pkg</id>
+  <formats>
+    <format>tar</format>
+  </formats>
+  <fileSets>
+    <fileSet>
+      <directory>target/metadata-store-directory-common-pkg/bin</directory>
+      <outputDirectory>bin</outputDirectory>
+      <lineEnding>unix</lineEnding>
+      <fileMode>0755</fileMode>
+      <directoryMode>0755</directoryMode>
+    </fileSet>
+    <fileSet>
+      <directory>target/metadata-store-directory-common-pkg/repo/</directory>
+      <outputDirectory>repo</outputDirectory>
+      <fileMode>0755</fileMode>
+      <directoryMode>0755</directoryMode>
+      <excludes>
+        <exclude>**/*.xml</exclude>
+      </excludes>
+    </fileSet>
+   <fileSet>
+      <directory>target/metadata-store-directory-common-pkg/conf</directory>
+      <outputDirectory>conf</outputDirectory>
+      <lineEnding>unix</lineEnding>
+      <fileMode>0755</fileMode>
+      <directoryMode>0755</directoryMode>
+    </fileSet>
+    <fileSet>
+      <directory>${project.basedir}</directory>
+      <outputDirectory>/</outputDirectory>
+      <includes>
+        <include>LICENSE</include>
+        <include>NOTICE</include>
+        <include>DISCLAIMER</include>
+      </includes>
+      <fileMode>0755</fileMode>
+    </fileSet>
+  </fileSets>
+</assembly>
diff --git a/metadata-store-directory-common/src/main/config/log4j.properties b/metadata-store-directory-common/src/main/config/log4j.properties
new file mode 100644
index 0000000..375c7ba
--- /dev/null
+++ b/metadata-store-directory-common/src/main/config/log4j.properties
@@ -0,0 +1,32 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# Set root logger level to DEBUG and its only appender to A1.
+log4j.rootLogger=WARN,A1
+
+# A1 is set to be a ConsoleAppender.
+log4j.appender.A1=org.apache.log4j.ConsoleAppender
+
+# A1 uses PatternLayout.
+log4j.appender.A1.layout=org.apache.log4j.PatternLayout
+log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
+
+log4j.logger.org.I0Itec=ERROR
+log4j.logger.org.apache.zookeeper=ERROR
+log4j.logger.org.apache.helix=INFO
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/RoutingDataListener.java b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/callback/RoutingDataListener.java
similarity index 95%
rename from helix-rest/src/main/java/org/apache/helix/rest/metadatastore/RoutingDataListener.java
rename to metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/callback/RoutingDataListener.java
index a44fc17..dc87f7e 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/RoutingDataListener.java
+++ b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/callback/RoutingDataListener.java
@@ -1,4 +1,4 @@
-package org.apache.helix.rest.metadatastore;
+package org.apache.helix.msdcommon.callback;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/constant/MetadataStoreRoutingConstants.java b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/constant/MetadataStoreRoutingConstants.java
similarity index 96%
rename from helix-rest/src/main/java/org/apache/helix/rest/metadatastore/constant/MetadataStoreRoutingConstants.java
rename to metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/constant/MetadataStoreRoutingConstants.java
index 846aa30..009e7f3 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/constant/MetadataStoreRoutingConstants.java
+++ b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/constant/MetadataStoreRoutingConstants.java
@@ -1,4 +1,4 @@
-package org.apache.helix.rest.metadatastore.constant;
+package org.apache.helix.msdcommon.constant;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/MetadataStoreRoutingData.java b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/datamodel/MetadataStoreRoutingData.java
similarity index 98%
rename from helix-rest/src/main/java/org/apache/helix/rest/metadatastore/MetadataStoreRoutingData.java
rename to metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/datamodel/MetadataStoreRoutingData.java
index 3bd9baa..2bab8c7 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/MetadataStoreRoutingData.java
+++ b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/datamodel/MetadataStoreRoutingData.java
@@ -1,4 +1,4 @@
-package org.apache.helix.rest.metadatastore;
+package org.apache.helix.msdcommon.datamodel;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -64,4 +64,4 @@ public interface MetadataStoreRoutingData {
    * @return true if the sharding key and realm address pair exist in the routing data
    */
   boolean containsKeyRealmPair(String shardingKey, String realmAddress);
-}
+}
\ No newline at end of file
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/TrieRoutingData.java b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/datamodel/TrieRoutingData.java
similarity index 97%
rename from helix-rest/src/main/java/org/apache/helix/rest/metadatastore/TrieRoutingData.java
rename to metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/datamodel/TrieRoutingData.java
index f82b718..7a05089 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/TrieRoutingData.java
+++ b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/datamodel/TrieRoutingData.java
@@ -1,4 +1,4 @@
-package org.apache.helix.rest.metadatastore;
+package org.apache.helix.msdcommon.datamodel;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -27,8 +27,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
 
-import org.apache.helix.rest.metadatastore.exceptions.InvalidRoutingDataException;
-import org.apache.helix.zookeeper.util.ZkValidationUtil;
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
+import org.apache.helix.msdcommon.util.ZkValidationUtil;
 
 
 /**
@@ -42,7 +42,8 @@ public class TrieRoutingData implements MetadataStoreRoutingData {
 
   private final TrieNode _rootNode;
 
-  public TrieRoutingData(Map<String, List<String>> routingData) throws InvalidRoutingDataException {
+  public TrieRoutingData(Map<String, List<String>> routingData)
+      throws InvalidRoutingDataException {
     if (routingData == null || routingData.isEmpty()) {
       throw new InvalidRoutingDataException("routingData cannot be null or empty");
     }
@@ -56,7 +57,8 @@ public class TrieRoutingData implements MetadataStoreRoutingData {
     }
   }
 
-  public Map<String, String> getAllMappingUnderPath(String path) throws IllegalArgumentException {
+  public Map<String, String> getAllMappingUnderPath(String path)
+      throws IllegalArgumentException {
     if (!ZkValidationUtil.isPathValid(path)) {
       throw new IllegalArgumentException("Provided path is not a valid Zookeeper path: " + path);
     }
@@ -289,4 +291,4 @@ public class TrieRoutingData implements MetadataStoreRoutingData {
       _children.put(key, node);
     }
   }
-}
+}
\ No newline at end of file
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/exceptions/InvalidRoutingDataException.java b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/exception/InvalidRoutingDataException.java
similarity index 95%
rename from helix-rest/src/main/java/org/apache/helix/rest/metadatastore/exceptions/InvalidRoutingDataException.java
rename to metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/exception/InvalidRoutingDataException.java
index 267aadc..9a361b9 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/exceptions/InvalidRoutingDataException.java
+++ b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/exception/InvalidRoutingDataException.java
@@ -1,4 +1,4 @@
-package org.apache.helix.rest.metadatastore.exceptions;
+package org.apache.helix.msdcommon.exception;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/util/ZkValidationUtil.java b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/util/ZkValidationUtil.java
similarity index 96%
rename from zookeeper-api/src/main/java/org/apache/helix/zookeeper/util/ZkValidationUtil.java
rename to metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/util/ZkValidationUtil.java
index 59070ac..472b3d9 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/util/ZkValidationUtil.java
+++ b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/util/ZkValidationUtil.java
@@ -1,4 +1,4 @@
-package org.apache.helix.zookeeper.util;
+package org.apache.helix.msdcommon.util;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
diff --git a/metadata-store-directory-common/src/test/conf/testng.xml b/metadata-store-directory-common/src/test/conf/testng.xml
new file mode 100644
index 0000000..2ee64fa
--- /dev/null
+++ b/metadata-store-directory-common/src/test/conf/testng.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
+<suite name="Suite" parallel="false">
+  <test name="Test" preserve-order="true">
+    <packages>
+      <package name="org.apache.helix.msdcommon.*"/>
+    </packages>
+  </test>
+</suite>
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestTrieRoutingData.java b/metadata-store-directory-common/src/test/java/org/apache/helix/msdcommon/datamodel/TestTrieRoutingData.java
similarity index 98%
rename from helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestTrieRoutingData.java
rename to metadata-store-directory-common/src/test/java/org/apache/helix/msdcommon/datamodel/TestTrieRoutingData.java
index dedde19..bad10a4 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestTrieRoutingData.java
+++ b/metadata-store-directory-common/src/test/java/org/apache/helix/msdcommon/datamodel/TestTrieRoutingData.java
@@ -1,4 +1,4 @@
-package org.apache.helix.rest.metadatastore;
+package org.apache.helix.msdcommon.datamodel;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -26,7 +26,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
 
-import org.apache.helix.rest.metadatastore.exceptions.InvalidRoutingDataException;
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
@@ -304,4 +304,4 @@ public class TestTrieRoutingData {
   public void testContainsKeyRealmPairNoRealm() {
     Assert.assertFalse(_trie.containsKeyRealmPair("/h/i", "realmAddress0"));
   }
-}
+}
\ No newline at end of file
diff --git a/metadata-store-directory-common/src/test/resources/log4j.properties b/metadata-store-directory-common/src/test/resources/log4j.properties
new file mode 100644
index 0000000..24b6d10
--- /dev/null
+++ b/metadata-store-directory-common/src/test/resources/log4j.properties
@@ -0,0 +1,41 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+# Set root logger level to DEBUG and its only appender to R.
+log4j.rootLogger=ERROR, C
+
+# A1 is set to be a ConsoleAppender.
+log4j.appender.C=org.apache.log4j.ConsoleAppender
+log4j.appender.C.layout=org.apache.log4j.PatternLayout
+log4j.appender.C.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
+
+log4j.appender.R=org.apache.log4j.RollingFileAppender
+log4j.appender.R.layout=org.apache.log4j.PatternLayout
+log4j.appender.R.layout.ConversionPattern=%5p [%C:%M] (%F:%L) - %m%n
+log4j.appender.R.File=target/ClusterManagerLogs/log.txt
+
+log4j.appender.STATUSDUMP=org.apache.log4j.RollingFileAppender
+log4j.appender.STATUSDUMP.layout=org.apache.log4j.SimpleLayout
+log4j.appender.STATUSDUMP.File=target/ClusterManagerLogs/statusUpdates.log
+
+log4j.logger.org.I0Itec=ERROR
+log4j.logger.org.apache=ERROR
+log4j.logger.com.noelios=ERROR
+log4j.logger.org.restlet=ERROR
+
+log4j.logger.org.apache.helix.monitoring.ZKPathDataDumpTask=ERROR,STATUSDUMP
diff --git a/pom.xml b/pom.xml
index 6a51c9c..1681850 100644
--- a/pom.xml
+++ b/pom.xml
@@ -247,6 +247,7 @@ under the License.
   </developers>
   <modules>
     <module>metrics-common</module>
+    <module>metadata-store-directory-common</module>
     <module>zookeeper-api</module>
     <module>helix-common</module>
     <module>helix-core</module>


[helix] 19/49: Add REST read endpoints to helix-rest for metadata store directory (#761)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit ac4be5a95b40c236fd3f25ba9bdda3e94faad264
Author: Huizhi Lu <ih...@gmail.com>
AuthorDate: Sat Feb 22 18:12:53 2020 -0800

    Add REST read endpoints to helix-rest for metadata store directory (#761)
    
    We need restful metadata store directory service to help scale out zookeeper.
    
    This commit adds REST read endpoints to helix-rest to get sharding keys, realms and namespaces.
---
 .../datamodel/MetadataStoreShardingKey.java        |  61 +++++
 .../MetadataStoreDirectoryAccessor.java            | 172 +++++++++++---
 .../server/TestMetadataStoreDirectoryAccessor.java | 255 +++++++++++++++++----
 .../constant/MetadataStoreRoutingConstants.java    |  33 ++-
 4 files changed, 442 insertions(+), 79 deletions(-)

diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/datamodel/MetadataStoreShardingKey.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/datamodel/MetadataStoreShardingKey.java
new file mode 100644
index 0000000..ac5ca59
--- /dev/null
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/datamodel/MetadataStoreShardingKey.java
@@ -0,0 +1,61 @@
+package org.apache.helix.rest.metadatastore.datamodel;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+
+/**
+ * A POJO class that represents a sharding key can be easily converted to JSON
+ * in REST API response. The JSON object for a sharding key looks like:
+ * {
+ * 	"shardingKey": "/sharding/key/10/abc",
+ * 	"realm": "realm.github.com"
+ * }
+ */
+@JsonAutoDetect
+public class MetadataStoreShardingKey {
+  private String shardingKey;
+  private String realm;
+
+  @JsonCreator
+  public MetadataStoreShardingKey(@JsonProperty String shardingKey, @JsonProperty String realm) {
+    this.shardingKey = shardingKey;
+    this.realm = realm;
+  }
+
+  @JsonProperty
+  public String getShardingKey() {
+    return shardingKey;
+  }
+
+  @JsonProperty
+  public String getRealm() {
+    return realm;
+  }
+
+  @Override
+  public String toString() {
+    return "MetadataStoreShardingKey{" + "shardingKey='" + shardingKey + '\'' + ", realm='" + realm
+        + '\'' + '}';
+  }
+}
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/server/resources/metadatastore/MetadataStoreDirectoryAccessor.java b/helix-rest/src/main/java/org/apache/helix/rest/server/resources/metadatastore/MetadataStoreDirectoryAccessor.java
index 5d84d8a..bc6571a 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/server/resources/metadatastore/MetadataStoreDirectoryAccessor.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/server/resources/metadatastore/MetadataStoreDirectoryAccessor.java
@@ -20,11 +20,12 @@ package org.apache.helix.rest.server.resources.metadatastore;
  */
 
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
+import java.util.stream.Collectors;
 import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
 import javax.ws.rs.DELETE;
 import javax.ws.rs.GET;
 import javax.ws.rs.PUT;
@@ -41,6 +42,7 @@ import org.apache.helix.rest.common.HelixRestNamespace;
 import org.apache.helix.rest.common.HelixRestUtils;
 import org.apache.helix.rest.metadatastore.MetadataStoreDirectory;
 import org.apache.helix.rest.metadatastore.ZkMetadataStoreDirectory;
+import org.apache.helix.rest.metadatastore.datamodel.MetadataStoreShardingKey;
 import org.apache.helix.rest.server.resources.AbstractResource;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -65,25 +67,53 @@ public class MetadataStoreDirectoryAccessor extends AbstractResource {
     buildMetadataStoreDirectory(_namespace, helixRestNamespace.getMetadataStoreAddress());
   }
 
+  @PreDestroy
+  private void preDestroy() {
+    _metadataStoreDirectory.close();
+  }
+
   /**
-   * Gets all metadata store realms in a namespace with the endpoint.
+   * Gets all existing namespaces in the routing metadata store at endpoint:
+   * "GET /metadata-store-namespaces"
+   *
+   * @return Json response of all namespaces.
+   */
+  @GET
+  @Path("/metadata-store-namespaces")
+  public Response getAllNamespaces() {
+    Collection<String> namespaces = _metadataStoreDirectory.getAllNamespaces();
+    Map<String, Collection<String>> responseMap =
+        ImmutableMap.of(MetadataStoreRoutingConstants.METADATA_STORE_NAMESPACES, namespaces);
+
+    return JSONRepresentation(responseMap);
+  }
+
+  /**
+   * Gets all metadata store realms in a namespace at path: "GET /metadata-store-realms",
+   * or gets a metadata store realm with the sharding key at path:
+   * "GET /metadata-store-realms?sharding-key={sharding-key}"
    *
    * @return Json representation of all realms.
    */
   @GET
   @Path("/metadata-store-realms")
-  public Response getAllMetadataStoreRealms() {
-    Map<String, Collection<String>> responseMap;
+  public Response getAllMetadataStoreRealms(@QueryParam("sharding-key") String shardingKey) {
     try {
-      Collection<String> realms = _metadataStoreDirectory.getAllMetadataStoreRealms(_namespace);
+      if (shardingKey == null) {
+        // Get all realms: "GET /metadata-store-realms"
+        Collection<String> realms = _metadataStoreDirectory.getAllMetadataStoreRealms(_namespace);
+        Map<String, Collection<String>> responseMap =
+            ImmutableMap.of(MetadataStoreRoutingConstants.METADATA_STORE_REALMS, realms);
+        return JSONRepresentation(responseMap);
+      }
 
-      responseMap = new HashMap<>(1);
-      responseMap.put(MetadataStoreRoutingConstants.METADATA_STORE_REALMS, realms);
+      // Get a single realm filtered by sharding key:
+      // "GET /metadata-store-realms?sharding-key={sharding-key}"
+      String realm = _metadataStoreDirectory.getMetadataStoreRealm(_namespace, shardingKey);
+      return JSONRepresentation(new MetadataStoreShardingKey(shardingKey, realm));
     } catch (NoSuchElementException ex) {
       return notFound(ex.getMessage());
     }
-
-    return JSONRepresentation(responseMap);
   }
 
   @PUT
@@ -111,41 +141,70 @@ public class MetadataStoreDirectoryAccessor extends AbstractResource {
   }
 
   /**
-   * Gets sharding keys mapped at path "HTTP GET /sharding-keys" which returns all sharding keys in
-   * a namespace, or path "HTTP GET /sharding-keys?realm={realmName}" which returns sharding keys in
-   * a realm.
+   * Gets all sharding keys for following requests:
+   * - "HTTP GET /sharding-keys" which returns all sharding keys in a namespace.
+   * - "HTTP GET /sharding-keys?prefix={prefix}" which returns sharding keys that have the prefix.
+   * -- JSON response example for this path:
+   * {
+   * 	"prefix": "/sharding/key",
+   * 	"shardingKeys": [{
+   * 		"realm": "testRealm2",
+   * 		"shardingKey": "/sharding/key/1/f"
+   *    }, {
+   * 		"realm": "testRealm2",
+   * 		"shardingKey": "/sharding/key/1/e"
+   *  }, {
+   * 		"realm": "testRealm1",
+   * 		"shardingKey": "/sharding/key/1/b"
+   *  }, {
+   * 		"realm": "testRealm1",
+   * 		"shardingKey": "/sharding/key/1/a"
+   *  }]
+   * }
    *
-   * @param realm Query param in endpoint path
-   * @return Json representation of a map: shardingKeys -> collection of sharding keys.
+   * @param prefix Query param in endpoint path: prefix substring of sharding key.
+   * @return Json representation for the sharding keys.
    */
   @GET
   @Path("/sharding-keys")
-  public Response getShardingKeys(@QueryParam("realm") String realm) {
-    Map<String, Object> responseMap;
-    Collection<String> shardingKeys;
+  public Response getShardingKeys(@QueryParam("prefix") String prefix) {
     try {
-      // If realm is not set in query param, the endpoint is: "/sharding-keys"
-      // to get all sharding keys in a namespace.
-      if (realm == null) {
-        shardingKeys = _metadataStoreDirectory.getAllShardingKeys(_namespace);
-        // To avoid allocating unnecessary resource, limit the map's capacity only for
-        // SHARDING_KEYS.
-        responseMap = new HashMap<>(1);
-      } else {
-        // For endpoint: "/sharding-keys?realm={realmName}"
-        shardingKeys = _metadataStoreDirectory.getAllShardingKeysInRealm(_namespace, realm);
-        // To avoid allocating unnecessary resource, limit the map's capacity only for
-        // SHARDING_KEYS and SINGLE_METADATA_STORE_REALM.
-        responseMap = new HashMap<>(2);
-        responseMap.put(MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_REALM, realm);
+      if (prefix == null) {
+        // For endpoint: "/sharding-keys" to get all sharding keys in a namespace.
+        return getAllShardingKeys();
       }
+      // For endpoint: "/sharding-keys?prefix={prefix}"
+      return getAllShardingKeysUnderPath(prefix);
     } catch (NoSuchElementException ex) {
       return notFound(ex.getMessage());
     }
+  }
 
-    responseMap.put(MetadataStoreRoutingConstants.SHARDING_KEYS, shardingKeys);
+  /**
+   * Gets all path-based sharding keys for a queried realm at endpoint:
+   * "GET /metadata-store-realms/{realm}/sharding-keys"
+   * <p>
+   * "GET /metadata-store-realms/{realm}/sharding-keys?prefix={prefix}" is also supported,
+   * which is helpful when you want to check what sharding keys have the prefix substring.
+   *
+   * @param realm Queried metadata store realm to get sharding keys.
+   * @param prefix Query param in endpoint path: prefix substring of sharding key.
+   * @return All path-based sharding keys in the queried realm.
+   */
+  @GET
+  @Path("/metadata-store-realms/{realm}/sharding-keys")
+  public Response getRealmShardingKeys(@PathParam("realm") String realm,
+      @QueryParam("prefix") String prefix) {
+    try {
+      if (prefix == null) {
+        return getAllShardingKeysInRealm(realm);
+      }
 
-    return JSONRepresentation(responseMap);
+      // For "GET /metadata-store-realms/{realm}/sharding-keys?prefix={prefix}"
+      return getRealmShardingKeysUnderPath(realm, prefix);
+    } catch (NoSuchElementException ex) {
+      return notFound(ex.getMessage());
+    }
   }
 
   @PUT
@@ -209,4 +268,51 @@ public class MetadataStoreDirectoryAccessor extends AbstractResource {
           routingZkAddressMap, ex);
     }
   }
+
+  private Response getAllShardingKeys() {
+    Collection<String> shardingKeys = _metadataStoreDirectory.getAllShardingKeys(_namespace);
+    Map<String, Object> responseMap = ImmutableMap
+        .of(MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_NAMESPACE, _namespace,
+            MetadataStoreRoutingConstants.SHARDING_KEYS, shardingKeys);
+
+    return JSONRepresentation(responseMap);
+  }
+
+  private Response getAllShardingKeysInRealm(String realm) {
+    Collection<String> shardingKeys =
+        _metadataStoreDirectory.getAllShardingKeysInRealm(_namespace, realm);
+
+    Map<String, Object> responseMap = ImmutableMap
+        .of(MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_REALM, realm,
+            MetadataStoreRoutingConstants.SHARDING_KEYS, shardingKeys);
+
+    return JSONRepresentation(responseMap);
+  }
+
+  private Response getAllShardingKeysUnderPath(String prefix) {
+    List<MetadataStoreShardingKey> shardingKeyList =
+        _metadataStoreDirectory.getAllMappingUnderPath(_namespace, prefix).entrySet().stream()
+            .map(entry -> new MetadataStoreShardingKey(entry.getKey(), entry.getValue()))
+            .collect(Collectors.toList());
+
+    Map<String, Object> responseMap = ImmutableMap
+        .of(MetadataStoreRoutingConstants.SHARDING_KEY_PATH_PREFIX, prefix,
+            MetadataStoreRoutingConstants.SHARDING_KEYS, shardingKeyList);
+
+    return JSONRepresentation(responseMap);
+  }
+
+  private Response getRealmShardingKeysUnderPath(String realm, String prefix) {
+    List<String> shardingKeyList =
+        _metadataStoreDirectory.getAllMappingUnderPath(_namespace, prefix).entrySet().stream()
+            .filter(entry -> entry.getValue().equals(realm)).map(Map.Entry::getKey)
+            .collect(Collectors.toList());
+
+    Map<String, Object> responseMap = ImmutableMap
+        .of(MetadataStoreRoutingConstants.SHARDING_KEY_PATH_PREFIX, prefix,
+            MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_REALM, realm,
+            MetadataStoreRoutingConstants.SHARDING_KEYS, shardingKeyList);
+
+    return JSONRepresentation(responseMap);
+  }
 }
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/server/TestMetadataStoreDirectoryAccessor.java b/helix-rest/src/test/java/org/apache/helix/rest/server/TestMetadataStoreDirectoryAccessor.java
index 27e2b10..6a9c598 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/server/TestMetadataStoreDirectoryAccessor.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/server/TestMetadataStoreDirectoryAccessor.java
@@ -47,6 +47,7 @@ import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
 
+// TODO: enable asserts and add verify for refreshed MSD once write operations are ready.
 public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
   /*
    * The following are constants to be used for testing.
@@ -61,15 +62,14 @@ public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
   private static final List<String> TEST_SHARDING_KEYS_2 =
       Arrays.asList("/sharding/key/1/d", "/sharding/key/1/e", "/sharding/key/1/f");
   private static final String TEST_REALM_3 = "testRealm3";
-  private static final String TEST_SHARDING_KEY = "/sharding/key/1/x";
+  private static final String TEST_SHARDING_KEY = "/sharding/key/3/x";
 
   // List of all ZK addresses, each of which corresponds to a namespace/routing ZK
   private List<String> _zkList;
   private MetadataStoreDirectory _metadataStoreDirectory;
 
   @BeforeClass
-  public void beforeClass()
-      throws Exception {
+  public void beforeClass() throws Exception {
     _zkList = new ArrayList<>(ZK_SERVER_MAP.keySet());
 
     deleteRoutingDataPath();
@@ -111,15 +111,39 @@ public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
   }
 
   @AfterClass
-  public void afterClass()
-      throws Exception {
+  public void afterClass() throws Exception {
     _metadataStoreDirectory.close();
     deleteRoutingDataPath();
   }
 
+  /*
+   * Tests REST endpoint: "GET /namespaces/{namespace}/metadata-store-namespaces"
+   */
   @Test
-  public void testGetAllMetadataStoreRealms()
-      throws IOException {
+  public void testGetAllNamespaces() throws IOException {
+    String responseBody = get(TEST_NAMESPACE_URI_PREFIX + "/metadata-store-namespaces", null,
+        Response.Status.OK.getStatusCode(), true);
+
+    // It is safe to cast the object and suppress warnings.
+    @SuppressWarnings("unchecked")
+    Map<String, Collection<String>> queriedNamespacesMap =
+        OBJECT_MAPPER.readValue(responseBody, Map.class);
+
+    Assert.assertEquals(queriedNamespacesMap.keySet(),
+        ImmutableSet.of(MetadataStoreRoutingConstants.METADATA_STORE_NAMESPACES));
+
+    Set<String> queriedNamespacesSet = new HashSet<>(
+        queriedNamespacesMap.get(MetadataStoreRoutingConstants.METADATA_STORE_NAMESPACES));
+    Set<String> expectedNamespaces = ImmutableSet.of(TEST_NAMESPACE);
+
+    Assert.assertEquals(queriedNamespacesSet, expectedNamespaces);
+  }
+
+  /*
+   * Tests REST endpoint: "GET /metadata-store-realms"
+   */
+  @Test(dependsOnMethods = "testGetAllNamespaces")
+  public void testGetAllMetadataStoreRealms() throws IOException {
     get(NON_EXISTING_NAMESPACE_URI_PREFIX + "metadata-store-realms", null,
         Response.Status.NOT_FOUND.getStatusCode(), false);
 
@@ -130,8 +154,8 @@ public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
     Map<String, Collection<String>> queriedRealmsMap =
         OBJECT_MAPPER.readValue(responseBody, Map.class);
 
-    Assert.assertTrue(
-        queriedRealmsMap.containsKey(MetadataStoreRoutingConstants.METADATA_STORE_REALMS));
+    Assert.assertEquals(queriedRealmsMap.keySet(),
+        ImmutableSet.of(MetadataStoreRoutingConstants.METADATA_STORE_REALMS));
 
     Set<String> queriedRealmsSet =
         new HashSet<>(queriedRealmsMap.get(MetadataStoreRoutingConstants.METADATA_STORE_REALMS));
@@ -140,7 +164,36 @@ public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
     Assert.assertEquals(queriedRealmsSet, expectedRealms);
   }
 
+  /*
+   * Tests REST endpoint: "GET /metadata-store-realms?sharding-key={sharding-key}"
+   */
   @Test(dependsOnMethods = "testGetAllMetadataStoreRealms")
+  public void testGetMetadataStoreRealmWithShardingKey() throws IOException {
+    String shardingKey = TEST_SHARDING_KEYS_1.get(0);
+
+    new JerseyUriRequestBuilder(
+        NON_EXISTING_NAMESPACE_URI_PREFIX + "metadata-store-realms?sharding-key=" + shardingKey)
+        .expectedReturnStatusCode(Response.Status.NOT_FOUND.getStatusCode()).get(this);
+
+    String responseBody = new JerseyUriRequestBuilder(
+        TEST_NAMESPACE_URI_PREFIX + "/metadata-store-realms?sharding-key=" + shardingKey)
+        .isBodyReturnExpected(true).get(this);
+
+    // It is safe to cast the object and suppress warnings.
+    @SuppressWarnings("unchecked")
+    Map<String, String> queriedRealmMap = OBJECT_MAPPER.readValue(responseBody, Map.class);
+
+    Map<String, String> expectedRealm = ImmutableMap
+        .of(MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_REALM, TEST_REALM_1,
+            MetadataStoreRoutingConstants.SINGLE_SHARDING_KEY, shardingKey);
+
+    Assert.assertEquals(queriedRealmMap, expectedRealm);
+  }
+
+  /*
+   * Tests REST endpoint: "PUT /metadata-store-realms/{realm}"
+   */
+  @Test(dependsOnMethods = "testGetMetadataStoreRealmWithShardingKey")
   public void testAddMetadataStoreRealm() {
     Collection<String> previousRealms =
         _metadataStoreDirectory.getAllMetadataStoreRealms(TEST_NAMESPACE);
@@ -164,10 +217,12 @@ public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
     Set<String> updateRealmsSet = new HashSet<>(updatedRealms);
     expectedRealmsSet.add(TEST_REALM_3);
 
-    // TODO: enable asserts and add verify for refreshed MSD once write operations are ready.
 //    Assert.assertEquals(updateRealmsSet, previousRealms);
   }
 
+  /*
+   * Tests REST endpoint: "DELETE /metadata-store-realms/{realm}"
+   */
   @Test(dependsOnMethods = "testAddMetadataStoreRealm")
   public void testDeleteMetadataStoreRealm() {
     Collection<String> previousRealms =
@@ -194,11 +249,10 @@ public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
   }
 
   /*
-   * Tests REST endpoints: "/sharding-keys"
+   * Tests REST endpoint: "GET /sharding-keys"
    */
   @Test(dependsOnMethods = "testDeleteMetadataStoreRealm")
-  public void testGetShardingKeysInNamespace()
-      throws IOException {
+  public void testGetShardingKeysInNamespace() throws IOException {
     get(NON_EXISTING_NAMESPACE_URI_PREFIX + "sharding-keys", null,
         Response.Status.NOT_FOUND.getStatusCode(), true);
 
@@ -207,14 +261,19 @@ public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
             true);
     // It is safe to cast the object and suppress warnings.
     @SuppressWarnings("unchecked")
-    Map<String, Collection<String>> queriedShardingKeysMap =
-        OBJECT_MAPPER.readValue(responseBody, Map.class);
+    Map<String, Object> queriedShardingKeysMap = OBJECT_MAPPER.readValue(responseBody, Map.class);
+
+    Assert.assertEquals(queriedShardingKeysMap.keySet(), ImmutableSet
+        .of(MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_NAMESPACE,
+            MetadataStoreRoutingConstants.SHARDING_KEYS));
 
-    Assert.assertTrue(
-        queriedShardingKeysMap.containsKey(MetadataStoreRoutingConstants.SHARDING_KEYS));
+    Assert.assertEquals(
+        queriedShardingKeysMap.get(MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_NAMESPACE),
+        TEST_NAMESPACE);
 
-    Set<String> queriedShardingKeys =
-        new HashSet<>(queriedShardingKeysMap.get(MetadataStoreRoutingConstants.SHARDING_KEYS));
+    @SuppressWarnings("unchecked")
+    Set<String> queriedShardingKeys = new HashSet<>((Collection<String>) queriedShardingKeysMap
+        .get(MetadataStoreRoutingConstants.SHARDING_KEYS));
     Set<String> expectedShardingKeys = new HashSet<>();
     expectedShardingKeys.addAll(TEST_SHARDING_KEYS_1);
     expectedShardingKeys.addAll(TEST_SHARDING_KEYS_2);
@@ -223,38 +282,125 @@ public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
   }
 
   /*
-   * Tests REST endpoint: "/sharding-keys?realm={realmName}"
+   * Tests REST endpoint: "GET /metadata-store-realms/{realm}/sharding-keys"
    */
   @Test(dependsOnMethods = "testGetShardingKeysInNamespace")
-  public void testGetShardingKeysInRealm()
-      throws IOException {
+  public void testGetShardingKeysInRealm() throws IOException {
     // Test NOT_FOUND response for a non existed realm.
-    new JerseyUriRequestBuilder(TEST_NAMESPACE_URI_PREFIX + "/sharding-keys?realm=nonExistedRealm")
+    new JerseyUriRequestBuilder(
+        TEST_NAMESPACE_URI_PREFIX + "/metadata-store-realms/nonExistedRealm/sharding-keys")
         .expectedReturnStatusCode(Response.Status.NOT_FOUND.getStatusCode()).get(this);
 
-    // Query param realm is set to empty, so NOT_FOUND response is returned.
-    new JerseyUriRequestBuilder(TEST_NAMESPACE_URI_PREFIX + "/sharding-keys?realm=")
-        .expectedReturnStatusCode(Response.Status.NOT_FOUND.getStatusCode()).get(this);
+    // Success response for "GET /metadata-store-realms/{realm}/sharding-keys"
+    String responseBody = new JerseyUriRequestBuilder(
+        TEST_NAMESPACE_URI_PREFIX + "/metadata-store-realms/" + TEST_REALM_1 + "/sharding-keys")
+        .isBodyReturnExpected(true).get(this);
+
+    verifyRealmShardingKeys(responseBody);
+  }
 
-    // Success response.
+  /*
+   * Tests REST endpoint: "GET /sharding-keys?prefix={prefix}"
+   */
+  @SuppressWarnings("unchecked")
+  @Test(dependsOnMethods = "testGetShardingKeysInRealm")
+  public void testGetShardingKeysUnderPath() throws IOException {
+    // Test non existed prefix and empty sharding keys in response.
     String responseBody = new JerseyUriRequestBuilder(
-        TEST_NAMESPACE_URI_PREFIX + "/sharding-keys?realm=" + TEST_REALM_1)
+        TEST_NAMESPACE_URI_PREFIX + "/sharding-keys?prefix=/non/Existed/Prefix")
         .isBodyReturnExpected(true).get(this);
-    // It is safe to cast the object and suppress warnings.
-    @SuppressWarnings("unchecked")
+
     Map<String, Object> queriedShardingKeysMap = OBJECT_MAPPER.readValue(responseBody, Map.class);
+    Collection<Map<String, String>> emptyKeysList =
+        (Collection<Map<String, String>>) queriedShardingKeysMap
+            .get(MetadataStoreRoutingConstants.SHARDING_KEYS);
+    Assert.assertTrue(emptyKeysList.isEmpty());
+
+    // Success response with non empty sharding keys.
+    String shardingKeyPrefix = "/sharding/key";
+    responseBody = new JerseyUriRequestBuilder(
+        TEST_NAMESPACE_URI_PREFIX + "/sharding-keys?prefix=" + shardingKeyPrefix)
+        .isBodyReturnExpected(true).get(this);
+
+    queriedShardingKeysMap = OBJECT_MAPPER.readValue(responseBody, Map.class);
+
+    // Check fields.
+    Assert.assertEquals(queriedShardingKeysMap.keySet(), ImmutableSet
+        .of(MetadataStoreRoutingConstants.SHARDING_KEY_PATH_PREFIX,
+            MetadataStoreRoutingConstants.SHARDING_KEYS));
+
+    // Check sharding key prefix in json response.
+    Assert.assertEquals(
+        queriedShardingKeysMap.get(MetadataStoreRoutingConstants.SHARDING_KEY_PATH_PREFIX),
+        shardingKeyPrefix);
+
+    Collection<Map<String, String>> queriedShardingKeys =
+        (Collection<Map<String, String>>) queriedShardingKeysMap
+            .get(MetadataStoreRoutingConstants.SHARDING_KEYS);
+    Set<Map<String, String>> queriedShardingKeysSet = new HashSet<>(queriedShardingKeys);
+    Set<Map<String, String>> expectedShardingKeysSet = new HashSet<>();
+
+    TEST_SHARDING_KEYS_1.forEach(key -> expectedShardingKeysSet.add(ImmutableMap
+        .of(MetadataStoreRoutingConstants.SINGLE_SHARDING_KEY, key,
+            MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_REALM, TEST_REALM_1)));
+
+    TEST_SHARDING_KEYS_2.forEach(key -> expectedShardingKeysSet.add(ImmutableMap
+        .of(MetadataStoreRoutingConstants.SINGLE_SHARDING_KEY, key,
+            MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_REALM, TEST_REALM_2)));
+
+    Assert.assertEquals(queriedShardingKeysSet, expectedShardingKeysSet);
+  }
+
+  /*
+   * Tests REST endpoint: "GET /metadata-store-realms/{realm}/sharding-keys?prefix={prefix}"
+   */
+  @SuppressWarnings("unchecked")
+  @Test(dependsOnMethods = "testGetShardingKeysUnderPath")
+  public void testGetRealmShardingKeysUnderPath() throws IOException {
+    // Test non existed prefix and empty sharding keys in response.
+    String responseBody = new JerseyUriRequestBuilder(
+        TEST_NAMESPACE_URI_PREFIX + "/metadata-store-realms/" + TEST_REALM_1
+            + "/sharding-keys?prefix=/non/Existed/Prefix").isBodyReturnExpected(true).get(this);
+
+    Map<String, Object> queriedShardingKeysMap = OBJECT_MAPPER.readValue(responseBody, Map.class);
+    Collection<Map<String, String>> emptyKeysList =
+        (Collection<Map<String, String>>) queriedShardingKeysMap
+            .get(MetadataStoreRoutingConstants.SHARDING_KEYS);
+    Assert.assertTrue(emptyKeysList.isEmpty());
+
+    // Test non existed realm and empty sharding keys in response.
+    responseBody = new JerseyUriRequestBuilder(TEST_NAMESPACE_URI_PREFIX
+        + "/metadata-store-realms/nonExistedRealm/sharding-keys?prefix=/sharding/key")
+        .isBodyReturnExpected(true).get(this);
+
+    queriedShardingKeysMap = OBJECT_MAPPER.readValue(responseBody, Map.class);
+    emptyKeysList = (Collection<Map<String, String>>) queriedShardingKeysMap
+        .get(MetadataStoreRoutingConstants.SHARDING_KEYS);
+    Assert.assertTrue(emptyKeysList.isEmpty());
+
+    // Valid query params and non empty sharding keys.
+    String shardingKeyPrefix = "/sharding/key/1";
+    responseBody = new JerseyUriRequestBuilder(
+        TEST_NAMESPACE_URI_PREFIX + "/metadata-store-realms/" + TEST_REALM_1
+            + "/sharding-keys?prefix=" + shardingKeyPrefix).isBodyReturnExpected(true).get(this);
+
+    queriedShardingKeysMap = OBJECT_MAPPER.readValue(responseBody, Map.class);
+
+    // Check fields.
+    Assert.assertEquals(queriedShardingKeysMap.keySet(), ImmutableSet
+        .of(MetadataStoreRoutingConstants.SHARDING_KEY_PATH_PREFIX,
+            MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_REALM,
+            MetadataStoreRoutingConstants.SHARDING_KEYS));
+
+    // Check sharding key prefix in json response.
+    Assert.assertEquals(
+        queriedShardingKeysMap.get(MetadataStoreRoutingConstants.SHARDING_KEY_PATH_PREFIX),
+        shardingKeyPrefix);
 
-    // Check realm name in json response.
-    Assert.assertTrue(queriedShardingKeysMap
-        .containsKey(MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_REALM));
     Assert.assertEquals(
         queriedShardingKeysMap.get(MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_REALM),
         TEST_REALM_1);
-    Assert.assertTrue(
-        queriedShardingKeysMap.containsKey(MetadataStoreRoutingConstants.SHARDING_KEYS));
 
-    // It is safe to cast the object and suppress warnings.
-    @SuppressWarnings("unchecked")
     Set<String> queriedShardingKeys = new HashSet<>((Collection<String>) queriedShardingKeysMap
         .get(MetadataStoreRoutingConstants.SHARDING_KEYS));
     Set<String> expectedShardingKeys = new HashSet<>(TEST_SHARDING_KEYS_1);
@@ -262,7 +408,10 @@ public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
     Assert.assertEquals(queriedShardingKeys, expectedShardingKeys);
   }
 
-  @Test(dependsOnMethods = "testGetShardingKeysInRealm")
+  /*
+   * Tests REST endpoint: "PUT /metadata-store-realms/{realm}/sharding-keys/{sharding-key}"
+   */
+  @Test(dependsOnMethods = "testGetRealmShardingKeysUnderPath")
   public void testAddShardingKey() {
     Set<String> expectedShardingKeysSet = new HashSet<>(
         _metadataStoreDirectory.getAllShardingKeysInRealm(TEST_NAMESPACE, TEST_REALM_1));
@@ -287,6 +436,9 @@ public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
 //    Assert.assertEquals(updatedShardingKeysSet, expectedShardingKeysSet);
   }
 
+  /*
+   * Tests REST endpoint: "PUT /metadata-store-realms/{realm}/sharding-keys/{sharding-key}"
+   */
   @Test(dependsOnMethods = "testAddShardingKey")
   public void testDeleteShardingKey() {
     Set<String> expectedShardingKeysSet = new HashSet<>(
@@ -310,8 +462,31 @@ public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
 //    Assert.assertEquals(updatedShardingKeysSet, expectedShardingKeysSet);
   }
 
-  private void deleteRoutingDataPath()
-      throws Exception {
+  private void verifyRealmShardingKeys(String responseBody) throws IOException {
+    // It is safe to cast the object and suppress warnings.
+    @SuppressWarnings("unchecked")
+    Map<String, Object> queriedShardingKeysMap = OBJECT_MAPPER.readValue(responseBody, Map.class);
+
+    // Check fields in JSON response.
+    Assert.assertEquals(queriedShardingKeysMap.keySet(), ImmutableSet
+        .of(MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_REALM,
+            MetadataStoreRoutingConstants.SHARDING_KEYS));
+
+    // Check realm name in json response.
+    Assert.assertEquals(
+        queriedShardingKeysMap.get(MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_REALM),
+        TEST_REALM_1);
+
+    // It is safe to cast the object and suppress warnings.
+    @SuppressWarnings("unchecked")
+    Set<String> queriedShardingKeys = new HashSet<>((Collection<String>) queriedShardingKeysMap
+        .get(MetadataStoreRoutingConstants.SHARDING_KEYS));
+    Set<String> expectedShardingKeys = new HashSet<>(TEST_SHARDING_KEYS_1);
+
+    Assert.assertEquals(queriedShardingKeys, expectedShardingKeys);
+  }
+
+  private void deleteRoutingDataPath() throws Exception {
     Assert.assertTrue(TestHelper.verify(() -> {
       _zkList.forEach(zk -> ZK_SERVER_MAP.get(zk).getZkClient()
           .deleteRecursively(MetadataStoreRoutingConstants.ROUTING_DATA_PATH));
diff --git a/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/constant/MetadataStoreRoutingConstants.java b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/constant/MetadataStoreRoutingConstants.java
index 009e7f3..13e78b0 100644
--- a/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/constant/MetadataStoreRoutingConstants.java
+++ b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/constant/MetadataStoreRoutingConstants.java
@@ -28,14 +28,35 @@ public class MetadataStoreRoutingConstants {
   // Leader election ZNode for ZkRoutingDataWriter
   public static final String LEADER_ELECTION_ZNODE = "/_ZK_ROUTING_DATA_WRITER_LEADER";
 
-  // Field name in JSON REST response of getting metadata store realms in one namespace.
-  public static final String METADATA_STORE_REALMS = "metadataStoreRealms";
+  /** Field name in JSON REST response of getting all metadata store namespaces. */
+  public static final String METADATA_STORE_NAMESPACES = "namespaces";
 
-  // Field name in JSON REST response of getting sharding keys in one realm.
-  public static final String SINGLE_METADATA_STORE_REALM = "metadataStoreRealm";
+  /** Field name in JSON REST response of getting all sharding keys in a single namespace. */
+  public static final String SINGLE_METADATA_STORE_NAMESPACE = "namespace";
 
-  // Field name in JSON REST response of getting sharding keys.
-  public static final String SHARDING_KEYS = "shardingKeys";
+  /** Field name in JSON REST response of getting metadata store realms in one namespace. */
+  public static final String METADATA_STORE_REALMS = "realms";
+
+  /** Field name in JSON REST response of getting sharding keys in one realm. */
+  public static final String SINGLE_METADATA_STORE_REALM = "realm";
 
+  /** Field name in JSON REST response of getting sharding keys. */
+  public static final String SHARDING_KEYS = "shardingKeys";
 
+  /** Field name in JSON REST response related to one single sharding key. */
+  public static final String SINGLE_SHARDING_KEY = "shardingKey";
+
+  /**
+   * Field name in JSON response of the REST endpoint getting sharding keys with prefix:
+   * "GET /sharding-keys?prefix={prefix}"
+   * It is used in below response as an example:
+   * {
+   * 	"prefix": "/sharding/key",
+   * 	"shardingKeys": [{
+   * 		"realm": "testRealm2",
+   * 		"shardingKey": "/sharding/key/1/f"
+   *  }]
+   * }
+   */
+  public static final String SHARDING_KEY_PATH_PREFIX = "prefix";
 }


[helix] 02/49: Enable two test runs with multiZk system property (#710)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit fac1d05618132dabc6d6a2edd7531f6ca621fea8
Author: Hunter Lee <hu...@linkedin.com>
AuthorDate: Fri Jan 31 16:53:45 2020 -0800

    Enable two test runs with multiZk system property (#710)
    
    We want to achieve horizontal scalability for ZK, meaning we want to allow users to add more ZK deployments and shard based on the ZK read/write path. To test this going forward, we want to be able to execute existing tests in two different environments: 1. single-ZK env, and 2. multi-ZK env.
    Changelist:
    1. Create two executions for maven-surefire-plugin with multiZk system property
---
 pom.xml | 34 +++++++++++++++++++++++++++++++++-
 1 file changed, 33 insertions(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index c131ce8..e773d07 100644
--- a/pom.xml
+++ b/pom.xml
@@ -628,7 +628,39 @@ under the License.
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-surefire-plugin</artifactId>
-          <version>2.18.1</version>
+          <version>3.0.0-M3</version>
+          <executions>
+            <!--
+            "executions" enables multiple runs of integration test suites. This is to enable two
+            runs: 1. run in a single-ZK environment, 2. run in a multi-ZK environment.
+            "multiZk" is the config accessible via Systems.Properties so that the two runs could be
+            differentiated.
+            -->
+            <execution>
+              <goals>
+                <goal>test</goal>
+              </goals>
+              <id>default-test</id>
+              <phase>test</phase>
+              <configuration>
+                <systemPropertyVariables>
+                  <multiZk>false</multiZk>
+                </systemPropertyVariables>
+              </configuration>
+            </execution>
+            <execution>
+              <goals>
+                <goal>test</goal>
+              </goals>
+              <id>multi-zk</id>
+              <phase>test</phase>
+              <configuration>
+                <systemPropertyVariables>
+                  <multiZk>true</multiZk>
+                </systemPropertyVariables>
+              </configuration>
+            </execution>
+          </executions>
         </plugin>
         <plugin>
           <groupId>org.apache.rat</groupId>


[helix] 03/49: Upgrade AbstractTestClass with multi-ZK support in helix-rest (#717)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit 6d09be62670ffada692569d961bd3d80f1a7d6da
Author: Hunter Lee <hu...@linkedin.com>
AuthorDate: Mon Feb 3 18:05:49 2020 -0800

    Upgrade AbstractTestClass with multi-ZK support in helix-rest (#717)
    
    Prior to instrumenting Helix APIs and components so that they would be aware of multiple ZKs for horizontal scalability, we need to have a way to run all integration tests involving ZooKeeper in different environments: one with a single ZK and another with multiple ZKs.
    Changelist:
    
    Implement the logic in AbstractTestClass so that in conjunction with maven-surefire-plugin configs, there will be two executions of the test suite
    Remove system property variable from default-test since it's unnecessary
---
 .../helix/rest/server/AbstractTestClass.java       | 38 ++++++++++++++++++++--
 1 file changed, 35 insertions(+), 3 deletions(-)

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 cc04984..0302758 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
@@ -93,6 +93,12 @@ import org.testng.annotations.AfterSuite;
 import org.testng.annotations.BeforeSuite;
 
 public class AbstractTestClass extends JerseyTestNg.ContainerPerClassTest {
+  private static final String MULTI_ZK_PROPERTY_KEY = "multiZk";
+  private static final String NUM_ZK_PROPERTY_KEY = "numZk";
+  private static final String ZK_PREFIX = "localhost:";
+  private static final int ZK_START_PORT = 2123;
+  protected Map<String, ZkServer> _zkServerMap;
+
   protected static final String ZK_ADDR = "localhost:2123";
   protected static final String WORKFLOW_PREFIX = "Workflow_";
   protected static final String JOB_PREFIX = "Job_";
@@ -149,20 +155,43 @@ public class AbstractTestClass extends JerseyTestNg.ContainerPerClassTest {
   @Override
   protected Application configure() {
     // start zk
+    _zkServerMap = new HashMap<>();
     try {
       if (_zkServer == null) {
         _zkServer = TestHelper.startZkServer(ZK_ADDR);
-        Assert.assertTrue(_zkServer != null);
+        Assert.assertNotNull(_zkServer);
+        _zkServerMap.put(ZK_ADDR, _zkServer);
         ZKClientPool.reset();
       }
 
       if (_zkServerTestNS == null) {
         _zkServerTestNS = TestHelper.startZkServer(_zkAddrTestNS);
-        Assert.assertTrue(_zkServerTestNS != null);
+        Assert.assertNotNull(_zkServerTestNS);
+        _zkServerMap.put(_zkAddrTestNS, _zkServerTestNS);
         ZKClientPool.reset();
       }
     } catch (Exception e) {
-      Assert.assertTrue(false, String.format("Failed to start ZK server: %s", e.toString()));
+      Assert.fail(String.format("Failed to start ZK server: %s", e.toString()));
+    }
+
+    // Start additional ZKs in a multi-ZK setup
+    String multiZkConfig = System.getProperty(MULTI_ZK_PROPERTY_KEY);
+    if (multiZkConfig != null && multiZkConfig.equalsIgnoreCase(Boolean.TRUE.toString())) {
+      String numZkFromConfig = System.getProperty(NUM_ZK_PROPERTY_KEY);
+      if (numZkFromConfig != null) {
+        try {
+          int numZkFromConfigInt = Integer.parseInt(numZkFromConfig);
+          // Start (numZkFromConfigInt - 2) ZooKeepers
+          for (int i = 2; i < numZkFromConfigInt; i++) {
+            String zkAddr = ZK_PREFIX + (ZK_START_PORT + i);
+            ZkServer zkServer = TestHelper.startZkServer(zkAddr);
+            Assert.assertNotNull(zkServer);
+            _zkServerMap.put(zkAddr, zkServer);
+          }
+        } catch (Exception e) {
+          Assert.fail("Failed to create multiple ZooKeepers!");
+        }
+      }
     }
 
     // Configure server context
@@ -286,6 +315,9 @@ public class AbstractTestClass extends JerseyTestNg.ContainerPerClassTest {
       _zkServerTestNS = null;
     }
 
+    // Stop all ZkServers
+    _zkServerMap.forEach((zkAddr, zkServer) -> TestHelper.stopZkServer(zkServer));
+
     if (_helixRestServer != null) {
       _helixRestServer.shutdown();
       _helixRestServer = null;


[helix] 42/49: Make ZkBucketDataAccessor realm-aware (#894)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit 9ae4b5faa0f99ea2f272ae4b22a62f20deb4f744
Author: Hunter Lee <hu...@linkedin.com>
AuthorDate: Fri Mar 13 17:30:32 2020 -0700

    Make ZkBucketDataAccessor realm-aware (#894)
    
    Because Helix Controller now uses WAGED rebalancer as the default rebalancer, it tries to create an instance of ZkBucketDataAccessor. This will fail unless ZkBucketDataAccessor was also made realm-aware. We can simply use a FederatedZkClient here since BucketDataAccessor does not support ephemeral operations.
---
 .../helix/manager/zk/ZkBucketDataAccessor.java     | 24 +++++++++++++++++++---
 1 file changed, 21 insertions(+), 3 deletions(-)

diff --git a/helix-core/src/main/java/org/apache/helix/manager/zk/ZkBucketDataAccessor.java b/helix-core/src/main/java/org/apache/helix/manager/zk/ZkBucketDataAccessor.java
index ffed9f3..1ebab28 100644
--- a/helix-core/src/main/java/org/apache/helix/manager/zk/ZkBucketDataAccessor.java
+++ b/helix-core/src/main/java/org/apache/helix/manager/zk/ZkBucketDataAccessor.java
@@ -34,9 +34,13 @@ import org.apache.helix.AccessOption;
 import org.apache.helix.BucketDataAccessor;
 import org.apache.helix.HelixException;
 import org.apache.helix.HelixProperty;
+import org.apache.helix.SystemPropertyKeys;
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
 import org.apache.helix.util.GZipCompressionUtil;
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
+import org.apache.helix.zookeeper.impl.client.FederatedZkClient;
 import org.apache.helix.zookeeper.impl.factory.DedicatedZkClientFactory;
 import org.apache.helix.zookeeper.zkclient.DataUpdater;
 import org.apache.helix.zookeeper.zkclient.exception.ZkMarshallingError;
@@ -63,7 +67,7 @@ public class ZkBucketDataAccessor implements BucketDataAccessor, AutoCloseable {
   private final int _bucketSize;
   private final long _versionTTL;
   private ZkSerializer _zkSerializer;
-  private HelixZkClient _zkClient;
+  private RealmAwareZkClient _zkClient;
   private ZkBaseDataAccessor<byte[]> _zkBaseDataAccessor;
 
   /**
@@ -73,8 +77,22 @@ public class ZkBucketDataAccessor implements BucketDataAccessor, AutoCloseable {
    * @param versionTTL in ms
    */
   public ZkBucketDataAccessor(String zkAddr, int bucketSize, long versionTTL) {
-    _zkClient = DedicatedZkClientFactory.getInstance()
-        .buildZkClient(new HelixZkClient.ZkConnectionConfig(zkAddr));
+    if (Boolean.getBoolean(SystemPropertyKeys.MULTI_ZK_ENABLED)) {
+      try {
+        // Create realm-aware ZkClient.
+        RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig =
+            new RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder().build();
+        RealmAwareZkClient.RealmAwareZkClientConfig clientConfig =
+            new RealmAwareZkClient.RealmAwareZkClientConfig();
+        _zkClient = new FederatedZkClient(connectionConfig, clientConfig);
+      } catch (IllegalArgumentException | IOException | InvalidRoutingDataException e) {
+        throw new HelixException("Not able to connect on realm-aware mode", e);
+      }
+    } else {
+      _zkClient = DedicatedZkClientFactory.getInstance()
+          .buildZkClient(new HelixZkClient.ZkConnectionConfig(zkAddr));
+    }
+
     _zkClient.setZkSerializer(new ZkSerializer() {
       @Override
       public byte[] serialize(Object data) throws ZkMarshallingError {


[helix] 23/49: Implement request forwarding for ZkRoutingDataWriter (#788)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit 7442ef860b8045f9bf00ebe8530cc703fbc07927
Author: Neal Sun <ne...@gmail.com>
AuthorDate: Wed Feb 26 17:21:44 2020 -0800

    Implement request forwarding for ZkRoutingDataWriter (#788)
    
    This PR added the request forwarding feature to ZkRoutingDataWriter. It also included a lot of other work, most notably: changing ZkMetadataStoreDirectory to singleton in order to allow leader forwarding, added integration tests for the request forwarding flow, modified MetadataStoreDirectoryAccessor for it to respect underlying return values, fixed numerous behavior bugs.
---
 .../apache/helix/rest/common/HttpConstants.java    |  29 +++
 .../metadatastore/ZkMetadataStoreDirectory.java    |  83 ++++---
 .../accessor/ZkRoutingDataReader.java              |   6 +-
 .../accessor/ZkRoutingDataWriter.java              | 256 +++++++++++++++------
 .../concurrency/ZkDistributedLeaderElection.java   |   5 +-
 .../apache/helix/rest/server/HelixRestServer.java  |   2 +-
 .../apache/helix/rest/server/ServerContext.java    |   7 +
 .../MetadataStoreDirectoryAccessor.java            |  37 +--
 .../TestZkMetadataStoreDirectory.java              |  21 +-
 .../accessor/TestZkRoutingDataWriter.java          |  84 +++++++
 .../MetadataStoreDirectoryAccessorTestBase.java    | 131 +++++++++++
 .../rest/server/TestMSDAccessorLeaderElection.java | 227 ++++++++++++++++++
 .../server/TestMetadataStoreDirectoryAccessor.java | 165 ++-----------
 .../mock/MockMetadataStoreDirectoryAccessor.java   | 124 ++++++++++
 .../constant/MetadataStoreRoutingConstants.java    |  10 +
 15 files changed, 914 insertions(+), 273 deletions(-)

diff --git a/helix-rest/src/main/java/org/apache/helix/rest/common/HttpConstants.java b/helix-rest/src/main/java/org/apache/helix/rest/common/HttpConstants.java
new file mode 100644
index 0000000..369f1a4
--- /dev/null
+++ b/helix-rest/src/main/java/org/apache/helix/rest/common/HttpConstants.java
@@ -0,0 +1,29 @@
+package org.apache.helix.rest.common;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+public class HttpConstants {
+  public enum RestVerbs {
+    GET,
+    POST,
+    PUT,
+    DELETE
+  }
+}
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java
index 5b64f7b..9d54c82 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java
@@ -21,7 +21,6 @@ package org.apache.helix.rest.metadatastore;
 
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -29,6 +28,7 @@ import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
+import com.google.common.annotations.VisibleForTesting;
 import org.apache.helix.msdcommon.callback.RoutingDataListener;
 import org.apache.helix.msdcommon.datamodel.MetadataStoreRoutingData;
 import org.apache.helix.msdcommon.datamodel.TrieRoutingData;
@@ -42,49 +42,68 @@ import org.slf4j.LoggerFactory;
 
 
 /**
+ * NOTE: This is a singleton class. DO NOT EXTEND!
  * ZK-based MetadataStoreDirectory that listens on the routing data in routing ZKs with a update
  * callback.
  */
 public class ZkMetadataStoreDirectory implements MetadataStoreDirectory, RoutingDataListener {
   private static final Logger LOG = LoggerFactory.getLogger(ZkMetadataStoreDirectory.class);
 
-  // TODO: enable the line below when implementation is complete
   // The following maps' keys represent the namespace
-  private final Map<String, MetadataStoreRoutingDataReader> _routingDataReaderMap;
-  private final Map<String, MetadataStoreRoutingDataWriter> _routingDataWriterMap;
-  private final Map<String, MetadataStoreRoutingData> _routingDataMap;
-  private final Map<String, String> _routingZkAddressMap;
+  // NOTE: made protected for testing reasons. DO NOT MODIFY!
+  protected final Map<String, MetadataStoreRoutingDataReader> _routingDataReaderMap;
+  protected final Map<String, MetadataStoreRoutingDataWriter> _routingDataWriterMap;
+  protected final Map<String, MetadataStoreRoutingData> _routingDataMap;
+  protected final Map<String, String> _routingZkAddressMap;
   // <namespace, <realm, <list of sharding keys>> mappings
-  private final Map<String, Map<String, List<String>>> _realmToShardingKeysMap;
+  protected final Map<String, Map<String, List<String>>> _realmToShardingKeysMap;
+
+  private static volatile ZkMetadataStoreDirectory _zkMetadataStoreDirectoryInstance;
+
+  public static ZkMetadataStoreDirectory getInstance() {
+    if (_zkMetadataStoreDirectoryInstance == null) {
+      synchronized (ZkMetadataStoreDirectory.class) {
+        if (_zkMetadataStoreDirectoryInstance == null) {
+          _zkMetadataStoreDirectoryInstance = new ZkMetadataStoreDirectory();
+        }
+      }
+    }
+    return _zkMetadataStoreDirectoryInstance;
+  }
+
+  public static ZkMetadataStoreDirectory getInstance(String namespace, String zkAddress)
+      throws InvalidRoutingDataException {
+    getInstance().init(namespace, zkAddress);
+    return _zkMetadataStoreDirectoryInstance;
+  }
 
   /**
-   * Creates a ZkMetadataStoreDirectory based on the given routing ZK addresses.
-   * @param routingZkAddressMap (namespace, routing ZK connect string)
-   * @throws InvalidRoutingDataException
+   * Note: this is a singleton class. The constructor is made protected for testing. DO NOT EXTEND!
    */
-  public ZkMetadataStoreDirectory(Map<String, String> routingZkAddressMap)
-      throws InvalidRoutingDataException {
-    if (routingZkAddressMap == null || routingZkAddressMap.isEmpty()) {
-      throw new InvalidRoutingDataException("Routing ZK Addresses given are invalid!");
-    }
-    _routingDataReaderMap = new HashMap<>();
-    _routingDataWriterMap = new HashMap<>();
-    _routingZkAddressMap = routingZkAddressMap;
+  @VisibleForTesting
+  protected ZkMetadataStoreDirectory() {
+    _routingDataReaderMap = new ConcurrentHashMap<>();
+    _routingDataWriterMap = new ConcurrentHashMap<>();
+    _routingZkAddressMap = new ConcurrentHashMap<>();
     _realmToShardingKeysMap = new ConcurrentHashMap<>();
     _routingDataMap = new ConcurrentHashMap<>();
+  }
 
-    // Create RoutingDataReaders and RoutingDataWriters
-    for (Map.Entry<String, String> routingEntry : _routingZkAddressMap.entrySet()) {
-      _routingDataReaderMap.put(routingEntry.getKey(),
-          new ZkRoutingDataReader(routingEntry.getKey(), routingEntry.getValue(), this));
-      _routingDataWriterMap.put(routingEntry.getKey(),
-          new ZkRoutingDataWriter(routingEntry.getKey(), routingEntry.getValue()));
+  private void init(String namespace, String zkAddress) throws InvalidRoutingDataException {
+    if (!_routingZkAddressMap.containsKey(namespace)) {
+      synchronized (_routingZkAddressMap) {
+        if (!_routingZkAddressMap.containsKey(namespace)) {
+          _routingZkAddressMap.put(namespace, zkAddress);
+          _routingDataReaderMap.put(namespace, new ZkRoutingDataReader(namespace, zkAddress, this));
+          _routingDataWriterMap.put(namespace, new ZkRoutingDataWriter(namespace, zkAddress));
 
-      // Populate realmToShardingKeys with ZkRoutingDataReader
-      _realmToShardingKeysMap.put(routingEntry.getKey(),
-          _routingDataReaderMap.get(routingEntry.getKey()).getRoutingData());
-      _routingDataMap.put(routingEntry.getKey(),
-          new TrieRoutingData(_realmToShardingKeysMap.get(routingEntry.getKey())));
+          // Populate realmToShardingKeys with ZkRoutingDataReader
+          _realmToShardingKeysMap
+              .put(namespace, _routingDataReaderMap.get(namespace).getRoutingData());
+          _routingDataMap
+              .put(namespace, new TrieRoutingData(_realmToShardingKeysMap.get(namespace)));
+        }
+      }
     }
   }
 
@@ -238,5 +257,11 @@ public class ZkMetadataStoreDirectory implements MetadataStoreDirectory, Routing
   public synchronized void close() {
     _routingDataReaderMap.values().forEach(MetadataStoreRoutingDataReader::close);
     _routingDataWriterMap.values().forEach(MetadataStoreRoutingDataWriter::close);
+    _routingDataReaderMap.clear();
+    _routingDataWriterMap.clear();
+    _routingZkAddressMap.clear();
+    _realmToShardingKeysMap.clear();
+    _routingDataMap.clear();
+    _zkMetadataStoreDirectoryInstance = null;
   }
 }
\ No newline at end of file
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataReader.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataReader.java
index 12de9e2..76465f9 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataReader.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataReader.java
@@ -19,6 +19,7 @@ package org.apache.helix.rest.metadatastore.accessor;
  * under the License.
  */
 
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -91,9 +92,8 @@ public class ZkRoutingDataReader implements MetadataStoreRoutingDataReader, IZkD
           _zkClient.readData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + realmAddress);
       List<String> shardingKeys =
           record.getListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY);
-      if (shardingKeys != null) {
-        routingData.put(realmAddress, shardingKeys);
-      }
+      routingData
+          .put(realmAddress, shardingKeys != null ? shardingKeys : Collections.emptyList());
     }
     return routingData;
   }
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataWriter.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataWriter.java
index 48aeb13..061372c 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataWriter.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataWriter.java
@@ -19,27 +19,45 @@ package org.apache.helix.rest.metadatastore.accessor;
  * under the License.
  */
 
-import java.util.Collections;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 
+import javax.ws.rs.core.Response;
+
 import org.apache.helix.msdcommon.constant.MetadataStoreRoutingConstants;
+import org.apache.helix.rest.common.HttpConstants;
 import org.apache.helix.rest.metadatastore.concurrency.ZkDistributedLeaderElection;
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
 import org.apache.helix.zookeeper.impl.factory.DedicatedZkClientFactory;
 import org.apache.helix.zookeeper.zkclient.exception.ZkNodeExistsException;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.util.EntityUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 
 public class ZkRoutingDataWriter implements MetadataStoreRoutingDataWriter {
+  // Time out for http requests that are forwarded to leader instances measured in milliseconds
+  private static final int HTTP_REQUEST_FORWARDING_TIMEOUT = 60 * 1000;
   private static final Logger LOG = LoggerFactory.getLogger(ZkRoutingDataWriter.class);
 
   private final String _namespace;
   private final HelixZkClient _zkClient;
   private final ZkDistributedLeaderElection _leaderElection;
+  private final CloseableHttpClient _forwardHttpClient;
+  private final String _myHostName;
 
   public ZkRoutingDataWriter(String namespace, String zkAddress) {
     if (namespace == null || namespace.isEmpty()) {
@@ -62,10 +80,26 @@ public class ZkRoutingDataWriter implements MetadataStoreRoutingDataWriter {
     }
 
     // Get the hostname (REST endpoint) from System property
-    // TODO: Fill in when Helix REST implementations are ready
-    ZNRecord myServerInfo = new ZNRecord("dummy hostname");
+    String hostName = System.getProperty(MetadataStoreRoutingConstants.MSDS_SERVER_HOSTNAME_KEY);
+    if (hostName == null || hostName.isEmpty()) {
+      throw new IllegalStateException(
+          "Unable to get the hostname of this server instance. System.getProperty fails to fetch "
+              + MetadataStoreRoutingConstants.MSDS_SERVER_HOSTNAME_KEY + ".");
+    }
+    // remove trailing slash
+    if (hostName.charAt(hostName.length() - 1) == '/') {
+      hostName = hostName.substring(0, hostName.length() - 1);
+    }
+    _myHostName = hostName;
+    ZNRecord myServerInfo = new ZNRecord(_myHostName);
+
     _leaderElection = new ZkDistributedLeaderElection(_zkClient,
         MetadataStoreRoutingConstants.LEADER_ELECTION_ZNODE, myServerInfo);
+
+    RequestConfig config = RequestConfig.custom().setConnectTimeout(HTTP_REQUEST_FORWARDING_TIMEOUT)
+        .setConnectionRequestTimeout(HTTP_REQUEST_FORWARDING_TIMEOUT)
+        .setSocketTimeout(HTTP_REQUEST_FORWARDING_TIMEOUT).build();
+    _forwardHttpClient = HttpClientBuilder.create().setDefaultRequestConfig(config).build();
   }
 
   @Override
@@ -77,8 +111,10 @@ public class ZkRoutingDataWriter implements MetadataStoreRoutingDataWriter {
       return createZkRealm(realm);
     }
 
-    // TODO: Forward the request to leader
-    return true;
+    String urlSuffix =
+        constructUrlSuffix(MetadataStoreRoutingConstants.MSDS_GET_ALL_REALMS_ENDPOINT, realm);
+    return forwardRequestToLeader(urlSuffix, HttpConstants.RestVerbs.PUT,
+        Response.Status.CREATED.getStatusCode());
   }
 
   @Override
@@ -87,11 +123,13 @@ public class ZkRoutingDataWriter implements MetadataStoreRoutingDataWriter {
       if (_zkClient.isClosed()) {
         throw new IllegalStateException("ZkClient is closed!");
       }
-      return _zkClient.delete(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + realm);
+      return deleteZkRealm(realm);
     }
 
-    // TODO: Forward the request to leader
-    return true;
+    String urlSuffix =
+        constructUrlSuffix(MetadataStoreRoutingConstants.MSDS_GET_ALL_REALMS_ENDPOINT, realm);
+    return forwardRequestToLeader(urlSuffix, HttpConstants.RestVerbs.DELETE,
+        Response.Status.OK.getStatusCode());
   }
 
   @Override
@@ -100,44 +138,14 @@ public class ZkRoutingDataWriter implements MetadataStoreRoutingDataWriter {
       if (_zkClient.isClosed()) {
         throw new IllegalStateException("ZkClient is closed!");
       }
-      // If the realm does not exist already, then create the realm
-      String realmPath = MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + realm;
-      if (!_zkClient.exists(realmPath)) {
-        // Create the realm
-        if (!createZkRealm(realm)) {
-          // Failed to create the realm - log and return false
-          LOG.error(
-              "Failed to add sharding key because ZkRealm creation failed! Namespace: {}, Realm: {}, Sharding key: {}",
-              _namespace, realm, shardingKey);
-          return false;
-        }
-      }
-
-      // Add the sharding key to an empty ZNRecord
-      ZNRecord znRecord;
-      try {
-        znRecord = _zkClient.readData(realmPath);
-      } catch (Exception e) {
-        LOG.error(
-            "Failed to read the realm ZNRecord in addShardingKey()! Namespace: {}, Realm: {}, ShardingKey: {}",
-            _namespace, realm, shardingKey, e);
-        return false;
-      }
-      znRecord.setListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY,
-          Collections.singletonList(shardingKey));
-      try {
-        _zkClient.writeData(realmPath, znRecord);
-      } catch (Exception e) {
-        LOG.error(
-            "Failed to write the realm ZNRecord in addShardingKey()! Namespace: {}, Realm: {}, ShardingKey: {}",
-            _namespace, realm, shardingKey, e);
-        return false;
-      }
-      return true;
+      return createZkShardingKey(realm, shardingKey);
     }
 
-    // TODO: Forward the request to leader
-    return true;
+    String urlSuffix =
+        constructUrlSuffix(MetadataStoreRoutingConstants.MSDS_GET_ALL_REALMS_ENDPOINT, realm,
+            MetadataStoreRoutingConstants.MSDS_GET_ALL_SHARDING_KEYS_ENDPOINT, shardingKey);
+    return forwardRequestToLeader(urlSuffix, HttpConstants.RestVerbs.PUT,
+        Response.Status.CREATED.getStatusCode());
   }
 
   @Override
@@ -146,31 +154,14 @@ public class ZkRoutingDataWriter implements MetadataStoreRoutingDataWriter {
       if (_zkClient.isClosed()) {
         throw new IllegalStateException("ZkClient is closed!");
       }
-      ZNRecord znRecord =
-          _zkClient.readData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + realm, true);
-      if (znRecord == null || !znRecord
-          .getListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY)
-          .contains(shardingKey)) {
-        // This realm does not exist or shardingKey doesn't exist. Return true!
-        return true;
-      }
-      znRecord.getListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY)
-          .remove(shardingKey);
-      // Overwrite this ZNRecord with the sharding key removed
-      try {
-        _zkClient
-            .writeData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + realm, znRecord);
-      } catch (Exception e) {
-        LOG.error(
-            "Failed to write the data back in deleteShardingKey()! Namespace: {}, Realm: {}, ShardingKey: {}",
-            _namespace, realm, shardingKey, e);
-        return false;
-      }
-      return true;
+      return deleteZkShardingKey(realm, shardingKey);
     }
 
-    // TODO: Forward the request to leader
-    return true;
+    String urlSuffix =
+        constructUrlSuffix(MetadataStoreRoutingConstants.MSDS_GET_ALL_REALMS_ENDPOINT, realm,
+            MetadataStoreRoutingConstants.MSDS_GET_ALL_SHARDING_KEYS_ENDPOINT, shardingKey);
+    return forwardRequestToLeader(urlSuffix, HttpConstants.RestVerbs.DELETE,
+        Response.Status.OK.getStatusCode());
   }
 
   @Override
@@ -225,6 +216,11 @@ public class ZkRoutingDataWriter implements MetadataStoreRoutingDataWriter {
   @Override
   public synchronized void close() {
     _zkClient.close();
+    try {
+      _forwardHttpClient.close();
+    } catch (IOException e) {
+      LOG.error("HttpClient failed to close. ", e);
+    }
   }
 
   /**
@@ -232,7 +228,7 @@ public class ZkRoutingDataWriter implements MetadataStoreRoutingDataWriter {
    * @param realm
    * @return
    */
-  private boolean createZkRealm(String realm) {
+  protected boolean createZkRealm(String realm) {
     if (_zkClient.exists(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + realm)) {
       LOG.warn("createZkRealm() called for realm: {}, but this realm already exists! Namespace: {}",
           realm, _namespace);
@@ -249,4 +245,128 @@ public class ZkRoutingDataWriter implements MetadataStoreRoutingDataWriter {
 
     return true;
   }
+
+  protected boolean deleteZkRealm(String realm) {
+    return _zkClient.delete(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + realm);
+  }
+
+  protected boolean createZkShardingKey(String realm, String shardingKey) {
+    // If the realm does not exist already, then create the realm
+    String realmPath = MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + realm;
+    if (!_zkClient.exists(realmPath)) {
+      // Create the realm
+      if (!createZkRealm(realm)) {
+        // Failed to create the realm - log and return false
+        LOG.error(
+            "Failed to add sharding key because ZkRealm creation failed! Namespace: {}, Realm: {}, Sharding key: {}",
+            _namespace, realm, shardingKey);
+        return false;
+      }
+    }
+
+    ZNRecord znRecord;
+    try {
+      znRecord = _zkClient.readData(realmPath);
+    } catch (Exception e) {
+      LOG.error(
+          "Failed to read the realm ZNRecord in addShardingKey()! Namespace: {}, Realm: {}, ShardingKey: {}",
+          _namespace, realm, shardingKey, e);
+      return false;
+    }
+    List<String> shardingKeys =
+        znRecord.getListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY);
+    if (shardingKeys == null || shardingKeys.isEmpty()) {
+      shardingKeys = new ArrayList<>();
+    }
+    shardingKeys.add(shardingKey);
+    znRecord.setListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY, shardingKeys);
+    try {
+      _zkClient.writeData(realmPath, znRecord);
+    } catch (Exception e) {
+      LOG.error(
+          "Failed to write the realm ZNRecord in addShardingKey()! Namespace: {}, Realm: {}, ShardingKey: {}",
+          _namespace, realm, shardingKey, e);
+      return false;
+    }
+    return true;
+  }
+
+  protected boolean deleteZkShardingKey(String realm, String shardingKey) {
+    ZNRecord znRecord =
+        _zkClient.readData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + realm, true);
+    if (znRecord == null || !znRecord
+        .getListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY)
+        .contains(shardingKey)) {
+      // This realm does not exist or shardingKey doesn't exist. Return true!
+      return true;
+    }
+    znRecord.getListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY)
+        .remove(shardingKey);
+    // Overwrite this ZNRecord with the sharding key removed
+    try {
+      _zkClient.writeData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + realm, znRecord);
+    } catch (Exception e) {
+      LOG.error(
+          "Failed to write the data back in deleteShardingKey()! Namespace: {}, Realm: {}, ShardingKey: {}",
+          _namespace, realm, shardingKey, e);
+      return false;
+    }
+    return true;
+  }
+
+  private String constructUrlSuffix(String... urlParams) {
+    List<String> allUrlParameters = new ArrayList<>(
+        Arrays.asList(MetadataStoreRoutingConstants.MSDS_NAMESPACES_URL_PREFIX, "/", _namespace));
+    for (String urlParam : urlParams) {
+      if (urlParam.charAt(0) != '/') {
+        urlParam = "/" + urlParam;
+      }
+      allUrlParameters.add(urlParam);
+    }
+    return String.join("", allUrlParameters);
+  }
+
+  private boolean forwardRequestToLeader(String urlSuffix, HttpConstants.RestVerbs request_method,
+      int expectedResponseCode) throws IllegalArgumentException {
+    String leaderHostName = _leaderElection.getCurrentLeaderInfo().getId();
+    String url = leaderHostName + urlSuffix;
+    HttpUriRequest request;
+    switch (request_method) {
+      case PUT:
+        request = new HttpPut(url);
+        break;
+      case DELETE:
+        request = new HttpDelete(url);
+        break;
+      default:
+        throw new IllegalArgumentException("Unsupported request_method: " + request_method);
+    }
+
+    return sendRequestToLeader(request, expectedResponseCode, leaderHostName);
+  }
+
+  // Set to be protected for testing purposes
+  protected boolean sendRequestToLeader(HttpUriRequest request, int expectedResponseCode,
+      String leaderHostName) {
+    try {
+      HttpResponse response = _forwardHttpClient.execute(request);
+      if (response.getStatusLine().getStatusCode() != expectedResponseCode) {
+        HttpEntity respEntity = response.getEntity();
+        String errorLog = "The forwarded request to leader has failed. Uri: " + request.getURI()
+            + ". Error code: " + response.getStatusLine().getStatusCode() + " Current hostname: "
+            + _myHostName + " Leader hostname: " + leaderHostName;
+        if (respEntity != null) {
+          errorLog += " Response: " + EntityUtils.toString(respEntity);
+        }
+        LOG.error(errorLog);
+        return false;
+      }
+    } catch (IOException e) {
+      LOG.error(
+          "The forwarded request to leader raised an exception. Uri: {} Current hostname: {} Leader hostname: {}",
+          request.getURI(), _myHostName, leaderHostName, e);
+      return false;
+    }
+    return true;
+  }
 }
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/concurrency/ZkDistributedLeaderElection.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/concurrency/ZkDistributedLeaderElection.java
index 330611f..fca7b76 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/concurrency/ZkDistributedLeaderElection.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/concurrency/ZkDistributedLeaderElection.java
@@ -88,7 +88,7 @@ public class ZkDistributedLeaderElection implements IZkDataListener, IZkStateLis
     List<String> children = _zkClient.getChildren(_basePath);
     Collections.sort(children);
     String leaderName = children.get(0);
-    ZNRecord leaderInfo = _zkClient.readData(_basePath + "/" + leaderName, true);
+    _currentLeaderInfo = _zkClient.readData(_basePath + "/" + leaderName, true);
 
     String[] myNameArray = _myEphemeralSequentialPath.split("/");
     String myName = myNameArray[myNameArray.length - 1];
@@ -96,8 +96,7 @@ public class ZkDistributedLeaderElection implements IZkDataListener, IZkStateLis
     if (leaderName.equals(myName)) {
       // My turn for leadership
       _isLeader = true;
-      _currentLeaderInfo = leaderInfo;
-      LOG.info("{} acquired leadership! Info: {}", myName, leaderInfo);
+      LOG.info("{} acquired leadership! Info: {}", myName, _currentLeaderInfo);
     } else {
       // Watch the ephemeral ZNode before me for a deletion event
       String beforeMe = children.get(children.indexOf(myName) - 1);
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/server/HelixRestServer.java b/helix-rest/src/main/java/org/apache/helix/rest/server/HelixRestServer.java
index b1880f4..64c1139 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/server/HelixRestServer.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/server/HelixRestServer.java
@@ -141,7 +141,7 @@ public class HelixRestServer {
     return String.format("%s_%s", type.name(), namespace.getName());
   }
 
-  private ResourceConfig getResourceConfig(HelixRestNamespace namespace, ServletType type) {
+  protected ResourceConfig getResourceConfig(HelixRestNamespace namespace, ServletType type) {
     ResourceConfig cfg = new ResourceConfig();
     cfg.packages(type.getServletPackageArray());
     cfg.setApplicationName(namespace.getName());
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/server/ServerContext.java b/helix-rest/src/main/java/org/apache/helix/rest/server/ServerContext.java
index 5a0530a..b845356 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/server/ServerContext.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/server/ServerContext.java
@@ -27,6 +27,7 @@ import org.apache.helix.ConfigAccessor;
 import org.apache.helix.HelixAdmin;
 import org.apache.helix.HelixDataAccessor;
 import org.apache.helix.InstanceType;
+import org.apache.helix.rest.metadatastore.ZkMetadataStoreDirectory;
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.apache.helix.manager.zk.ZKHelixAdmin;
 import org.apache.helix.manager.zk.ZKHelixDataAccessor;
@@ -54,6 +55,7 @@ public class ServerContext {
   private final Map<String, HelixDataAccessor> _helixDataAccessorPool;
   // 1 Cluster name will correspond to 1 task driver
   private final Map<String, TaskDriver> _taskDriverPool;
+  private ZkMetadataStoreDirectory _zkMetadataStoreDirectory;
 
   public ServerContext(String zkAddr) {
     _zkAddr = zkAddr;
@@ -64,6 +66,8 @@ public class ServerContext {
     // cannot be started correctly.
     _helixDataAccessorPool = new HashMap<>();
     _taskDriverPool = new HashMap<>();
+    // Initialize the singleton ZkMetadataStoreDirectory instance to allow it to be closed later
+    _zkMetadataStoreDirectory = ZkMetadataStoreDirectory.getInstance();
   }
 
   public HelixZkClient getHelixZkClient() {
@@ -158,5 +162,8 @@ public class ServerContext {
     if (_zkClient != null) {
       _zkClient.close();
     }
+    if (_zkMetadataStoreDirectory != null) {
+      _zkMetadataStoreDirectory.close();
+    }
   }
 }
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/server/resources/metadatastore/MetadataStoreDirectoryAccessor.java b/helix-rest/src/main/java/org/apache/helix/rest/server/resources/metadatastore/MetadataStoreDirectoryAccessor.java
index 0f22d81..38b764d 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/server/resources/metadatastore/MetadataStoreDirectoryAccessor.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/server/resources/metadatastore/MetadataStoreDirectoryAccessor.java
@@ -25,7 +25,6 @@ import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.stream.Collectors;
 import javax.annotation.PostConstruct;
-import javax.annotation.PreDestroy;
 import javax.ws.rs.DELETE;
 import javax.ws.rs.GET;
 import javax.ws.rs.PUT;
@@ -58,7 +57,7 @@ public class MetadataStoreDirectoryAccessor extends AbstractResource {
   private static final Logger LOG = LoggerFactory.getLogger(MetadataStoreDirectoryAccessor.class);
 
   private String _namespace;
-  private MetadataStoreDirectory _metadataStoreDirectory;
+  protected MetadataStoreDirectory _metadataStoreDirectory;
 
   @PostConstruct
   private void postConstruct() {
@@ -68,11 +67,6 @@ public class MetadataStoreDirectoryAccessor extends AbstractResource {
     buildMetadataStoreDirectory(_namespace, helixRestNamespace.getMetadataStoreAddress());
   }
 
-  @PreDestroy
-  private void preDestroy() {
-    _metadataStoreDirectory.close();
-  }
-
   /**
    * Gets all existing namespaces in the routing metadata store at endpoint:
    * "GET /metadata-store-namespaces"
@@ -121,7 +115,9 @@ public class MetadataStoreDirectoryAccessor extends AbstractResource {
   @Path("/metadata-store-realms/{realm}")
   public Response addMetadataStoreRealm(@PathParam("realm") String realm) {
     try {
-      _metadataStoreDirectory.addMetadataStoreRealm(_namespace, realm);
+      if (!_metadataStoreDirectory.addMetadataStoreRealm(_namespace, realm)) {
+        return serverError();
+      }
     } catch (IllegalArgumentException ex) {
       return notFound(ex.getMessage());
     }
@@ -133,7 +129,9 @@ public class MetadataStoreDirectoryAccessor extends AbstractResource {
   @Path("/metadata-store-realms/{realm}")
   public Response deleteMetadataStoreRealm(@PathParam("realm") String realm) {
     try {
-      _metadataStoreDirectory.deleteMetadataStoreRealm(_namespace, realm);
+      if (!_metadataStoreDirectory.deleteMetadataStoreRealm(_namespace, realm)) {
+        return serverError();
+      }
     } catch (IllegalArgumentException ex) {
       return notFound(ex.getMessage());
     }
@@ -249,8 +247,11 @@ public class MetadataStoreDirectoryAccessor extends AbstractResource {
   @Path("/metadata-store-realms/{realm}/sharding-keys/{sharding-key: .+}")
   public Response addShardingKey(@PathParam("realm") String realm,
       @PathParam("sharding-key") String shardingKey) {
+    shardingKey = "/" + shardingKey;
     try {
-      _metadataStoreDirectory.addShardingKey(_namespace, realm, shardingKey);
+      if (!_metadataStoreDirectory.addShardingKey(_namespace, realm, shardingKey)) {
+        return serverError();
+      }
     } catch (IllegalArgumentException ex) {
       return notFound(ex.getMessage());
     }
@@ -262,8 +263,11 @@ public class MetadataStoreDirectoryAccessor extends AbstractResource {
   @Path("/metadata-store-realms/{realm}/sharding-keys/{sharding-key: .+}")
   public Response deleteShardingKey(@PathParam("realm") String realm,
       @PathParam("sharding-key") String shardingKey) {
+    shardingKey = "/" + shardingKey;
     try {
-      _metadataStoreDirectory.deleteShardingKey(_namespace, realm, shardingKey);
+      if (!_metadataStoreDirectory.deleteShardingKey(_namespace, realm, shardingKey)) {
+        return serverError();
+      }
     } catch (IllegalArgumentException ex) {
       return notFound(ex.getMessage());
     }
@@ -295,15 +299,12 @@ public class MetadataStoreDirectoryAccessor extends AbstractResource {
     return helixRestNamespace;
   }
 
-  private void buildMetadataStoreDirectory(String namespace, String address) {
-    Map<String, String> routingZkAddressMap = ImmutableMap.of(namespace, address);
+  protected void buildMetadataStoreDirectory(String namespace, String address) {
     try {
-      _metadataStoreDirectory = new ZkMetadataStoreDirectory(routingZkAddressMap);
+      _metadataStoreDirectory = ZkMetadataStoreDirectory.getInstance(namespace, address);
     } catch (InvalidRoutingDataException ex) {
-      // In this case, the InvalidRoutingDataException should not happen because routing
-      // ZK address is always valid here.
-      LOG.warn("Unable to create metadata store directory for routing ZK address: {}",
-          routingZkAddressMap, ex);
+      LOG.warn("Unable to create metadata store directory for namespace: {}, ZK address: {}",
+          namespace, address, ex);
     }
   }
 
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java
index 604d331..6fe5f32 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java
@@ -62,8 +62,7 @@ public class TestZkMetadataStoreDirectory extends AbstractTestClass {
   private MetadataStoreDirectory _metadataStoreDirectory;
 
   @BeforeClass
-  public void beforeClass()
-      throws InvalidRoutingDataException {
+  public void beforeClass() throws InvalidRoutingDataException {
     _zkList = new ArrayList<>(ZK_SERVER_MAP.keySet());
 
     // Populate routingZkAddrMap
@@ -101,8 +100,14 @@ public class TestZkMetadataStoreDirectory extends AbstractTestClass {
               znRecord);
     });
 
+    System.setProperty(MetadataStoreRoutingConstants.MSDS_SERVER_HOSTNAME_KEY,
+        getBaseUri().toString());
+
     // Create metadataStoreDirectory
-    _metadataStoreDirectory = new ZkMetadataStoreDirectory(_routingZkAddrMap);
+    for (Map.Entry<String, String> entry : _routingZkAddrMap.entrySet()) {
+      _metadataStoreDirectory =
+          ZkMetadataStoreDirectory.getInstance(entry.getKey(), entry.getValue());
+    }
   }
 
   @AfterClass
@@ -110,11 +115,13 @@ public class TestZkMetadataStoreDirectory extends AbstractTestClass {
     _metadataStoreDirectory.close();
     _zkList.forEach(zk -> ZK_SERVER_MAP.get(zk).getZkClient()
         .deleteRecursive(MetadataStoreRoutingConstants.ROUTING_DATA_PATH));
+    System.clearProperty(MetadataStoreRoutingConstants.MSDS_SERVER_HOSTNAME_KEY);
   }
 
   @Test
   public void testGetAllNamespaces() {
-    Assert.assertEquals(_metadataStoreDirectory.getAllNamespaces(), _routingZkAddrMap.keySet());
+    Assert.assertTrue(
+        _metadataStoreDirectory.getAllNamespaces().containsAll(_routingZkAddrMap.keySet()));
   }
 
   @Test(dependsOnMethods = "testGetAllNamespaces")
@@ -187,8 +194,7 @@ public class TestZkMetadataStoreDirectory extends AbstractTestClass {
   }
 
   @Test(dependsOnMethods = "testGetMetadataStoreRealm")
-  public void testDataChangeCallback()
-      throws Exception {
+  public void testDataChangeCallback() throws Exception {
     // For all namespaces (Routing ZKs), add an extra sharding key to TEST_REALM_1
     String newKey = "/a/b/c/d/e";
     _zkList.forEach(zk -> {
@@ -216,8 +222,7 @@ public class TestZkMetadataStoreDirectory extends AbstractTestClass {
   }
 
   @Test(dependsOnMethods = "testDataChangeCallback")
-  public void testChildChangeCallback()
-      throws Exception {
+  public void testChildChangeCallback() throws Exception {
     // For all namespaces (Routing ZKs), add a realm with a sharding key list
     _zkList.forEach(zk -> {
       ZK_SERVER_MAP.get(zk).getZkClient()
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataWriter.java b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataWriter.java
index 1aba067..e61e905 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataWriter.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataWriter.java
@@ -19,6 +19,7 @@ package org.apache.helix.rest.metadatastore.accessor;
  * under the License.
  */
 
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -28,6 +29,7 @@ import org.apache.helix.AccessOption;
 import org.apache.helix.msdcommon.constant.MetadataStoreRoutingConstants;
 import org.apache.helix.rest.server.AbstractTestClass;
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
+import org.apache.http.client.methods.HttpUriRequest;
 import org.junit.Assert;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
@@ -40,15 +42,35 @@ public class TestZkRoutingDataWriter extends AbstractTestClass {
   private static final String DUMMY_SHARDING_KEY = "SHARDING_KEY";
   private MetadataStoreRoutingDataWriter _zkRoutingDataWriter;
 
+  // MockWriter is used for testing request forwarding features in non-leader situations
+  class MockWriter extends ZkRoutingDataWriter {
+    HttpUriRequest calledRequest;
+
+    MockWriter(String namespace, String zkAddress) {
+      super(namespace, zkAddress);
+    }
+
+    // This method does not call super() because the http call should not be actually made
+    @Override
+    protected boolean sendRequestToLeader(HttpUriRequest request, int expectedResponseCode,
+        String leaderHostName) {
+      calledRequest = request;
+      return false;
+    }
+  }
+
   @BeforeClass
   public void beforeClass() {
     _baseAccessor.remove(MetadataStoreRoutingConstants.ROUTING_DATA_PATH, AccessOption.PERSISTENT);
+    System.setProperty(MetadataStoreRoutingConstants.MSDS_SERVER_HOSTNAME_KEY,
+        getBaseUri().toString());
     _zkRoutingDataWriter = new ZkRoutingDataWriter(DUMMY_NAMESPACE, ZK_ADDR);
   }
 
   @AfterClass
   public void afterClass() {
     _baseAccessor.remove(MetadataStoreRoutingConstants.ROUTING_DATA_PATH, AccessOption.PERSISTENT);
+    System.clearProperty(MetadataStoreRoutingConstants.MSDS_SERVER_HOSTNAME_KEY);
     _zkRoutingDataWriter.close();
   }
 
@@ -105,4 +127,66 @@ public class TestZkRoutingDataWriter extends AbstractTestClass {
     Assert.assertTrue(znRecord.getListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY)
         .contains(DUMMY_SHARDING_KEY));
   }
+
+  @Test(dependsOnMethods = "testSetRoutingData")
+  public void testAddMetadataStoreRealmNonLeader() {
+    MockWriter mockWriter = new MockWriter(DUMMY_NAMESPACE, ZK_ADDR);
+    mockWriter.addMetadataStoreRealm(DUMMY_REALM);
+    Assert.assertEquals("PUT", mockWriter.calledRequest.getMethod());
+    List<String> expectedUrlParams = Arrays
+        .asList(MetadataStoreRoutingConstants.MSDS_NAMESPACES_URL_PREFIX, DUMMY_NAMESPACE,
+            MetadataStoreRoutingConstants.MSDS_GET_ALL_REALMS_ENDPOINT, DUMMY_REALM);
+    String expectedUrl =
+        getBaseUri().toString() + String.join("/", expectedUrlParams).replaceAll("//", "/")
+            .substring(1);
+    Assert.assertEquals(expectedUrl, mockWriter.calledRequest.getURI().toString());
+    mockWriter.close();
+  }
+
+  @Test(dependsOnMethods = "testAddMetadataStoreRealmNonLeader")
+  public void testDeleteMetadataStoreRealmNonLeader() {
+    MockWriter mockWriter = new MockWriter(DUMMY_NAMESPACE, ZK_ADDR);
+    mockWriter.deleteMetadataStoreRealm(DUMMY_REALM);
+    Assert.assertEquals("DELETE", mockWriter.calledRequest.getMethod());
+    List<String> expectedUrlParams = Arrays
+        .asList(MetadataStoreRoutingConstants.MSDS_NAMESPACES_URL_PREFIX, DUMMY_NAMESPACE,
+            MetadataStoreRoutingConstants.MSDS_GET_ALL_REALMS_ENDPOINT, DUMMY_REALM);
+    String expectedUrl =
+        getBaseUri().toString() + String.join("/", expectedUrlParams).replaceAll("//", "/")
+            .substring(1);
+    Assert.assertEquals(expectedUrl, mockWriter.calledRequest.getURI().toString());
+    mockWriter.close();
+  }
+
+  @Test(dependsOnMethods = "testDeleteMetadataStoreRealmNonLeader")
+  public void testAddShardingKeyNonLeader() {
+    MockWriter mockWriter = new MockWriter(DUMMY_NAMESPACE, ZK_ADDR);
+    mockWriter.addShardingKey(DUMMY_REALM, DUMMY_SHARDING_KEY);
+    Assert.assertEquals("PUT", mockWriter.calledRequest.getMethod());
+    List<String> expectedUrlParams = Arrays
+        .asList(MetadataStoreRoutingConstants.MSDS_NAMESPACES_URL_PREFIX, DUMMY_NAMESPACE,
+            MetadataStoreRoutingConstants.MSDS_GET_ALL_REALMS_ENDPOINT, DUMMY_REALM,
+            MetadataStoreRoutingConstants.MSDS_GET_ALL_SHARDING_KEYS_ENDPOINT, DUMMY_SHARDING_KEY);
+    String expectedUrl =
+        getBaseUri().toString() + String.join("/", expectedUrlParams).replaceAll("//", "/")
+            .substring(1);
+    Assert.assertEquals(expectedUrl, mockWriter.calledRequest.getURI().toString());
+    mockWriter.close();
+  }
+
+  @Test(dependsOnMethods = "testAddShardingKeyNonLeader")
+  public void testDeleteShardingKeyNonLeader() {
+    MockWriter mockWriter = new MockWriter(DUMMY_NAMESPACE, ZK_ADDR);
+    mockWriter.deleteShardingKey(DUMMY_REALM, DUMMY_SHARDING_KEY);
+    Assert.assertEquals("DELETE", mockWriter.calledRequest.getMethod());
+    List<String> expectedUrlParams = Arrays
+        .asList(MetadataStoreRoutingConstants.MSDS_NAMESPACES_URL_PREFIX, DUMMY_NAMESPACE,
+            MetadataStoreRoutingConstants.MSDS_GET_ALL_REALMS_ENDPOINT, DUMMY_REALM,
+            MetadataStoreRoutingConstants.MSDS_GET_ALL_SHARDING_KEYS_ENDPOINT, DUMMY_SHARDING_KEY);
+    String expectedUrl =
+        getBaseUri().toString() + String.join("/", expectedUrlParams).replaceAll("//", "/")
+            .substring(1);
+    Assert.assertEquals(expectedUrl, mockWriter.calledRequest.getURI().toString());
+    mockWriter.close();
+  }
 }
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/server/MetadataStoreDirectoryAccessorTestBase.java b/helix-rest/src/test/java/org/apache/helix/rest/server/MetadataStoreDirectoryAccessorTestBase.java
new file mode 100644
index 0000000..f234795
--- /dev/null
+++ b/helix-rest/src/test/java/org/apache/helix/rest/server/MetadataStoreDirectoryAccessorTestBase.java
@@ -0,0 +1,131 @@
+package org.apache.helix.rest.server;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.helix.TestHelper;
+import org.apache.helix.msdcommon.constant.MetadataStoreRoutingConstants;
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
+import org.apache.helix.rest.metadatastore.accessor.MetadataStoreRoutingDataReader;
+import org.apache.helix.rest.metadatastore.accessor.ZkRoutingDataReader;
+import org.apache.helix.zookeeper.datamodel.ZNRecord;
+import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+
+
+public class MetadataStoreDirectoryAccessorTestBase extends AbstractTestClass {
+  /*
+   * The following are constants to be used for testing.
+   */
+  protected static final String TEST_NAMESPACE_URI_PREFIX = "/namespaces/" + TEST_NAMESPACE;
+  protected static final String NON_EXISTING_NAMESPACE_URI_PREFIX =
+      "/namespaces/not-existed-namespace/metadata-store-realms/";
+  protected static final String TEST_REALM_1 = "testRealm1";
+  protected static final List<String> TEST_SHARDING_KEYS_1 =
+      Arrays.asList("/sharding/key/1/a", "/sharding/key/1/b", "/sharding/key/1/c");
+  protected static final String TEST_REALM_2 = "testRealm2";
+  protected static final List<String> TEST_SHARDING_KEYS_2 =
+      Arrays.asList("/sharding/key/1/d", "/sharding/key/1/e", "/sharding/key/1/f");
+  protected static final String TEST_REALM_3 = "testRealm3";
+  protected static final String TEST_SHARDING_KEY = "/sharding/key/1/x";
+
+  // List of all ZK addresses, each of which corresponds to a namespace/routing ZK
+  protected List<String> _zkList;
+  protected MetadataStoreRoutingDataReader _routingDataReader;
+
+  @BeforeClass
+  public void beforeClass() throws Exception {
+    _zkList = new ArrayList<>(ZK_SERVER_MAP.keySet());
+
+    deleteRoutingDataPath();
+
+    // Write dummy mappings in ZK
+    // Create a node that represents a realm address and add 3 sharding keys to it
+    ZNRecord znRecord = new ZNRecord("RoutingInfo");
+
+    _zkList.forEach(zk -> {
+      ZK_SERVER_MAP.get(zk).getZkClient().setZkSerializer(new ZNRecordSerializer());
+      // Write first realm and sharding keys pair
+      znRecord.setListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY,
+          TEST_SHARDING_KEYS_1);
+      ZK_SERVER_MAP.get(zk).getZkClient()
+          .createPersistent(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + TEST_REALM_1,
+              true);
+      ZK_SERVER_MAP.get(zk).getZkClient()
+          .writeData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + TEST_REALM_1,
+              znRecord);
+
+      // Create another realm and sharding keys pair
+      znRecord.setListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY,
+          TEST_SHARDING_KEYS_2);
+      ZK_SERVER_MAP.get(zk).getZkClient()
+          .createPersistent(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + TEST_REALM_2,
+              true);
+      ZK_SERVER_MAP.get(zk).getZkClient()
+          .writeData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + TEST_REALM_2,
+              znRecord);
+    });
+
+    _routingDataReader = new ZkRoutingDataReader(TEST_NAMESPACE, _zkAddrTestNS, null);
+
+    System.setProperty(MetadataStoreRoutingConstants.MSDS_SERVER_HOSTNAME_KEY,
+        getBaseUri().toString());
+  }
+
+  @AfterClass
+  public void afterClass() throws Exception {
+    System.clearProperty(MetadataStoreRoutingConstants.MSDS_SERVER_HOSTNAME_KEY);
+    deleteRoutingDataPath();
+  }
+
+  protected void deleteRoutingDataPath() throws Exception {
+    Assert.assertTrue(TestHelper.verify(() -> {
+      _zkList.forEach(zk -> ZK_SERVER_MAP.get(zk).getZkClient()
+          .deleteRecursively(MetadataStoreRoutingConstants.ROUTING_DATA_PATH));
+
+      for (String zk : _zkList) {
+        if (ZK_SERVER_MAP.get(zk).getZkClient()
+            .exists(MetadataStoreRoutingConstants.ROUTING_DATA_PATH)) {
+          return false;
+        }
+      }
+
+      return true;
+    }, TestHelper.WAIT_DURATION), "Routing data path should be deleted after the tests.");
+  }
+
+  // Uses routingDataReader to get the latest realms in test-namespace; returns a modifiable copy
+  // because it'll be modified in test cases
+  protected Set<String> getAllRealms() throws InvalidRoutingDataException {
+    return new HashSet<>(_routingDataReader.getRoutingData().keySet());
+  }
+
+  // Uses routingDataReader to get the latest sharding keys in test-namespace, testRealm1
+  protected Set<String> getAllShardingKeysInTestRealm1() throws InvalidRoutingDataException {
+    return new HashSet<>(_routingDataReader.getRoutingData().get(TEST_REALM_1));
+  }
+}
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/server/TestMSDAccessorLeaderElection.java b/helix-rest/src/test/java/org/apache/helix/rest/server/TestMSDAccessorLeaderElection.java
new file mode 100644
index 0000000..9a122a9
--- /dev/null
+++ b/helix-rest/src/test/java/org/apache/helix/rest/server/TestMSDAccessorLeaderElection.java
@@ -0,0 +1,227 @@
+package org.apache.helix.rest.server;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+import javax.ws.rs.core.Response;
+
+import org.apache.helix.msdcommon.constant.MetadataStoreRoutingConstants;
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
+import org.apache.helix.rest.common.ContextPropertyKeys;
+import org.apache.helix.rest.common.HelixRestNamespace;
+import org.apache.helix.rest.common.HttpConstants;
+import org.apache.helix.rest.common.ServletType;
+import org.apache.helix.rest.server.auditlog.AuditLogger;
+import org.apache.helix.rest.server.filters.CORSFilter;
+import org.apache.helix.rest.server.mock.MockMetadataStoreDirectoryAccessor;
+import org.apache.helix.zookeeper.api.client.HelixZkClient;
+import org.apache.helix.zookeeper.datamodel.ZNRecord;
+import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
+import org.apache.helix.zookeeper.impl.factory.DedicatedZkClientFactory;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+
+public class TestMSDAccessorLeaderElection extends MetadataStoreDirectoryAccessorTestBase {
+  private static final Logger LOG = LoggerFactory.getLogger(TestMSDAccessorLeaderElection.class);
+  private static final String MOCK_URL_PREFIX = "/mock";
+
+  private HelixRestServer _mockHelixRestServer;
+  private String _mockBaseUri;
+  private String _leaderBaseUri;
+  private CloseableHttpClient _httpClient;
+  private HelixZkClient _zkClient;
+
+  @BeforeClass
+  public void beforeClass() throws Exception {
+    super.beforeClass();
+    _leaderBaseUri = getBaseUri().toString();
+    _leaderBaseUri = _leaderBaseUri.substring(0, _leaderBaseUri.length() - 1);
+    int newPort = getBaseUri().getPort() + 1;
+
+    // Start a second server for testing Distributed Leader Election for writes
+    _mockBaseUri = getBaseUri().getScheme() + "://" + getBaseUri().getHost() + ":" + newPort;
+    try {
+      List<HelixRestNamespace> namespaces = new ArrayList<>();
+      // Add test namespace
+      namespaces.add(new HelixRestNamespace(TEST_NAMESPACE,
+          HelixRestNamespace.HelixMetadataStoreType.ZOOKEEPER, _zkAddrTestNS, false));
+      _mockHelixRestServer = new MockHelixRestServer(namespaces, newPort, getBaseUri().getPath(),
+          Collections.singletonList(_auditLogger));
+      _mockHelixRestServer.start();
+    } catch (InterruptedException e) {
+      LOG.error("MockHelixRestServer starting encounter an exception.", e);
+    }
+
+    // Calling the original endpoint to create an instance of MetadataStoreDirectory in case
+    // it didn't exist yet.
+    get(TEST_NAMESPACE_URI_PREFIX + "/metadata-store-realms", null,
+        Response.Status.OK.getStatusCode(), true);
+
+    // Set the new uri to be used in leader election
+    System.setProperty(MetadataStoreRoutingConstants.MSDS_SERVER_HOSTNAME_KEY, _mockBaseUri);
+
+    // Start http client for testing
+    _httpClient = HttpClients.createDefault();
+
+    // Start zkclient to verify leader election behavior
+    _zkClient = DedicatedZkClientFactory.getInstance()
+        .buildZkClient(new HelixZkClient.ZkConnectionConfig(_zkAddrTestNS),
+            new HelixZkClient.ZkClientConfig().setZkSerializer(new ZNRecordSerializer()));
+  }
+
+  @AfterClass
+  public void afterClass() throws Exception {
+    super.afterClass();
+    MockMetadataStoreDirectoryAccessor._mockMSDInstance.close();
+    _mockHelixRestServer.shutdown();
+    _httpClient.close();
+    _zkClient.close();
+  }
+
+  @Test
+  public void testAddMetadataStoreRealmRequestForwarding()
+      throws InvalidRoutingDataException, IOException {
+    Set<String> expectedRealmsSet = getAllRealms();
+    Assert.assertFalse(expectedRealmsSet.contains(TEST_REALM_3),
+        "Metadata store directory should not have realm: " + TEST_REALM_3);
+    sendRequestAndValidate("/metadata-store-realms/" + TEST_REALM_3, HttpConstants.RestVerbs.PUT,
+        Response.Status.CREATED.getStatusCode());
+    expectedRealmsSet.add(TEST_REALM_3);
+    Assert.assertEquals(getAllRealms(), expectedRealmsSet);
+    MockMetadataStoreDirectoryAccessor._mockMSDInstance.close();
+  }
+
+  @Test(dependsOnMethods = "testAddMetadataStoreRealmRequestForwarding")
+  public void testDeleteMetadataStoreRealmRequestForwarding()
+      throws InvalidRoutingDataException, IOException {
+    Set<String> expectedRealmsSet = getAllRealms();
+    sendRequestAndValidate("/metadata-store-realms/" + TEST_REALM_3, HttpConstants.RestVerbs.DELETE,
+        Response.Status.OK.getStatusCode());
+    expectedRealmsSet.remove(TEST_REALM_3);
+    Assert.assertEquals(getAllRealms(), expectedRealmsSet);
+    MockMetadataStoreDirectoryAccessor._mockMSDInstance.close();
+  }
+
+  @Test(dependsOnMethods = "testDeleteMetadataStoreRealmRequestForwarding")
+  public void testAddShardingKeyRequestForwarding()
+      throws InvalidRoutingDataException, IOException {
+    Set<String> expectedShardingKeysSet = getAllShardingKeysInTestRealm1();
+    Assert.assertFalse(expectedShardingKeysSet.contains(TEST_SHARDING_KEY),
+        "Realm does not have sharding key: " + TEST_SHARDING_KEY);
+    sendRequestAndValidate(
+        "/metadata-store-realms/" + TEST_REALM_1 + "/sharding-keys/" + TEST_SHARDING_KEY,
+        HttpConstants.RestVerbs.PUT, Response.Status.CREATED.getStatusCode());
+    expectedShardingKeysSet.add(TEST_SHARDING_KEY);
+    Assert.assertEquals(getAllShardingKeysInTestRealm1(), expectedShardingKeysSet);
+    MockMetadataStoreDirectoryAccessor._mockMSDInstance.close();
+  }
+
+  @Test(dependsOnMethods = "testAddShardingKeyRequestForwarding")
+  public void testDeleteShardingKeyRequestForwarding()
+      throws InvalidRoutingDataException, IOException {
+    Set<String> expectedShardingKeysSet = getAllShardingKeysInTestRealm1();
+    sendRequestAndValidate(
+        "/metadata-store-realms/" + TEST_REALM_1 + "/sharding-keys/" + TEST_SHARDING_KEY,
+        HttpConstants.RestVerbs.DELETE, Response.Status.OK.getStatusCode());
+    expectedShardingKeysSet.remove(TEST_SHARDING_KEY);
+    Assert.assertEquals(getAllShardingKeysInTestRealm1(), expectedShardingKeysSet);
+    MockMetadataStoreDirectoryAccessor._mockMSDInstance.close();
+  }
+
+  private void sendRequestAndValidate(String urlSuffix, HttpConstants.RestVerbs requestMethod,
+      int expectedResponseCode) throws IllegalArgumentException, IOException {
+    String url = _mockBaseUri + TEST_NAMESPACE_URI_PREFIX + MOCK_URL_PREFIX + urlSuffix;
+    HttpUriRequest request;
+    switch (requestMethod) {
+      case PUT:
+        request = new HttpPut(url);
+        break;
+      case DELETE:
+        request = new HttpDelete(url);
+        break;
+      default:
+        throw new IllegalArgumentException("Unsupported requestMethod: " + requestMethod);
+    }
+    HttpResponse response = _httpClient.execute(request);
+    Assert.assertEquals(response.getStatusLine().getStatusCode(), expectedResponseCode);
+
+    // Validate leader election behavior
+    List<String> leaderSelectionNodes =
+        _zkClient.getChildren(MetadataStoreRoutingConstants.LEADER_ELECTION_ZNODE);
+    leaderSelectionNodes.sort(Comparator.comparing(String::toString));
+    Assert.assertEquals(leaderSelectionNodes.size(), 2);
+    ZNRecord firstEphemeralNode = _zkClient.readData(
+        MetadataStoreRoutingConstants.LEADER_ELECTION_ZNODE + "/" + leaderSelectionNodes.get(0));
+    ZNRecord secondEphemeralNode = _zkClient.readData(
+        MetadataStoreRoutingConstants.LEADER_ELECTION_ZNODE + "/" + leaderSelectionNodes.get(1));
+    Assert.assertEquals(firstEphemeralNode.getId(), _leaderBaseUri);
+    Assert.assertEquals(secondEphemeralNode.getId(), _mockBaseUri);
+
+    // Make sure the operation is not done by the follower instance
+    Assert.assertFalse(MockMetadataStoreDirectoryAccessor.operatedOnZk);
+  }
+
+  /**
+   * A class that mocks HelixRestServer for testing. It overloads getResourceConfig to inject
+   * MockMetadataStoreDirectoryAccessor as a servlet.
+   */
+  class MockHelixRestServer extends HelixRestServer {
+    public MockHelixRestServer(List<HelixRestNamespace> namespaces, int port, String urlPrefix,
+        List<AuditLogger> auditLoggers) {
+      super(namespaces, port, urlPrefix, auditLoggers);
+    }
+
+    public MockHelixRestServer(String zkAddr, int port, String urlPrefix) {
+      super(zkAddr, port, urlPrefix);
+    }
+
+    @Override
+    protected ResourceConfig getResourceConfig(HelixRestNamespace namespace, ServletType type) {
+      ResourceConfig cfg = new ResourceConfig();
+      List<String> packages = new ArrayList<>(Arrays.asList(type.getServletPackageArray()));
+      packages.add(MockMetadataStoreDirectoryAccessor.class.getPackage().getName());
+      cfg.packages(packages.toArray(new String[0]));
+      cfg.setApplicationName(namespace.getName());
+      cfg.property(ContextPropertyKeys.SERVER_CONTEXT.name(),
+          new ServerContext(namespace.getMetadataStoreAddress()));
+      cfg.property(ContextPropertyKeys.METADATA.name(), namespace);
+      cfg.register(new CORSFilter());
+      return cfg;
+    }
+  }
+}
\ No newline at end of file
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/server/TestMetadataStoreDirectoryAccessor.java b/helix-rest/src/test/java/org/apache/helix/rest/server/TestMetadataStoreDirectoryAccessor.java
index ee49239..b6179aa 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/server/TestMetadataStoreDirectoryAccessor.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/server/TestMetadataStoreDirectoryAccessor.java
@@ -20,8 +20,6 @@ package org.apache.helix.rest.server;
  */
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
@@ -33,89 +31,14 @@ import javax.ws.rs.core.Response;
 
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
-import org.apache.helix.TestHelper;
 import org.apache.helix.msdcommon.constant.MetadataStoreRoutingConstants;
-import org.apache.helix.rest.common.HelixRestNamespace;
-import org.apache.helix.rest.metadatastore.MetadataStoreDirectory;
-import org.apache.helix.rest.metadatastore.ZkMetadataStoreDirectory;
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
 import org.apache.helix.rest.server.util.JerseyUriRequestBuilder;
-import org.apache.helix.zookeeper.datamodel.ZNRecord;
-import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
 import org.testng.Assert;
-import org.testng.annotations.AfterClass;
-import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
 
-// TODO: enable asserts and add verify for refreshed MSD once write operations are ready.
-public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
-  /*
-   * The following are constants to be used for testing.
-   */
-  private static final String TEST_NAMESPACE_URI_PREFIX = "/namespaces/" + TEST_NAMESPACE;
-  private static final String NON_EXISTING_NAMESPACE_URI_PREFIX =
-      "/namespaces/not-existed-namespace/metadata-store-realms/";
-  private static final String TEST_REALM_1 = "testRealm1";
-  private static final List<String> TEST_SHARDING_KEYS_1 =
-      Arrays.asList("/sharding/key/1/a", "/sharding/key/1/b", "/sharding/key/1/c");
-  private static final String TEST_REALM_2 = "testRealm2";
-  private static final List<String> TEST_SHARDING_KEYS_2 =
-      Arrays.asList("/sharding/key/1/d", "/sharding/key/1/e", "/sharding/key/1/f");
-  private static final String TEST_REALM_3 = "testRealm3";
-  private static final String TEST_SHARDING_KEY = "/sharding/key/3/x";
-
-  // List of all ZK addresses, each of which corresponds to a namespace/routing ZK
-  private List<String> _zkList;
-  private MetadataStoreDirectory _metadataStoreDirectory;
-
-  @BeforeClass
-  public void beforeClass() throws Exception {
-    _zkList = new ArrayList<>(ZK_SERVER_MAP.keySet());
-
-    deleteRoutingDataPath();
-
-    // Populate routingZkAddrMap according namespaces in helix rest server.
-    // <Namespace, ZkAddr> mapping
-    Map<String, String> routingZkAddrMap = ImmutableMap
-        .of(HelixRestNamespace.DEFAULT_NAMESPACE_NAME, ZK_ADDR, TEST_NAMESPACE, _zkAddrTestNS);
-
-    // Write dummy mappings in ZK
-    // Create a node that represents a realm address and add 3 sharding keys to it
-    ZNRecord znRecord = new ZNRecord("RoutingInfo");
-
-    _zkList.forEach(zk -> {
-      ZK_SERVER_MAP.get(zk).getZkClient().setZkSerializer(new ZNRecordSerializer());
-      // Write first realm and sharding keys pair
-      znRecord.setListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY,
-          TEST_SHARDING_KEYS_1);
-      ZK_SERVER_MAP.get(zk).getZkClient()
-          .createPersistent(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + TEST_REALM_1,
-              true);
-      ZK_SERVER_MAP.get(zk).getZkClient()
-          .writeData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + TEST_REALM_1,
-              znRecord);
-
-      // Create another realm and sharding keys pair
-      znRecord.setListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY,
-          TEST_SHARDING_KEYS_2);
-      ZK_SERVER_MAP.get(zk).getZkClient()
-          .createPersistent(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + TEST_REALM_2,
-              true);
-      ZK_SERVER_MAP.get(zk).getZkClient()
-          .writeData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + TEST_REALM_2,
-              znRecord);
-    });
-
-    // Create metadataStoreDirectory
-    _metadataStoreDirectory = new ZkMetadataStoreDirectory(routingZkAddrMap);
-  }
-
-  @AfterClass
-  public void afterClass() throws Exception {
-    _metadataStoreDirectory.close();
-    deleteRoutingDataPath();
-  }
-
+public class TestMetadataStoreDirectoryAccessor extends MetadataStoreDirectoryAccessorTestBase {
   /*
    * Tests REST endpoint: "GET /namespaces/{namespace}/metadata-store-namespaces"
    */
@@ -168,6 +91,7 @@ public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
    * Tests REST endpoint: "GET /metadata-store-realms?sharding-key={sharding-key}"
    */
   @Test(dependsOnMethods = "testGetAllMetadataStoreRealms")
+
   public void testGetMetadataStoreRealmWithShardingKey() throws IOException {
     String shardingKey = TEST_SHARDING_KEYS_1.get(0);
 
@@ -194,11 +118,8 @@ public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
    * Tests REST endpoint: "PUT /metadata-store-realms/{realm}"
    */
   @Test(dependsOnMethods = "testGetMetadataStoreRealmWithShardingKey")
-  public void testAddMetadataStoreRealm() {
-    Collection<String> previousRealms =
-        _metadataStoreDirectory.getAllMetadataStoreRealms(TEST_NAMESPACE);
-    Set<String> expectedRealmsSet = new HashSet<>(previousRealms);
-
+  public void testAddMetadataStoreRealm() throws InvalidRoutingDataException {
+    Set<String> expectedRealmsSet = getAllRealms();
     Assert.assertFalse(expectedRealmsSet.contains(TEST_REALM_3),
         "Metadata store directory should not have realm: " + TEST_REALM_3);
 
@@ -212,26 +133,16 @@ public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
         Entity.entity("", MediaType.APPLICATION_JSON_TYPE),
         Response.Status.CREATED.getStatusCode());
 
-    Collection<String> updatedRealms =
-        _metadataStoreDirectory.getAllMetadataStoreRealms(TEST_NAMESPACE);
-    Set<String> updateRealmsSet = new HashSet<>(updatedRealms);
     expectedRealmsSet.add(TEST_REALM_3);
-
-//    Assert.assertEquals(updateRealmsSet, previousRealms);
+    Assert.assertEquals(getAllRealms(), expectedRealmsSet);
   }
 
   /*
    * Tests REST endpoint: "DELETE /metadata-store-realms/{realm}"
    */
   @Test(dependsOnMethods = "testAddMetadataStoreRealm")
-  public void testDeleteMetadataStoreRealm() {
-    Collection<String> previousRealms =
-        _metadataStoreDirectory.getAllMetadataStoreRealms(TEST_NAMESPACE);
-    Set<String> expectedRealmsSet = new HashSet<>(previousRealms);
-
-//    Assert.assertTrue(expectedRealmsSet.contains(TEST_REALM_3),
-//        "Metadata store directory should have realm: " + TEST_REALM_3);
-
+  public void testDeleteMetadataStoreRealm() throws InvalidRoutingDataException {
+    Set<String> expectedRealmsSet = getAllRealms();
     // Test a request that has not found response.
     delete(NON_EXISTING_NAMESPACE_URI_PREFIX + TEST_REALM_3,
         Response.Status.NOT_FOUND.getStatusCode());
@@ -240,12 +151,9 @@ public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
     delete(TEST_NAMESPACE_URI_PREFIX + "/metadata-store-realms/" + TEST_REALM_3,
         Response.Status.OK.getStatusCode());
 
-    Collection<String> updatedRealms =
-        _metadataStoreDirectory.getAllMetadataStoreRealms(TEST_NAMESPACE);
-    Set<String> updateRealmsSet = new HashSet<>(updatedRealms);
+    Set<String> updateRealmsSet = getAllRealms();
     expectedRealmsSet.remove(TEST_REALM_3);
-
-//    Assert.assertEquals(updateRealmsSet, previousRealms);
+    Assert.assertEquals(updateRealmsSet, expectedRealmsSet);
   }
 
   /*
@@ -299,9 +207,8 @@ public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
      *   } ]
      * }
      */
-    String responseBody =
-        new JerseyUriRequestBuilder(TEST_NAMESPACE_URI_PREFIX + "/routing-data")
-            .isBodyReturnExpected(true).get(this);
+    String responseBody = new JerseyUriRequestBuilder(TEST_NAMESPACE_URI_PREFIX + "/routing-data")
+        .isBodyReturnExpected(true).get(this);
 
     // It is safe to cast the object and suppress warnings.
     @SuppressWarnings("unchecked")
@@ -463,54 +370,42 @@ public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
    * Tests REST endpoint: "PUT /metadata-store-realms/{realm}/sharding-keys/{sharding-key}"
    */
   @Test(dependsOnMethods = "testGetRealmShardingKeysUnderPath")
-  public void testAddShardingKey() {
-    Set<String> expectedShardingKeysSet = new HashSet<>(
-        _metadataStoreDirectory.getAllShardingKeysInRealm(TEST_NAMESPACE, TEST_REALM_1));
-
+  public void testAddShardingKey() throws InvalidRoutingDataException {
+    Set<String> expectedShardingKeysSet = getAllShardingKeysInTestRealm1();
     Assert.assertFalse(expectedShardingKeysSet.contains(TEST_SHARDING_KEY),
         "Realm does not have sharding key: " + TEST_SHARDING_KEY);
 
     // Request that gets not found response.
-    put(NON_EXISTING_NAMESPACE_URI_PREFIX + TEST_REALM_1 + "/sharding-keys/" + TEST_SHARDING_KEY,
+    put(NON_EXISTING_NAMESPACE_URI_PREFIX + TEST_REALM_1 + "/sharding-keys" + TEST_SHARDING_KEY,
         null, Entity.entity("", MediaType.APPLICATION_JSON_TYPE),
         Response.Status.NOT_FOUND.getStatusCode());
 
     // Successful request.
-    put(TEST_NAMESPACE_URI_PREFIX + "/metadata-store-realms/" + TEST_REALM_1 + "/sharding-keys/"
+    put(TEST_NAMESPACE_URI_PREFIX + "/metadata-store-realms/" + TEST_REALM_1 + "/sharding-keys"
             + TEST_SHARDING_KEY, null, Entity.entity("", MediaType.APPLICATION_JSON_TYPE),
         Response.Status.CREATED.getStatusCode());
 
-    Set<String> updatedShardingKeysSet = new HashSet<>(
-        _metadataStoreDirectory.getAllShardingKeysInRealm(TEST_NAMESPACE, TEST_REALM_1));
     expectedShardingKeysSet.add(TEST_SHARDING_KEY);
-
-//    Assert.assertEquals(updatedShardingKeysSet, expectedShardingKeysSet);
+    Assert.assertEquals(getAllShardingKeysInTestRealm1(), expectedShardingKeysSet);
   }
 
   /*
    * Tests REST endpoint: "PUT /metadata-store-realms/{realm}/sharding-keys/{sharding-key}"
    */
   @Test(dependsOnMethods = "testAddShardingKey")
-  public void testDeleteShardingKey() {
-    Set<String> expectedShardingKeysSet = new HashSet<>(
-        _metadataStoreDirectory.getAllShardingKeysInRealm(TEST_NAMESPACE, TEST_REALM_1));
-
-//    Assert.assertTrue(expectedShardingKeysSet.contains(TEST_SHARDING_KEY),
-//        "Realm should have sharding key: " + TEST_SHARDING_KEY);
+  public void testDeleteShardingKey() throws InvalidRoutingDataException {
+    Set<String> expectedShardingKeysSet = getAllShardingKeysInTestRealm1();
 
     // Request that gets not found response.
-    delete(NON_EXISTING_NAMESPACE_URI_PREFIX + TEST_REALM_1 + "/sharding-keys/" + TEST_SHARDING_KEY,
+    delete(NON_EXISTING_NAMESPACE_URI_PREFIX + TEST_REALM_1 + "/sharding-keys" + TEST_SHARDING_KEY,
         Response.Status.NOT_FOUND.getStatusCode());
 
     // Successful request.
-    delete(TEST_NAMESPACE_URI_PREFIX + "/metadata-store-realms/" + TEST_REALM_1 + "/sharding-keys/"
+    delete(TEST_NAMESPACE_URI_PREFIX + "/metadata-store-realms/" + TEST_REALM_1 + "/sharding-keys"
         + TEST_SHARDING_KEY, Response.Status.OK.getStatusCode());
 
-    Set<String> updatedShardingKeysSet = new HashSet<>(
-        _metadataStoreDirectory.getAllShardingKeysInRealm(TEST_NAMESPACE, TEST_REALM_1));
     expectedShardingKeysSet.remove(TEST_SHARDING_KEY);
-
-//    Assert.assertEquals(updatedShardingKeysSet, expectedShardingKeysSet);
+    Assert.assertEquals(getAllShardingKeysInTestRealm1(), expectedShardingKeysSet);
   }
 
   private void verifyRealmShardingKeys(String responseBody) throws IOException {
@@ -536,20 +431,4 @@ public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
 
     Assert.assertEquals(queriedShardingKeys, expectedShardingKeys);
   }
-
-  private void deleteRoutingDataPath() throws Exception {
-    Assert.assertTrue(TestHelper.verify(() -> {
-      _zkList.forEach(zk -> ZK_SERVER_MAP.get(zk).getZkClient()
-          .deleteRecursively(MetadataStoreRoutingConstants.ROUTING_DATA_PATH));
-
-      for (String zk : _zkList) {
-        if (ZK_SERVER_MAP.get(zk).getZkClient()
-            .exists(MetadataStoreRoutingConstants.ROUTING_DATA_PATH)) {
-          return false;
-        }
-      }
-
-      return true;
-    }, TestHelper.WAIT_DURATION), "Routing data path should be deleted after the tests.");
-  }
 }
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/server/mock/MockMetadataStoreDirectoryAccessor.java b/helix-rest/src/test/java/org/apache/helix/rest/server/mock/MockMetadataStoreDirectoryAccessor.java
new file mode 100644
index 0000000..b5452f1
--- /dev/null
+++ b/helix-rest/src/test/java/org/apache/helix/rest/server/mock/MockMetadataStoreDirectoryAccessor.java
@@ -0,0 +1,124 @@
+package org.apache.helix.rest.server.mock;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import javax.ws.rs.Path;
+
+import org.apache.helix.msdcommon.datamodel.TrieRoutingData;
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
+import org.apache.helix.rest.metadatastore.MetadataStoreDirectory;
+import org.apache.helix.rest.metadatastore.ZkMetadataStoreDirectory;
+import org.apache.helix.rest.metadatastore.accessor.MetadataStoreRoutingDataReader;
+import org.apache.helix.rest.metadatastore.accessor.MetadataStoreRoutingDataWriter;
+import org.apache.helix.rest.metadatastore.accessor.ZkRoutingDataReader;
+import org.apache.helix.rest.metadatastore.accessor.ZkRoutingDataWriter;
+import org.apache.helix.rest.server.resources.metadatastore.MetadataStoreDirectoryAccessor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * An accessor that mocks the MetadataStoreDirectoryAccessor for testing purpose.
+ */
+@Path("/mock")
+public class MockMetadataStoreDirectoryAccessor extends MetadataStoreDirectoryAccessor {
+  //TODO: use this class as a template for https://github.com/apache/helix/issues/816
+
+  private static final Logger LOG =
+      LoggerFactory.getLogger(MockMetadataStoreDirectoryAccessor.class);
+  // A flag that will be modified if the underlying MockZkRoutingDataWriter makes an operation
+  // against ZooKeeper
+  public static boolean operatedOnZk = false;
+  // The instance of mockMSD that's created by this accessor; it's saved here to be closed later
+  public static MetadataStoreDirectory _mockMSDInstance;
+
+  /**
+   * This method is overriden so that an instance of MockZkMetadataStoreDirectory can be passed in
+   */
+  @Override
+  protected void buildMetadataStoreDirectory(String namespace, String address) {
+    try {
+      _metadataStoreDirectory = new MockZkMetadataStoreDirectory(namespace, address);
+      _mockMSDInstance = _metadataStoreDirectory;
+    } catch (InvalidRoutingDataException e) {
+      LOG.error("buildMetadataStoreDirectory encountered an exception.", e);
+    }
+  }
+
+  /**
+   * Used to artificially create another instance of ZkMetadataStoreDirectory.
+   * ZkMetadataStoreDirectory being a singleton makes it difficult to test it,
+   * therefore this is the only way to create another instance.
+   */
+  class MockZkMetadataStoreDirectory extends ZkMetadataStoreDirectory {
+    MockZkMetadataStoreDirectory(String namespace, String zkAddress)
+        throws InvalidRoutingDataException {
+      super();
+
+      // Manually populate the map so that MockZkRoutingDataWriter can be passed in
+      _routingZkAddressMap.put(namespace, zkAddress);
+      _routingDataReaderMap.put(namespace, new ZkRoutingDataReader(namespace, zkAddress, this));
+      _routingDataWriterMap.put(namespace, new MockZkRoutingDataWriter(namespace, zkAddress));
+      _realmToShardingKeysMap.put(namespace, _routingDataReaderMap.get(namespace).getRoutingData());
+      _routingDataMap.put(namespace, new TrieRoutingData(_realmToShardingKeysMap.get(namespace)));
+    }
+
+    @Override
+    public void close() {
+      _routingDataReaderMap.values().forEach(MetadataStoreRoutingDataReader::close);
+      _routingDataWriterMap.values().forEach(MetadataStoreRoutingDataWriter::close);
+    }
+  }
+
+  /**
+   * A mock to ZkRoutingDataWriter. The only purpose is to set the static flag signifying that
+   * this writer is used for zookeeper operations.
+   */
+  class MockZkRoutingDataWriter extends ZkRoutingDataWriter {
+    public MockZkRoutingDataWriter(String namespace, String zkAddress) {
+      super(namespace, zkAddress);
+      operatedOnZk = false;
+    }
+
+    @Override
+    protected boolean createZkRealm(String realm) {
+      operatedOnZk = true;
+      return super.createZkRealm(realm);
+    }
+
+    @Override
+    protected boolean deleteZkRealm(String realm) {
+      operatedOnZk = true;
+      return super.deleteZkRealm(realm);
+    }
+
+    @Override
+    protected boolean createZkShardingKey(String realm, String shardingKey) {
+      operatedOnZk = true;
+      return super.createZkShardingKey(realm, shardingKey);
+    }
+
+    @Override
+    protected boolean deleteZkShardingKey(String realm, String shardingKey) {
+      operatedOnZk = true;
+      return super.deleteZkShardingKey(realm, shardingKey);
+    }
+  }
+}
diff --git a/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/constant/MetadataStoreRoutingConstants.java b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/constant/MetadataStoreRoutingConstants.java
index e3d541b..766f98a 100644
--- a/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/constant/MetadataStoreRoutingConstants.java
+++ b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/constant/MetadataStoreRoutingConstants.java
@@ -66,9 +66,19 @@ public class MetadataStoreRoutingConstants {
   // System Property Metadata Store Directory Server endpoint key
   public static final String MSDS_SERVER_ENDPOINT_KEY = "metadataStoreDirectoryServerEndpoint";
 
+  // Prefix to MSDS resource endpoints
+  public static final String MSDS_NAMESPACES_URL_PREFIX = "/namespaces";
+
   // MSDS resource getAllRealms endpoint string
   public static final String MSDS_GET_ALL_REALMS_ENDPOINT = "/metadata-store-realms";
 
   // MSDS resource get all routing data endpoint string
   public static final String MSDS_GET_ALL_ROUTING_DATA_ENDPOINT = "/routing-data";
+
+  // MSDS resource get all sharding keys endpoint string
+  public static final String MSDS_GET_ALL_SHARDING_KEYS_ENDPOINT = "/sharding-keys";
+
+  // The key for system properties that contains the hostname of of the
+  // MetadataStoreDirectoryService server instance
+  public static final String MSDS_SERVER_HOSTNAME_KEY = "msds_hostname";
 }


[helix] 26/49: Add SharedZkClient/InnerSharedZkClient implementation (#796)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit edda27390a445fca8f9305ddead4adecaa1015ee
Author: kaisun2000 <52...@users.noreply.github.com>
AuthorDate: Thu Feb 27 14:55:41 2020 -0800

    Add SharedZkClient/InnerSharedZkClient implementation (#796)
    
    Refactor the original SharedZkClient to InnerSharedZkClient. Add
    SharedZkClient implementation. The implementation use composition
    pattern. It would check the ZkPath validity and delegate the
    implementation to InnerSharedZkClient. In sum, InnerSharedZkClient
    is shared ZkClient but not realm aware. SharedZkClient is truely
    realm aware ZkClient.
---
 .../helix/manager/zk/client/SharedZkClient.java    |  16 +-
 .../helix/zookeeper/api/client/HelixZkClient.java  |  69 +--
 .../zookeeper/api/client/RealmAwareZkClient.java   |  14 +-
 .../api/factory/RealmAwareZkClientFactory.java     |   1 +
 .../zookeeper/impl/client/SharedZkClient.java      | 523 ++++++++++++++++++---
 .../impl/factory/SharedZkClientFactory.java        | 107 ++++-
 .../impl/client/RealmAwareZkClientTestBase.java    |   8 +-
 .../zookeeper/impl/client/TestSharedZkClient.java  |  47 ++
 8 files changed, 635 insertions(+), 150 deletions(-)

diff --git a/helix-core/src/main/java/org/apache/helix/manager/zk/client/SharedZkClient.java b/helix-core/src/main/java/org/apache/helix/manager/zk/client/SharedZkClient.java
index 52a2c17..be6b54d 100644
--- a/helix-core/src/main/java/org/apache/helix/manager/zk/client/SharedZkClient.java
+++ b/helix-core/src/main/java/org/apache/helix/manager/zk/client/SharedZkClient.java
@@ -19,19 +19,17 @@ package org.apache.helix.manager.zk.client;
  * under the License.
  */
 
+import org.apache.helix.zookeeper.impl.factory.SharedZkClientFactory;
+import org.apache.helix.zookeeper.impl.factory.ZkConnectionManager;
+
+
 /**
  * Deprecated; use SharedZkClient in zookeeper-api instead.
  */
 @Deprecated
-class SharedZkClient extends org.apache.helix.zookeeper.impl.client.SharedZkClient {
-  /**
-   * Construct a shared RealmAwareZkClient that uses a shared ZkConnection.
-   *  @param connectionManager     The manager of the shared ZkConnection.
-   * @param clientConfig          ZkClientConfig details to create the shared RealmAwareZkClient.
-   * @param callback              Clean up logic when the shared RealmAwareZkClient is closed.
-   */
-  protected SharedZkClient(ZkConnectionManager connectionManager, ZkClientConfig clientConfig,
-      org.apache.helix.zookeeper.impl.client.SharedZkClient.OnCloseCallback callback) {
+class SharedZkClient extends SharedZkClientFactory.InnerSharedZkClient {
+  SharedZkClient(ZkConnectionManager connectionManager, ZkClientConfig clientConfig,
+      SharedZkClientFactory.OnCloseCallback callback) {
     super(connectionManager, clientConfig, callback);
   }
 }
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/HelixZkClient.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/HelixZkClient.java
index 9a1a69d..03bf000 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/HelixZkClient.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/HelixZkClient.java
@@ -19,9 +19,9 @@ package org.apache.helix.zookeeper.api.client;
  * under the License.
  */
 
+import org.apache.helix.zookeeper.zkclient.ZkClient;
 import org.apache.helix.zookeeper.zkclient.serialize.BasicZkSerializer;
 import org.apache.helix.zookeeper.zkclient.serialize.PathBasedZkSerializer;
-import org.apache.helix.zookeeper.zkclient.serialize.SerializableSerializer;
 import org.apache.helix.zookeeper.zkclient.serialize.ZkSerializer;
 
 
@@ -42,7 +42,7 @@ public interface HelixZkClient extends RealmAwareZkClient {
   class ZkConnectionConfig {
     // Connection configs
     private final String _zkServers;
-    private int _sessionTimeout = HelixZkClient.DEFAULT_SESSION_TIMEOUT;
+    private int _sessionTimeout = DEFAULT_SESSION_TIMEOUT;
 
     public ZkConnectionConfig(String zkServers) {
       _zkServers = zkServers;
@@ -53,10 +53,10 @@ public interface HelixZkClient extends RealmAwareZkClient {
       if (obj == this) {
         return true;
       }
-      if (!(obj instanceof HelixZkClient.ZkConnectionConfig)) {
+      if (!(obj instanceof ZkConnectionConfig)) {
         return false;
       }
-      HelixZkClient.ZkConnectionConfig configObj = (HelixZkClient.ZkConnectionConfig) obj;
+      ZkConnectionConfig configObj = (ZkConnectionConfig) obj;
       return (_zkServers == null && configObj._zkServers == null || _zkServers != null && _zkServers
           .equals(configObj._zkServers)) && _sessionTimeout == configObj._sessionTimeout;
     }
@@ -71,7 +71,7 @@ public interface HelixZkClient extends RealmAwareZkClient {
       return (_zkServers + "_" + _sessionTimeout).replaceAll("[\\W]", "_");
     }
 
-    public HelixZkClient.ZkConnectionConfig setSessionTimeout(Integer sessionTimeout) {
+    public ZkConnectionConfig setSessionTimeout(Integer sessionTimeout) {
       this._sessionTimeout = sessionTimeout;
       return this;
     }
@@ -89,32 +89,16 @@ public interface HelixZkClient extends RealmAwareZkClient {
    * Deprecated - please use RealmAwareZkClient and RealmAwareZkClientConfig instead.
    *
    * Configuration for creating a new HelixZkClient with serializer and monitor.
-   *
-   * TODO: If possible, try to merge with RealmAwareZkClient's RealmAwareZkClientConfig to reduce duplicate logic/code (without breaking backward-compatibility).
-   * Simply making this a subclass of RealmAwareZkClientConfig will break backward-compatiblity.
    */
   @Deprecated
-  class ZkClientConfig {
-    // For client to init the connection
-    private long _connectInitTimeout = DEFAULT_CONNECTION_TIMEOUT;
-
-    // Data access configs
-    private long _operationRetryTimeout = DEFAULT_OPERATION_TIMEOUT;
-
-    // Others
-    private PathBasedZkSerializer _zkSerializer;
-
-    // Monitoring
-    private String _monitorType;
-    private String _monitorKey;
-    private String _monitorInstanceName = null;
-    private boolean _monitorRootPathOnly = true;
-
+  class ZkClientConfig extends RealmAwareZkClientConfig {
+    @Override
     public ZkClientConfig setZkSerializer(PathBasedZkSerializer zkSerializer) {
       this._zkSerializer = zkSerializer;
       return this;
     }
 
+    @Override
     public ZkClientConfig setZkSerializer(ZkSerializer zkSerializer) {
       this._zkSerializer = new BasicZkSerializer(zkSerializer);
       return this;
@@ -125,6 +109,7 @@ public interface HelixZkClient extends RealmAwareZkClient {
      *
      * @param monitorType
      */
+    @Override
     public ZkClientConfig setMonitorType(String monitorType) {
       this._monitorType = monitorType;
       return this;
@@ -135,6 +120,7 @@ public interface HelixZkClient extends RealmAwareZkClient {
      *
      * @param monitorKey
      */
+    @Override
     public ZkClientConfig setMonitorKey(String monitorKey) {
       this._monitorKey = monitorKey;
       return this;
@@ -145,55 +131,28 @@ public interface HelixZkClient extends RealmAwareZkClient {
      *
      * @param instanceName
      */
+    @Override
     public ZkClientConfig setMonitorInstanceName(String instanceName) {
       this._monitorInstanceName = instanceName;
       return this;
     }
 
+    @Override
     public ZkClientConfig setMonitorRootPathOnly(Boolean monitorRootPathOnly) {
       this._monitorRootPathOnly = monitorRootPathOnly;
       return this;
     }
 
+    @Override
     public ZkClientConfig setOperationRetryTimeout(Long operationRetryTimeout) {
       this._operationRetryTimeout = operationRetryTimeout;
       return this;
     }
 
+    @Override
     public ZkClientConfig setConnectInitTimeout(long _connectInitTimeout) {
       this._connectInitTimeout = _connectInitTimeout;
       return this;
     }
-
-    public PathBasedZkSerializer getZkSerializer() {
-      if (_zkSerializer == null) {
-        _zkSerializer = new BasicZkSerializer(new SerializableSerializer());
-      }
-      return _zkSerializer;
-    }
-
-    public long getOperationRetryTimeout() {
-      return _operationRetryTimeout;
-    }
-
-    public String getMonitorType() {
-      return _monitorType;
-    }
-
-    public String getMonitorKey() {
-      return _monitorKey;
-    }
-
-    public String getMonitorInstanceName() {
-      return _monitorInstanceName;
-    }
-
-    public boolean isMonitorRootPathOnly() {
-      return _monitorRootPathOnly;
-    }
-
-    public long getConnectInitTimeout() {
-      return _connectInitTimeout;
-    }
   }
 }
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/RealmAwareZkClient.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/RealmAwareZkClient.java
index aa8bf7e..e466d36 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/RealmAwareZkClient.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/RealmAwareZkClient.java
@@ -380,19 +380,19 @@ public interface RealmAwareZkClient {
    */
   class RealmAwareZkClientConfig {
     // For client to init the connection
-    private long _connectInitTimeout = DEFAULT_CONNECTION_TIMEOUT;
+    protected long _connectInitTimeout = DEFAULT_CONNECTION_TIMEOUT;
 
     // Data access configs
-    private long _operationRetryTimeout = DEFAULT_OPERATION_TIMEOUT;
+    protected long _operationRetryTimeout = DEFAULT_OPERATION_TIMEOUT;
 
     // Others
-    private PathBasedZkSerializer _zkSerializer;
+    protected PathBasedZkSerializer _zkSerializer;
 
     // Monitoring
-    private String _monitorType;
-    private String _monitorKey;
-    private String _monitorInstanceName = null;
-    private boolean _monitorRootPathOnly = true;
+    protected String _monitorType;
+    protected String _monitorKey;
+    protected String _monitorInstanceName = null;
+    protected boolean _monitorRootPathOnly = true;
 
     public RealmAwareZkClientConfig setZkSerializer(PathBasedZkSerializer zkSerializer) {
       this._zkSerializer = zkSerializer;
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/factory/RealmAwareZkClientFactory.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/factory/RealmAwareZkClientFactory.java
index 8c1f7a3..cdfa778 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/factory/RealmAwareZkClientFactory.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/factory/RealmAwareZkClientFactory.java
@@ -45,6 +45,7 @@ public interface RealmAwareZkClientFactory {
    * @param metadataStoreRoutingData
    * @return RealmAwareZkClient
    */
+
   // TODO: remove MetadataStoreRoutingData
   default RealmAwareZkClient buildZkClient(
       RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig,
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/client/SharedZkClient.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/client/SharedZkClient.java
index 70d58a8..f2d9416 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/client/SharedZkClient.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/client/SharedZkClient.java
@@ -20,13 +20,27 @@ package org.apache.helix.zookeeper.impl.client;
  */
 
 import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.concurrent.TimeUnit;
 
+import org.apache.helix.msdcommon.datamodel.MetadataStoreRoutingData;
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
-import org.apache.helix.zookeeper.impl.factory.ZkConnectionManager;
-import org.apache.helix.zookeeper.zkclient.IZkConnection;
-import org.apache.helix.zookeeper.zkclient.ZkConnection;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
+import org.apache.helix.zookeeper.impl.factory.SharedZkClientFactory;
+import org.apache.helix.zookeeper.zkclient.DataUpdater;
+import org.apache.helix.zookeeper.zkclient.IZkChildListener;
+import org.apache.helix.zookeeper.zkclient.IZkDataListener;
+import org.apache.helix.zookeeper.zkclient.callback.ZkAsyncCallbacks;
+import org.apache.helix.zookeeper.zkclient.deprecated.IZkStateListener;
+import org.apache.helix.zookeeper.zkclient.exception.ZkNoNodeException;
+import org.apache.helix.zookeeper.zkclient.serialize.PathBasedZkSerializer;
+import org.apache.helix.zookeeper.zkclient.serialize.ZkSerializer;
 import org.apache.zookeeper.CreateMode;
+import org.apache.zookeeper.Op;
+import org.apache.zookeeper.OpResult;
+import org.apache.zookeeper.ZooDefs;
 import org.apache.zookeeper.data.ACL;
+import org.apache.zookeeper.data.Stat;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -37,79 +51,460 @@ import org.slf4j.LoggerFactory;
  * HelixZkClient that uses shared ZkConnection.
  * A SharedZkClient won't manipulate the shared ZkConnection directly.
  */
-public class SharedZkClient extends ZkClient implements HelixZkClient {
+public class SharedZkClient implements RealmAwareZkClient {
   private static Logger LOG = LoggerFactory.getLogger(SharedZkClient.class);
-  /*
-   * Since we cannot really disconnect the ZkConnection, we need a dummy ZkConnection placeholder.
-   * This is to ensure connection field is never null even the shared RealmAwareZkClient instance is closed so as to avoid NPE.
-   */
-  private final static ZkConnection IDLE_CONNECTION = new ZkConnection("Dummy_ZkServers");
-  private final OnCloseCallback _onCloseCallback;
-  private final ZkConnectionManager _connectionManager;
-
-  public interface OnCloseCallback {
-    /**
-     * Triggered after the RealmAwareZkClient is closed.
-     */
-    void onClose();
-  }
-
-  /**
-   * Construct a shared RealmAwareZkClient that uses a shared ZkConnection.
-   *
-   * @param connectionManager     The manager of the shared ZkConnection.
-   * @param clientConfig          ZkClientConfig details to create the shared RealmAwareZkClient.
-   * @param callback              Clean up logic when the shared RealmAwareZkClient is closed.
-   */
-  public SharedZkClient(ZkConnectionManager connectionManager, ZkClientConfig clientConfig,
-      OnCloseCallback callback) {
-    super(connectionManager.getConnection(), 0, clientConfig.getOperationRetryTimeout(),
-        clientConfig.getZkSerializer(), clientConfig.getMonitorType(), clientConfig.getMonitorKey(),
-        clientConfig.getMonitorInstanceName(), clientConfig.isMonitorRootPathOnly());
-    _connectionManager = connectionManager;
-    // Register to the base dedicated RealmAwareZkClient
-    _connectionManager.registerWatcher(this);
-    _onCloseCallback = callback;
+
+  private final HelixZkClient _innerSharedZkClient;
+  private final String _zkRealmShardingKey;
+  private final MetadataStoreRoutingData _metadataStoreRoutingData;
+  private final String _zkRealmAddress;
+
+  public SharedZkClient(RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig,
+      RealmAwareZkClient.RealmAwareZkClientConfig clientConfig,
+      MetadataStoreRoutingData metadataStoreRoutingData) {
+
+    if (connectionConfig == null) {
+      throw new IllegalArgumentException("RealmAwareZkConnectionConfig cannot be null!");
+    }
+    _zkRealmShardingKey = connectionConfig.getZkRealmShardingKey();
+
+    if (metadataStoreRoutingData == null) {
+      throw new IllegalArgumentException("MetadataStoreRoutingData cannot be null!");
+    }
+    _metadataStoreRoutingData = metadataStoreRoutingData;
+
+    // TODO: use _zkRealmShardingKey to generate zkRealmAddress. This can done the same way of pull 765 once @hunter check it in.
+    // Get the ZkRealm address based on the ZK path sharding key
+    String zkRealmAddress = _metadataStoreRoutingData.getMetadataStoreRealm(_zkRealmShardingKey);
+    if (zkRealmAddress == null || zkRealmAddress.isEmpty()) {
+      throw new IllegalArgumentException(
+          "ZK realm address for the given ZK realm sharding key is invalid! ZK realm address: "
+              + zkRealmAddress + " ZK realm sharding key: " + _zkRealmShardingKey);
+    }
+    _zkRealmAddress = zkRealmAddress;
+
+    // Create an InnerSharedZkClient to actually serve ZK requests
+    // TODO: Rename HelixZkClient in the future or remove it entirely - this will be a backward-compatibility breaking change because HelixZkClient is being used by Helix users.
+
+    // Note, here delegate _innerSharedZkClient would share the same connectionManager. Once the close() API of
+    // SharedZkClient is invoked, we can just call the close() API of delegate _innerSharedZkClient. This would follow
+    // exactly the pattern of innerSharedZkClient closing logic, which would close the connectionManager when the last
+    // sharedInnerZkClient is closed.
+    HelixZkClient.ZkConnectionConfig zkConnectionConfig =
+        new HelixZkClient.ZkConnectionConfig(zkRealmAddress)
+            .setSessionTimeout(connectionConfig.getSessionTimeout());
+    HelixZkClient.ZkClientConfig zkClientConfig = new HelixZkClient.ZkClientConfig();
+    zkClientConfig.setZkSerializer(clientConfig.getZkSerializer())
+        .setConnectInitTimeout(clientConfig.getConnectInitTimeout())
+        .setOperationRetryTimeout(clientConfig.getOperationRetryTimeout())
+        .setMonitorInstanceName(clientConfig.getMonitorInstanceName())
+        .setMonitorKey(clientConfig.getMonitorKey()).setMonitorType(clientConfig.getMonitorType())
+        .setMonitorRootPathOnly(clientConfig.isMonitorRootPathOnly());
+    _innerSharedZkClient =
+        SharedZkClientFactory.getInstance().buildZkClient(zkConnectionConfig, zkClientConfig);
+  }
+
+  @Override
+  public List<String> subscribeChildChanges(String path, IZkChildListener listener) {
+    checkIfPathContainsShardingKey(path);
+    return _innerSharedZkClient.subscribeChildChanges(path, listener);
+  }
+
+  @Override
+  public void unsubscribeChildChanges(String path, IZkChildListener listener) {
+    checkIfPathContainsShardingKey(path);
+    _innerSharedZkClient.unsubscribeChildChanges(path, listener);
+  }
+
+  @Override
+  public void subscribeDataChanges(String path, IZkDataListener listener) {
+    checkIfPathContainsShardingKey(path);
+    _innerSharedZkClient.subscribeDataChanges(path, listener);
+  }
+
+  @Override
+  public void unsubscribeDataChanges(String path, IZkDataListener listener) {
+    checkIfPathContainsShardingKey(path);
+    _innerSharedZkClient.unsubscribeDataChanges(path, listener);
+  }
+
+  @Override
+  public void subscribeStateChanges(IZkStateListener listener) {
+    _innerSharedZkClient.subscribeStateChanges(listener);
+  }
+
+  @Override
+  public void unsubscribeStateChanges(IZkStateListener listener) {
+    _innerSharedZkClient.unsubscribeStateChanges(listener);
+  }
+
+  @Override
+  public void unsubscribeAll() {
+    _innerSharedZkClient.unsubscribeAll();
+  }
+
+  @Override
+  public void createPersistent(String path) {
+    checkIfPathContainsShardingKey(path);
+    _innerSharedZkClient.createPersistent(path);
+  }
+
+  @Override
+  public void createPersistent(String path, boolean createParents) {
+    checkIfPathContainsShardingKey(path);
+    _innerSharedZkClient.createPersistent(path, createParents);
+  }
+
+  @Override
+  public void createPersistent(String path, boolean createParents, List<ACL> acl) {
+    checkIfPathContainsShardingKey(path);
+    _innerSharedZkClient.createPersistent(path, createParents, acl);
+  }
+
+  @Override
+  public void createPersistent(String path, Object data) {
+    checkIfPathContainsShardingKey(path);
+    _innerSharedZkClient.createPersistent(path, data);
+  }
+
+  @Override
+  public void createPersistent(String path, Object data, List<ACL> acl) {
+    checkIfPathContainsShardingKey(path);
+    _innerSharedZkClient.createPersistent(path, data, acl);
+  }
+
+  @Override
+  public String createPersistentSequential(String path, Object data) {
+    checkIfPathContainsShardingKey(path);
+    return _innerSharedZkClient.createPersistentSequential(path, data);
+  }
+
+  @Override
+  public String createPersistentSequential(String path, Object data, List<ACL> acl) {
+    checkIfPathContainsShardingKey(path);
+    return _innerSharedZkClient.createPersistentSequential(path, data, acl);
+  }
+
+  @Override
+  public void createEphemeral(String path) {
+    throw new UnsupportedOperationException(
+        "Creating ephemeral nodes using " + SharedZkClient.class.getSimpleName()
+            + " is not supported.");
+  }
+
+  @Override
+  public void createEphemeral(String path, String sessionId) {
+    throw new UnsupportedOperationException(
+        "Creating ephemeral nodes using " + SharedZkClient.class.getSimpleName()
+            + " is not supported.");
+  }
+
+  @Override
+  public void createEphemeral(String path, List<ACL> acl) {
+    throw new UnsupportedOperationException(
+        "Creating ephemeral nodes using " + SharedZkClient.class.getSimpleName()
+            + " is not supported.");
+  }
+
+  @Override
+  public void createEphemeral(String path, List<ACL> acl, String sessionId) {
+    throw new UnsupportedOperationException(
+        "Creating ephemeral nodes using " + SharedZkClient.class.getSimpleName()
+            + " is not supported.");
+  }
+
+  @Override
+  public String create(String path, Object data, CreateMode mode) {
+    checkIfPathContainsShardingKey(path);
+    // delegate to _innerSharedZkClient is fine as _innerSharedZkClient would not allow creating ephemeral node.
+    // this still keeps the same behavior.
+    return _innerSharedZkClient.create(path, data, mode);
+  }
+
+  @Override
+  public String create(String path, Object datat, List<ACL> acl, CreateMode mode) {
+    checkIfPathContainsShardingKey(path);
+    return _innerSharedZkClient.create(path, datat, acl, mode);
+  }
+
+  @Override
+  public void createEphemeral(String path, Object data) {
+    throw new UnsupportedOperationException(
+        "Creating ephemeral nodes using " + SharedZkClient.class.getSimpleName()
+            + " is not supported.");
+  }
+
+  @Override
+  public void createEphemeral(String path, Object data, String sessionId) {
+    throw new UnsupportedOperationException(
+        "Creating ephemeral nodes using " + SharedZkClient.class.getSimpleName()
+            + " is not supported.");
+  }
+
+  @Override
+  public void createEphemeral(String path, Object data, List<ACL> acl) {
+    throw new UnsupportedOperationException(
+        "Creating ephemeral nodes using " + SharedZkClient.class.getSimpleName()
+            + " is not supported.");
+  }
+
+  @Override
+  public void createEphemeral(String path, Object data, List<ACL> acl, String sessionId) {
+    throw new UnsupportedOperationException(
+        "Creating ephemeral nodes using " + SharedZkClient.class.getSimpleName()
+            + " is not supported.");
+  }
+
+  @Override
+  public String createEphemeralSequential(String path, Object data) {
+    throw new UnsupportedOperationException(
+        "Creating ephemeral nodes using " + SharedZkClient.class.getSimpleName()
+            + " is not supported.");
+  }
+
+  @Override
+  public String createEphemeralSequential(String path, Object data, List<ACL> acl) {
+    throw new UnsupportedOperationException(
+        "Creating ephemeral nodes using " + SharedZkClient.class.getSimpleName()
+            + " is not supported.");
+  }
+
+  @Override
+  public String createEphemeralSequential(String path, Object data, String sessionId) {
+    throw new UnsupportedOperationException(
+        "Creating ephemeral nodes using " + SharedZkClient.class.getSimpleName()
+            + " is not supported.");
+  }
+
+  @Override
+  public String createEphemeralSequential(String path, Object data, List<ACL> acl,
+      String sessionId) {
+    throw new UnsupportedOperationException(
+        "Creating ephemeral nodes using " + SharedZkClient.class.getSimpleName()
+            + " is not supported.");
+  }
+
+  @Override
+  public List<String> getChildren(String path) {
+    checkIfPathContainsShardingKey(path);
+    return _innerSharedZkClient.getChildren(path);
+  }
+
+  @Override
+  public int countChildren(String path) {
+    checkIfPathContainsShardingKey(path);
+    return _innerSharedZkClient.countChildren(path);
+  }
+
+  @Override
+  public boolean exists(String path) {
+    checkIfPathContainsShardingKey(path);
+    return _innerSharedZkClient.exists(path);
+  }
+
+  @Override
+  public Stat getStat(String path) {
+    checkIfPathContainsShardingKey(path);
+    return _innerSharedZkClient.getStat(path);
+  }
+
+  @Override
+  public boolean waitUntilExists(String path, TimeUnit timeUnit, long time) {
+    checkIfPathContainsShardingKey(path);
+    return _innerSharedZkClient.waitUntilExists(path, timeUnit, time);
+  }
+
+  @Override
+  public void deleteRecursively(String path) {
+    checkIfPathContainsShardingKey(path);
+    _innerSharedZkClient.deleteRecursively(path);
+  }
+
+  @Override
+  public boolean delete(String path) {
+    checkIfPathContainsShardingKey(path);
+    return _innerSharedZkClient.delete(path);
+  }
+
+  @Override
+  public <T> T readData(String path) {
+    checkIfPathContainsShardingKey(path);
+    return _innerSharedZkClient.readData(path);
+  }
+
+  @Override
+  public <T> T readData(String path, boolean returnNullIfPathNotExists) {
+    checkIfPathContainsShardingKey(path);
+    return _innerSharedZkClient.readData(path, returnNullIfPathNotExists);
+  }
+
+  @Override
+  public <T> T readData(String path, Stat stat) {
+    checkIfPathContainsShardingKey(path);
+    return _innerSharedZkClient.readData(path, stat);
+  }
+
+  @Override
+  public <T> T readData(String path, Stat stat, boolean watch) {
+    checkIfPathContainsShardingKey(path);
+    return _innerSharedZkClient.readData(path, stat, watch);
+  }
+
+  @Override
+  public <T> T readDataAndStat(String path, Stat stat, boolean returnNullIfPathNotExists) {
+    checkIfPathContainsShardingKey(path);
+    return _innerSharedZkClient.readDataAndStat(path, stat, returnNullIfPathNotExists);
+  }
+
+  @Override
+  public void writeData(String path, Object object) {
+    checkIfPathContainsShardingKey(path);
+    _innerSharedZkClient.writeData(path, object);
+  }
+
+  @Override
+  public <T> void updateDataSerialized(String path, DataUpdater<T> updater) {
+    checkIfPathContainsShardingKey(path);
+    _innerSharedZkClient.updateDataSerialized(path, updater);
+  }
+
+  @Override
+  public void writeData(String path, Object datat, int expectedVersion) {
+    checkIfPathContainsShardingKey(path);
+    _innerSharedZkClient.writeDataReturnStat(path, datat, expectedVersion);
+  }
+
+  @Override
+  public Stat writeDataReturnStat(String path, Object datat, int expectedVersion) {
+    checkIfPathContainsShardingKey(path);
+    return _innerSharedZkClient.writeDataReturnStat(path, datat, expectedVersion);
+  }
+
+  @Override
+  public Stat writeDataGetStat(String path, Object datat, int expectedVersion) {
+    checkIfPathContainsShardingKey(path);
+    return _innerSharedZkClient.writeDataReturnStat(path, datat, expectedVersion);
+  }
+
+  @Override
+  public void asyncCreate(String path, Object datat, CreateMode mode,
+      ZkAsyncCallbacks.CreateCallbackHandler cb) {
+    checkIfPathContainsShardingKey(path);
+    _innerSharedZkClient.asyncCreate(path, datat, mode, cb);
+  }
+
+  @Override
+  public void asyncSetData(String path, Object datat, int version,
+      ZkAsyncCallbacks.SetDataCallbackHandler cb) {
+    checkIfPathContainsShardingKey(path);
+    _innerSharedZkClient.asyncSetData(path, datat, version, cb);
+  }
+
+  @Override
+  public void asyncGetData(String path, ZkAsyncCallbacks.GetDataCallbackHandler cb) {
+    checkIfPathContainsShardingKey(path);
+    _innerSharedZkClient.asyncGetData(path, cb);
+  }
+
+  @Override
+  public void asyncExists(String path, ZkAsyncCallbacks.ExistsCallbackHandler cb) {
+    checkIfPathContainsShardingKey(path);
+    _innerSharedZkClient.asyncExists(path, cb);
+  }
+
+  @Override
+  public void asyncDelete(String path, ZkAsyncCallbacks.DeleteCallbackHandler cb) {
+    checkIfPathContainsShardingKey(path);
+    _innerSharedZkClient.asyncDelete(path, cb);
+  }
+
+  @Override
+  public void watchForData(String path) {
+    checkIfPathContainsShardingKey(path);
+    _innerSharedZkClient.watchForData(path);
+  }
+
+  @Override
+  public List<String> watchForChilds(String path) {
+    checkIfPathContainsShardingKey(path);
+    return _innerSharedZkClient.watchForChilds(path);
+  }
+
+  @Override
+  public long getCreationTime(String path) {
+    checkIfPathContainsShardingKey(path);
+    return _innerSharedZkClient.getCreationTime(path);
+  }
+
+  @Override
+  public List<OpResult> multi(Iterable<Op> ops) {
+    return _innerSharedZkClient.multi(ops);
+  }
+
+  @Override
+  public boolean waitUntilConnected(long time, TimeUnit timeUnit) {
+    return _innerSharedZkClient.waitUntilConnected(time, timeUnit);
+  }
+
+  @Override
+  public String getServers() {
+    return _innerSharedZkClient.getServers();
+  }
+
+  @Override
+  public long getSessionId() {
+    return _innerSharedZkClient.getSessionId();
   }
 
   @Override
   public void close() {
-    super.close();
-    if (isClosed()) {
-      // Note that if register is not done while constructing, these private fields may not be init yet.
-      if (_connectionManager != null) {
-        _connectionManager.unregisterWatcher(this);
-      }
-      if (_onCloseCallback != null) {
-        _onCloseCallback.onClose();
-      }
-    }
+    _innerSharedZkClient.close();
   }
 
   @Override
-  public IZkConnection getConnection() {
-    if (isClosed()) {
-      return IDLE_CONNECTION;
-    }
-    return super.getConnection();
+  public boolean isClosed() {
+    return _innerSharedZkClient.isClosed();
   }
 
-  /**
-   * Since ZkConnection session is shared in this RealmAwareZkClient, do not create ephemeral node using a SharedZKClient.
-   */
   @Override
-  public String create(final String path, Object datat, final List<ACL> acl,
-      final CreateMode mode) {
-    if (mode.isEphemeral()) {
-      throw new UnsupportedOperationException(
-          "Create ephemeral nodes using a " + SharedZkClient.class.getSimpleName()
-              + " is not supported.");
-    }
-    return super.create(path, datat, acl, mode);
+  public byte[] serialize(Object data, String path) {
+    checkIfPathContainsShardingKey(path);
+    return _innerSharedZkClient.serialize(data, path);
+  }
+
+  @Override
+  public <T> T deserialize(byte[] data, String path) {
+    checkIfPathContainsShardingKey(path);
+    return _innerSharedZkClient.deserialize(data, path);
+  }
+
+  @Override
+  public void setZkSerializer(ZkSerializer zkSerializer) {
+    _innerSharedZkClient.setZkSerializer(zkSerializer);
+  }
+
+  @Override
+  public void setZkSerializer(PathBasedZkSerializer zkSerializer) {
+    _innerSharedZkClient.setZkSerializer(zkSerializer);
   }
 
   @Override
-  protected boolean isManagingZkConnection() {
-    return false;
+  public PathBasedZkSerializer getZkSerializer() {
+    return _innerSharedZkClient.getZkSerializer();
+  }
+
+  private void checkIfPathContainsShardingKey(String path) {
+    // TODO: replace with the singleton MetadataStoreRoutingData
+    try {
+      String zkRealmForPath = _metadataStoreRoutingData.getMetadataStoreRealm(path);
+      if (!_zkRealmAddress.equals(zkRealmForPath)) {
+        throw new IllegalArgumentException("Given path: " + path + "'s ZK realm: " + zkRealmForPath
+            + " does not match the ZK realm: " + _zkRealmAddress + " and sharding key: "
+            + _zkRealmShardingKey + " for this DedicatedZkClient!");
+      }
+    } catch (NoSuchElementException e) {
+      throw new IllegalArgumentException(
+          "Given path: " + path + " does not have a valid sharding key!");
+    }
   }
-}
+}
\ No newline at end of file
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/SharedZkClientFactory.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/SharedZkClientFactory.java
index 1801614..80c58bf 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/SharedZkClientFactory.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/SharedZkClientFactory.java
@@ -20,12 +20,18 @@ package org.apache.helix.zookeeper.impl.factory;
  */
 
 import java.util.HashMap;
+import java.util.List;
 
 import org.apache.helix.msdcommon.datamodel.MetadataStoreRoutingData;
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
 import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 import org.apache.helix.zookeeper.exception.ZkClientException;
 import org.apache.helix.zookeeper.impl.client.SharedZkClient;
+import org.apache.helix.zookeeper.impl.client.ZkClient;
+import org.apache.helix.zookeeper.zkclient.IZkConnection;
+import org.apache.helix.zookeeper.zkclient.ZkConnection;
+import org.apache.zookeeper.CreateMode;
+import org.apache.zookeeper.data.ACL;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -39,6 +45,12 @@ public class SharedZkClientFactory extends HelixZkClientFactory {
   private final HashMap<HelixZkClient.ZkConnectionConfig, ZkConnectionManager>
       _connectionManagerPool = new HashMap<>();
 
+  /*
+   * Since we cannot really disconnect the ZkConnection, we need a dummy ZkConnection placeholder.
+   * This is to ensure connection field is never null even the shared RealmAwareZkClient instance is closed so as to avoid NPE.
+   */
+  private final static ZkConnection IDLE_CONNECTION = new ZkConnection("Dummy_ZkServers");
+
   protected SharedZkClientFactory() {
   }
 
@@ -47,9 +59,8 @@ public class SharedZkClientFactory extends HelixZkClientFactory {
       RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig,
       RealmAwareZkClient.RealmAwareZkClientConfig clientConfig,
       MetadataStoreRoutingData metadataStoreRoutingData) {
-    // TODO: Implement the logic
-    // Return an instance of SharedZkClient
-    return null;
+    // Note, the logic sharing connectionManager logic is inside SharedZkClient, similar to innerSharedZkClient.
+    return new SharedZkClient(connectionConfig, clientConfig, metadataStoreRoutingData);
   }
 
   @Override
@@ -84,14 +95,14 @@ public class SharedZkClientFactory extends HelixZkClientFactory {
       if (zkConnectionManager == null) {
         throw new ZkClientException("Failed to create a connection manager in the pool to share.");
       }
-      LOG.info("Sharing ZkConnection {} to a new SharedZkClient.", connectionConfig.toString());
-      return new SharedZkClient(zkConnectionManager, clientConfig,
-          new SharedZkClient.OnCloseCallback() {
-            @Override
-            public void onClose() {
-              cleanupConnectionManager(zkConnectionManager);
-            }
-          });
+      LOG.info("Sharing ZkConnection {} to a new InnerSharedZkClient.",
+          connectionConfig.toString());
+      return new InnerSharedZkClient(zkConnectionManager, clientConfig, new OnCloseCallback() {
+        @Override
+        public void onClose() {
+          cleanupConnectionManager(zkConnectionManager);
+        }
+      });
     }
   }
 
@@ -128,4 +139,78 @@ public class SharedZkClientFactory extends HelixZkClientFactory {
     }
     return count;
   }
+
+  public interface OnCloseCallback {
+    /**
+     * Triggered after the SharedZkClient is closed.
+     */
+    void onClose();
+  }
+
+  /**
+   * NOTE: do NOT use this class directly. Please use SharedZkClientFactory to create an instance of SharedZkClient.
+   * InnerSharedZkClient is a ZkClient used by SharedZkClient to power ZK operations against a single ZK realm.
+   *
+   * NOTE2: current InnerSharedZkClient replace the original SharedZKClient. We intend to keep the behavior of original
+   * SharedZkClient intact. (Think of rename the original SharedZkClient as InnerSharedZkClient. This would maintain
+   * backward compatibility.
+   */
+  public static class InnerSharedZkClient extends ZkClient implements HelixZkClient {
+
+    private final OnCloseCallback _onCloseCallback;
+    private final ZkConnectionManager _connectionManager;
+
+    public InnerSharedZkClient(ZkConnectionManager connectionManager, ZkClientConfig clientConfig,
+        OnCloseCallback callback) {
+      super(connectionManager.getConnection(), 0, clientConfig.getOperationRetryTimeout(),
+          clientConfig.getZkSerializer(), clientConfig.getMonitorType(),
+          clientConfig.getMonitorKey(), clientConfig.getMonitorInstanceName(),
+          clientConfig.isMonitorRootPathOnly());
+      _connectionManager = connectionManager;
+      // Register to the base dedicated RealmAwareZkClient
+      _connectionManager.registerWatcher(this);
+      _onCloseCallback = callback;
+    }
+
+    @Override
+    public void close() {
+      super.close();
+      if (isClosed()) {
+        // Note that if register is not done while constructing, these private fields may not be init yet.
+        if (_connectionManager != null) {
+          _connectionManager.unregisterWatcher(this);
+        }
+        if (_onCloseCallback != null) {
+          _onCloseCallback.onClose();
+        }
+      }
+    }
+
+    @Override
+    public IZkConnection getConnection() {
+      if (isClosed()) {
+        return IDLE_CONNECTION;
+      }
+      return super.getConnection();
+    }
+
+    /**
+     * Since ZkConnection session is shared in this HelixZkClient, do not create ephemeral node using a SharedZKClient.
+     */
+    @Override
+    public String create(final String path, Object datat, final List<ACL> acl,
+        final CreateMode mode) {
+      if (mode.isEphemeral()) {
+        throw new UnsupportedOperationException(
+            "Create ephemeral nodes using " + SharedZkClient.class.getSimpleName()
+                + " is not supported.");
+      }
+      return super.create(path, datat, acl, mode);
+    }
+
+    @Override
+    protected boolean isManagingZkConnection() {
+      return false;
+    }
+  }
 }
diff --git a/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/RealmAwareZkClientTestBase.java b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/RealmAwareZkClientTestBase.java
index cd74975..d500ce4 100644
--- a/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/RealmAwareZkClientTestBase.java
+++ b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/RealmAwareZkClientTestBase.java
@@ -38,16 +38,16 @@ import org.testng.annotations.Test;
 
 
 public abstract class RealmAwareZkClientTestBase extends ZkTestBase {
-  private static final String ZK_SHARDING_KEY_PREFIX = "/TEST_SHARDING_KEY";
-  private static final String TEST_VALID_PATH = ZK_SHARDING_KEY_PREFIX + "_" + 0 + "/a/b/c";
-  private static final String TEST_INVALID_PATH = ZK_SHARDING_KEY_PREFIX + "_invalid" + "/a/b/c";
+  protected static final String ZK_SHARDING_KEY_PREFIX = "/TEST_SHARDING_KEY";
+  protected static final String TEST_VALID_PATH = ZK_SHARDING_KEY_PREFIX + "_" + 0 + "/a/b/c";
+  protected static final String TEST_INVALID_PATH = ZK_SHARDING_KEY_PREFIX + "_invalid" + "/a/b/c";
 
   // <Realm, List of sharding keys> Mapping
   private static final Map<String, List<String>> RAW_ROUTING_DATA = new HashMap<>();
 
   // The following RealmAwareZkClientFactory is to be defined in subclasses
   protected RealmAwareZkClientFactory _realmAwareZkClientFactory;
-  private RealmAwareZkClient _realmAwareZkClient;
+  protected RealmAwareZkClient _realmAwareZkClient;
   private MetadataStoreRoutingData _metadataStoreRoutingData;
 
   @BeforeClass
diff --git a/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/TestSharedZkClient.java b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/TestSharedZkClient.java
new file mode 100644
index 0000000..364f66f
--- /dev/null
+++ b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/TestSharedZkClient.java
@@ -0,0 +1,47 @@
+package org.apache.helix.zookeeper.impl.client;
+
+import org.apache.helix.zookeeper.datamodel.ZNRecord;
+import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
+import org.apache.helix.zookeeper.impl.factory.DedicatedZkClientFactory;
+import org.apache.helix.zookeeper.impl.factory.SharedZkClientFactory;
+import org.apache.zookeeper.CreateMode;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+
+public class TestSharedZkClient extends RealmAwareZkClientTestBase {
+  @BeforeClass
+  public void beforeClass() throws Exception {
+    super.beforeClass();
+    // Set the factory to SharedZkClientFactory
+    _realmAwareZkClientFactory = SharedZkClientFactory.getInstance();
+  }
+
+  @Test
+  public void testCreateEphemeralFailure() {
+    _realmAwareZkClient.setZkSerializer(new ZNRecordSerializer());
+
+    // Create a dummy ZNRecord
+    ZNRecord znRecord = new ZNRecord("DummyRecord");
+    znRecord.setSimpleField("Dummy", "Value");
+
+    // test createEphemeral should fail
+    try {
+      _realmAwareZkClient.createEphemeral(TEST_VALID_PATH);
+      Assert.fail(
+          "sharedReamlAwareZkClient is not expected to be able to create ephemeral node via createEphemeral");
+    } catch (UnsupportedOperationException e) {
+      // this is expected
+    }
+
+    // test creating Ephemeral via creat would also fail
+    try {
+      _realmAwareZkClient.create(TEST_VALID_PATH, znRecord, CreateMode.EPHEMERAL);
+      Assert.fail(
+          "sharedRealmAwareZkClient is not expected to be able to create ephmeral node via create");
+    } catch (UnsupportedOperationException e) {
+      // this is expected.
+    }
+  }
+}
\ No newline at end of file


[helix] 22/49: Add getShardingKeyInPath to MetadataStoreRoutingData (#817)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit 33519e0bc2cf77e4952beb2b2e84b59b86a26188
Author: Neal Sun <ne...@gmail.com>
AuthorDate: Wed Feb 26 14:07:39 2020 -0800

    Add getShardingKeyInPath to MetadataStoreRoutingData (#817)
    
    Add getShardingKeyInPath to MetadataStoreRoutingData
---
 .../datamodel/MetadataStoreRoutingData.java        | 11 ++++++++
 .../helix/msdcommon/datamodel/TrieRoutingData.java | 14 ++++++++++
 .../msdcommon/datamodel/TestTrieRoutingData.java   | 31 ++++++++++++++++++++++
 3 files changed, 56 insertions(+)

diff --git a/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/datamodel/MetadataStoreRoutingData.java b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/datamodel/MetadataStoreRoutingData.java
index 2bab8c7..25e5c7e 100644
--- a/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/datamodel/MetadataStoreRoutingData.java
+++ b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/datamodel/MetadataStoreRoutingData.java
@@ -22,6 +22,7 @@ package org.apache.helix.msdcommon.datamodel;
 import java.util.Map;
 import java.util.NoSuchElementException;
 
+
 public interface MetadataStoreRoutingData {
   /**
    * Given a path, return all the "metadata store sharding key-metadata store realm address" pairs
@@ -47,6 +48,16 @@ public interface MetadataStoreRoutingData {
   String getMetadataStoreRealm(String path) throws IllegalArgumentException, NoSuchElementException;
 
   /**
+   * Given a path, return the sharding key contained in the path. If the path doesn't contain a
+   * sharding key, throw NoSuchElementException.
+   * @param path - the path that may contain a sharding key
+   * @return the sharding key contained in the path
+   * @throws IllegalArgumentException - when the path is invalid
+   * @throws NoSuchElementException - when the path doesn't contain a sharding key
+   */
+  String getShardingKeyInPath(String path) throws IllegalArgumentException, NoSuchElementException;
+
+  /**
    * Check if the provided sharding key can be inserted to the routing data. The insertion is
    * invalid if: 1. the sharding key is a parent key to an existing sharding key; 2. the sharding
    * key has a parent key that is an existing sharding key; 3. the sharding key already exists. In
diff --git a/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/datamodel/TrieRoutingData.java b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/datamodel/TrieRoutingData.java
index 7a05089..0f53c23 100644
--- a/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/datamodel/TrieRoutingData.java
+++ b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/datamodel/TrieRoutingData.java
@@ -98,6 +98,20 @@ public class TrieRoutingData implements MetadataStoreRoutingData {
     return node.getRealmAddress();
   }
 
+  public String getShardingKeyInPath(String path)
+      throws IllegalArgumentException, NoSuchElementException {
+    if (!ZkValidationUtil.isPathValid(path)) {
+      throw new IllegalArgumentException("Provided path is not a valid Zookeeper path: " + path);
+    }
+
+    TrieNode node = getLongestPrefixNodeAlongPath(path);
+    if (!node.isShardingKey()) {
+      throw new NoSuchElementException(
+          "No sharding key found within the provided path. Path: " + path);
+    }
+    return node.getPath();
+  }
+
   public boolean isShardingKeyInsertionValid(String shardingKey) {
     if (!ZkValidationUtil.isPathValid(shardingKey)) {
       throw new IllegalArgumentException(
diff --git a/metadata-store-directory-common/src/test/java/org/apache/helix/msdcommon/datamodel/TestTrieRoutingData.java b/metadata-store-directory-common/src/test/java/org/apache/helix/msdcommon/datamodel/TestTrieRoutingData.java
index bad10a4..2dc5dfe 100644
--- a/metadata-store-directory-common/src/test/java/org/apache/helix/msdcommon/datamodel/TestTrieRoutingData.java
+++ b/metadata-store-directory-common/src/test/java/org/apache/helix/msdcommon/datamodel/TestTrieRoutingData.java
@@ -255,6 +255,37 @@ public class TestTrieRoutingData {
   }
 
   @Test(dependsOnMethods = "testConstructionNormal")
+  public void testGetShardingKeyInPath() {
+    try {
+      Assert.assertEquals(_trie.getShardingKeyInPath("/b/c/d/x/y/z"), "/b/c/d");
+    } catch (NoSuchElementException e) {
+      Assert.fail("Not expecting NoSuchElementException");
+    }
+  }
+
+  @Test(dependsOnMethods = "testConstructionNormal")
+  public void testGetShardingKeyInPathWrongPath() {
+    try {
+      _trie.getShardingKeyInPath("/x/y/z");
+      Assert.fail("Expecting NoSuchElementException");
+    } catch (NoSuchElementException e) {
+      Assert.assertTrue(
+          e.getMessage().contains("No sharding key found within the provided path. Path: /x/y/z"));
+    }
+  }
+
+  @Test(dependsOnMethods = "testConstructionNormal")
+  public void testGetShardingKeyInPathNoLeaf() {
+    try {
+      _trie.getShardingKeyInPath("/b/c");
+      Assert.fail("Expecting NoSuchElementException");
+    } catch (NoSuchElementException e) {
+      Assert.assertTrue(
+          e.getMessage().contains("No sharding key found within the provided path. Path: /b/c"));
+    }
+  }
+
+  @Test(dependsOnMethods = "testConstructionNormal")
   public void testIsShardingKeyInsertionValidNoSlash() {
     try {
       _trie.isShardingKeyInsertionValid("x/y/z");


[helix] 09/49: Add MetadataStoreRoutingDataWriter with DistributedLeaderElection (#727)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit fbeefcfb218ce08344c2cdd76b1028a6c3caa1fe
Author: Hunter Lee <hu...@linkedin.com>
AuthorDate: Mon Feb 10 16:57:58 2020 -0800

    Add MetadataStoreRoutingDataWriter with DistributedLeaderElection (#727)
    
    We need a separate ZkClient-based writer that could allow users to write routing data to ZK. This diff adds such an interface, an implementation, and a distributed lock implementation that could help users to manipulate the routing data.
    
    Changelist:
    
    Add ZkRoutingDataWriter (+ interface)
    Add ZkDistributedLock (+ interface) to guarantee that there's at most one active writer at a time (where there are multiple Helix REST deployables)
    Add a test for ZkRoutingDataWriter
    Integrate ZkRoutingDataWriter with ZkMetadataStoreDirectory
    Add test methods to TestZkMetadataStoreDirectory
    Add ZkDistributedElection to replace ZkDistributedLock (and move ZkDistributedLock to a separate PR)
---
 .../metadatastore/ZkMetadataStoreDirectory.java    |  62 +++--
 .../MetadataStoreRoutingDataReader.java            |   3 +-
 .../accessor/MetadataStoreRoutingDataWriter.java   |  74 ++++++
 .../{ => accessor}/ZkRoutingDataReader.java        |  25 +-
 .../accessor/ZkRoutingDataWriter.java              | 253 +++++++++++++++++++++
 .../concurrency/ZkDistributedLeaderElection.java   | 142 ++++++++++++
 .../constant/MetadataStoreRoutingConstants.java    |   3 +
 .../{ => accessor}/TestZkRoutingDataReader.java    |   2 +-
 .../accessor/TestZkRoutingDataWriter.java          | 107 +++++++++
 .../helix/rest/server/AbstractTestClass.java       |   9 -
 10 files changed, 627 insertions(+), 53 deletions(-)

diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java
index 5a88ca9..536d058 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java
@@ -29,16 +29,11 @@ import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
-import org.I0Itec.zkclient.IZkChildListener;
-import org.I0Itec.zkclient.IZkDataListener;
-import org.apache.helix.HelixManagerProperties;
-import org.apache.helix.manager.zk.ZNRecordSerializer;
-import org.apache.helix.manager.zk.client.DedicatedZkClientFactory;
-import org.apache.helix.manager.zk.client.HelixZkClient;
-import org.apache.helix.manager.zk.client.HelixZkClient.ZkClientConfig;
-import org.apache.helix.manager.zk.zookeeper.IZkStateListener;
+import org.apache.helix.rest.metadatastore.accessor.MetadataStoreRoutingDataReader;
+import org.apache.helix.rest.metadatastore.accessor.MetadataStoreRoutingDataWriter;
+import org.apache.helix.rest.metadatastore.accessor.ZkRoutingDataReader;
+import org.apache.helix.rest.metadatastore.accessor.ZkRoutingDataWriter;
 import org.apache.helix.rest.metadatastore.exceptions.InvalidRoutingDataException;
-import org.apache.zookeeper.Watcher;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -52,6 +47,7 @@ public class ZkMetadataStoreDirectory implements MetadataStoreDirectory, Routing
   // TODO: enable the line below when implementation is complete
   // The following maps' keys represent the namespace
   private final Map<String, MetadataStoreRoutingDataReader> _routingDataReaderMap;
+  private final Map<String, MetadataStoreRoutingDataWriter> _routingDataWriterMap;
   private final Map<String, MetadataStoreRoutingData> _routingDataMap;
   private final Map<String, String> _routingZkAddressMap;
   // <namespace, <realm, <list of sharding keys>> mappings
@@ -68,14 +64,17 @@ public class ZkMetadataStoreDirectory implements MetadataStoreDirectory, Routing
       throw new InvalidRoutingDataException("Routing ZK Addresses given are invalid!");
     }
     _routingDataReaderMap = new HashMap<>();
+    _routingDataWriterMap = new HashMap<>();
     _routingZkAddressMap = routingZkAddressMap;
     _realmToShardingKeysMap = new ConcurrentHashMap<>();
     _routingDataMap = new ConcurrentHashMap<>();
 
-    // Create RoutingDataReaders
+    // Create RoutingDataReaders and RoutingDataWriters
     for (Map.Entry<String, String> routingEntry : _routingZkAddressMap.entrySet()) {
       _routingDataReaderMap.put(routingEntry.getKey(),
           new ZkRoutingDataReader(routingEntry.getKey(), routingEntry.getValue(), this));
+      _routingDataWriterMap.put(routingEntry.getKey(),
+          new ZkRoutingDataWriter(routingEntry.getKey(), routingEntry.getValue()));
 
       // Populate realmToShardingKeys with ZkRoutingDataReader
       _realmToShardingKeysMap.put(routingEntry.getKey(),
@@ -132,26 +131,38 @@ public class ZkMetadataStoreDirectory implements MetadataStoreDirectory, Routing
 
   @Override
   public boolean addMetadataStoreRealm(String namespace, String realm) {
-    // TODO implement when MetadataStoreRoutingDataWriter is ready
-    throw new UnsupportedOperationException();
+    if (!_routingDataWriterMap.containsKey(namespace)) {
+      throw new IllegalArgumentException(
+          "Failed to add metadata store realm: Namespace " + namespace + " is not found!");
+    }
+    return _routingDataWriterMap.get(namespace).addMetadataStoreRealm(realm);
   }
 
   @Override
   public boolean deleteMetadataStoreRealm(String namespace, String realm) {
-    // TODO implement when MetadataStoreRoutingDataWriter is ready
-    throw new UnsupportedOperationException();
+    if (!_routingDataWriterMap.containsKey(namespace)) {
+      throw new IllegalArgumentException(
+          "Failed to delete metadata store realm: Namespace " + namespace + " is not found!");
+    }
+    return _routingDataWriterMap.get(namespace).deleteMetadataStoreRealm(realm);
   }
 
   @Override
   public boolean addShardingKey(String namespace, String realm, String shardingKey) {
-    // TODO implement when MetadataStoreRoutingDataWriter is ready
-    throw new UnsupportedOperationException();
+    if (!_routingDataWriterMap.containsKey(namespace)) {
+      throw new IllegalArgumentException(
+          "Failed to add sharding key: Namespace " + namespace + " is not found!");
+    }
+    return _routingDataWriterMap.get(namespace).addShardingKey(realm, shardingKey);
   }
 
   @Override
   public boolean deleteShardingKey(String namespace, String realm, String shardingKey) {
-    // TODO implement when MetadataStoreRoutingDataWriter is ready
-    throw new UnsupportedOperationException();
+    if (!_routingDataWriterMap.containsKey(namespace)) {
+      throw new IllegalArgumentException(
+          "Failed to delete sharding key: Namespace " + namespace + " is not found!");
+    }
+    return _routingDataWriterMap.get(namespace).deleteShardingKey(realm, shardingKey);
   }
 
   /**
@@ -165,20 +176,20 @@ public class ZkMetadataStoreDirectory implements MetadataStoreDirectory, Routing
    */
   @Override
   public void refreshRoutingData(String namespace) {
-    // Safe to ignore the callback if any of the mapping is null.
+    // Safe to ignore the callback if any of the maps are null.
     // If routingDataMap is null, then it will be populated by the constructor anyway
     // If routingDataMap is not null, then it's safe for the callback function to update it
-    if (_routingZkAddressMap == null || _routingDataMap == null
-        || _realmToShardingKeysMap == null) {
-      LOG.error("Construction is not completed! ");
+    if (_routingZkAddressMap == null || _routingDataMap == null || _realmToShardingKeysMap == null
+        || _routingDataReaderMap == null || _routingDataWriterMap == null) {
+      LOG.warn(
+          "refreshRoutingData callback called before ZKMetadataStoreDirectory was fully initialized. Skipping refresh!");
       return;
     }
 
     // Check if namespace exists; otherwise, return as a NOP and log it
     if (!_routingZkAddressMap.containsKey(namespace)) {
-      LOG.error("Failed to refresh internally-cached routing data! Namespace not found: {}",
-          namespace);
-      return;
+      LOG.error(
+          "Failed to refresh internally-cached routing data! Namespace not found: " + namespace);
     }
 
     try {
@@ -197,5 +208,6 @@ public class ZkMetadataStoreDirectory implements MetadataStoreDirectory, Routing
   @Override
   public synchronized void close() {
     _routingDataReaderMap.values().forEach(MetadataStoreRoutingDataReader::close);
+    _routingDataWriterMap.values().forEach(MetadataStoreRoutingDataWriter::close);
   }
 }
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/MetadataStoreRoutingDataReader.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/MetadataStoreRoutingDataReader.java
similarity index 93%
rename from helix-rest/src/main/java/org/apache/helix/rest/metadatastore/MetadataStoreRoutingDataReader.java
rename to helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/MetadataStoreRoutingDataReader.java
index 3cc9a06..f19e8ff 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/MetadataStoreRoutingDataReader.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/MetadataStoreRoutingDataReader.java
@@ -1,4 +1,4 @@
-package org.apache.helix.rest.metadatastore;
+package org.apache.helix.rest.metadatastore.accessor;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -26,6 +26,7 @@ import org.apache.helix.rest.metadatastore.exceptions.InvalidRoutingDataExceptio
 /**
  * An interface for a DAO that fetches routing data from a source and return a key-value mapping
  * that represent the said routing data.
+ * Note: Each data reader connects to a single namespace.
  */
 public interface MetadataStoreRoutingDataReader {
 
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/MetadataStoreRoutingDataWriter.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/MetadataStoreRoutingDataWriter.java
new file mode 100644
index 0000000..349bbd0
--- /dev/null
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/MetadataStoreRoutingDataWriter.java
@@ -0,0 +1,74 @@
+package org.apache.helix.rest.metadatastore.accessor;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * An interface for a DAO that writes to the metadata store that stores routing data.
+ * Note: Each data writer connects to a single namespace.
+ */
+public interface MetadataStoreRoutingDataWriter {
+
+  /**
+   * Creates a realm. If the namespace does not exist, it creates one.
+   * @param realm
+   * @return true if successful or if the realm already exists. false otherwise.
+   */
+  boolean addMetadataStoreRealm(String realm);
+
+  /**
+   * Deletes a realm.
+   * @param realm
+   * @return true if successful or the realm or namespace does not exist. false otherwise.
+   */
+  boolean deleteMetadataStoreRealm(String realm);
+
+  /**
+   * Creates a mapping between the sharding key to the realm. If realm doesn't exist, it will be created (this call is idempotent).
+   * @param realm
+   * @param shardingKey
+   * @return false if failed
+   */
+  boolean addShardingKey(String realm, String shardingKey);
+
+  /**
+   * Deletes the mapping between the sharding key to the realm.
+   * @param realm
+   * @param shardingKey
+   * @return false if failed; true if the deletion is successful or the key does not exist.
+   */
+  boolean deleteShardingKey(String realm, String shardingKey);
+
+  /**
+   * Sets (overwrites) the routing data with the given <realm, list of sharding keys> mapping.
+   * WARNING: This overwrites all existing routing data. Use with care!
+   * @param routingData
+   * @return
+   */
+  boolean setRoutingData(Map<String, List<String>> routingData);
+
+  /**
+   * Closes any stateful resources such as connections or threads.
+   */
+  void close();
+}
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkRoutingDataReader.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataReader.java
similarity index 92%
rename from helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkRoutingDataReader.java
rename to helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataReader.java
index 453180f..ea8c290 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkRoutingDataReader.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataReader.java
@@ -1,4 +1,4 @@
-package org.apache.helix.rest.metadatastore;
+package org.apache.helix.rest.metadatastore.accessor;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -31,6 +31,7 @@ import org.apache.helix.manager.zk.ZNRecordSerializer;
 import org.apache.helix.manager.zk.client.DedicatedZkClientFactory;
 import org.apache.helix.manager.zk.client.HelixZkClient;
 import org.apache.helix.manager.zk.zookeeper.IZkStateListener;
+import org.apache.helix.rest.metadatastore.RoutingDataListener;
 import org.apache.helix.rest.metadatastore.constant.MetadataStoreRoutingConstants;
 import org.apache.helix.rest.metadatastore.exceptions.InvalidRoutingDataException;
 import org.apache.zookeeper.Watcher;
@@ -42,10 +43,6 @@ public class ZkRoutingDataReader implements MetadataStoreRoutingDataReader, IZkD
   private final HelixZkClient _zkClient;
   private final RoutingDataListener _routingDataListener;
 
-  public ZkRoutingDataReader(String namespace, String zkAddress) {
-    this(namespace, zkAddress, null);
-  }
-
   public ZkRoutingDataReader(String namespace, String zkAddress,
       RoutingDataListener routingDataListener) {
     if (namespace == null || namespace.isEmpty()) {
@@ -115,8 +112,7 @@ public class ZkRoutingDataReader implements MetadataStoreRoutingDataReader, IZkD
   }
 
   @Override
-  public synchronized void handleDataChange(String s, Object o)
-      throws Exception {
+  public synchronized void handleDataChange(String s, Object o) {
     if (_zkClient.isClosed()) {
       return;
     }
@@ -124,8 +120,7 @@ public class ZkRoutingDataReader implements MetadataStoreRoutingDataReader, IZkD
   }
 
   @Override
-  public synchronized void handleDataDeleted(String s)
-      throws Exception {
+  public synchronized void handleDataDeleted(String s) {
     if (_zkClient.isClosed()) {
       return;
     }
@@ -140,8 +135,7 @@ public class ZkRoutingDataReader implements MetadataStoreRoutingDataReader, IZkD
   }
 
   @Override
-  public synchronized void handleChildChange(String s, List<String> list)
-      throws Exception {
+  public synchronized void handleChildChange(String s, List<String> list) {
     if (_zkClient.isClosed()) {
       return;
     }
@@ -156,8 +150,7 @@ public class ZkRoutingDataReader implements MetadataStoreRoutingDataReader, IZkD
   }
 
   @Override
-  public synchronized void handleStateChanged(Watcher.Event.KeeperState state)
-      throws Exception {
+  public synchronized void handleStateChanged(Watcher.Event.KeeperState state) {
     if (_zkClient.isClosed()) {
       return;
     }
@@ -165,8 +158,7 @@ public class ZkRoutingDataReader implements MetadataStoreRoutingDataReader, IZkD
   }
 
   @Override
-  public synchronized void handleNewSession(String sessionId)
-      throws Exception {
+  public synchronized void handleNewSession(String sessionId) {
     if (_zkClient.isClosed()) {
       return;
     }
@@ -174,8 +166,7 @@ public class ZkRoutingDataReader implements MetadataStoreRoutingDataReader, IZkD
   }
 
   @Override
-  public synchronized void handleSessionEstablishmentError(Throwable error)
-      throws Exception {
+  public synchronized void handleSessionEstablishmentError(Throwable error) {
     if (_zkClient.isClosed()) {
       return;
     }
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataWriter.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataWriter.java
new file mode 100644
index 0000000..3e43202
--- /dev/null
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataWriter.java
@@ -0,0 +1,253 @@
+package org.apache.helix.rest.metadatastore.accessor;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.I0Itec.zkclient.exception.ZkNodeExistsException;
+import org.apache.helix.ZNRecord;
+import org.apache.helix.manager.zk.ZNRecordSerializer;
+import org.apache.helix.manager.zk.ZkBaseDataAccessor;
+import org.apache.helix.manager.zk.client.DedicatedZkClientFactory;
+import org.apache.helix.manager.zk.client.HelixZkClient;
+import org.apache.helix.rest.metadatastore.concurrency.ZkDistributedLeaderElection;
+import org.apache.helix.rest.metadatastore.constant.MetadataStoreRoutingConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public class ZkRoutingDataWriter implements MetadataStoreRoutingDataWriter {
+  private static final Logger LOG = LoggerFactory.getLogger(ZkBaseDataAccessor.class);
+
+  private final String _namespace;
+  private final HelixZkClient _zkClient;
+  private final ZkDistributedLeaderElection _leaderElection;
+
+  public ZkRoutingDataWriter(String namespace, String zkAddress) {
+    if (namespace == null || namespace.isEmpty()) {
+      throw new IllegalArgumentException("namespace cannot be null or empty!");
+    }
+    _namespace = namespace;
+    if (zkAddress == null || zkAddress.isEmpty()) {
+      throw new IllegalArgumentException("Zk address cannot be null or empty!");
+    }
+    _zkClient = DedicatedZkClientFactory.getInstance()
+        .buildZkClient(new HelixZkClient.ZkConnectionConfig(zkAddress),
+            new HelixZkClient.ZkClientConfig().setZkSerializer(new ZNRecordSerializer()));
+
+    // Ensure that ROUTING_DATA_PATH exists in ZK. If not, create
+    // create() semantic will fail if it already exists
+    try {
+      _zkClient.createPersistent(MetadataStoreRoutingConstants.ROUTING_DATA_PATH, true);
+    } catch (ZkNodeExistsException e) {
+      // This is okay
+    }
+
+    // Get the hostname (REST endpoint) from System property
+    // TODO: Fill in when Helix REST implementations are ready
+    ZNRecord myServerInfo = new ZNRecord("dummy hostname");
+    _leaderElection = new ZkDistributedLeaderElection(_zkClient,
+        MetadataStoreRoutingConstants.LEADER_ELECTION_ZNODE, myServerInfo);
+  }
+
+  @Override
+  public synchronized boolean addMetadataStoreRealm(String realm) {
+    if (_leaderElection.isLeader()) {
+      if (_zkClient.isClosed()) {
+        throw new IllegalStateException("ZkClient is closed!");
+      }
+      return createZkRealm(realm);
+    }
+
+    // TODO: Forward the request to leader
+    return true;
+  }
+
+  @Override
+  public synchronized boolean deleteMetadataStoreRealm(String realm) {
+    if (_leaderElection.isLeader()) {
+      if (_zkClient.isClosed()) {
+        throw new IllegalStateException("ZkClient is closed!");
+      }
+      return _zkClient.delete(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + realm);
+    }
+
+    // TODO: Forward the request to leader
+    return true;
+  }
+
+  @Override
+  public synchronized boolean addShardingKey(String realm, String shardingKey) {
+    if (_leaderElection.isLeader()) {
+      if (_zkClient.isClosed()) {
+        throw new IllegalStateException("ZkClient is closed!");
+      }
+      // If the realm does not exist already, then create the realm
+      String realmPath = MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + realm;
+      if (!_zkClient.exists(realmPath)) {
+        // Create the realm
+        if (!createZkRealm(realm)) {
+          // Failed to create the realm - log and return false
+          LOG.error(
+              "Failed to add sharding key because ZkRealm creation failed! Namespace: {}, Realm: {}, Sharding key: {}",
+              _namespace, realm, shardingKey);
+          return false;
+        }
+      }
+
+      // Add the sharding key to an empty ZNRecord
+      ZNRecord znRecord;
+      try {
+        znRecord = _zkClient.readData(realmPath);
+      } catch (Exception e) {
+        LOG.error(
+            "Failed to read the realm ZNRecord in addShardingKey()! Namespace: {}, Realm: {}, ShardingKey: {}",
+            _namespace, realm, shardingKey, e);
+        return false;
+      }
+      znRecord.setListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY,
+          Collections.singletonList(shardingKey));
+      try {
+        _zkClient.writeData(realmPath, znRecord);
+      } catch (Exception e) {
+        LOG.error(
+            "Failed to write the realm ZNRecord in addShardingKey()! Namespace: {}, Realm: {}, ShardingKey: {}",
+            _namespace, realm, shardingKey, e);
+        return false;
+      }
+      return true;
+    }
+
+    // TODO: Forward the request to leader
+    return true;
+  }
+
+  @Override
+  public synchronized boolean deleteShardingKey(String realm, String shardingKey) {
+    if (_leaderElection.isLeader()) {
+      if (_zkClient.isClosed()) {
+        throw new IllegalStateException("ZkClient is closed!");
+      }
+      ZNRecord znRecord =
+          _zkClient.readData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + realm, true);
+      if (znRecord == null || !znRecord
+          .getListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY)
+          .contains(shardingKey)) {
+        // This realm does not exist or shardingKey doesn't exist. Return true!
+        return true;
+      }
+      znRecord.getListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY)
+          .remove(shardingKey);
+      // Overwrite this ZNRecord with the sharding key removed
+      try {
+        _zkClient
+            .writeData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + realm, znRecord);
+      } catch (Exception e) {
+        LOG.error(
+            "Failed to write the data back in deleteShardingKey()! Namespace: {}, Realm: {}, ShardingKey: {}",
+            _namespace, realm, shardingKey, e);
+        return false;
+      }
+      return true;
+    }
+
+    // TODO: Forward the request to leader
+    return true;
+  }
+
+  @Override
+  public synchronized boolean setRoutingData(Map<String, List<String>> routingData) {
+    if (_leaderElection.isLeader()) {
+      if (_zkClient.isClosed()) {
+        throw new IllegalStateException("ZkClient is closed!");
+      }
+      if (routingData == null) {
+        throw new IllegalArgumentException("routingData given is null!");
+      }
+
+      // Remove existing routing data
+      for (String zkRealm : _zkClient
+          .getChildren(MetadataStoreRoutingConstants.ROUTING_DATA_PATH)) {
+        if (!_zkClient.delete(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + zkRealm)) {
+          LOG.error(
+              "Failed to delete existing routing data in setRoutingData()! Namespace: {}, Realm: {}",
+              _namespace, zkRealm);
+          return false;
+        }
+      }
+
+      // For each ZkRealm, write the given routing data to ZooKeeper
+      for (Map.Entry<String, List<String>> routingDataEntry : routingData.entrySet()) {
+        String zkRealm = routingDataEntry.getKey();
+        List<String> shardingKeyList = routingDataEntry.getValue();
+
+        ZNRecord znRecord = new ZNRecord(zkRealm);
+        znRecord
+            .setListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY, shardingKeyList);
+
+        String realmPath = MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + zkRealm;
+        try {
+          if (!_zkClient.exists(realmPath)) {
+            _zkClient.createPersistent(realmPath);
+          }
+          _zkClient.writeData(realmPath, znRecord);
+        } catch (Exception e) {
+          LOG.error("Failed to write data in setRoutingData()! Namespace: {}, Realm: {}",
+              _namespace, zkRealm, e);
+          return false;
+        }
+      }
+      return true;
+    }
+
+    // TODO: Forward the request to leader
+    return true;
+  }
+
+  @Override
+  public synchronized void close() {
+    _zkClient.close();
+  }
+
+  /**
+   * Creates a ZK realm ZNode and populates it with an empty ZNRecord if it doesn't exist already.
+   * @param realm
+   * @return
+   */
+  private boolean createZkRealm(String realm) {
+    if (_zkClient.exists(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + realm)) {
+      LOG.warn("createZkRealm() called for realm: {}, but this realm already exists! Namespace: {}",
+          realm, _namespace);
+      return true;
+    }
+    try {
+      _zkClient.createPersistent(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + realm);
+      _zkClient.writeData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + realm,
+          new ZNRecord(realm));
+    } catch (Exception e) {
+      LOG.error("Failed to create ZkRealm: {}, Namespace: ", realm, _namespace);
+      return false;
+    }
+
+    return true;
+  }
+}
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/concurrency/ZkDistributedLeaderElection.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/concurrency/ZkDistributedLeaderElection.java
new file mode 100644
index 0000000..c9b6bb2
--- /dev/null
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/concurrency/ZkDistributedLeaderElection.java
@@ -0,0 +1,142 @@
+package org.apache.helix.rest.metadatastore.concurrency;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Collections;
+import java.util.List;
+
+import org.I0Itec.zkclient.IZkDataListener;
+import org.I0Itec.zkclient.exception.ZkNodeExistsException;
+import org.apache.helix.ZNRecord;
+import org.apache.helix.manager.zk.ZNRecordSerializer;
+import org.apache.helix.manager.zk.client.HelixZkClient;
+import org.apache.helix.manager.zk.zookeeper.IZkStateListener;
+import org.apache.zookeeper.CreateMode;
+import org.apache.zookeeper.Watcher;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public class ZkDistributedLeaderElection implements IZkDataListener, IZkStateListener {
+  private static final Logger LOG = LoggerFactory.getLogger(ZkDistributedLeaderElection.class);
+  private static final String PREFIX = "MSDS_SERVER_";
+
+  private final HelixZkClient _zkClient;
+  private final String _basePath;
+  private final ZNRecord _participantInfo;
+  private ZNRecord _currentLeaderInfo;
+
+  private String _myEphemeralSequentialPath;
+  private volatile boolean _isLeader;
+
+  public ZkDistributedLeaderElection(HelixZkClient zkClient, String basePath,
+      ZNRecord participantInfo) {
+    synchronized (this) {
+      if (zkClient == null || zkClient.isClosed()) {
+        throw new IllegalArgumentException("ZkClient cannot be null or closed!");
+      }
+      _zkClient = zkClient;
+      _zkClient.setZkSerializer(new ZNRecordSerializer());
+      if (basePath == null || basePath.isEmpty()) {
+        throw new IllegalArgumentException("lockBasePath cannot be null or empty!");
+      }
+      _basePath = basePath;
+      _participantInfo = participantInfo;
+      _isLeader = false;
+    }
+    init();
+  }
+
+  /**
+   * Create the base path if it doesn't exist and create an ephemeral sequential ZNode.
+   */
+  private void init() {
+    try {
+      _zkClient.createPersistent(_basePath, true);
+    } catch (ZkNodeExistsException e) {
+      // Okay if it exists already
+    }
+
+    // Create my ephemeral sequential node with my information
+    _myEphemeralSequentialPath = _zkClient
+        .create(_basePath + "/" + PREFIX, _participantInfo, CreateMode.EPHEMERAL_SEQUENTIAL);
+    if (_myEphemeralSequentialPath == null) {
+      throw new IllegalStateException(
+          "Unable to create ephemeral sequential node at path: " + _basePath);
+    }
+    tryAcquiringLeadership();
+  }
+
+  private void tryAcquiringLeadership() {
+    List<String> children = _zkClient.getChildren(_basePath);
+    Collections.sort(children);
+    String leaderName = children.get(0);
+    ZNRecord leaderInfo = _zkClient.readData(_basePath + "/" + leaderName, true);
+
+    String[] myNameArray = _myEphemeralSequentialPath.split("/");
+    String myName = myNameArray[myNameArray.length - 1];
+
+    if (leaderName.equals(myName)) {
+      // My turn for leadership
+      _isLeader = true;
+      _currentLeaderInfo = leaderInfo;
+      LOG.info("{} acquired leadership! Info: {}", myName, leaderInfo);
+    } else {
+      // Watch the ephemeral ZNode before me for a deletion event
+      String beforeMe = children.get(children.indexOf(myName) - 1);
+      _zkClient.subscribeDataChanges(_basePath + "/" + beforeMe, this);
+    }
+  }
+
+  public synchronized boolean isLeader() {
+    return _isLeader;
+  }
+
+  public synchronized ZNRecord getCurrentLeaderInfo() {
+    return _currentLeaderInfo;
+  }
+
+  @Override
+  public synchronized void handleStateChanged(Watcher.Event.KeeperState state) {
+    if (state == Watcher.Event.KeeperState.SyncConnected) {
+      init();
+    }
+  }
+
+  @Override
+  public void handleNewSession(String sessionId) {
+    return;
+  }
+
+  @Override
+  public void handleSessionEstablishmentError(Throwable error) {
+    return;
+  }
+
+  @Override
+  public void handleDataChange(String s, Object o) {
+    return;
+  }
+
+  @Override
+  public void handleDataDeleted(String s) {
+    tryAcquiringLeadership();
+  }
+}
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/constant/MetadataStoreRoutingConstants.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/constant/MetadataStoreRoutingConstants.java
index fda355b..e4240e7 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/constant/MetadataStoreRoutingConstants.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/constant/MetadataStoreRoutingConstants.java
@@ -24,4 +24,7 @@ public class MetadataStoreRoutingConstants {
 
   // For ZK only
   public static final String ZNRECORD_LIST_FIELD_KEY = "ZK_PATH_SHARDING_KEYS";
+
+  // Leader election ZNode for ZkRoutingDataWriter
+  public static final String LEADER_ELECTION_ZNODE = "/_ZK_ROUTING_DATA_WRITER_LEADER";
 }
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkRoutingDataReader.java b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataReader.java
similarity index 99%
rename from helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkRoutingDataReader.java
rename to helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataReader.java
index 4479f68..77eb5eb 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkRoutingDataReader.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataReader.java
@@ -1,4 +1,4 @@
-package org.apache.helix.rest.metadatastore;
+package org.apache.helix.rest.metadatastore.accessor;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataWriter.java b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataWriter.java
new file mode 100644
index 0000000..441bf65
--- /dev/null
+++ b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataWriter.java
@@ -0,0 +1,107 @@
+package org.apache.helix.rest.metadatastore.accessor;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.helix.AccessOption;
+import org.apache.helix.ZNRecord;
+import org.apache.helix.rest.metadatastore.constant.MetadataStoreRoutingConstants;
+import org.apache.helix.rest.server.AbstractTestClass;
+import org.junit.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+
+public class TestZkRoutingDataWriter extends AbstractTestClass {
+  private static final String DUMMY_NAMESPACE = "NAMESPACE";
+  private static final String DUMMY_REALM = "REALM";
+  private static final String DUMMY_SHARDING_KEY = "SHARDING_KEY";
+  private MetadataStoreRoutingDataWriter _zkRoutingDataWriter;
+
+  @BeforeClass
+  public void beforeClass() {
+    _zkRoutingDataWriter = new ZkRoutingDataWriter(DUMMY_NAMESPACE, ZK_ADDR);
+  }
+
+  @AfterClass
+  public void afterClass() {
+    _baseAccessor.remove(MetadataStoreRoutingConstants.ROUTING_DATA_PATH, AccessOption.PERSISTENT);
+    _zkRoutingDataWriter.close();
+  }
+
+  @Test
+  public void testAddMetadataStoreRealm() {
+    _zkRoutingDataWriter.addMetadataStoreRealm(DUMMY_REALM);
+    ZNRecord znRecord = _baseAccessor
+        .get(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + DUMMY_REALM, null,
+            AccessOption.PERSISTENT);
+    Assert.assertNotNull(znRecord);
+  }
+
+  @Test(dependsOnMethods = "testAddMetadataStoreRealm")
+  public void testDeleteMetadataStoreRealm() {
+    _zkRoutingDataWriter.deleteMetadataStoreRealm(DUMMY_REALM);
+    Assert.assertFalse(_baseAccessor
+        .exists(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + DUMMY_REALM,
+            AccessOption.PERSISTENT));
+  }
+
+  @Test(dependsOnMethods = "testDeleteMetadataStoreRealm")
+  public void testAddShardingKey() {
+    _zkRoutingDataWriter.addShardingKey(DUMMY_REALM, DUMMY_SHARDING_KEY);
+    ZNRecord znRecord = _baseAccessor
+        .get(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + DUMMY_REALM, null,
+            AccessOption.PERSISTENT);
+    Assert.assertNotNull(znRecord);
+    Assert.assertTrue(znRecord.getListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY)
+        .contains(DUMMY_SHARDING_KEY));
+  }
+
+  @Test(dependsOnMethods = "testAddShardingKey")
+  public void testDeleteShardingKey() {
+    _zkRoutingDataWriter.deleteShardingKey(DUMMY_REALM, DUMMY_SHARDING_KEY);
+    ZNRecord znRecord = _baseAccessor
+        .get(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + DUMMY_REALM, null,
+            AccessOption.PERSISTENT);
+    Assert.assertNotNull(znRecord);
+    Assert.assertFalse(znRecord.getListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY)
+        .contains(DUMMY_SHARDING_KEY));
+  }
+
+  @Test(dependsOnMethods = "testDeleteShardingKey")
+  public void testSetRoutingData() {
+    Map<String, List<String>> testRoutingDataMap =
+        ImmutableMap.of(DUMMY_REALM, Collections.singletonList(DUMMY_SHARDING_KEY));
+    _zkRoutingDataWriter.setRoutingData(testRoutingDataMap);
+    ZNRecord znRecord = _baseAccessor
+        .get(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + DUMMY_REALM, null,
+            AccessOption.PERSISTENT);
+    Assert.assertNotNull(znRecord);
+    Assert.assertEquals(
+        znRecord.getListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY).size(), 1);
+    Assert.assertTrue(znRecord.getListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY)
+        .contains(DUMMY_SHARDING_KEY));
+  }
+}
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 e6ecb82..c5ffd41 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
@@ -269,19 +269,10 @@ public class AbstractTestClass extends JerseyTestNg.ContainerPerClassTest {
       _gZkClient = null;
     }
 
-    if (_zkServer != null) {
-      TestHelper.stopZkServer(_zkServer);
-      _zkServer = null;
-    }
-
     if (_gZkClientTestNS != null) {
       _gZkClientTestNS.close();
       _gZkClientTestNS = null;
     }
-    if (_zkServerTestNS != null) {
-      TestHelper.stopZkServer(_zkServerTestNS);
-      _zkServerTestNS = null;
-    }
 
     if (_helixRestServer != null) {
       _helixRestServer.shutdown();


[helix] 32/49: Make RealmAwareZkClient implementations use HttpRoutingDataReader for routing data (#819)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit 0e7ab0e52b3c481c590a2d3ed7d057c595d41f2e
Author: Hunter Lee <hu...@linkedin.com>
AuthorDate: Wed Mar 4 00:01:40 2020 -0800

    Make RealmAwareZkClient implementations use HttpRoutingDataReader for routing data (#819)
    
    We want all implementations of RealmAwareZkClient to do a one-time query to Metadata Store Directory Service for routing data and cache it in memory. In order to accomplish that, we have introduced HttpRoutingDataReader, which is a Singleton class that makes a REST call to read routing data and caches it in memory. This diff updates the initialization logic in RealmAwareZkClients accordingly.
    
    Changelist:
    1. Update all RealmAwareZkClient initialization logic
    2. Fix tests
---
 .../main/java/org/apache/helix/ConfigAccessor.java |   6 +-
 .../mock/MockMetadataStoreDirectoryServer.java     |   6 +-
 .../helix/msdcommon/constant/TestConstants.java    |  26 ++--
 .../mock/TestMockMetadataStoreDirectoryServer.java |  19 ++-
 .../api/factory/RealmAwareZkClientFactory.java     |  26 ++--
 .../zookeeper/impl/client/DedicatedZkClient.java   |  55 +++++---
 .../zookeeper/impl/client/FederatedZkClient.java   |  25 +++-
 .../zookeeper/impl/client/SharedZkClient.java      |  32 +++--
 .../impl/factory/DedicatedZkClientFactory.java     |  10 +-
 .../impl/factory/SharedZkClientFactory.java        |  17 +--
 .../zookeeper/util/HttpRoutingDataReader.java      |   3 +-
 zookeeper-api/src/test/conf/testng.xml             |   4 +-
 .../helix/zookeeper/constant/TestConstants.java    |  45 +++++++
 .../apache/helix/zookeeper/impl/ZkTestBase.java    |   5 +-
 ...java => RealmAwareZkClientFactoryTestBase.java} | 100 ++++++++------
 .../impl/client/RealmAwareZkClientTestBase.java    | 150 ++++-----------------
 .../impl/client/TestDedicatedZkClient.java         |   8 +-
 .../impl/client/TestFederatedZkClient.java         |  53 ++------
 .../zookeeper/impl/client/TestSharedZkClient.java  |   8 +-
 .../zookeeper/util/TestHttpRoutingDataReader.java  |  35 ++---
 20 files changed, 296 insertions(+), 337 deletions(-)

diff --git a/helix-core/src/main/java/org/apache/helix/ConfigAccessor.java b/helix-core/src/main/java/org/apache/helix/ConfigAccessor.java
index 0751886..8e36f83 100644
--- a/helix-core/src/main/java/org/apache/helix/ConfigAccessor.java
+++ b/helix-core/src/main/java/org/apache/helix/ConfigAccessor.java
@@ -89,10 +89,8 @@ public class ConfigAccessor {
   private ConfigAccessor(Builder builder) throws IOException, InvalidRoutingDataException {
     switch (builder._realmMode) {
       case MULTI_REALM:
-        // TODO: make sure FederatedZkClient is created correctly
-        // TODO: pass in MSDS endpoint or pass in _realmAwareZkConnectionConfig
-        String msdsEndpoint = builder._realmAwareZkConnectionConfig.getMsdsEndpoint();
-        _zkClient = new FederatedZkClient();
+        _zkClient = new FederatedZkClient(builder._realmAwareZkConnectionConfig,
+            builder._realmAwareZkClientConfig);
         break;
       case SINGLE_REALM:
         // Create a HelixZkClient: Use a SharedZkClient because ConfigAccessor does not need to do
diff --git a/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/mock/MockMetadataStoreDirectoryServer.java b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/mock/MockMetadataStoreDirectoryServer.java
index f2a59d6..7253092 100644
--- a/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/mock/MockMetadataStoreDirectoryServer.java
+++ b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/mock/MockMetadataStoreDirectoryServer.java
@@ -23,7 +23,6 @@ import java.io.IOException;
 import java.io.OutputStream;
 import java.net.InetSocketAddress;
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Executors;
@@ -68,7 +67,7 @@ public class MockMetadataStoreDirectoryServer {
   /**
    * Constructs a Mock MSDS.
    * A sample GET might look like the following:
-   *     curl localhost:11000/admin/v2/namespaces/MY-HELIX-NAMESPACE/METADATA_STORE_ROUTING_DATA/zk-1
+   *     curl localhost:11000/admin/v2/namespaces/MY-HELIX-NAMESPACE/metadata-store-realms/zk-1
    * @param hostname hostname for the REST server. E.g.) "localhost"
    * @param port port to use. E.g.) 11000
    * @param namespace the Helix REST namespace to mock. E.g.) "MY-HELIX-NAMESPACE"
@@ -94,8 +93,7 @@ public class MockMetadataStoreDirectoryServer {
     _routingDataMap = routingData;
   }
 
-  public void startServer()
-      throws IOException {
+  public void startServer() throws IOException {
     _server = HttpServer.create(new InetSocketAddress(_hostname, _mockServerPort), 0);
     generateContexts();
     _server.setExecutor(_executor);
diff --git a/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/TestDedicatedZkClient.java b/metadata-store-directory-common/src/test/java/org/apache/helix/msdcommon/constant/TestConstants.java
similarity index 53%
copy from zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/TestDedicatedZkClient.java
copy to metadata-store-directory-common/src/test/java/org/apache/helix/msdcommon/constant/TestConstants.java
index 8cf3f85..c471a1a 100644
--- a/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/TestDedicatedZkClient.java
+++ b/metadata-store-directory-common/src/test/java/org/apache/helix/msdcommon/constant/TestConstants.java
@@ -1,4 +1,4 @@
-package org.apache.helix.zookeeper.impl.client;
+package org.apache.helix.msdcommon.constant;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -9,7 +9,7 @@ package org.apache.helix.zookeeper.impl.client;
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
- *     http://www.apache.org/licenses/LICENSE-2.0
+ *   http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
@@ -19,17 +19,19 @@ package org.apache.helix.zookeeper.impl.client;
  * under the License.
  */
 
-import org.apache.helix.zookeeper.impl.factory.DedicatedZkClientFactory;
-import org.testng.annotations.BeforeClass;
+import java.util.Collection;
+import java.util.Map;
 
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 
-public class TestDedicatedZkClient extends RealmAwareZkClientTestBase {
 
-  @BeforeClass
-  public void beforeClass()
-      throws Exception {
-    super.beforeClass();
-    // Set the factory to DedicatedZkClientFactory
-    _realmAwareZkClientFactory = DedicatedZkClientFactory.getInstance();
-  }
+/**
+ * Constants to be used for testing.
+ */
+public class TestConstants {
+  public static final Map<String, Collection<String>> FAKE_ROUTING_DATA = ImmutableMap.of(
+      "zk-0", ImmutableList.of("/sharding-key-0", "/sharding-key-1", "/sharding-key-2"),
+      "zk-1", ImmutableList.of("/sharding-key-3", "/sharding-key-4", "/sharding-key-5"),
+      "zk-2", ImmutableList.of("/sharding-key-6", "/sharding-key-7", "/sharding-key-8"));
 }
diff --git a/metadata-store-directory-common/src/test/java/org/apache/helix/msdcommon/mock/TestMockMetadataStoreDirectoryServer.java b/metadata-store-directory-common/src/test/java/org/apache/helix/msdcommon/mock/TestMockMetadataStoreDirectoryServer.java
index 1dc006a..015bc8c 100644
--- a/metadata-store-directory-common/src/test/java/org/apache/helix/msdcommon/mock/TestMockMetadataStoreDirectoryServer.java
+++ b/metadata-store-directory-common/src/test/java/org/apache/helix/msdcommon/mock/TestMockMetadataStoreDirectoryServer.java
@@ -22,12 +22,14 @@ package org.apache.helix.msdcommon.mock;
 import java.io.IOException;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
 
 import com.google.common.collect.ImmutableList;
 import org.apache.helix.msdcommon.constant.MetadataStoreRoutingConstants;
+import org.apache.helix.msdcommon.constant.TestConstants;
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.client.methods.HttpPost;
@@ -40,12 +42,6 @@ import org.testng.Assert;
 public class TestMockMetadataStoreDirectoryServer {
   @Test
   public void testMockMetadataStoreDirectoryServer() throws IOException {
-    // Create fake routing data
-    Map<String, Collection<String>> routingData = new HashMap<>();
-    routingData.put("zk-0", ImmutableList.of("sharding-key-0", "sharding-key-1", "sharding-key-2"));
-    routingData.put("zk-1", ImmutableList.of("sharding-key-3", "sharding-key-4", "sharding-key-5"));
-    routingData.put("zk-2", ImmutableList.of("sharding-key-6", "sharding-key-7", "sharding-key-8"));
-
     // Start MockMSDS
     String host = "localhost";
     int port = 11000;
@@ -53,7 +49,8 @@ public class TestMockMetadataStoreDirectoryServer {
     String namespace = "MY-HELIX-NAMESPACE";
 
     MockMetadataStoreDirectoryServer server =
-        new MockMetadataStoreDirectoryServer(host, port, namespace, routingData);
+        new MockMetadataStoreDirectoryServer(host, port, namespace,
+            TestConstants.FAKE_ROUTING_DATA);
     server.startServer();
     try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
       // Send a GET request for all routing data
@@ -69,13 +66,13 @@ public class TestMockMetadataStoreDirectoryServer {
       Collection<String> allRealms = routingDataList.stream().map(mapEntry -> (String) mapEntry
           .get(MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_REALM))
           .collect(Collectors.toSet());
-      Assert.assertEquals(allRealms, routingData.keySet());
+      Assert.assertEquals(new HashSet(allRealms), TestConstants.FAKE_ROUTING_DATA.keySet());
       Map<String, List<String>> retrievedRoutingData = routingDataList.stream().collect(Collectors
           .toMap(mapEntry -> (String) mapEntry
                   .get(MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_REALM),
               mapEntry -> (List<String>) mapEntry
                   .get(MetadataStoreRoutingConstants.SHARDING_KEYS)));
-      Assert.assertEquals(retrievedRoutingData, routingData);
+      Assert.assertEquals(retrievedRoutingData, TestConstants.FAKE_ROUTING_DATA);
 
       // Send a GET request for all realms
       getRequest = new HttpGet(endpoint + MockMetadataStoreDirectoryServer.REST_PREFIX + namespace
@@ -86,7 +83,7 @@ public class TestMockMetadataStoreDirectoryServer {
       Assert.assertTrue(
           allRealmsMap.containsKey(MetadataStoreRoutingConstants.METADATA_STORE_REALMS));
       allRealms = allRealmsMap.get(MetadataStoreRoutingConstants.METADATA_STORE_REALMS);
-      Assert.assertEquals(allRealms, routingData.keySet());
+      Assert.assertEquals(allRealms, TestConstants.FAKE_ROUTING_DATA.keySet());
 
       // Send a GET request for testZkRealm
       String testZkRealm = "zk-0";
@@ -103,7 +100,7 @@ public class TestMockMetadataStoreDirectoryServer {
       Collection<String> shardingKeyList =
           (Collection) shardingKeysMap.get(MetadataStoreRoutingConstants.SHARDING_KEYS);
       Assert.assertEquals(zkRealm, testZkRealm);
-      Assert.assertEquals(shardingKeyList, routingData.get(testZkRealm));
+      Assert.assertEquals(shardingKeyList, TestConstants.FAKE_ROUTING_DATA.get(testZkRealm));
 
       // Try sending a POST request (not supported)
       HttpPost postRequest = new HttpPost(
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/factory/RealmAwareZkClientFactory.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/factory/RealmAwareZkClientFactory.java
index cdfa778..d8fa9a7 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/factory/RealmAwareZkClientFactory.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/factory/RealmAwareZkClientFactory.java
@@ -19,7 +19,9 @@ package org.apache.helix.zookeeper.api.factory;
  * under the License.
  */
 
-import org.apache.helix.msdcommon.datamodel.MetadataStoreRoutingData;
+import java.io.IOException;
+
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
 import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 
 
@@ -31,26 +33,24 @@ public interface RealmAwareZkClientFactory {
    * Build a RealmAwareZkClient using specified connection config and client config.
    * @param connectionConfig
    * @param clientConfig
-   * @param metadataStoreRoutingData
-   * @return HelixZkClient
+   * @return RealmAwareZkClient
+   * @throws IOException if Metadata Store Directory Service is unresponsive over HTTP
+   * @throws InvalidRoutingDataException if the routing data received is invalid or empty
    */
-  // TODO: remove MetadataStoreRoutingData
   RealmAwareZkClient buildZkClient(RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig,
-      RealmAwareZkClient.RealmAwareZkClientConfig clientConfig,
-      MetadataStoreRoutingData metadataStoreRoutingData);
+      RealmAwareZkClient.RealmAwareZkClientConfig clientConfig)
+      throws IOException, InvalidRoutingDataException;
 
   /**
    * Builds a RealmAwareZkClient using specified connection config and default client config.
    * @param connectionConfig
-   * @param metadataStoreRoutingData
    * @return RealmAwareZkClient
+   * @throws IOException if Metadata Store Directory Service is unresponsive over HTTP
+   * @throws InvalidRoutingDataException if the routing data received is invalid or empty
    */
-
-  // TODO: remove MetadataStoreRoutingData
   default RealmAwareZkClient buildZkClient(
-      RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig,
-      MetadataStoreRoutingData metadataStoreRoutingData) {
-    return buildZkClient(connectionConfig, new RealmAwareZkClient.RealmAwareZkClientConfig(),
-        metadataStoreRoutingData);
+      RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig)
+      throws IOException, InvalidRoutingDataException {
+    return buildZkClient(connectionConfig, new RealmAwareZkClient.RealmAwareZkClientConfig());
   }
 }
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/client/DedicatedZkClient.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/client/DedicatedZkClient.java
index 8352b2b..beeb085 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/client/DedicatedZkClient.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/client/DedicatedZkClient.java
@@ -19,12 +19,15 @@ package org.apache.helix.zookeeper.impl.client;
  * under the License.
  */
 
+import java.io.IOException;
 import java.util.List;
 import java.util.NoSuchElementException;
 import java.util.concurrent.TimeUnit;
 
 import org.apache.helix.msdcommon.datamodel.MetadataStoreRoutingData;
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
 import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
+import org.apache.helix.zookeeper.util.HttpRoutingDataReader;
 import org.apache.helix.zookeeper.zkclient.DataUpdater;
 import org.apache.helix.zookeeper.zkclient.IZkChildListener;
 import org.apache.helix.zookeeper.zkclient.IZkConnection;
@@ -56,24 +59,39 @@ public class DedicatedZkClient implements RealmAwareZkClient {
   private final ZkClient _rawZkClient;
   private final MetadataStoreRoutingData _metadataStoreRoutingData;
   private final String _zkRealmShardingKey;
-  private final String _zkRealmAddress;
 
-  // TODO: Remove MetadataStoreRoutingData from constructor
+  /**
+   * DedicatedZkClient connects to a single ZK realm and supports full ZkClient functionalities
+   * such as CRUD, change callback, and ephemeral operations for a single ZkRealmShardingKey.
+   * @param connectionConfig
+   * @param clientConfig
+   * @throws IOException
+   * @throws InvalidRoutingDataException
+   */
   public DedicatedZkClient(RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig,
-      RealmAwareZkClient.RealmAwareZkClientConfig clientConfig,
-      MetadataStoreRoutingData metadataStoreRoutingData) {
-
+      RealmAwareZkClient.RealmAwareZkClientConfig clientConfig)
+      throws IOException, InvalidRoutingDataException {
     if (connectionConfig == null) {
       throw new IllegalArgumentException("RealmAwareZkConnectionConfig cannot be null!");
     }
-    _zkRealmShardingKey = connectionConfig.getZkRealmShardingKey();
+    if (clientConfig == null) {
+      throw new IllegalArgumentException("RealmAwareZkClientConfig cannot be null!");
+    }
 
-    if (metadataStoreRoutingData == null) {
-      throw new IllegalArgumentException("MetadataStoreRoutingData cannot be null!");
+    // Get the routing data from a static Singleton HttpRoutingDataReader
+    String msdsEndpoint = connectionConfig.getMsdsEndpoint();
+    if (msdsEndpoint == null || msdsEndpoint.isEmpty()) {
+      _metadataStoreRoutingData = HttpRoutingDataReader.getMetadataStoreRoutingData();
+    } else {
+      _metadataStoreRoutingData = HttpRoutingDataReader.getMetadataStoreRoutingData(msdsEndpoint);
+    }
+
+    _zkRealmShardingKey = connectionConfig.getZkRealmShardingKey();
+    if (_zkRealmShardingKey == null || _zkRealmShardingKey.isEmpty()) {
+      throw new IllegalArgumentException(
+          "RealmAwareZkConnectionConfig's ZK realm sharding key cannot be null or empty for DedicatedZkClient!");
     }
-    _metadataStoreRoutingData = metadataStoreRoutingData;
 
-    // TODO: Get it from static map/singleton (HttpRoutingDataReader)
     // Get the ZkRealm address based on the ZK path sharding key
     String zkRealmAddress = _metadataStoreRoutingData.getMetadataStoreRealm(_zkRealmShardingKey);
     if (zkRealmAddress == null || zkRealmAddress.isEmpty()) {
@@ -81,7 +99,6 @@ public class DedicatedZkClient implements RealmAwareZkClient {
           "ZK realm address for the given ZK realm sharding key is invalid! ZK realm address: "
               + zkRealmAddress + " ZK realm sharding key: " + _zkRealmShardingKey);
     }
-    _zkRealmAddress = zkRealmAddress;
 
     // Create a ZK connection
     IZkConnection zkConnection =
@@ -457,17 +474,17 @@ public class DedicatedZkClient implements RealmAwareZkClient {
    * @return
    */
   private void checkIfPathContainsShardingKey(String path) {
-    // TODO: replace with the singleton MetadataStoreRoutingData
     try {
-      String zkRealmForPath = _metadataStoreRoutingData.getMetadataStoreRealm(path);
-      if (!_zkRealmAddress.equals(zkRealmForPath)) {
-        throw new IllegalArgumentException("Given path: " + path + "'s ZK realm: " + zkRealmForPath
-            + " does not match the ZK realm: " + _zkRealmAddress + " and sharding key: "
-            + _zkRealmShardingKey + " for this DedicatedZkClient!");
+      String targetShardingKey = _metadataStoreRoutingData.getShardingKeyInPath(path);
+      if (!_zkRealmShardingKey.equals(targetShardingKey)) {
+        throw new IllegalArgumentException(
+            "Given path: " + path + "'s ZK sharding key: " + targetShardingKey
+                + " does not match the ZK sharding key: " + _zkRealmShardingKey
+                + " for this DedicatedZkClient!");
       }
     } catch (NoSuchElementException e) {
-      throw new IllegalArgumentException(
-          "Given path: " + path + " does not have a valid sharding key!");
+      throw new IllegalArgumentException("Given path: " + path
+          + " does not have a valid sharding key or its ZK sharding key is not found in the cached routing data!");
     }
   }
 }
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/client/FederatedZkClient.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/client/FederatedZkClient.java
index 5f63408..1bfff66 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/client/FederatedZkClient.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/client/FederatedZkClient.java
@@ -19,6 +19,7 @@ package org.apache.helix.zookeeper.impl.client;
  * under the License.
  */
 
+import java.io.IOException;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -27,14 +28,16 @@ import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.TimeUnit;
 
 import org.apache.helix.msdcommon.datamodel.MetadataStoreRoutingData;
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
 import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 import org.apache.helix.zookeeper.impl.factory.DedicatedZkClientFactory;
+import org.apache.helix.zookeeper.util.HttpRoutingDataReader;
 import org.apache.helix.zookeeper.zkclient.DataUpdater;
 import org.apache.helix.zookeeper.zkclient.IZkChildListener;
 import org.apache.helix.zookeeper.zkclient.IZkDataListener;
+import org.apache.helix.zookeeper.zkclient.IZkStateListener;
 import org.apache.helix.zookeeper.zkclient.ZkConnection;
 import org.apache.helix.zookeeper.zkclient.callback.ZkAsyncCallbacks;
-import org.apache.helix.zookeeper.zkclient.IZkStateListener;
 import org.apache.helix.zookeeper.zkclient.serialize.BasicZkSerializer;
 import org.apache.helix.zookeeper.zkclient.serialize.PathBasedZkSerializer;
 import org.apache.helix.zookeeper.zkclient.serialize.ZkSerializer;
@@ -80,19 +83,27 @@ public class FederatedZkClient implements RealmAwareZkClient {
   private PathBasedZkSerializer _pathBasedZkSerializer;
 
   // TODO: support capacity of ZkClient number in one FederatedZkClient and do garbage collection.
-  public FederatedZkClient(RealmAwareZkClient.RealmAwareZkClientConfig clientConfig,
-      MetadataStoreRoutingData metadataStoreRoutingData) {
-    if (metadataStoreRoutingData == null) {
-      throw new IllegalArgumentException("MetadataStoreRoutingData cannot be null!");
+  public FederatedZkClient(RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig,
+      RealmAwareZkClient.RealmAwareZkClientConfig clientConfig)
+      throws IOException, InvalidRoutingDataException {
+    if (connectionConfig == null) {
+      throw new IllegalArgumentException("RealmAwareZkConnectionConfig cannot be null!");
     }
     if (clientConfig == null) {
-      throw new IllegalArgumentException("Client config cannot be null!");
+      throw new IllegalArgumentException("RealmAwareZkClientConfig cannot be null!");
+    }
+
+    // Attempt to get MetadataStoreRoutingData
+    String msdsEndpoint = connectionConfig.getMsdsEndpoint();
+    if (msdsEndpoint == null || msdsEndpoint.isEmpty()) {
+      _metadataStoreRoutingData = HttpRoutingDataReader.getMetadataStoreRoutingData();
+    } else {
+      _metadataStoreRoutingData = HttpRoutingDataReader.getMetadataStoreRoutingData(msdsEndpoint);
     }
 
     _isClosed = false;
     _clientConfig = clientConfig;
     _pathBasedZkSerializer = clientConfig.getZkSerializer();
-    _metadataStoreRoutingData = metadataStoreRoutingData;
     _zkRealmToZkClientMap = new ConcurrentHashMap<>();
   }
 
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/client/SharedZkClient.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/client/SharedZkClient.java
index f2d9416..dd78aba 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/client/SharedZkClient.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/client/SharedZkClient.java
@@ -19,26 +19,27 @@ package org.apache.helix.zookeeper.impl.client;
  * under the License.
  */
 
+import java.io.IOException;
 import java.util.List;
 import java.util.NoSuchElementException;
 import java.util.concurrent.TimeUnit;
 
 import org.apache.helix.msdcommon.datamodel.MetadataStoreRoutingData;
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
 import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 import org.apache.helix.zookeeper.impl.factory.SharedZkClientFactory;
+import org.apache.helix.zookeeper.util.HttpRoutingDataReader;
 import org.apache.helix.zookeeper.zkclient.DataUpdater;
 import org.apache.helix.zookeeper.zkclient.IZkChildListener;
 import org.apache.helix.zookeeper.zkclient.IZkDataListener;
 import org.apache.helix.zookeeper.zkclient.callback.ZkAsyncCallbacks;
 import org.apache.helix.zookeeper.zkclient.deprecated.IZkStateListener;
-import org.apache.helix.zookeeper.zkclient.exception.ZkNoNodeException;
 import org.apache.helix.zookeeper.zkclient.serialize.PathBasedZkSerializer;
 import org.apache.helix.zookeeper.zkclient.serialize.ZkSerializer;
 import org.apache.zookeeper.CreateMode;
 import org.apache.zookeeper.Op;
 import org.apache.zookeeper.OpResult;
-import org.apache.zookeeper.ZooDefs;
 import org.apache.zookeeper.data.ACL;
 import org.apache.zookeeper.data.Stat;
 import org.slf4j.Logger;
@@ -55,25 +56,34 @@ public class SharedZkClient implements RealmAwareZkClient {
   private static Logger LOG = LoggerFactory.getLogger(SharedZkClient.class);
 
   private final HelixZkClient _innerSharedZkClient;
-  private final String _zkRealmShardingKey;
   private final MetadataStoreRoutingData _metadataStoreRoutingData;
+  private final String _zkRealmShardingKey;
   private final String _zkRealmAddress;
 
   public SharedZkClient(RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig,
-      RealmAwareZkClient.RealmAwareZkClientConfig clientConfig,
-      MetadataStoreRoutingData metadataStoreRoutingData) {
-
+      RealmAwareZkClient.RealmAwareZkClientConfig clientConfig)
+      throws IOException, InvalidRoutingDataException {
     if (connectionConfig == null) {
       throw new IllegalArgumentException("RealmAwareZkConnectionConfig cannot be null!");
     }
-    _zkRealmShardingKey = connectionConfig.getZkRealmShardingKey();
+    if (clientConfig == null) {
+      throw new IllegalArgumentException("RealmAwareZkClientConfig cannot be null!");
+    }
 
-    if (metadataStoreRoutingData == null) {
-      throw new IllegalArgumentException("MetadataStoreRoutingData cannot be null!");
+    // Get the routing data from a static Singleton HttpRoutingDataReader
+    String msdsEndpoint = connectionConfig.getMsdsEndpoint();
+    if (msdsEndpoint == null || msdsEndpoint.isEmpty()) {
+      _metadataStoreRoutingData = HttpRoutingDataReader.getMetadataStoreRoutingData();
+    } else {
+      _metadataStoreRoutingData = HttpRoutingDataReader.getMetadataStoreRoutingData(msdsEndpoint);
+    }
+
+    _zkRealmShardingKey = connectionConfig.getZkRealmShardingKey();
+    if (_zkRealmShardingKey == null || _zkRealmShardingKey.isEmpty()) {
+      throw new IllegalArgumentException(
+          "RealmAwareZkConnectionConfig's ZK realm sharding key cannot be null or empty for SharedZkClient!");
     }
-    _metadataStoreRoutingData = metadataStoreRoutingData;
 
-    // TODO: use _zkRealmShardingKey to generate zkRealmAddress. This can done the same way of pull 765 once @hunter check it in.
     // Get the ZkRealm address based on the ZK path sharding key
     String zkRealmAddress = _metadataStoreRoutingData.getMetadataStoreRealm(_zkRealmShardingKey);
     if (zkRealmAddress == null || zkRealmAddress.isEmpty()) {
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/DedicatedZkClientFactory.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/DedicatedZkClientFactory.java
index 6694497..92d628c 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/DedicatedZkClientFactory.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/DedicatedZkClientFactory.java
@@ -19,7 +19,9 @@ package org.apache.helix.zookeeper.impl.factory;
  * under the License.
  */
 
-import org.apache.helix.msdcommon.datamodel.MetadataStoreRoutingData;
+import java.io.IOException;
+
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
 import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 import org.apache.helix.zookeeper.impl.client.DedicatedZkClient;
@@ -37,9 +39,9 @@ public class DedicatedZkClientFactory extends HelixZkClientFactory {
   @Override
   public RealmAwareZkClient buildZkClient(
       RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig,
-      RealmAwareZkClient.RealmAwareZkClientConfig clientConfig,
-      MetadataStoreRoutingData metadataStoreRoutingData) {
-    return new DedicatedZkClient(connectionConfig, clientConfig, metadataStoreRoutingData);
+      RealmAwareZkClient.RealmAwareZkClientConfig clientConfig)
+      throws IOException, InvalidRoutingDataException {
+    return new DedicatedZkClient(connectionConfig, clientConfig);
   }
 
   private static class SingletonHelper {
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/SharedZkClientFactory.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/SharedZkClientFactory.java
index 80c58bf..fb46ef2 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/SharedZkClientFactory.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/SharedZkClientFactory.java
@@ -19,10 +19,11 @@ package org.apache.helix.zookeeper.impl.factory;
  * under the License.
  */
 
+import java.io.IOException;
 import java.util.HashMap;
 import java.util.List;
 
-import org.apache.helix.msdcommon.datamodel.MetadataStoreRoutingData;
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
 import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 import org.apache.helix.zookeeper.exception.ZkClientException;
@@ -57,18 +58,10 @@ public class SharedZkClientFactory extends HelixZkClientFactory {
   @Override
   public RealmAwareZkClient buildZkClient(
       RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig,
-      RealmAwareZkClient.RealmAwareZkClientConfig clientConfig,
-      MetadataStoreRoutingData metadataStoreRoutingData) {
+      RealmAwareZkClient.RealmAwareZkClientConfig clientConfig)
+      throws IOException, InvalidRoutingDataException {
     // Note, the logic sharing connectionManager logic is inside SharedZkClient, similar to innerSharedZkClient.
-    return new SharedZkClient(connectionConfig, clientConfig, metadataStoreRoutingData);
-  }
-
-  @Override
-  public RealmAwareZkClient buildZkClient(
-      RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig,
-      MetadataStoreRoutingData metadataStoreRoutingData) {
-    // TODO: Implement the logic
-    return null;
+    return new SharedZkClient(connectionConfig, clientConfig);
   }
 
   private static class SingletonHelper {
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/util/HttpRoutingDataReader.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/util/HttpRoutingDataReader.java
index 04f6c44..b4c1f9c 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/util/HttpRoutingDataReader.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/util/HttpRoutingDataReader.java
@@ -141,7 +141,8 @@ public class HttpRoutingDataReader {
    * @throws IOException
    */
   private static String getAllRoutingData() throws IOException {
-    // Note that MSDS_ENDPOINT should provide high-availability - it risks becoming a single point of failure if it's backed by a single IP address/host
+    // Note that MSDS_ENDPOINT should provide high-availability - it risks becoming a single point
+    // of failure if it's backed by a single IP address/host
     // Retry count is 3 by default.
     HttpGet requestAllData = new HttpGet(
         SYSTEM_MSDS_ENDPOINT + MetadataStoreRoutingConstants.MSDS_GET_ALL_ROUTING_DATA_ENDPOINT);
diff --git a/zookeeper-api/src/test/conf/testng.xml b/zookeeper-api/src/test/conf/testng.xml
index 0847f47..bf78089 100644
--- a/zookeeper-api/src/test/conf/testng.xml
+++ b/zookeeper-api/src/test/conf/testng.xml
@@ -18,8 +18,8 @@ specific language governing permissions and limitations
 under the License.
 -->
 <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
-<suite name="Suite" parallel="false">
-  <test name="Test" preserve-order="true">
+<suite name="Suite" parallel="false" preserve-order="true">
+  <test name="Test">
     <packages>
       <package name="org.apache.helix.zookeeper.*"/>
     </packages>
diff --git a/zookeeper-api/src/test/java/org/apache/helix/zookeeper/constant/TestConstants.java b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/constant/TestConstants.java
new file mode 100644
index 0000000..a217245
--- /dev/null
+++ b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/constant/TestConstants.java
@@ -0,0 +1,45 @@
+package org.apache.helix.zookeeper.constant;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Collection;
+import java.util.Map;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+
+/**
+ * Constants to be used for testing.
+ */
+public class TestConstants {
+  // ZK hostname prefix and port to be used throughout the zookeeper-api module
+  public static final String ZK_PREFIX = "localhost:";
+  public static final int ZK_START_PORT = 2127;
+
+  // Based on the ZK hostname constants, construct a set of fake routing data mappings
+  public static final Map<String, Collection<String>> FAKE_ROUTING_DATA = ImmutableMap
+      .of(ZK_PREFIX + ZK_START_PORT,
+          ImmutableList.of("/sharding-key-0", "/sharding-key-1", "/sharding-key-2"),
+          ZK_PREFIX + (ZK_START_PORT + 1),
+          ImmutableList.of("/sharding-key-3", "/sharding-key-4", "/sharding-key-5"),
+          ZK_PREFIX + (ZK_START_PORT + 2),
+          ImmutableList.of("/sharding-key-6", "/sharding-key-7", "/sharding-key-8"));
+}
diff --git a/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/ZkTestBase.java b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/ZkTestBase.java
index 7e59652..51eda80 100644
--- a/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/ZkTestBase.java
+++ b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/ZkTestBase.java
@@ -29,6 +29,7 @@ import javax.management.MBeanServerConnection;
 import javax.management.ObjectName;
 
 import org.apache.commons.io.FileUtils;
+import org.apache.helix.zookeeper.constant.TestConstants;
 import org.apache.helix.zookeeper.zkclient.IDefaultNameSpace;
 import org.apache.helix.zookeeper.zkclient.ZkServer;
 import org.slf4j.Logger;
@@ -50,8 +51,8 @@ public class ZkTestBase {
   private static final String MULTI_ZK_PROPERTY_KEY = "multiZk";
   private static final String NUM_ZK_PROPERTY_KEY = "numZk";
 
-  protected static final String ZK_PREFIX = "localhost:";
-  protected static final int ZK_START_PORT = 2127;
+  public static final String ZK_PREFIX = TestConstants.ZK_PREFIX;
+  public static final int ZK_START_PORT = TestConstants.ZK_START_PORT;
 
   /*
    * Multiple ZK references
diff --git a/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/RealmAwareZkClientTestBase.java b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/RealmAwareZkClientFactoryTestBase.java
similarity index 61%
copy from zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/RealmAwareZkClientTestBase.java
copy to zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/RealmAwareZkClientFactoryTestBase.java
index 323d5f4..6c3555f 100644
--- a/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/RealmAwareZkClientTestBase.java
+++ b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/RealmAwareZkClientFactoryTestBase.java
@@ -19,55 +19,44 @@ package org.apache.helix.zookeeper.impl.client;
  * under the License.
  */
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.io.IOException;
+import java.util.NoSuchElementException;
 
-import org.apache.helix.msdcommon.datamodel.MetadataStoreRoutingData;
-import org.apache.helix.msdcommon.datamodel.TrieRoutingData;
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
 import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 import org.apache.helix.zookeeper.api.factory.RealmAwareZkClientFactory;
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
-import org.apache.helix.zookeeper.impl.ZkTestBase;
 import org.testng.Assert;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
 
-public abstract class RealmAwareZkClientTestBase extends ZkTestBase {
-  protected static final String ZK_SHARDING_KEY_PREFIX = "/TEST_SHARDING_KEY";
-  protected static final String TEST_VALID_PATH = ZK_SHARDING_KEY_PREFIX + "_" + 0 + "/a/b/c";
-  protected static final String TEST_INVALID_PATH = ZK_SHARDING_KEY_PREFIX + "_invalid" + "/a/b/c";
-
-  // <Realm, List of sharding keys> Mapping
-  private static final Map<String, List<String>> RAW_ROUTING_DATA = new HashMap<>();
-
+/**
+ * Test Base for DedicatedZkClient and SharedZkClient, which are implementations of
+ * RealmAwareZkClient.
+ * This class allows TestDedicatedZkClient and TestSharedZkClient to share the common test logic by
+ * just swapping out the factory classes.
+ */
+public abstract class RealmAwareZkClientFactoryTestBase extends RealmAwareZkClientTestBase {
   // The following RealmAwareZkClientFactory is to be defined in subclasses
   protected RealmAwareZkClientFactory _realmAwareZkClientFactory;
   protected RealmAwareZkClient _realmAwareZkClient;
-  private MetadataStoreRoutingData _metadataStoreRoutingData;
+  private static final ZNRecord DUMMY_RECORD = new ZNRecord("DummyRecord");
 
   @BeforeClass
-  public void beforeClass() throws Exception {
-    // Populate RAW_ROUTING_DATA
-    for (int i = 0; i < _numZk; i++) {
-      List<String> shardingKeyList = new ArrayList<>();
-      shardingKeyList.add(ZK_SHARDING_KEY_PREFIX + "_" + i);
-      String realmName = ZK_PREFIX + (ZK_START_PORT + i);
-      RAW_ROUTING_DATA.put(realmName, shardingKeyList);
-    }
-
-    // Feed the raw routing data into TrieRoutingData to construct an in-memory representation of routing information
-    _metadataStoreRoutingData = new TrieRoutingData(RAW_ROUTING_DATA);
+  public void beforeClass() throws IOException, InvalidRoutingDataException {
+    super.beforeClass();
+    DUMMY_RECORD.setSimpleField("Dummy", "Value");
   }
 
   @AfterClass
   public void afterClass() {
+    super.afterClass();
     if (_realmAwareZkClient != null && !_realmAwareZkClient.isClosed()) {
       _realmAwareZkClient.close();
+      _realmAwareZkClient = null;
     }
   }
 
@@ -80,7 +69,7 @@ public abstract class RealmAwareZkClientTestBase extends ZkTestBase {
   @Test
   public void testRealmAwareZkClientCreation() {
     // Create a RealmAwareZkClient
-    String invalidShardingKey = "InvalidShardingKey";
+    String invalidShardingKey = "InvalidShardingKeyNoLeadingSlash";
     RealmAwareZkClient.RealmAwareZkClientConfig clientConfig =
         new RealmAwareZkClient.RealmAwareZkClientConfig();
 
@@ -91,19 +80,37 @@ public abstract class RealmAwareZkClientTestBase extends ZkTestBase {
         builder.setZkRealmShardingKey(invalidShardingKey).build();
 
     try {
-      _realmAwareZkClient = _realmAwareZkClientFactory
-          .buildZkClient(connectionConfig, clientConfig, _metadataStoreRoutingData);
+      _realmAwareZkClient =
+          _realmAwareZkClientFactory.buildZkClient(connectionConfig, clientConfig);
       Assert.fail("Should not succeed with an invalid sharding key!");
     } catch (IllegalArgumentException e) {
-      // Expected
+      // Expected because invalid sharding key would cause an IllegalArgumentException to be thrown
+    } catch (Exception e) {
+      Assert.fail("Should not see any other types of Exceptions: " + e);
+    }
+
+    // Create a connection config with a valid sharding key, but one that does not exist in
+    // the routing data
+    String nonExistentShardingKey = "/NonExistentShardingKey";
+    connectionConfig = builder.setZkRealmShardingKey(nonExistentShardingKey).build();
+    try {
+      _realmAwareZkClient =
+          _realmAwareZkClientFactory.buildZkClient(connectionConfig, clientConfig);
+      Assert.fail("Should not succeed with a non-existent sharding key!");
+    } catch (NoSuchElementException e) {
+      // Expected non-existent sharding key would cause a NoSuchElementException to be thrown
+    } catch (Exception e) {
+      Assert.fail("Should not see any other types of Exceptions: " + e);
     }
 
     // Use a valid sharding key this time around
-    String validShardingKey = ZK_SHARDING_KEY_PREFIX + "_" + 0; // Use TEST_SHARDING_KEY_0
-    builder.setZkRealmShardingKey(validShardingKey);
-    connectionConfig = builder.build();
-    _realmAwareZkClient = _realmAwareZkClientFactory
-        .buildZkClient(connectionConfig, clientConfig, _metadataStoreRoutingData);
+    connectionConfig = builder.setZkRealmShardingKey(ZK_SHARDING_KEY_PREFIX).build();
+    try {
+      _realmAwareZkClient =
+          _realmAwareZkClientFactory.buildZkClient(connectionConfig, clientConfig);
+    } catch (Exception e) {
+      Assert.fail("All other exceptions not allowed: " + e);
+    }
   }
 
   /**
@@ -115,14 +122,10 @@ public abstract class RealmAwareZkClientTestBase extends ZkTestBase {
   public void testRealmAwareZkClientCreatePersistent() {
     _realmAwareZkClient.setZkSerializer(new ZNRecordSerializer());
 
-    // Create a dummy ZNRecord
-    ZNRecord znRecord = new ZNRecord("DummyRecord");
-    znRecord.setSimpleField("Dummy", "Value");
-
     // Test writing and reading against the validPath
     _realmAwareZkClient.createPersistent(TEST_VALID_PATH, true);
-    _realmAwareZkClient.writeData(TEST_VALID_PATH, znRecord);
-    Assert.assertEquals(_realmAwareZkClient.readData(TEST_VALID_PATH), znRecord);
+    _realmAwareZkClient.writeData(TEST_VALID_PATH, DUMMY_RECORD);
+    Assert.assertEquals(_realmAwareZkClient.readData(TEST_VALID_PATH), DUMMY_RECORD);
 
     // Test writing and reading against the invalid path
     try {
@@ -138,6 +141,12 @@ public abstract class RealmAwareZkClientTestBase extends ZkTestBase {
    */
   @Test(dependsOnMethods = "testRealmAwareZkClientCreatePersistent")
   public void testExists() {
+    // Create a ZNode for testing
+    _realmAwareZkClient.createPersistent(TEST_VALID_PATH, true);
+    _realmAwareZkClient.writeData(TEST_VALID_PATH, DUMMY_RECORD);
+    Assert.assertEquals(_realmAwareZkClient.readData(TEST_VALID_PATH), DUMMY_RECORD);
+
+    // Test exists()
     Assert.assertTrue(_realmAwareZkClient.exists(TEST_VALID_PATH));
 
     try {
@@ -153,9 +162,14 @@ public abstract class RealmAwareZkClientTestBase extends ZkTestBase {
    */
   @Test(dependsOnMethods = "testExists")
   public void testDelete() {
+    // Create a ZNode for testing
+    _realmAwareZkClient.createPersistent(TEST_VALID_PATH, true);
+    _realmAwareZkClient.writeData(TEST_VALID_PATH, DUMMY_RECORD);
+    Assert.assertEquals(_realmAwareZkClient.readData(TEST_VALID_PATH), DUMMY_RECORD);
+
     try {
       _realmAwareZkClient.delete(TEST_INVALID_PATH);
-      Assert.fail("Exists() should not succeed on an invalid path!");
+      Assert.fail("delete() should not succeed on an invalid path!");
     } catch (IllegalArgumentException e) {
       // Okay - expected
     }
diff --git a/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/RealmAwareZkClientTestBase.java b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/RealmAwareZkClientTestBase.java
index 323d5f4..900c79f 100644
--- a/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/RealmAwareZkClientTestBase.java
+++ b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/RealmAwareZkClientTestBase.java
@@ -19,148 +19,48 @@ package org.apache.helix.zookeeper.impl.client;
  * under the License.
  */
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.io.IOException;
 
-import org.apache.helix.msdcommon.datamodel.MetadataStoreRoutingData;
-import org.apache.helix.msdcommon.datamodel.TrieRoutingData;
-import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
-import org.apache.helix.zookeeper.api.factory.RealmAwareZkClientFactory;
-import org.apache.helix.zookeeper.datamodel.ZNRecord;
-import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
+import org.apache.helix.msdcommon.constant.MetadataStoreRoutingConstants;
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
+import org.apache.helix.msdcommon.mock.MockMetadataStoreDirectoryServer;
+import org.apache.helix.zookeeper.constant.TestConstants;
 import org.apache.helix.zookeeper.impl.ZkTestBase;
-import org.testng.Assert;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
-import org.testng.annotations.Test;
 
 
 public abstract class RealmAwareZkClientTestBase extends ZkTestBase {
-  protected static final String ZK_SHARDING_KEY_PREFIX = "/TEST_SHARDING_KEY";
-  protected static final String TEST_VALID_PATH = ZK_SHARDING_KEY_PREFIX + "_" + 0 + "/a/b/c";
+  protected static final String ZK_SHARDING_KEY_PREFIX = "/sharding-key-0";
+  protected static final String TEST_VALID_PATH = ZK_SHARDING_KEY_PREFIX + "/a/b/c";
   protected static final String TEST_INVALID_PATH = ZK_SHARDING_KEY_PREFIX + "_invalid" + "/a/b/c";
 
-  // <Realm, List of sharding keys> Mapping
-  private static final Map<String, List<String>> RAW_ROUTING_DATA = new HashMap<>();
-
-  // The following RealmAwareZkClientFactory is to be defined in subclasses
-  protected RealmAwareZkClientFactory _realmAwareZkClientFactory;
-  protected RealmAwareZkClient _realmAwareZkClient;
-  private MetadataStoreRoutingData _metadataStoreRoutingData;
+  // Create a MockMSDS for testing
+  private static MockMetadataStoreDirectoryServer _msdsServer;
+  private static final String MSDS_HOSTNAME = "localhost";
+  private static final int MSDS_PORT = 1111;
+  private static final String MSDS_NAMESPACE = "test";
 
   @BeforeClass
-  public void beforeClass() throws Exception {
-    // Populate RAW_ROUTING_DATA
-    for (int i = 0; i < _numZk; i++) {
-      List<String> shardingKeyList = new ArrayList<>();
-      shardingKeyList.add(ZK_SHARDING_KEY_PREFIX + "_" + i);
-      String realmName = ZK_PREFIX + (ZK_START_PORT + i);
-      RAW_ROUTING_DATA.put(realmName, shardingKeyList);
+  public void beforeClass() throws IOException, InvalidRoutingDataException {
+    // Create a mock MSDS so that HttpRoudingDataReader could fetch the routing data
+    if (_msdsServer == null) {
+      // Do not create again if Mock MSDS server has already been created by other tests
+      _msdsServer = new MockMetadataStoreDirectoryServer(MSDS_HOSTNAME, MSDS_PORT, MSDS_NAMESPACE,
+          TestConstants.FAKE_ROUTING_DATA);
+      _msdsServer.startServer();
     }
 
-    // Feed the raw routing data into TrieRoutingData to construct an in-memory representation of routing information
-    _metadataStoreRoutingData = new TrieRoutingData(RAW_ROUTING_DATA);
+    // Register the MSDS endpoint as a System variable
+    String msdsEndpoint =
+        "http://" + MSDS_HOSTNAME + ":" + MSDS_PORT + "/admin/v2/namespaces/" + MSDS_NAMESPACE;
+    System.setProperty(MetadataStoreRoutingConstants.MSDS_SERVER_ENDPOINT_KEY, msdsEndpoint);
   }
 
   @AfterClass
   public void afterClass() {
-    if (_realmAwareZkClient != null && !_realmAwareZkClient.isClosed()) {
-      _realmAwareZkClient.close();
-    }
-  }
-
-  /**
-   * 1. Create a RealmAwareZkClient with a non-existing sharding key (for which there is no valid ZK realm)
-   * -> This should fail with an exception
-   * 2. Create a RealmAwareZkClient with a valid sharding key
-   * -> This should pass
-   */
-  @Test
-  public void testRealmAwareZkClientCreation() {
-    // Create a RealmAwareZkClient
-    String invalidShardingKey = "InvalidShardingKey";
-    RealmAwareZkClient.RealmAwareZkClientConfig clientConfig =
-        new RealmAwareZkClient.RealmAwareZkClientConfig();
-
-    // Create a connection config with the invalid sharding key
-    RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder builder =
-        new RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder();
-    RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig =
-        builder.setZkRealmShardingKey(invalidShardingKey).build();
-
-    try {
-      _realmAwareZkClient = _realmAwareZkClientFactory
-          .buildZkClient(connectionConfig, clientConfig, _metadataStoreRoutingData);
-      Assert.fail("Should not succeed with an invalid sharding key!");
-    } catch (IllegalArgumentException e) {
-      // Expected
+    if (_msdsServer != null) {
+      _msdsServer.stopServer();
     }
-
-    // Use a valid sharding key this time around
-    String validShardingKey = ZK_SHARDING_KEY_PREFIX + "_" + 0; // Use TEST_SHARDING_KEY_0
-    builder.setZkRealmShardingKey(validShardingKey);
-    connectionConfig = builder.build();
-    _realmAwareZkClient = _realmAwareZkClientFactory
-        .buildZkClient(connectionConfig, clientConfig, _metadataStoreRoutingData);
-  }
-
-  /**
-   * Test the persistent create() call against a valid path and an invalid path.
-   * Valid path is one that belongs to the realm designated by the sharding key.
-   * Invalid path is one that does not belong to the realm designated by the sharding key.
-   */
-  @Test(dependsOnMethods = "testRealmAwareZkClientCreation")
-  public void testRealmAwareZkClientCreatePersistent() {
-    _realmAwareZkClient.setZkSerializer(new ZNRecordSerializer());
-
-    // Create a dummy ZNRecord
-    ZNRecord znRecord = new ZNRecord("DummyRecord");
-    znRecord.setSimpleField("Dummy", "Value");
-
-    // Test writing and reading against the validPath
-    _realmAwareZkClient.createPersistent(TEST_VALID_PATH, true);
-    _realmAwareZkClient.writeData(TEST_VALID_PATH, znRecord);
-    Assert.assertEquals(_realmAwareZkClient.readData(TEST_VALID_PATH), znRecord);
-
-    // Test writing and reading against the invalid path
-    try {
-      _realmAwareZkClient.createPersistent(TEST_INVALID_PATH, true);
-      Assert.fail("Create() should not succeed on an invalid path!");
-    } catch (IllegalArgumentException e) {
-      // Okay - expected
-    }
-  }
-
-  /**
-   * Test that exists() works on valid path and fails on invalid path.
-   */
-  @Test(dependsOnMethods = "testRealmAwareZkClientCreatePersistent")
-  public void testExists() {
-    Assert.assertTrue(_realmAwareZkClient.exists(TEST_VALID_PATH));
-
-    try {
-      _realmAwareZkClient.exists(TEST_INVALID_PATH);
-      Assert.fail("Exists() should not succeed on an invalid path!");
-    } catch (IllegalArgumentException e) {
-      // Okay - expected
-    }
-  }
-
-  /**
-   * Test that delete() works on valid path and fails on invalid path.
-   */
-  @Test(dependsOnMethods = "testExists")
-  public void testDelete() {
-    try {
-      _realmAwareZkClient.delete(TEST_INVALID_PATH);
-      Assert.fail("Exists() should not succeed on an invalid path!");
-    } catch (IllegalArgumentException e) {
-      // Okay - expected
-    }
-
-    Assert.assertTrue(_realmAwareZkClient.delete(TEST_VALID_PATH));
-    Assert.assertFalse(_realmAwareZkClient.exists(TEST_VALID_PATH));
   }
 }
\ No newline at end of file
diff --git a/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/TestDedicatedZkClient.java b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/TestDedicatedZkClient.java
index 8cf3f85..fe8d8bd 100644
--- a/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/TestDedicatedZkClient.java
+++ b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/TestDedicatedZkClient.java
@@ -19,15 +19,17 @@ package org.apache.helix.zookeeper.impl.client;
  * under the License.
  */
 
+import java.io.IOException;
+
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
 import org.apache.helix.zookeeper.impl.factory.DedicatedZkClientFactory;
 import org.testng.annotations.BeforeClass;
 
 
-public class TestDedicatedZkClient extends RealmAwareZkClientTestBase {
+public class TestDedicatedZkClient extends RealmAwareZkClientFactoryTestBase {
 
   @BeforeClass
-  public void beforeClass()
-      throws Exception {
+  public void beforeClass() throws IOException, InvalidRoutingDataException {
     super.beforeClass();
     // Set the factory to DedicatedZkClientFactory
     _realmAwareZkClientFactory = DedicatedZkClientFactory.getInstance();
diff --git a/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/TestFederatedZkClient.java b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/TestFederatedZkClient.java
index 5801690..0472df7 100644
--- a/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/TestFederatedZkClient.java
+++ b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/TestFederatedZkClient.java
@@ -19,22 +19,17 @@ package org.apache.helix.zookeeper.impl.client;
  * under the License.
  */
 
+import java.io.IOException;
 import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.concurrent.TimeUnit;
 
-import org.apache.helix.msdcommon.datamodel.TrieRoutingData;
 import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
 import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
-import org.apache.helix.zookeeper.impl.ZkTestBase;
 import org.apache.helix.zookeeper.zkclient.IZkStateListener;
-import org.apache.helix.zookeeper.zkclient.ZkServer;
 import org.apache.zookeeper.CreateMode;
 import org.apache.zookeeper.Op;
 import org.apache.zookeeper.Watcher;
@@ -45,57 +40,33 @@ import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
 
-public class TestFederatedZkClient extends ZkTestBase {
-  private static final String TEST_SHARDING_KEY_PREFIX = "/test_sharding_key_";
-  private static final String TEST_REALM_ONE_VALID_PATH = TEST_SHARDING_KEY_PREFIX + "1/a/b/c";
-  private static final String TEST_REALM_TWO_VALID_PATH = TEST_SHARDING_KEY_PREFIX + "2/x/y/z";
+public class TestFederatedZkClient extends RealmAwareZkClientTestBase {
+  private static final String TEST_SHARDING_KEY_PREFIX = ZK_SHARDING_KEY_PREFIX;
+  private static final String TEST_REALM_ONE_VALID_PATH = TEST_SHARDING_KEY_PREFIX + "/1/a/b/c";
+  private static final String TEST_REALM_TWO_VALID_PATH = TEST_SHARDING_KEY_PREFIX + "/2/x/y/z";
   private static final String TEST_INVALID_PATH = TEST_SHARDING_KEY_PREFIX + "invalid/a/b/c";
   private static final String UNSUPPORTED_OPERATION_MESSAGE =
       "Session-aware operation is not supported by FederatedZkClient.";
 
   private RealmAwareZkClient _realmAwareZkClient;
-  // Need to start an extra ZK server for multi-realm test, if only one ZK server is running.
-  private String _extraZkRealm;
-  private ZkServer _extraZkServer;
 
   @BeforeClass
-  public void beforeClass() throws InvalidRoutingDataException {
+  public void beforeClass() throws IOException, InvalidRoutingDataException {
     System.out.println("Starting " + TestFederatedZkClient.class.getSimpleName());
-
-    // Populate rawRoutingData
-    // <Realm, List of sharding keys> Mapping
-    Map<String, List<String>> rawRoutingData = new HashMap<>();
-    for (int i = 0; i < _numZk; i++) {
-      List<String> shardingKeyList = Collections.singletonList(TEST_SHARDING_KEY_PREFIX + (i + 1));
-      String realmName = ZK_PREFIX + (ZK_START_PORT + i);
-      rawRoutingData.put(realmName, shardingKeyList);
-    }
-
-    if (rawRoutingData.size() < 2) {
-      System.out.println("There is only one ZK realm. Starting one more ZK to test multi-realm.");
-      _extraZkRealm = ZK_PREFIX + (ZK_START_PORT + 1);
-      _extraZkServer = startZkServer(_extraZkRealm);
-      // RealmTwo's sharding key: /test_sharding_key_2
-      List<String> shardingKeyList = Collections.singletonList(TEST_SHARDING_KEY_PREFIX + "2");
-      rawRoutingData.put(_extraZkRealm, shardingKeyList);
-    }
+    super.beforeClass();
 
     // Feed the raw routing data into TrieRoutingData to construct an in-memory representation
     // of routing information.
-    _realmAwareZkClient = new FederatedZkClient(new RealmAwareZkClient.RealmAwareZkClientConfig(),
-        new TrieRoutingData(rawRoutingData));
+    _realmAwareZkClient =
+        new FederatedZkClient(new RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder().build(),
+            new RealmAwareZkClient.RealmAwareZkClientConfig());
   }
 
   @AfterClass
   public void afterClass() {
+    super.afterClass();
     // Close it as it is created in before class.
     _realmAwareZkClient.close();
-
-    // Close the extra zk server.
-    if (_extraZkServer != null) {
-      _extraZkServer.shutdown();
-    }
-
     System.out.println("Ending " + TestFederatedZkClient.class.getSimpleName());
   }
 
@@ -103,7 +74,7 @@ public class TestFederatedZkClient extends ZkTestBase {
    * Tests that an unsupported operation should throw an UnsupportedOperationException.
    */
   @Test
-  public void testUnsupportedOperations() {
+  public void testUnsupportedOperations() throws IOException, InvalidRoutingDataException {
     // Test creating ephemeral.
     try {
       _realmAwareZkClient.create(TEST_REALM_ONE_VALID_PATH, "Hello", CreateMode.EPHEMERAL);
diff --git a/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/TestSharedZkClient.java b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/TestSharedZkClient.java
index 1dd44f6..08ec790 100644
--- a/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/TestSharedZkClient.java
+++ b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/TestSharedZkClient.java
@@ -19,6 +19,9 @@ package org.apache.helix.zookeeper.impl.client;
  * under the License.
  */
 
+import java.io.IOException;
+
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
 import org.apache.helix.zookeeper.impl.factory.SharedZkClientFactory;
@@ -28,9 +31,10 @@ import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
 
-public class TestSharedZkClient extends RealmAwareZkClientTestBase {
+public class TestSharedZkClient extends RealmAwareZkClientFactoryTestBase {
+
   @BeforeClass
-  public void beforeClass() throws Exception {
+  public void beforeClass() throws IOException, InvalidRoutingDataException {
     super.beforeClass();
     // Set the factory to SharedZkClientFactory
     _realmAwareZkClientFactory = SharedZkClientFactory.getInstance();
diff --git a/zookeeper-api/src/test/java/org/apache/helix/zookeeper/util/TestHttpRoutingDataReader.java b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/util/TestHttpRoutingDataReader.java
index 1eccd1a..5c52d05 100644
--- a/zookeeper-api/src/test/java/org/apache/helix/zookeeper/util/TestHttpRoutingDataReader.java
+++ b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/util/TestHttpRoutingDataReader.java
@@ -33,6 +33,7 @@ import org.apache.helix.msdcommon.constant.MetadataStoreRoutingConstants;
 import org.apache.helix.msdcommon.datamodel.MetadataStoreRoutingData;
 import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
 import org.apache.helix.msdcommon.mock.MockMetadataStoreDirectoryServer;
+import org.apache.helix.zookeeper.constant.TestConstants;
 import org.apache.helix.zookeeper.impl.ZkTestBase;
 import org.testng.Assert;
 import org.testng.annotations.AfterClass;
@@ -42,25 +43,15 @@ import org.testng.annotations.Test;
 
 public class TestHttpRoutingDataReader extends ZkTestBase {
   private MockMetadataStoreDirectoryServer _msdsServer;
-  private Map<String, Collection<String>> _testRawRoutingData;
   private final String _host = "localhost";
   private final int _port = 1991;
   private final String _namespace = "TestHttpRoutingDataReader";
 
   @BeforeClass
   public void beforeClass() throws IOException {
-    // Create fake routing data
-    _testRawRoutingData = new HashMap<>();
-    _testRawRoutingData
-        .put("zk-0", ImmutableSet.of("/sharding-key-0", "/sharding-key-1", "/sharding-key-2"));
-    _testRawRoutingData
-        .put("zk-1", ImmutableSet.of("/sharding-key-3", "/sharding-key-4", "/sharding-key-5"));
-    _testRawRoutingData
-        .put("zk-2", ImmutableSet.of("/sharding-key-6", "/sharding-key-7", "/sharding-key-8"));
-
     // Start MockMSDS
-    _msdsServer =
-        new MockMetadataStoreDirectoryServer(_host, _port, _namespace, _testRawRoutingData);
+    _msdsServer = new MockMetadataStoreDirectoryServer(_host, _port, _namespace,
+        TestConstants.FAKE_ROUTING_DATA);
     _msdsServer.startServer();
 
     // Register the endpoint as a System property
@@ -76,7 +67,7 @@ public class TestHttpRoutingDataReader extends ZkTestBase {
   @Test
   public void testGetRawRoutingData() throws IOException {
     Map<String, List<String>> rawRoutingData = HttpRoutingDataReader.getRawRoutingData();
-    _testRawRoutingData.forEach((realm, keys) -> Assert
+    TestConstants.FAKE_ROUTING_DATA.forEach((realm, keys) -> Assert
         .assertEquals(new HashSet(rawRoutingData.get(realm)), new HashSet(keys)));
   }
 
@@ -87,8 +78,10 @@ public class TestHttpRoutingDataReader extends ZkTestBase {
     Map<String, Set<String>> groupedMappings = allMappings.entrySet().stream().collect(Collectors
         .groupingBy(Map.Entry::getValue,
             Collectors.mapping(Map.Entry::getKey, Collectors.toSet())));
-    _testRawRoutingData.forEach(
-        (realm, keys) -> Assert.assertEquals(groupedMappings.get(realm), new HashSet(keys)));
+
+    TestConstants.FAKE_ROUTING_DATA.forEach((realm, keys) -> {
+      Assert.assertEquals(groupedMappings.get(realm), new HashSet(keys));
+    });
   }
 
   /**
@@ -98,12 +91,12 @@ public class TestHttpRoutingDataReader extends ZkTestBase {
   public void testStaticMapping() throws IOException, InvalidRoutingDataException {
     // Modify routing data
     String newRealm = "newRealm";
-    _testRawRoutingData.put(newRealm, ImmutableSet.of("/newKey"));
+    Map<String, Collection<String>> newRoutingData = new HashMap<>(TestConstants.FAKE_ROUTING_DATA);
+    newRoutingData.put(newRealm, ImmutableSet.of("/newKey"));
 
     // Kill MSDS and restart with a new mapping
     _msdsServer.stopServer();
-    _msdsServer =
-        new MockMetadataStoreDirectoryServer(_host, _port, _namespace, _testRawRoutingData);
+    _msdsServer = new MockMetadataStoreDirectoryServer(_host, _port, _namespace, newRoutingData);
     _msdsServer.startServer();
 
     // HttpRoutingDataReader should still return old data because it's static
@@ -112,9 +105,9 @@ public class TestHttpRoutingDataReader extends ZkTestBase {
     Assert.assertFalse(rawRoutingData.containsKey(newRealm));
 
     // Remove newRealm and check for equality
-    _testRawRoutingData.remove(newRealm);
-    Assert.assertEquals(rawRoutingData.keySet(), _testRawRoutingData.keySet());
-    _testRawRoutingData.forEach((realm, keys) -> Assert
+    newRoutingData.remove(newRealm);
+    Assert.assertEquals(rawRoutingData.keySet(), TestConstants.FAKE_ROUTING_DATA.keySet());
+    TestConstants.FAKE_ROUTING_DATA.forEach((realm, keys) -> Assert
         .assertEquals(new HashSet(rawRoutingData.get(realm)), new HashSet(keys)));
 
     MetadataStoreRoutingData data = HttpRoutingDataReader.getMetadataStoreRoutingData();


[helix] 44/49: Add integration tests for Helix Java APIs (#892)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit 859646f9e331e4fa7e45b6f357e7edf0feaa2398
Author: Hunter Lee <hu...@linkedin.com>
AuthorDate: Tue Mar 17 10:35:43 2020 -0700

    Add integration tests for Helix Java APIs (#892)
    
    This commit adds a comprehensive integration test for Helix Java APIs. All Helix Java APIs are tested using regular resource rebalancing and task framework.
---
 .../helix/manager/zk/ZkCacheBaseDataAccessor.java  |  30 +-
 .../java/org/apache/helix/task/TaskDriver.java     |   6 +-
 .../BestPossibleExternalViewVerifier.java          |   4 +-
 .../StrictMatchExternalViewVerifier.java           |   4 +-
 .../src/test/java/org/apache/helix/TestHelper.java |   5 +-
 .../multizk/TestMultiZkHelixJavaApis.java          | 476 +++++++++++++++++++++
 .../helix/integration/task/WorkflowGenerator.java  |  27 +-
 7 files changed, 528 insertions(+), 24 deletions(-)

diff --git a/helix-core/src/main/java/org/apache/helix/manager/zk/ZkCacheBaseDataAccessor.java b/helix-core/src/main/java/org/apache/helix/manager/zk/ZkCacheBaseDataAccessor.java
index bd05ea7..72d5601 100644
--- a/helix-core/src/main/java/org/apache/helix/manager/zk/ZkCacheBaseDataAccessor.java
+++ b/helix-core/src/main/java/org/apache/helix/manager/zk/ZkCacheBaseDataAccessor.java
@@ -40,6 +40,7 @@ import org.apache.helix.store.zk.ZNode;
 import org.apache.helix.util.PathUtils;
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
 import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
+import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
 import org.apache.helix.zookeeper.impl.client.FederatedZkClient;
 import org.apache.helix.zookeeper.impl.factory.DedicatedZkClientFactory;
 import org.apache.helix.zookeeper.impl.factory.SharedZkClientFactory;
@@ -919,7 +920,7 @@ public class ZkCacheBaseDataAccessor<T> implements HelixPropertyStore<T> {
     }
   }
 
-  public static class Builder {
+  public static class Builder<T> {
     private String _zkAddress;
     private RealmAwareZkClient.RealmMode _realmMode;
     private RealmAwareZkClient.RealmAwareZkConnectionConfig _realmAwareZkConnectionConfig;
@@ -934,39 +935,39 @@ public class ZkCacheBaseDataAccessor<T> implements HelixPropertyStore<T> {
     public Builder() {
     }
 
-    public Builder setZkAddress(String zkAddress) {
+    public Builder<T> setZkAddress(String zkAddress) {
       _zkAddress = zkAddress;
       return this;
     }
 
-    public Builder setRealmMode(RealmAwareZkClient.RealmMode realmMode) {
+    public Builder<T> setRealmMode(RealmAwareZkClient.RealmMode realmMode) {
       _realmMode = realmMode;
       return this;
     }
 
-    public Builder setRealmAwareZkConnectionConfig(
+    public Builder<T> setRealmAwareZkConnectionConfig(
         RealmAwareZkClient.RealmAwareZkConnectionConfig realmAwareZkConnectionConfig) {
       _realmAwareZkConnectionConfig = realmAwareZkConnectionConfig;
       return this;
     }
 
-    public Builder setRealmAwareZkClientConfig(
+    public Builder<T> setRealmAwareZkClientConfig(
         RealmAwareZkClient.RealmAwareZkClientConfig realmAwareZkClientConfig) {
       _realmAwareZkClientConfig = realmAwareZkClientConfig;
       return this;
     }
 
-    public Builder setChrootPath(String chrootPath) {
+    public Builder<T> setChrootPath(String chrootPath) {
       _chrootPath = chrootPath;
       return this;
     }
 
-    public Builder setWtCachePaths(List<String> wtCachePaths) {
+    public Builder<T> setWtCachePaths(List<String> wtCachePaths) {
       _wtCachePaths = wtCachePaths;
       return this;
     }
 
-    public Builder setZkCachePaths(List<String> zkCachePaths) {
+    public Builder<T> setZkCachePaths(List<String> zkCachePaths) {
       _zkCachePaths = zkCachePaths;
       return this;
     }
@@ -977,14 +978,14 @@ public class ZkCacheBaseDataAccessor<T> implements HelixPropertyStore<T> {
      * @param zkClientType
      * @return
      */
-    public Builder setZkClientType(ZkBaseDataAccessor.ZkClientType zkClientType) {
+    public Builder<T> setZkClientType(ZkBaseDataAccessor.ZkClientType zkClientType) {
       _zkClientType = zkClientType;
       return this;
     }
 
-    public ZkCacheBaseDataAccessor build() {
+    public ZkCacheBaseDataAccessor<T> build() {
       validate();
-      return new ZkCacheBaseDataAccessor(this);
+      return new ZkCacheBaseDataAccessor<>(this);
     }
 
     private void validate() {
@@ -997,8 +998,8 @@ public class ZkCacheBaseDataAccessor<T> implements HelixPropertyStore<T> {
         throw new HelixException(
             "ZkCacheBaseDataAccessor: you cannot set ZkClientType on multi-realm mode!");
       }
-      // If ZkClientType is not set, default to SHARED
-      if (!isZkClientTypeSet) {
+      // If ZkClientType is not set and realmMode is single-realm, default to SHARED
+      if (!isZkClientTypeSet && _realmMode == RealmAwareZkClient.RealmMode.SINGLE_REALM) {
         _zkClientType = ZkBaseDataAccessor.ZkClientType.SHARED;
       }
 
@@ -1025,7 +1026,8 @@ public class ZkCacheBaseDataAccessor<T> implements HelixPropertyStore<T> {
 
       // Resolve RealmAwareZkClientConfig
       if (_realmAwareZkClientConfig == null) {
-        _realmAwareZkClientConfig = new RealmAwareZkClient.RealmAwareZkClientConfig();
+        _realmAwareZkClientConfig = new RealmAwareZkClient.RealmAwareZkClientConfig()
+            .setZkSerializer(new ZNRecordSerializer());
       }
 
       // Resolve RealmAwareZkConnectionConfig
diff --git a/helix-core/src/main/java/org/apache/helix/task/TaskDriver.java b/helix-core/src/main/java/org/apache/helix/task/TaskDriver.java
index 0b8fa17..987cc44 100644
--- a/helix-core/src/main/java/org/apache/helix/task/TaskDriver.java
+++ b/helix-core/src/main/java/org/apache/helix/task/TaskDriver.java
@@ -38,17 +38,17 @@ import org.apache.helix.HelixManager;
 import org.apache.helix.PropertyKey;
 import org.apache.helix.PropertyPathBuilder;
 import org.apache.helix.SystemPropertyKeys;
-import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.apache.helix.manager.zk.ZKHelixAdmin;
 import org.apache.helix.manager.zk.ZKHelixDataAccessor;
 import org.apache.helix.manager.zk.ZkBaseDataAccessor;
-import org.apache.helix.zookeeper.api.client.HelixZkClient;
 import org.apache.helix.model.IdealState;
 import org.apache.helix.model.ResourceConfig;
 import org.apache.helix.model.builder.CustomModeISBuilder;
 import org.apache.helix.store.HelixPropertyStore;
 import org.apache.helix.store.zk.ZkHelixPropertyStore;
 import org.apache.helix.util.HelixUtil;
+import org.apache.helix.zookeeper.api.client.HelixZkClient;
+import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.apache.helix.zookeeper.zkclient.DataUpdater;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -97,10 +97,12 @@ public class TaskDriver {
         manager.getHelixPropertyStore(), manager.getClusterName());
   }
 
+  @Deprecated
   public TaskDriver(HelixZkClient client, String clusterName) {
     this(client, new ZkBaseDataAccessor<>(client), clusterName);
   }
 
+  @Deprecated
   public TaskDriver(HelixZkClient client, ZkBaseDataAccessor<ZNRecord> baseAccessor,
       String clusterName) {
     this(new ZKHelixAdmin(client), new ZKHelixDataAccessor(clusterName, baseAccessor),
diff --git a/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/BestPossibleExternalViewVerifier.java b/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/BestPossibleExternalViewVerifier.java
index 7f57fa9..6d601c4 100644
--- a/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/BestPossibleExternalViewVerifier.java
+++ b/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/BestPossibleExternalViewVerifier.java
@@ -135,8 +135,8 @@ public class BestPossibleExternalViewVerifier extends ZkHelixClusterVerifier {
     }
 
     public BestPossibleExternalViewVerifier build() {
-      if (_clusterName == null || (_zkAddress == null && _zkClient == null)) {
-        throw new IllegalArgumentException("Cluster name or zookeeper info is missing!");
+      if (_clusterName == null) {
+        throw new IllegalArgumentException("Cluster name is missing!");
       }
 
       if (_zkClient != null) {
diff --git a/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/StrictMatchExternalViewVerifier.java b/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/StrictMatchExternalViewVerifier.java
index 9c4a587..76f0d09 100644
--- a/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/StrictMatchExternalViewVerifier.java
+++ b/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/StrictMatchExternalViewVerifier.java
@@ -103,8 +103,8 @@ public class StrictMatchExternalViewVerifier extends ZkHelixClusterVerifier {
     private boolean _isDeactivatedNodeAware = false;
 
     public StrictMatchExternalViewVerifier build() {
-      if (_clusterName == null || (_zkAddress == null && _zkClient == null)) {
-        throw new IllegalArgumentException("Cluster name or zookeeper info is missing!");
+      if (_clusterName == null) {
+        throw new IllegalArgumentException("Cluster name is missing!");
       }
 
       if (_zkClient != null) {
diff --git a/helix-core/src/test/java/org/apache/helix/TestHelper.java b/helix-core/src/test/java/org/apache/helix/TestHelper.java
index 9cac992..adb4812 100644
--- a/helix-core/src/test/java/org/apache/helix/TestHelper.java
+++ b/helix-core/src/test/java/org/apache/helix/TestHelper.java
@@ -47,6 +47,7 @@ import org.apache.helix.manager.zk.ZKHelixDataAccessor;
 import org.apache.helix.manager.zk.ZNRecordSerializer;
 import org.apache.helix.manager.zk.ZkBaseDataAccessor;
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 import org.apache.helix.zookeeper.impl.factory.SharedZkClientFactory;
 import org.apache.helix.model.CurrentState;
 import org.apache.helix.model.ExternalView;
@@ -295,12 +296,12 @@ public class TestHelper {
     zkClient.close();
   }
 
-  public static void dropCluster(String clusterName, HelixZkClient zkClient) throws Exception {
+  public static void dropCluster(String clusterName, RealmAwareZkClient zkClient) {
     ClusterSetup setupTool = new ClusterSetup(zkClient);
     dropCluster(clusterName, zkClient, setupTool);
   }
 
-  public static void dropCluster(String clusterName, HelixZkClient zkClient, ClusterSetup setup) {
+  public static void dropCluster(String clusterName, RealmAwareZkClient zkClient, ClusterSetup setup) {
     String namespace = "/" + clusterName;
     if (zkClient.exists(namespace)) {
       try {
diff --git a/helix-core/src/test/java/org/apache/helix/integration/multizk/TestMultiZkHelixJavaApis.java b/helix-core/src/test/java/org/apache/helix/integration/multizk/TestMultiZkHelixJavaApis.java
new file mode 100644
index 0000000..be8bb0b
--- /dev/null
+++ b/helix-core/src/test/java/org/apache/helix/integration/multizk/TestMultiZkHelixJavaApis.java
@@ -0,0 +1,476 @@
+package org.apache.helix.integration.multizk;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableList;
+import org.apache.helix.AccessOption;
+import org.apache.helix.BaseDataAccessor;
+import org.apache.helix.ConfigAccessor;
+import org.apache.helix.HelixAdmin;
+import org.apache.helix.InstanceType;
+import org.apache.helix.SystemPropertyKeys;
+import org.apache.helix.TestHelper;
+import org.apache.helix.api.config.RebalanceConfig;
+import org.apache.helix.controller.rebalancer.DelayedAutoRebalancer;
+import org.apache.helix.controller.rebalancer.strategy.CrushEdRebalanceStrategy;
+import org.apache.helix.integration.manager.ClusterControllerManager;
+import org.apache.helix.integration.manager.MockParticipantManager;
+import org.apache.helix.integration.task.MockTask;
+import org.apache.helix.integration.task.WorkflowGenerator;
+import org.apache.helix.manager.zk.ZKHelixAdmin;
+import org.apache.helix.manager.zk.ZKUtil;
+import org.apache.helix.manager.zk.ZkBaseDataAccessor;
+import org.apache.helix.model.ClusterConfig;
+import org.apache.helix.model.IdealState;
+import org.apache.helix.model.InstanceConfig;
+import org.apache.helix.msdcommon.constant.MetadataStoreRoutingConstants;
+import org.apache.helix.msdcommon.mock.MockMetadataStoreDirectoryServer;
+import org.apache.helix.participant.StateMachineEngine;
+import org.apache.helix.store.HelixPropertyStore;
+import org.apache.helix.store.zk.ZkHelixPropertyStore;
+import org.apache.helix.task.TaskDriver;
+import org.apache.helix.task.TaskFactory;
+import org.apache.helix.task.TaskState;
+import org.apache.helix.task.TaskStateModelFactory;
+import org.apache.helix.task.Workflow;
+import org.apache.helix.task.WorkflowContext;
+import org.apache.helix.tools.ClusterSetup;
+import org.apache.helix.tools.ClusterVerifiers.BestPossibleExternalViewVerifier;
+import org.apache.helix.tools.ClusterVerifiers.ZkHelixClusterVerifier;
+import org.apache.helix.zookeeper.api.client.HelixZkClient;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
+import org.apache.helix.zookeeper.datamodel.ZNRecord;
+import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
+import org.apache.helix.zookeeper.impl.client.FederatedZkClient;
+import org.apache.helix.zookeeper.impl.factory.DedicatedZkClientFactory;
+import org.apache.helix.zookeeper.zkclient.ZkServer;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+
+/**
+ * TestMultiZkHelixJavaApis spins up multiple in-memory ZooKeepers with a pre-configured
+ * cluster-Zk realm routing information.
+ * This test verifies that all Helix Java APIs work as expected.
+ */
+public class TestMultiZkHelixJavaApis {
+  private static final int NUM_ZK = 3;
+  private static final Map<String, ZkServer> ZK_SERVER_MAP = new HashMap<>();
+  private static final Map<String, HelixZkClient> ZK_CLIENT_MAP = new HashMap<>();
+  private static final Map<String, ClusterControllerManager> MOCK_CONTROLLERS = new HashMap<>();
+  private static final Set<MockParticipantManager> MOCK_PARTICIPANTS = new HashSet<>();
+  private static final List<String> CLUSTER_LIST =
+      ImmutableList.of("CLUSTER_1", "CLUSTER_2", "CLUSTER_3");
+
+  private MockMetadataStoreDirectoryServer _msds;
+  private static final Map<String, Collection<String>> _rawRoutingData = new HashMap<>();
+  private RealmAwareZkClient _zkClient;
+  private HelixAdmin _zkHelixAdmin;
+
+  // Save System property configs from before this test and pass onto after the test
+  private Map<String, String> _configStore = new HashMap<>();
+
+  @BeforeClass
+  public void beforeClass() throws Exception {
+    // Create 3 in-memory zookeepers and routing mapping
+    final String zkPrefix = "localhost:";
+    final int zkStartPort = 8777;
+
+    for (int i = 0; i < NUM_ZK; i++) {
+      String zkAddress = zkPrefix + (zkStartPort + i);
+      ZK_SERVER_MAP.put(zkAddress, TestHelper.startZkServer(zkAddress));
+      ZK_CLIENT_MAP.put(zkAddress, DedicatedZkClientFactory.getInstance()
+          .buildZkClient(new HelixZkClient.ZkConnectionConfig(zkAddress),
+              new HelixZkClient.ZkClientConfig().setZkSerializer(new ZNRecordSerializer())));
+
+      // One cluster per ZkServer created
+      _rawRoutingData.put(zkAddress, Collections.singletonList("/" + CLUSTER_LIST.get(i)));
+    }
+
+    // Create a Mock MSDS
+    final String msdsHostName = "localhost";
+    final int msdsPort = 11117;
+    final String msdsNamespace = "multiZkTest";
+    _msds = new MockMetadataStoreDirectoryServer(msdsHostName, msdsPort, msdsNamespace,
+        _rawRoutingData);
+    _msds.startServer();
+
+    // Save previously-set system configs
+    String prevMultiZkEnabled = System.getProperty(SystemPropertyKeys.MULTI_ZK_ENABLED);
+    String prevMsdsServerEndpoint =
+        System.getProperty(MetadataStoreRoutingConstants.MSDS_SERVER_ENDPOINT_KEY);
+    if (prevMultiZkEnabled != null) {
+      _configStore.put(SystemPropertyKeys.MULTI_ZK_ENABLED, prevMultiZkEnabled);
+    }
+    if (prevMsdsServerEndpoint != null) {
+      _configStore
+          .put(MetadataStoreRoutingConstants.MSDS_SERVER_ENDPOINT_KEY, prevMsdsServerEndpoint);
+    }
+
+    // Turn on multiZk mode in System config
+    System.setProperty(SystemPropertyKeys.MULTI_ZK_ENABLED, "true");
+    // MSDS endpoint: http://localhost:11117/admin/v2/namespaces/multiZkTest
+    System.setProperty(MetadataStoreRoutingConstants.MSDS_SERVER_ENDPOINT_KEY,
+        "http://" + msdsHostName + ":" + msdsPort + "/admin/v2/namespaces/" + msdsNamespace);
+
+    // Create a FederatedZkClient for admin work
+    _zkClient =
+        new FederatedZkClient(new RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder().build(),
+            new RealmAwareZkClient.RealmAwareZkClientConfig());
+  }
+
+  @AfterClass
+  public void afterClass() throws Exception {
+    try {
+      // Kill all mock controllers and participants
+      MOCK_CONTROLLERS.values().forEach(ClusterControllerManager::syncStop);
+      MOCK_PARTICIPANTS.forEach(MockParticipantManager::syncStop);
+
+      // Tear down all clusters
+      CLUSTER_LIST.forEach(cluster -> TestHelper.dropCluster(cluster, _zkClient));
+
+      // Verify that all clusters are gone in each zookeeper
+      Assert.assertTrue(TestHelper.verify(() -> {
+        for (Map.Entry<String, HelixZkClient> zkClientEntry : ZK_CLIENT_MAP.entrySet()) {
+          List<String> children = zkClientEntry.getValue().getChildren("/");
+          if (children.stream().anyMatch(CLUSTER_LIST::contains)) {
+            return false;
+          }
+        }
+        return true;
+      }, TestHelper.WAIT_DURATION));
+
+      // Tear down zookeepers
+      ZK_SERVER_MAP.forEach((zkAddress, zkServer) -> zkServer.shutdown());
+
+      // Stop MockMSDS
+      _msds.stopServer();
+    } finally {
+      // Restore System property configs
+      if (_configStore.containsKey(SystemPropertyKeys.MULTI_ZK_ENABLED)) {
+        System.setProperty(SystemPropertyKeys.MULTI_ZK_ENABLED,
+            _configStore.get(SystemPropertyKeys.MULTI_ZK_ENABLED));
+      } else {
+        System.clearProperty(SystemPropertyKeys.MULTI_ZK_ENABLED);
+      }
+      if (_configStore.containsKey(MetadataStoreRoutingConstants.MSDS_SERVER_ENDPOINT_KEY)) {
+        System.setProperty(MetadataStoreRoutingConstants.MSDS_SERVER_ENDPOINT_KEY,
+            _configStore.get(MetadataStoreRoutingConstants.MSDS_SERVER_ENDPOINT_KEY));
+      } else {
+        System.clearProperty(MetadataStoreRoutingConstants.MSDS_SERVER_ENDPOINT_KEY);
+      }
+    }
+  }
+
+  /**
+   * Test cluster creation according to the pre-set routing mapping.
+   * Helix Java API tested is ClusterSetup in this method.
+   */
+  @Test
+  public void testCreateClusters() {
+    // Create two ClusterSetups using two different constructors
+    // Note: ZK Address here could be anything because multiZk mode is on (it will be ignored)
+    ClusterSetup clusterSetupZkAddr = new ClusterSetup(ZK_SERVER_MAP.keySet().iterator().next());
+    ClusterSetup clusterSetupBuilder = new ClusterSetup.Builder().build();
+
+    createClusters(clusterSetupZkAddr);
+    verifyClusterCreation(clusterSetupZkAddr);
+
+    createClusters(clusterSetupBuilder);
+    verifyClusterCreation(clusterSetupBuilder);
+
+    // Create clusters again to continue with testing
+    createClusters(clusterSetupBuilder);
+  }
+
+  private void createClusters(ClusterSetup clusterSetup) {
+    // Create clusters
+    for (String clusterName : CLUSTER_LIST) {
+      clusterSetup.addCluster(clusterName, false);
+    }
+  }
+
+  private void verifyClusterCreation(ClusterSetup clusterSetup) {
+    // Verify that clusters have been created correctly according to routing mapping
+    _rawRoutingData.forEach((zkAddress, cluster) -> {
+      // Note: clusterNamePath already contains "/"
+      String clusterNamePath = cluster.iterator().next();
+
+      // Check with single-realm ZkClients
+      Assert.assertTrue(ZK_CLIENT_MAP.get(zkAddress).exists(clusterNamePath));
+      // Check with realm-aware ZkClient (federated)
+      Assert.assertTrue(_zkClient.exists(clusterNamePath));
+
+      // Remove clusters
+      clusterSetup
+          .deleteCluster(clusterNamePath.substring(1)); // Need to remove "/" at the beginning
+    });
+  }
+
+  /**
+   * Test Helix Participant creation and addition.
+   * Helix Java APIs tested in this method are:
+   * ZkHelixAdmin and ZKHelixManager (mock participant/controller)
+   */
+  @Test(dependsOnMethods = "testCreateClusters")
+  public void testCreateParticipants() throws Exception {
+    // Create two ClusterSetups using two different constructors
+    // Note: ZK Address here could be anything because multiZk mode is on (it will be ignored)
+    HelixAdmin helixAdminZkAddr = new ZKHelixAdmin(ZK_SERVER_MAP.keySet().iterator().next());
+    HelixAdmin helixAdminBuilder = new ZKHelixAdmin.Builder().build();
+    _zkHelixAdmin = helixAdminBuilder;
+
+    String participantNamePrefix = "Node_";
+    int numParticipants = 5;
+    createParticipantsAndVerify(helixAdminZkAddr, numParticipants, participantNamePrefix);
+    createParticipantsAndVerify(helixAdminBuilder, numParticipants, participantNamePrefix);
+
+    // Create mock controller and participants for next tests
+    for (String cluster : CLUSTER_LIST) {
+      // Start a controller
+      // Note: in multiZK mode, ZK Addr is ignored
+      ClusterControllerManager mockController =
+          new ClusterControllerManager("DummyZK", cluster, "controller");
+      mockController.syncStart();
+      MOCK_CONTROLLERS.put(cluster, mockController);
+
+      for (int i = 0; i < numParticipants; i++) {
+        // Note: in multiZK mode, ZK Addr is ignored
+        InstanceConfig instanceConfig = new InstanceConfig(participantNamePrefix + i);
+        helixAdminBuilder.addInstance(cluster, instanceConfig);
+        MockParticipantManager mockNode =
+            new MockParticipantManager("DummyZK", cluster, participantNamePrefix + i);
+
+        // Register task state model for task framework testing in later methods
+        Map<String, TaskFactory> taskFactoryReg = new HashMap<>();
+        taskFactoryReg.put(MockTask.TASK_COMMAND, MockTask::new);
+        // Register a Task state model factory.
+        StateMachineEngine stateMachine = mockNode.getStateMachineEngine();
+        stateMachine
+            .registerStateModelFactory("Task", new TaskStateModelFactory(mockNode, taskFactoryReg));
+
+        mockNode.syncStart();
+        MOCK_PARTICIPANTS.add(mockNode);
+      }
+      // Check that mockNodes are up
+      Assert.assertTrue(TestHelper
+          .verify(() -> helixAdminBuilder.getInstancesInCluster(cluster).size() == numParticipants,
+              TestHelper.WAIT_DURATION));
+    }
+  }
+
+  private void createParticipantsAndVerify(HelixAdmin admin, int numParticipants,
+      String participantNamePrefix) {
+    // Create participants in clusters
+    Set<String> participantNames = new HashSet<>();
+    CLUSTER_LIST.forEach(cluster -> {
+      for (int i = 0; i < numParticipants; i++) {
+        String participantName = participantNamePrefix + i;
+        participantNames.add(participantName);
+        InstanceConfig instanceConfig = new InstanceConfig(participantNamePrefix + i);
+        admin.addInstance(cluster, instanceConfig);
+      }
+    });
+
+    // Verify participants have been created properly
+    _rawRoutingData.forEach((zkAddress, cluster) -> {
+      // Note: clusterNamePath already contains "/"
+      String clusterNamePath = cluster.iterator().next();
+
+      // Check with single-realm ZkClients
+      List<String> instances =
+          ZK_CLIENT_MAP.get(zkAddress).getChildren(clusterNamePath + "/INSTANCES");
+      Assert.assertEquals(new HashSet<>(instances), participantNames);
+
+      // Check with realm-aware ZkClient (federated)
+      instances = _zkClient.getChildren(clusterNamePath + "/INSTANCES");
+      Assert.assertEquals(new HashSet<>(instances), participantNames);
+
+      // Remove Participants
+      participantNames.forEach(participant -> {
+        InstanceConfig instanceConfig = new InstanceConfig(participant);
+        admin.dropInstance(clusterNamePath.substring(1), instanceConfig);
+      });
+    });
+  }
+
+  /**
+   * Test that clusters and instances are set up properly.
+   * Helix Java APIs tested in this method is ZkUtil.
+   */
+  @Test(dependsOnMethods = "testCreateParticipants")
+  public void testZkUtil() {
+    CLUSTER_LIST.forEach(cluster -> {
+      _zkHelixAdmin.getInstancesInCluster(cluster).forEach(instance -> ZKUtil
+          .isInstanceSetup("DummyZk", cluster, instance, InstanceType.PARTICIPANT));
+    });
+  }
+
+  /**
+   * Create resources and see if things get rebalanced correctly.
+   * Helix Java API tested in this methods are:
+   * ZkBaseDataAccessor
+   * ZkHelixClusterVerifier (BestPossible)
+   */
+  @Test(dependsOnMethods = "testZkUtil")
+  public void testCreateAndRebalanceResources() {
+    BaseDataAccessor<ZNRecord> dataAccessorZkAddr = new ZkBaseDataAccessor<>("DummyZk");
+    BaseDataAccessor<ZNRecord> dataAccessorBuilder =
+        new ZkBaseDataAccessor.Builder<ZNRecord>().build();
+
+    String resourceNamePrefix = "DB_";
+    int numResources = 5;
+    int numPartitions = 3;
+    Map<String, Map<String, ZNRecord>> idealStateMap = new HashMap<>();
+
+    for (String cluster : CLUSTER_LIST) {
+      Set<String> resourceNames = new HashSet<>();
+      Set<String> liveInstancesNames = new HashSet<>(dataAccessorZkAddr
+          .getChildNames("/" + cluster + "/LIVEINSTANCES", AccessOption.PERSISTENT));
+
+      for (int i = 0; i < numResources; i++) {
+        String resource = cluster + "_" + resourceNamePrefix + i;
+        _zkHelixAdmin.addResource(cluster, resource, numPartitions, "MasterSlave",
+            IdealState.RebalanceMode.FULL_AUTO.name());
+        _zkHelixAdmin.rebalance(cluster, resource, 3);
+        resourceNames.add(resource);
+
+        // Update IdealState fields with ZkBaseDataAccessor
+        String resourcePath = "/" + cluster + "/IDEALSTATES/" + resource;
+        ZNRecord is = dataAccessorZkAddr.get(resourcePath, null, AccessOption.PERSISTENT);
+        is.setSimpleField(RebalanceConfig.RebalanceConfigProperty.REBALANCER_CLASS_NAME.name(),
+            DelayedAutoRebalancer.class.getName());
+        is.setSimpleField(RebalanceConfig.RebalanceConfigProperty.REBALANCE_STRATEGY.name(),
+            CrushEdRebalanceStrategy.class.getName());
+        dataAccessorZkAddr.set(resourcePath, is, AccessOption.PERSISTENT);
+        idealStateMap.computeIfAbsent(cluster, recordList -> new HashMap<>())
+            .putIfAbsent(is.getId(), is); // Save ZNRecord for comparison later
+      }
+
+      // Create a verifier to make sure all resources have been rebalanced
+      ZkHelixClusterVerifier verifier =
+          new BestPossibleExternalViewVerifier.Builder(cluster).setResources(resourceNames)
+              .setExpectLiveInstances(liveInstancesNames).build();
+      Assert.assertTrue(verifier.verifyByPolling());
+    }
+
+    // Using the ZkBaseDataAccessor created using the Builder, check that the correct IS is read
+    for (String cluster : CLUSTER_LIST) {
+      Map<String, ZNRecord> savedIdealStates = idealStateMap.get(cluster);
+      List<String> resources = dataAccessorBuilder
+          .getChildNames("/" + cluster + "/IDEALSTATES", AccessOption.PERSISTENT);
+      resources.forEach(resource -> {
+        ZNRecord is = dataAccessorBuilder
+            .get("/" + cluster + "/IDEALSTATES/" + resource, null, AccessOption.PERSISTENT);
+        Assert
+            .assertEquals(is.getSimpleFields(), savedIdealStates.get(is.getId()).getSimpleFields());
+      });
+    }
+  }
+
+  /**
+   * This method tests ConfigAccessor.
+   */
+  @Test(dependsOnMethods = "testCreateAndRebalanceResources")
+  public void testConfigAccessor() {
+    // Build two ConfigAccessors to read and write:
+    // 1. ConfigAccessor using a deprecated constructor
+    // 2. ConfigAccessor using the Builder
+    ConfigAccessor configAccessorZkAddr = new ConfigAccessor("DummyZk");
+    ConfigAccessor configAccessorBuilder = new ConfigAccessor.Builder().build();
+
+    setClusterConfigAndVerify(configAccessorZkAddr);
+    setClusterConfigAndVerify(configAccessorBuilder);
+  }
+
+  private void setClusterConfigAndVerify(ConfigAccessor configAccessorMultiZk) {
+    _rawRoutingData.forEach((zkAddr, clusterNamePathList) -> {
+      // Need to rid of "/" because this is a sharding key
+      String cluster = clusterNamePathList.iterator().next().substring(1);
+      ClusterConfig clusterConfig = new ClusterConfig(cluster);
+      clusterConfig.getRecord().setSimpleField("configAccessor", cluster);
+      configAccessorMultiZk.setClusterConfig(cluster, clusterConfig);
+
+      // Now check with a single-realm ConfigAccessor
+      ConfigAccessor configAccessorSingleZk =
+          new ConfigAccessor.Builder().setRealmMode(RealmAwareZkClient.RealmMode.SINGLE_REALM)
+              .setZkAddress(zkAddr).build();
+      Assert.assertEquals(configAccessorSingleZk.getClusterConfig(cluster), clusterConfig);
+
+      // Also check with a single-realm dedicated ZkClient
+      ZNRecord clusterConfigRecord =
+          ZK_CLIENT_MAP.get(zkAddr).readData("/" + cluster + "/CONFIGS/CLUSTER/" + cluster);
+      Assert.assertEquals(clusterConfigRecord, clusterConfig.getRecord());
+
+      // Clean up
+      clusterConfig = new ClusterConfig(cluster);
+      configAccessorMultiZk.setClusterConfig(cluster, clusterConfig);
+    });
+  }
+
+  /**
+   * This test submits multiple tasks to be run.
+   * The Helix Java APIs tested in this method are TaskDriver (HelixManager) and
+   * ZkHelixPropertyStore/ZkCacheBaseDataAccessor.
+   */
+  @Test(dependsOnMethods = "testConfigAccessor")
+  public void testTaskFramework() throws InterruptedException {
+    // Note: TaskDriver is like HelixManager - it only operates on one designated
+    // Create TaskDrivers for all clusters
+    Map<String, TaskDriver> taskDriverMap = new HashMap<>();
+    MOCK_CONTROLLERS
+        .forEach((cluster, controller) -> taskDriverMap.put(cluster, new TaskDriver(controller)));
+
+    // Create a Task Framework workload and start
+    Workflow workflow = WorkflowGenerator.generateNonTargetedSingleWorkflowBuilder("job").build();
+    for (TaskDriver taskDriver : taskDriverMap.values()) {
+      taskDriver.start(workflow);
+    }
+
+    // Use multi-ZK ZkHelixPropertyStore/ZkCacheBaseDataAccessor to query for workflow/job states
+    HelixPropertyStore<ZNRecord> propertyStore =
+        new ZkHelixPropertyStore.Builder<ZNRecord>().build();
+    for (Map.Entry<String, TaskDriver> entry : taskDriverMap.entrySet()) {
+      String cluster = entry.getKey();
+      TaskDriver driver = entry.getValue();
+      // Wait until workflow has completed
+      TaskState wfStateFromTaskDriver =
+          driver.pollForWorkflowState(workflow.getName(), TaskState.COMPLETED);
+      String workflowContextPath =
+          "/" + cluster + "/PROPERTYSTORE/TaskRebalancer/" + workflow.getName() + "/Context";
+      ZNRecord workflowContextRecord =
+          propertyStore.get(workflowContextPath, null, AccessOption.PERSISTENT);
+      WorkflowContext context = new WorkflowContext(workflowContextRecord);
+
+      // Compare the workflow state read from PropertyStore and TaskDriver
+      Assert.assertEquals(context.getWorkflowState(), wfStateFromTaskDriver);
+    }
+  }
+}
diff --git a/helix-core/src/test/java/org/apache/helix/integration/task/WorkflowGenerator.java b/helix-core/src/test/java/org/apache/helix/integration/task/WorkflowGenerator.java
index 40e2dcf..c582ab3 100644
--- a/helix-core/src/test/java/org/apache/helix/integration/task/WorkflowGenerator.java
+++ b/helix-core/src/test/java/org/apache/helix/integration/task/WorkflowGenerator.java
@@ -19,11 +19,14 @@ package org.apache.helix.integration.task;
  * under the License.
  */
 
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
 
 import org.apache.helix.task.JobConfig;
+import org.apache.helix.task.TaskConfig;
 import org.apache.helix.task.Workflow;
 
 /**
@@ -35,12 +38,14 @@ public class WorkflowGenerator {
   public static final String JOB_NAME_2 = "SomeJob2";
 
   public static final Map<String, String> DEFAULT_JOB_CONFIG;
+  public static final Map<String, String> DEFAULT_JOB_CONFIG_NOT_TARGETED;
   static {
     Map<String, String> tmpMap = new TreeMap<String, String>();
-    tmpMap.put("TargetResource", DEFAULT_TGT_DB);
-    tmpMap.put("TargetPartitionStates", "MASTER");
     tmpMap.put("Command", MockTask.TASK_COMMAND);
     tmpMap.put("TimeoutPerPartition", String.valueOf(10 * 1000));
+    DEFAULT_JOB_CONFIG_NOT_TARGETED = Collections.unmodifiableMap(new TreeMap<>(tmpMap));
+    tmpMap.put("TargetResource", DEFAULT_TGT_DB);
+    tmpMap.put("TargetPartitionStates", "MASTER");
     DEFAULT_JOB_CONFIG = Collections.unmodifiableMap(tmpMap);
   }
 
@@ -57,6 +62,24 @@ public class WorkflowGenerator {
     return generateSingleJobWorkflowBuilder(jobName, jobBuilder);
   }
 
+  public static Workflow.Builder generateNonTargetedSingleWorkflowBuilder(String jobName) {
+    JobConfig.Builder jobBuilder = JobConfig.Builder.fromMap(DEFAULT_JOB_CONFIG_NOT_TARGETED);
+    jobBuilder.setJobCommandConfigMap(DEFAULT_COMMAND_CONFIG);
+
+    // Create 5 TaskConfigs
+    List<TaskConfig> taskConfigs = new ArrayList<>();
+    for (int i = 0; i < 5; i++) {
+      TaskConfig.Builder taskConfigBuilder = new TaskConfig.Builder();
+      taskConfigBuilder.setTaskId("task_" + i);
+      taskConfigBuilder.addConfig("Timeout", String.valueOf(2000));
+      taskConfigBuilder.setCommand(MockTask.TASK_COMMAND);
+      taskConfigs.add(taskConfigBuilder.build());
+    }
+
+    jobBuilder.addTaskConfigs(taskConfigs);
+    return generateSingleJobWorkflowBuilder(jobName, jobBuilder);
+  }
+
   public static Workflow.Builder generateSingleJobWorkflowBuilder(String jobName,
       JobConfig.Builder jobBuilder) {
     return new Workflow.Builder(jobName).addJobConfig(jobName, jobBuilder);


[helix] 46/49: Use Java Generics and inheritance to reduce duplicate code in Helix API Builders (#899)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit 503e589caa233ef1f44ffad21be13ac1d142084c
Author: Hunter Lee <hu...@linkedin.com>
AuthorDate: Fri Mar 20 21:28:38 2020 -0700

    Use Java Generics and inheritance to reduce duplicate code in Helix API Builders (#899)
    
    This PR removes duplicate logic and refactors the ZK helix API Builder logic into one single public abstract class so that other Builders can inherit from it. It makes use of Builder inheritance and Java Generics. This PR promotes code reuse and better craftsmanship.
---
 .../main/java/org/apache/helix/ConfigAccessor.java |  96 +---------
 .../manager/zk/GenericBaseDataAccessorBuilder.java | 145 +++++++++++++++
 .../helix/manager/zk/GenericZkHelixApiBuilder.java | 139 ++++++++++++++
 .../org/apache/helix/manager/zk/ZKHelixAdmin.java  |  94 +---------
 .../helix/manager/zk/ZkBaseDataAccessor.java       | 173 +++--------------
 .../helix/manager/zk/ZkCacheBaseDataAccessor.java  | 204 ++-------------------
 .../java/org/apache/helix/tools/ClusterSetup.java  |  88 +--------
 .../BestPossibleExternalViewVerifier.java          |  39 ++--
 .../ClusterVerifiers/ClusterLiveNodesVerifier.java |  30 +--
 .../StrictMatchExternalViewVerifier.java           |  37 +---
 .../ClusterVerifiers/ZkHelixClusterVerifier.java   | 103 +++++------
 .../zookeeper/api/client/RealmAwareZkClient.java   |   6 +
 .../helix/zookeeper/api/client/ZkClientType.java   |  42 +++++
 13 files changed, 469 insertions(+), 727 deletions(-)

diff --git a/helix-core/src/main/java/org/apache/helix/ConfigAccessor.java b/helix-core/src/main/java/org/apache/helix/ConfigAccessor.java
index d0b3bba..a92f91c 100644
--- a/helix-core/src/main/java/org/apache/helix/ConfigAccessor.java
+++ b/helix-core/src/main/java/org/apache/helix/ConfigAccessor.java
@@ -28,6 +28,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
 
+import org.apache.helix.manager.zk.GenericZkHelixApiBuilder;
 import org.apache.helix.manager.zk.ZKUtil;
 import org.apache.helix.model.ClusterConfig;
 import org.apache.helix.model.ConfigScope;
@@ -82,32 +83,9 @@ public class ConfigAccessor {
   // This is used for close() to determine how ConfigAccessor should close the underlying ZkClient
   private final boolean _usesExternalZkClient;
 
-  /**
-   * Constructor that creates a realm-aware ConfigAccessor using a builder.
-   * @param builder
-   */
-  private ConfigAccessor(Builder builder) {
-    switch (builder._realmMode) {
-      case MULTI_REALM:
-        try {
-          _zkClient = new FederatedZkClient(builder._realmAwareZkConnectionConfig,
-              builder._realmAwareZkClientConfig.setZkSerializer(new ZNRecordSerializer()));
-        } catch (IOException | InvalidRoutingDataException | IllegalStateException e) {
-          throw new HelixException("Failed to create ConfigAccessor!", e);
-        }
-        break;
-      case SINGLE_REALM:
-        // Create a HelixZkClient: Use a SharedZkClient because ConfigAccessor does not need to do
-        // ephemeral operations
-        _zkClient = SharedZkClientFactory.getInstance()
-            .buildZkClient(new HelixZkClient.ZkConnectionConfig(builder._zkAddress),
-                builder._realmAwareZkClientConfig.createHelixZkClientConfig()
-                    .setZkSerializer(new ZNRecordSerializer()));
-        break;
-      default:
-        throw new HelixException("Invalid RealmMode given: " + builder._realmMode);
-    }
-    _usesExternalZkClient = false;
+  private ConfigAccessor(RealmAwareZkClient zkClient, boolean usesExternalZkClient) {
+    _zkClient = zkClient;
+    _usesExternalZkClient = usesExternalZkClient;
   }
 
   /**
@@ -932,73 +910,15 @@ public class ConfigAccessor {
     }
   }
 
-  public static class Builder {
-    private String _zkAddress;
-    private RealmAwareZkClient.RealmMode _realmMode;
-    private RealmAwareZkClient.RealmAwareZkConnectionConfig _realmAwareZkConnectionConfig;
-    private RealmAwareZkClient.RealmAwareZkClientConfig _realmAwareZkClientConfig;
-
+  public static class Builder extends GenericZkHelixApiBuilder<Builder> {
     public Builder() {
     }
 
-    public Builder setZkAddress(String zkAddress) {
-      _zkAddress = zkAddress;
-      return this;
-    }
-
-    public Builder setRealmMode(RealmAwareZkClient.RealmMode realmMode) {
-      _realmMode = realmMode;
-      return this;
-    }
-
-    public Builder setRealmAwareZkConnectionConfig(
-        RealmAwareZkClient.RealmAwareZkConnectionConfig realmAwareZkConnectionConfig) {
-      _realmAwareZkConnectionConfig = realmAwareZkConnectionConfig;
-      return this;
-    }
-
-    public Builder setRealmAwareZkClientConfig(
-        RealmAwareZkClient.RealmAwareZkClientConfig realmAwareZkClientConfig) {
-      _realmAwareZkClientConfig = realmAwareZkClientConfig;
-      return this;
-    }
-
     public ConfigAccessor build() {
       validate();
-      return new ConfigAccessor(this);
-    }
-
-    /**
-     * Validate the given parameters before creating an instance of ConfigAccessor.
-     */
-    private void validate() {
-      // Resolve RealmMode based on other parameters
-      boolean isZkAddressSet = _zkAddress != null && !_zkAddress.isEmpty();
-      if (_realmMode == RealmAwareZkClient.RealmMode.SINGLE_REALM && !isZkAddressSet) {
-        throw new HelixException(
-            "ConfigAccessor: RealmMode cannot be single-realm without a valid ZkAddress set!");
-      }
-      if (_realmMode == RealmAwareZkClient.RealmMode.MULTI_REALM && isZkAddressSet) {
-        throw new HelixException(
-            "ConfigAccessor: You cannot set the ZkAddress on multi-realm mode!");
-      }
-
-      if (_realmMode == null) {
-        _realmMode = isZkAddressSet ? RealmAwareZkClient.RealmMode.SINGLE_REALM
-            : RealmAwareZkClient.RealmMode.MULTI_REALM;
-      }
-
-      // Resolve RealmAwareZkClientConfig
-      if (_realmAwareZkClientConfig == null) {
-        _realmAwareZkClientConfig = new RealmAwareZkClient.RealmAwareZkClientConfig();
-      }
-
-      // Resolve RealmAwareZkConnectionConfig
-      if (_realmAwareZkConnectionConfig == null) {
-        // If not set, create a default one
-        _realmAwareZkConnectionConfig =
-            new RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder().build();
-      }
+      return new ConfigAccessor(
+          createZkClient(_realmMode, _realmAwareZkConnectionConfig, _realmAwareZkClientConfig,
+              _zkAddress), false);
     }
   }
 }
diff --git a/helix-core/src/main/java/org/apache/helix/manager/zk/GenericBaseDataAccessorBuilder.java b/helix-core/src/main/java/org/apache/helix/manager/zk/GenericBaseDataAccessorBuilder.java
new file mode 100644
index 0000000..d19ebb1
--- /dev/null
+++ b/helix-core/src/main/java/org/apache/helix/manager/zk/GenericBaseDataAccessorBuilder.java
@@ -0,0 +1,145 @@
+package org.apache.helix.manager.zk;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.helix.HelixException;
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
+import org.apache.helix.zookeeper.api.client.HelixZkClient;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
+import org.apache.helix.zookeeper.api.client.ZkClientType;
+import org.apache.helix.zookeeper.impl.client.FederatedZkClient;
+import org.apache.helix.zookeeper.impl.factory.DedicatedZkClientFactory;
+import org.apache.helix.zookeeper.impl.factory.SharedZkClientFactory;
+
+
+/**
+ * GenericBaseDataAccessorBuilder serves as the abstract parent class for Builders used by
+ * BaseDataAccessor APIs that create ZK connections. By having this class, we promote code-reuse.
+ * @param <B>
+ */
+public class GenericBaseDataAccessorBuilder<B extends GenericBaseDataAccessorBuilder<B>> extends GenericZkHelixApiBuilder<B> {
+  /** ZK-based BaseDataAccessor-specific parameter **/
+  private ZkClientType _zkClientType;
+
+  /**
+   * Sets the ZkClientType.
+   * If this is set to either DEDICATED or SHARED, this accessor will be created on
+   * single-realm mode.
+   * If this is set to FEDERATED, multi-realm mode will be used.
+   * @param zkClientType
+   * @return
+   */
+  public B setZkClientType(ZkBaseDataAccessor.ZkClientType zkClientType) {
+    return setZkClientType(Enum.valueOf(ZkClientType.class, zkClientType.name()));
+  }
+
+  public B setZkClientType(ZkClientType zkClientType) {
+    _zkClientType = zkClientType;
+    return self();
+  }
+
+  /**
+   * Validates the given parameters before building an instance of ZkBaseDataAccessor.
+   */
+  @Override
+  protected void validate() {
+    super.validate();
+    validateZkClientType(_zkClientType, _realmMode);
+  }
+
+  /**
+   * This method contains construction logic for ZK-based BaseDataAccessor
+   * implementations.
+   * It uses an implementation of GenericBaseDataAccessorBuilder to construct the right
+   * RealmAwareZkClient based on a host of configs provided in the Builder.
+   * @return RealmAwareZkClient
+   */
+  @Override
+  protected RealmAwareZkClient createZkClient(RealmAwareZkClient.RealmMode realmMode,
+      RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig,
+      RealmAwareZkClient.RealmAwareZkClientConfig clientConfig, String zkAddress) {
+    RealmAwareZkClient zkClient;
+    switch (realmMode) {
+      case MULTI_REALM:
+        try {
+          if (_zkClientType == ZkClientType.DEDICATED) {
+            // Use a realm-aware dedicated zk client
+            zkClient = DedicatedZkClientFactory.getInstance()
+                .buildZkClient(connectionConfig, clientConfig);
+          } else if (_zkClientType == ZkClientType.SHARED) {
+            // Use a realm-aware shared zk client
+            zkClient =
+                SharedZkClientFactory.getInstance().buildZkClient(connectionConfig, clientConfig);
+          } else {
+            zkClient = new FederatedZkClient(connectionConfig, clientConfig);
+          }
+        } catch (IOException | InvalidRoutingDataException | IllegalStateException e) {
+          throw new HelixException("Not able to connect on multi-realm mode.", e);
+        }
+        break;
+
+      case SINGLE_REALM:
+        // Create a HelixZkClient: Use a SharedZkClient because ZkBaseDataAccessor does not need to
+        // do ephemeral operations.
+        if (_zkClientType == ZkClientType.DEDICATED) {
+          // If DEDICATED, then we use a dedicated HelixZkClient because we must support ephemeral
+          // operations
+          zkClient = DedicatedZkClientFactory.getInstance()
+              .buildZkClient(new HelixZkClient.ZkConnectionConfig(zkAddress),
+                  clientConfig.createHelixZkClientConfig());
+        } else {
+          zkClient = SharedZkClientFactory.getInstance()
+              .buildZkClient(new HelixZkClient.ZkConnectionConfig(zkAddress),
+                  clientConfig.createHelixZkClientConfig());
+          zkClient
+              .waitUntilConnected(HelixZkClient.DEFAULT_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS);
+        }
+        break;
+      default:
+        throw new HelixException("Invalid RealmMode given: " + realmMode);
+    }
+    return zkClient;
+  }
+
+  /**
+   * Validate ZkClientType based on RealmMode.
+   * @param zkClientType
+   * @param realmMode
+   */
+  private void validateZkClientType(ZkClientType zkClientType,
+      RealmAwareZkClient.RealmMode realmMode) {
+    boolean isZkClientTypeSet = zkClientType != null;
+    // If ZkClientType is set, RealmMode must either be single-realm or not set.
+    if (isZkClientTypeSet && realmMode == RealmAwareZkClient.RealmMode.MULTI_REALM) {
+      throw new HelixException("ZkClientType cannot be set on multi-realm mode!");
+    }
+    // If ZkClientType is not set and realmMode is single-realm, default to SHARED
+    if (!isZkClientTypeSet && realmMode == RealmAwareZkClient.RealmMode.SINGLE_REALM) {
+      zkClientType = ZkClientType.SHARED;
+    }
+    if (realmMode == RealmAwareZkClient.RealmMode.SINGLE_REALM
+        && zkClientType == ZkClientType.FEDERATED) {
+      throw new HelixException("FederatedZkClient cannot be set on single-realm mode!");
+    }
+  }
+}
diff --git a/helix-core/src/main/java/org/apache/helix/manager/zk/GenericZkHelixApiBuilder.java b/helix-core/src/main/java/org/apache/helix/manager/zk/GenericZkHelixApiBuilder.java
new file mode 100644
index 0000000..f6015c4
--- /dev/null
+++ b/helix-core/src/main/java/org/apache/helix/manager/zk/GenericZkHelixApiBuilder.java
@@ -0,0 +1,139 @@
+package org.apache.helix.manager.zk;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+
+import org.apache.helix.HelixException;
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
+import org.apache.helix.zookeeper.api.client.HelixZkClient;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
+import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
+import org.apache.helix.zookeeper.impl.client.FederatedZkClient;
+import org.apache.helix.zookeeper.impl.factory.SharedZkClientFactory;
+
+
+/**
+ * GenericZkHelixApiBuilder serves as the abstract parent class for Builders used by Helix Java APIs
+ * that create ZK connections. By having this class, we reduce duplicate code as much as possible.
+ * @param <B>
+ */
+public abstract class GenericZkHelixApiBuilder<B extends GenericZkHelixApiBuilder<B>> {
+  protected String _zkAddress;
+  protected RealmAwareZkClient.RealmMode _realmMode;
+  protected RealmAwareZkClient.RealmAwareZkConnectionConfig _realmAwareZkConnectionConfig;
+  protected RealmAwareZkClient.RealmAwareZkClientConfig _realmAwareZkClientConfig;
+
+  public B setZkAddress(String zkAddress) {
+    _zkAddress = zkAddress;
+    return self();
+  }
+
+  public B setRealmMode(RealmAwareZkClient.RealmMode realmMode) {
+    _realmMode = realmMode;
+    return self();
+  }
+
+  public B setRealmAwareZkConnectionConfig(
+      RealmAwareZkClient.RealmAwareZkConnectionConfig realmAwareZkConnectionConfig) {
+    _realmAwareZkConnectionConfig = realmAwareZkConnectionConfig;
+    return self();
+  }
+
+  public B setRealmAwareZkClientConfig(
+      RealmAwareZkClient.RealmAwareZkClientConfig realmAwareZkClientConfig) {
+    _realmAwareZkClientConfig = realmAwareZkClientConfig;
+    return self();
+  }
+
+  /**
+   * Validates the given Builder parameters using a generic validation logic.
+   */
+  protected void validate() {
+    // Resolve RealmMode based on whether ZK address has been set
+    boolean isZkAddressSet = _zkAddress != null && !_zkAddress.isEmpty();
+    if (_realmMode == RealmAwareZkClient.RealmMode.SINGLE_REALM && !isZkAddressSet) {
+      throw new HelixException("RealmMode cannot be single-realm without a valid ZkAddress set!");
+    }
+    if (_realmMode == RealmAwareZkClient.RealmMode.MULTI_REALM && isZkAddressSet) {
+      throw new HelixException("ZkAddress cannot be set on multi-realm mode!");
+    }
+    if (_realmMode == null) {
+      _realmMode = isZkAddressSet ? RealmAwareZkClient.RealmMode.SINGLE_REALM
+          : RealmAwareZkClient.RealmMode.MULTI_REALM;
+    }
+
+    initializeConfigsIfNull();
+  }
+
+  /**
+   * Initializes Realm-aware ZkConnection and ZkClient configs if they haven't been set.
+   */
+  protected void initializeConfigsIfNull() {
+    // Resolve all default values
+    if (_realmAwareZkConnectionConfig == null) {
+      _realmAwareZkConnectionConfig =
+          new RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder().build();
+    }
+
+    // For Helix APIs, ZNRecord should be the default data model
+    if (_realmAwareZkClientConfig == null) {
+      _realmAwareZkClientConfig = new RealmAwareZkClient.RealmAwareZkClientConfig()
+          .setZkSerializer(new ZNRecordSerializer());
+    }
+  }
+
+  /**
+   * Creates a RealmAwareZkClient based on the parameters set.
+   * To be used in Helix ZK APIs' constructors: ConfigAccessor, ClusterSetup, ZKHelixAdmin
+   * @return
+   */
+  protected RealmAwareZkClient createZkClient(RealmAwareZkClient.RealmMode realmMode,
+      RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig,
+      RealmAwareZkClient.RealmAwareZkClientConfig clientConfig, String zkAddress) {
+    switch (realmMode) {
+      case MULTI_REALM:
+        try {
+          return new FederatedZkClient(connectionConfig,
+              clientConfig.setZkSerializer(new ZNRecordSerializer()));
+        } catch (IOException | InvalidRoutingDataException | IllegalStateException e) {
+          throw new HelixException("Failed to create FederatedZkClient!", e);
+        }
+      case SINGLE_REALM:
+        // Create a HelixZkClient: Use a SharedZkClient because ClusterSetup does not need to do
+        // ephemeral operations
+        return SharedZkClientFactory.getInstance()
+            .buildZkClient(new HelixZkClient.ZkConnectionConfig(zkAddress),
+                clientConfig.createHelixZkClientConfig().setZkSerializer(new ZNRecordSerializer()));
+      default:
+        throw new HelixException("Invalid RealmMode given: " + realmMode);
+    }
+  }
+
+  /**
+   * Returns an instance of a subclass-Builder in order to reduce duplicate code.
+   * SuppressWarnings is used to rid of IDE warnings.
+   * @return an instance of a subclass-Builder. E.g.) ConfigAccessor.Builder
+   */
+  @SuppressWarnings("unchecked")
+  final B self() {
+    return (B) this;
+  }
+}
diff --git a/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixAdmin.java b/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixAdmin.java
index 5804dd9..8cc327a 100644
--- a/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixAdmin.java
+++ b/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixAdmin.java
@@ -160,31 +160,10 @@ public class ZKHelixAdmin implements HelixAdmin {
     _usesExternalZkClient = false;
   }
 
-  private ZKHelixAdmin(Builder builder) {
-    RealmAwareZkClient zkClient;
-    switch (builder.realmMode) {
-      case MULTI_REALM:
-        try {
-          zkClient = new FederatedZkClient(builder.realmAwareZkConnectionConfig,
-              builder.realmAwareZkClientConfig);
-        } catch (IOException | InvalidRoutingDataException | IllegalStateException e) {
-          throw new HelixException("Not able to connect on multi-realm mode.", e);
-        }
-        break;
-      case SINGLE_REALM:
-        // Create a HelixZkClient: Use a SharedZkClient because ZKHelixAdmin does not need to do
-        // ephemeral operations
-        zkClient = SharedZkClientFactory.getInstance()
-            .buildZkClient(new HelixZkClient.ZkConnectionConfig(builder.zkAddress),
-                builder.realmAwareZkClientConfig.createHelixZkClientConfig());
-        break;
-      default:
-        throw new HelixException("Invalid RealmMode given: " + builder.realmMode);
-    }
-
+  private ZKHelixAdmin(RealmAwareZkClient zkClient, boolean usesExternalZkClient) {
     _zkClient = zkClient;
     _configAccessor = new ConfigAccessor(_zkClient);
-    _usesExternalZkClient = false;
+    _usesExternalZkClient = usesExternalZkClient;
   }
 
   @Override
@@ -1873,76 +1852,15 @@ public class ZKHelixAdmin implements HelixAdmin {
     return true;
   }
 
-  // TODO: refactor builder to reduce duplicate code with other Helix Java APIs
-  public static class Builder {
-    private String zkAddress;
-    private RealmAwareZkClient.RealmMode realmMode;
-    private RealmAwareZkClient.RealmAwareZkConnectionConfig realmAwareZkConnectionConfig;
-    private RealmAwareZkClient.RealmAwareZkClientConfig realmAwareZkClientConfig;
-
+  public static class Builder extends GenericZkHelixApiBuilder<Builder> {
     public Builder() {
     }
 
-    public ZKHelixAdmin.Builder setZkAddress(String zkAddress) {
-      this.zkAddress = zkAddress;
-      return this;
-    }
-
-    public ZKHelixAdmin.Builder setRealmMode(RealmAwareZkClient.RealmMode realmMode) {
-      this.realmMode = realmMode;
-      return this;
-    }
-
-    public ZKHelixAdmin.Builder setRealmAwareZkConnectionConfig(
-        RealmAwareZkClient.RealmAwareZkConnectionConfig realmAwareZkConnectionConfig) {
-      realmAwareZkConnectionConfig = realmAwareZkConnectionConfig;
-      return this;
-    }
-
-    public ZKHelixAdmin.Builder setRealmAwareZkClientConfig(
-        RealmAwareZkClient.RealmAwareZkClientConfig realmAwareZkClientConfig) {
-      realmAwareZkClientConfig = realmAwareZkClientConfig;
-      return this;
-    }
-
     public ZKHelixAdmin build() {
       validate();
-      return new ZKHelixAdmin(this);
-    }
-
-    /*
-     * Validates the given parameters before creating an instance of ZKHelixAdmin.
-     */
-    private void validate() {
-      // Resolve RealmMode based on other parameters
-      boolean isZkAddressSet = zkAddress != null && !zkAddress.isEmpty();
-      if (realmMode == RealmAwareZkClient.RealmMode.SINGLE_REALM && !isZkAddressSet) {
-        throw new HelixException(
-            "RealmMode cannot be single-realm without a valid ZkAddress set!");
-      }
-      if (realmMode == RealmAwareZkClient.RealmMode.MULTI_REALM && isZkAddressSet) {
-        throw new HelixException(
-            "ZkAddress cannot be set on multi-realm mode!");
-      }
-      if (realmMode == null) {
-        realmMode = isZkAddressSet ? RealmAwareZkClient.RealmMode.SINGLE_REALM
-            : RealmAwareZkClient.RealmMode.MULTI_REALM;
-      }
-
-      // Resolve RealmAwareZkClientConfig
-      if (realmAwareZkClientConfig == null) {
-        // ZkHelixAdmin should have ZNRecordSerializer set by default
-        realmAwareZkClientConfig = new RealmAwareZkClient.RealmAwareZkClientConfig()
-            .setZkSerializer(
-                new org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer());
-      }
-
-      // Resolve RealmAwareZkConnectionConfig
-      if (realmAwareZkConnectionConfig == null) {
-        // If not set, create a default one
-        realmAwareZkConnectionConfig =
-            new RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder().build();
-      }
+      return new ZKHelixAdmin(
+          createZkClient(_realmMode, _realmAwareZkConnectionConfig, _realmAwareZkClientConfig,
+              _zkAddress), false);
     }
   }
 }
diff --git a/helix-core/src/main/java/org/apache/helix/manager/zk/ZkBaseDataAccessor.java b/helix-core/src/main/java/org/apache/helix/manager/zk/ZkBaseDataAccessor.java
index 32f33f8..a7596f4 100644
--- a/helix-core/src/main/java/org/apache/helix/manager/zk/ZkBaseDataAccessor.java
+++ b/helix-core/src/main/java/org/apache/helix/manager/zk/ZkBaseDataAccessor.java
@@ -65,7 +65,6 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
 
   // Designates which mode ZkBaseDataAccessor should be created in. If not specified, it will be
   // created on SHARED mode.
-  // TODO: move this to RealmAwareZkClient
   public enum ZkClientType {
     /**
      * When ZkBaseDataAccessor is created with the DEDICATED type, it supports ephemeral node
@@ -141,51 +140,9 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
     _usesExternalZkClient = true;
   }
 
-  private ZkBaseDataAccessor(Builder<T> builder) {
-    switch (builder.realmMode) {
-      case MULTI_REALM:
-        try {
-          if (builder.zkClientType == ZkClientType.DEDICATED) {
-            // Use a realm-aware dedicated zk client
-            _zkClient = DedicatedZkClientFactory.getInstance()
-                .buildZkClient(builder.realmAwareZkConnectionConfig,
-                    builder.realmAwareZkClientConfig);
-          } else if (builder.zkClientType == ZkClientType.SHARED) {
-            // Use a realm-aware shared zk client
-            _zkClient = SharedZkClientFactory.getInstance()
-                .buildZkClient(builder.realmAwareZkConnectionConfig,
-                    builder.realmAwareZkClientConfig);
-          } else {
-            _zkClient = new FederatedZkClient(builder.realmAwareZkConnectionConfig,
-                builder.realmAwareZkClientConfig);
-          }
-        } catch (IOException | InvalidRoutingDataException | IllegalStateException e) {
-          throw new HelixException("Not able to connect on multi-realm mode.", e);
-        }
-        break;
-
-      case SINGLE_REALM:
-        // Create a HelixZkClient: Use a SharedZkClient because ZkBaseDataAccessor does not need to
-        // do ephemeral operations.
-        if (builder.zkClientType == ZkClientType.DEDICATED) {
-          // If DEDICATED, then we use a dedicated HelixZkClient because we must support ephemeral
-          // operations
-          _zkClient = DedicatedZkClientFactory.getInstance()
-              .buildZkClient(new HelixZkClient.ZkConnectionConfig(builder.zkAddress),
-                  builder.realmAwareZkClientConfig.createHelixZkClientConfig());
-        } else {
-          _zkClient = SharedZkClientFactory.getInstance()
-              .buildZkClient(new HelixZkClient.ZkConnectionConfig(builder.zkAddress),
-                  builder.realmAwareZkClientConfig.createHelixZkClientConfig());
-          _zkClient
-              .waitUntilConnected(HelixZkClient.DEFAULT_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS);
-        }
-        break;
-      default:
-        throw new HelixException("Invalid RealmMode given: " + builder.realmMode);
-    }
-
-    _usesExternalZkClient = false;
+  private ZkBaseDataAccessor(RealmAwareZkClient zkClient, boolean usesExternalZkClient) {
+    _zkClient = zkClient;
+    _usesExternalZkClient = usesExternalZkClient;
   }
 
   /**
@@ -260,10 +217,9 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
   @Deprecated
   public ZkBaseDataAccessor(String zkAddress, ZkSerializer zkSerializer,
       ZkClientType zkClientType) {
-    RealmAwareZkClient.RealmAwareZkClientConfig clientConfig =
-        new RealmAwareZkClient.RealmAwareZkClientConfig().setZkSerializer(zkSerializer);
-
-    _zkClient = buildRealmAwareZkClient(clientConfig, zkAddress, zkClientType);
+    _zkClient = buildRealmAwareZkClientWithDefaultConfigs(
+        new RealmAwareZkClient.RealmAwareZkClientConfig().setZkSerializer(zkSerializer), zkAddress,
+        zkClientType);
     _usesExternalZkClient = false;
   }
 
@@ -282,10 +238,9 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
   @Deprecated
   public ZkBaseDataAccessor(String zkAddress, PathBasedZkSerializer pathBasedZkSerializer,
       ZkClientType zkClientType) {
-    RealmAwareZkClient.RealmAwareZkClientConfig clientConfig =
-        new RealmAwareZkClient.RealmAwareZkClientConfig().setZkSerializer(pathBasedZkSerializer);
-
-    _zkClient = buildRealmAwareZkClient(clientConfig, zkAddress, zkClientType);
+    _zkClient = buildRealmAwareZkClientWithDefaultConfigs(
+        new RealmAwareZkClient.RealmAwareZkClientConfig().setZkSerializer(pathBasedZkSerializer),
+        zkAddress, zkClientType);
     _usesExternalZkClient = false;
   }
 
@@ -1308,7 +1263,7 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
   }
 
   /**
-   * Subscrie to zookeeper data changes
+   * Subscribe to zookeeper data changes
    */
   @Override
   public List<String> subscribeChildChanges(String path, IZkChildListener listener) {
@@ -1316,7 +1271,7 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
   }
 
   /**
-   * Unsubscrie to zookeeper data changes
+   * Unsubscribe to zookeeper data changes
    */
   @Override
   public void unsubscribeChildChanges(String path, IZkChildListener childListener) {
@@ -1341,44 +1296,10 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
     }
   }
 
-  // TODO: refactor Builder class to remove duplicate code with other Helix Java APIs
-  public static class Builder<T> {
-    private String zkAddress;
-    private ZkBaseDataAccessor.ZkClientType zkClientType;
-    private RealmAwareZkClient.RealmMode realmMode;
-    private RealmAwareZkClient.RealmAwareZkConnectionConfig realmAwareZkConnectionConfig;
-    private RealmAwareZkClient.RealmAwareZkClientConfig realmAwareZkClientConfig;
-
+  public static class Builder<T> extends GenericBaseDataAccessorBuilder<Builder<T>> {
     public Builder() {
     }
 
-    public Builder<T> setZkAddress(String zkAddress) {
-      this.zkAddress = zkAddress;
-      return this;
-    }
-
-    public Builder<T> setRealmMode(RealmAwareZkClient.RealmMode realmMode) {
-      this.realmMode = realmMode;
-      return this;
-    }
-
-    public Builder<T> setZkClientType(ZkClientType zkClientType) {
-      this.zkClientType = zkClientType;
-      return this;
-    }
-
-    public Builder<T> setRealmAwareZkConnectionConfig(
-        RealmAwareZkClient.RealmAwareZkConnectionConfig realmAwareZkConnectionConfig) {
-      this.realmAwareZkConnectionConfig = realmAwareZkConnectionConfig;
-      return this;
-    }
-
-    public Builder<T> setRealmAwareZkClientConfig(
-        RealmAwareZkClient.RealmAwareZkClientConfig realmAwareZkClientConfig) {
-      this.realmAwareZkClientConfig = realmAwareZkClientConfig;
-      return this;
-    }
-
     /**
      * Returns a <code>ZkBaseDataAccessor</code> instance.
      * <p>
@@ -1388,67 +1309,27 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
      */
     public ZkBaseDataAccessor<T> build() {
       validate();
-      return new ZkBaseDataAccessor<>(this);
-    }
-
-    /*
-     * Validates the given parameters before building an instance of ZkBaseDataAccessor.
-     */
-    private void validate() {
-      // Resolve RealmMode based on other parameters
-      boolean isZkAddressSet = zkAddress != null && !zkAddress.isEmpty();
-      boolean isZkClientTypeSet = zkClientType != null;
-
-      // If ZkClientType is set, RealmMode must either be single-realm or not set.
-      if (isZkClientTypeSet && realmMode == RealmAwareZkClient.RealmMode.MULTI_REALM) {
-        throw new HelixException("ZkClientType cannot be set on multi-realm mode!");
-      }
-      // If ZkClientType is not set and realmMode is single-realm, default to SHARED
-      if (!isZkClientTypeSet && realmMode == RealmAwareZkClient.RealmMode.SINGLE_REALM) {
-        zkClientType = ZkClientType.SHARED;
-      }
-
-      if (realmMode == RealmAwareZkClient.RealmMode.SINGLE_REALM && !isZkAddressSet) {
-        throw new HelixException("RealmMode cannot be single-realm without a valid ZkAddress set!");
-      }
-
-      if (realmMode == RealmAwareZkClient.RealmMode.MULTI_REALM && isZkAddressSet) {
-        throw new HelixException("ZkAddress cannot be set on multi-realm mode!");
-      }
-
-      if (realmMode == RealmAwareZkClient.RealmMode.SINGLE_REALM
-          && zkClientType == ZkClientType.FEDERATED) {
-        throw new HelixException("FederatedZkClient cannot be set on single-realm mode!");
-      }
-
-      if (realmMode == null) {
-        realmMode = isZkAddressSet ? RealmAwareZkClient.RealmMode.SINGLE_REALM
-            : RealmAwareZkClient.RealmMode.MULTI_REALM;
-      }
-
-      // Resolve RealmAwareZkClientConfig
-      if (realmAwareZkClientConfig == null) {
-        realmAwareZkClientConfig = new RealmAwareZkClient.RealmAwareZkClientConfig()
-            .setZkSerializer(new ZNRecordSerializer());
-      }
-
-      // Resolve RealmAwareZkConnectionConfig
-      if (realmAwareZkConnectionConfig == null) {
-        // If not set, create a default one
-        realmAwareZkConnectionConfig =
-            new RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder().build();
-      }
+      return new ZkBaseDataAccessor<>(
+          createZkClient(_realmMode, _realmAwareZkConnectionConfig, _realmAwareZkClientConfig,
+              _zkAddress));
     }
   }
 
-  /*
-   * This is used for constructors that do not take a Builder in as a parameter because of
-   * keeping backward-compatibility.
+  /**
+   * This method is used for constructors that are not based on the Builder for
+   * backward-compatibility.
+   * It checks if there is a System Property config set for Multi-ZK mode and determines if a
+   * FederatedZkClient should be created.
+   * @param clientConfig default RealmAwareZkClientConfig with ZK serializer set
+   * @param zkAddress
+   * @param zkClientType
+   * @return
    */
-  private RealmAwareZkClient buildRealmAwareZkClient(
+  static RealmAwareZkClient buildRealmAwareZkClientWithDefaultConfigs(
       RealmAwareZkClient.RealmAwareZkClientConfig clientConfig, String zkAddress,
       ZkClientType zkClientType) {
     if (Boolean.getBoolean(SystemPropertyKeys.MULTI_ZK_ENABLED)) {
+      // If the multi ZK config is enabled, use multi-realm mode with FederatedZkClient
       try {
         return new FederatedZkClient(
             new RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder().build(), clientConfig);
@@ -1458,7 +1339,6 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
     }
 
     RealmAwareZkClient zkClient;
-
     switch (zkClientType) {
       case DEDICATED:
         zkClient = DedicatedZkClientFactory.getInstance()
@@ -1475,7 +1355,6 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
             .waitUntilConnected(HelixZkClient.DEFAULT_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS);
         break;
     }
-
     return zkClient;
   }
 }
diff --git a/helix-core/src/main/java/org/apache/helix/manager/zk/ZkCacheBaseDataAccessor.java b/helix-core/src/main/java/org/apache/helix/manager/zk/ZkCacheBaseDataAccessor.java
index 72d5601..fdae5dc 100644
--- a/helix-core/src/main/java/org/apache/helix/manager/zk/ZkCacheBaseDataAccessor.java
+++ b/helix-core/src/main/java/org/apache/helix/manager/zk/ZkCacheBaseDataAccessor.java
@@ -19,31 +19,22 @@ package org.apache.helix.manager.zk;
  * under the License.
  */
 
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
-import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.ReentrantLock;
 
 import org.apache.helix.AccessOption;
 import org.apache.helix.HelixException;
-import org.apache.helix.SystemPropertyKeys;
 import org.apache.helix.manager.zk.ZkBaseDataAccessor.RetCode;
-import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
 import org.apache.helix.store.HelixPropertyListener;
 import org.apache.helix.store.HelixPropertyStore;
 import org.apache.helix.store.zk.ZNode;
 import org.apache.helix.util.PathUtils;
-import org.apache.helix.zookeeper.api.client.HelixZkClient;
 import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
-import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
-import org.apache.helix.zookeeper.impl.client.FederatedZkClient;
-import org.apache.helix.zookeeper.impl.factory.DedicatedZkClientFactory;
-import org.apache.helix.zookeeper.impl.factory.SharedZkClientFactory;
 import org.apache.helix.zookeeper.zkclient.DataUpdater;
 import org.apache.helix.zookeeper.zkclient.IZkChildListener;
 import org.apache.helix.zookeeper.zkclient.IZkDataListener;
@@ -126,41 +117,9 @@ public class ZkCacheBaseDataAccessor<T> implements HelixPropertyStore<T> {
   public ZkCacheBaseDataAccessor(String zkAddress, ZkSerializer serializer, String chrootPath,
       List<String> wtCachePaths, List<String> zkCachePaths, String monitorType, String monitorkey,
       ZkBaseDataAccessor.ZkClientType zkClientType) {
-
-    // If the multi ZK config is enabled, use multi-realm mode with FederatedZkClient
-    if (Boolean.parseBoolean(System.getProperty(SystemPropertyKeys.MULTI_ZK_ENABLED))) {
-      try {
-        RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder connectionConfigBuilder =
-            new RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder();
-        RealmAwareZkClient.RealmAwareZkClientConfig clientConfig =
-            new RealmAwareZkClient.RealmAwareZkClientConfig();
-        clientConfig.setZkSerializer(serializer).setMonitorType(monitorType)
-            .setMonitorKey(monitorkey);
-        // Use a federated zk client
-        _zkClient = new FederatedZkClient(connectionConfigBuilder.build(), clientConfig);
-      } catch (IOException | InvalidRoutingDataException | IllegalStateException e) {
-        // Note: IllegalStateException is for HttpRoutingDataReader if MSDS endpoint cannot be
-        // found
-        throw new HelixException("Failed to create ZkCacheBaseDataAccessor!", e);
-      }
-    } else {
-      HelixZkClient.ZkClientConfig clientConfig = new HelixZkClient.ZkClientConfig();
-      clientConfig.setZkSerializer(serializer).setMonitorType(monitorType)
-          .setMonitorKey(monitorkey);
-      switch (zkClientType) {
-        case DEDICATED:
-          _zkClient = DedicatedZkClientFactory.getInstance()
-              .buildZkClient(new HelixZkClient.ZkConnectionConfig(zkAddress),
-                  new HelixZkClient.ZkClientConfig().setZkSerializer(serializer));
-          break;
-        case SHARED:
-        default:
-          _zkClient = SharedZkClientFactory.getInstance()
-              .buildZkClient(new HelixZkClient.ZkConnectionConfig(zkAddress), clientConfig);
-      }
-      _zkClient.waitUntilConnected(HelixZkClient.DEFAULT_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS);
-    }
-
+    _zkClient = ZkBaseDataAccessor.buildRealmAwareZkClientWithDefaultConfigs(
+        new RealmAwareZkClient.RealmAwareZkClientConfig().setZkSerializer(serializer)
+            .setMonitorType(monitorType).setMonitorKey(monitorkey), zkAddress, zkClientType);
     _baseAccessor = new ZkBaseDataAccessor<>(_zkClient);
 
     if (chrootPath == null || chrootPath.equals("/")) {
@@ -176,65 +135,15 @@ public class ZkCacheBaseDataAccessor<T> implements HelixPropertyStore<T> {
     start();
   }
 
-  /**
-   * Constructor using a Builder that allows users to set connection and client configs.
-   * @param builder
-   */
-  private ZkCacheBaseDataAccessor(Builder builder) {
-    _chrootPath = builder._chrootPath;
-    _wtCachePaths = builder._wtCachePaths;
-    _zkCachePaths = builder._zkCachePaths;
-
-    RealmAwareZkClient zkClient;
-    switch (builder._realmMode) {
-      case MULTI_REALM:
-        try {
-          if (builder._zkClientType == ZkBaseDataAccessor.ZkClientType.DEDICATED) {
-            // Use a realm-aware dedicated zk client
-            zkClient = DedicatedZkClientFactory.getInstance()
-                .buildZkClient(builder._realmAwareZkConnectionConfig,
-                    builder._realmAwareZkClientConfig);
-          } else if (builder._zkClientType == ZkBaseDataAccessor.ZkClientType.SHARED) {
-            // Use a realm-aware shared zk client
-            zkClient = SharedZkClientFactory.getInstance()
-                .buildZkClient(builder._realmAwareZkConnectionConfig,
-                    builder._realmAwareZkClientConfig);
-          } else {
-            // Use a federated zk client
-            zkClient = new FederatedZkClient(builder._realmAwareZkConnectionConfig,
-                builder._realmAwareZkClientConfig);
-          }
-          break; // Must break out of the switch statement here since zkClient has been created
-        } catch (IOException | InvalidRoutingDataException | IllegalStateException e) {
-          // Note: IllegalStateException is for HttpRoutingDataReader if MSDS endpoint cannot be
-          // found
-          throw new HelixException("Failed to create ZkCacheBaseDataAccessor!", e);
-        }
-      case SINGLE_REALM:
-        switch (builder._zkClientType) {
-          case DEDICATED:
-            // If DEDICATED, then we use a dedicated HelixZkClient because we must support ephemeral
-            // operations
-            zkClient = DedicatedZkClientFactory.getInstance()
-                .buildZkClient(new HelixZkClient.ZkConnectionConfig(builder._zkAddress),
-                    builder._realmAwareZkClientConfig.createHelixZkClientConfig());
-            break;
-          case SHARED:
-          default:
-            zkClient = SharedZkClientFactory.getInstance()
-                .buildZkClient(new HelixZkClient.ZkConnectionConfig(builder._zkAddress),
-                    builder._realmAwareZkClientConfig.createHelixZkClientConfig());
-            zkClient.waitUntilConnected(HelixZkClient.DEFAULT_CONNECTION_TIMEOUT,
-                TimeUnit.MILLISECONDS);
-            break;
-        }
-      default:
-        throw new HelixException("Invalid RealmMode given: " + builder._realmMode);
-    }
-
+  private ZkCacheBaseDataAccessor(RealmAwareZkClient zkClient, String chrootPath,
+      List<String> wtCachePaths, List<String> zkCachePaths) {
     _zkClient = zkClient;
     _baseAccessor = new ZkBaseDataAccessor<>(_zkClient);
 
+    _chrootPath = chrootPath;
+    _wtCachePaths = wtCachePaths;
+    _zkCachePaths = zkCachePaths;
+
     start();
   }
 
@@ -920,43 +829,15 @@ public class ZkCacheBaseDataAccessor<T> implements HelixPropertyStore<T> {
     }
   }
 
-  public static class Builder<T> {
-    private String _zkAddress;
-    private RealmAwareZkClient.RealmMode _realmMode;
-    private RealmAwareZkClient.RealmAwareZkConnectionConfig _realmAwareZkConnectionConfig;
-    private RealmAwareZkClient.RealmAwareZkClientConfig _realmAwareZkClientConfig;
-
+  public static class Builder<T> extends GenericBaseDataAccessorBuilder<Builder<T>> {
     /** ZkCacheBaseDataAccessor-specific parameters */
     private String _chrootPath;
     private List<String> _wtCachePaths;
     private List<String> _zkCachePaths;
-    private ZkBaseDataAccessor.ZkClientType _zkClientType;
 
     public Builder() {
     }
 
-    public Builder<T> setZkAddress(String zkAddress) {
-      _zkAddress = zkAddress;
-      return this;
-    }
-
-    public Builder<T> setRealmMode(RealmAwareZkClient.RealmMode realmMode) {
-      _realmMode = realmMode;
-      return this;
-    }
-
-    public Builder<T> setRealmAwareZkConnectionConfig(
-        RealmAwareZkClient.RealmAwareZkConnectionConfig realmAwareZkConnectionConfig) {
-      _realmAwareZkConnectionConfig = realmAwareZkConnectionConfig;
-      return this;
-    }
-
-    public Builder<T> setRealmAwareZkClientConfig(
-        RealmAwareZkClient.RealmAwareZkClientConfig realmAwareZkClientConfig) {
-      _realmAwareZkClientConfig = realmAwareZkClientConfig;
-      return this;
-    }
-
     public Builder<T> setChrootPath(String chrootPath) {
       _chrootPath = chrootPath;
       return this;
@@ -972,70 +853,11 @@ public class ZkCacheBaseDataAccessor<T> implements HelixPropertyStore<T> {
       return this;
     }
 
-    /**
-     * Sets the ZkClientType. If this is set, ZkCacheBaseDataAccessor will be created on
-     * single-realm mode.
-     * @param zkClientType
-     * @return
-     */
-    public Builder<T> setZkClientType(ZkBaseDataAccessor.ZkClientType zkClientType) {
-      _zkClientType = zkClientType;
-      return this;
-    }
-
     public ZkCacheBaseDataAccessor<T> build() {
       validate();
-      return new ZkCacheBaseDataAccessor<>(this);
-    }
-
-    private void validate() {
-      // Resolve RealmMode based on other parameters
-      boolean isZkAddressSet = _zkAddress != null && !_zkAddress.isEmpty();
-      boolean isZkClientTypeSet = _zkClientType != null;
-
-      // If ZkClientType is set, RealmMode must either be single-realm or not set.
-      if (isZkClientTypeSet && _realmMode == RealmAwareZkClient.RealmMode.MULTI_REALM) {
-        throw new HelixException(
-            "ZkCacheBaseDataAccessor: you cannot set ZkClientType on multi-realm mode!");
-      }
-      // If ZkClientType is not set and realmMode is single-realm, default to SHARED
-      if (!isZkClientTypeSet && _realmMode == RealmAwareZkClient.RealmMode.SINGLE_REALM) {
-        _zkClientType = ZkBaseDataAccessor.ZkClientType.SHARED;
-      }
-
-      if (_realmMode == RealmAwareZkClient.RealmMode.SINGLE_REALM && !isZkAddressSet) {
-        throw new HelixException(
-            "ZkCacheBaseDataAccessor: RealmMode cannot be single-realm without a valid ZkAddress set!");
-      }
-
-      if (_realmMode == RealmAwareZkClient.RealmMode.MULTI_REALM && isZkAddressSet) {
-        throw new HelixException(
-            "ZkCacheBaseDataAccessor: You cannot set the ZkAddress on multi-realm mode!");
-      }
-
-      if (_realmMode == RealmAwareZkClient.RealmMode.SINGLE_REALM
-          && _zkClientType == ZkBaseDataAccessor.ZkClientType.FEDERATED) {
-        throw new HelixException(
-            "ZkCacheBaseDataAccessor: You cannot use FederatedZkClient on single-realm mode!");
-      }
-
-      if (_realmMode == null) {
-        _realmMode = isZkAddressSet ? RealmAwareZkClient.RealmMode.SINGLE_REALM
-            : RealmAwareZkClient.RealmMode.MULTI_REALM;
-      }
-
-      // Resolve RealmAwareZkClientConfig
-      if (_realmAwareZkClientConfig == null) {
-        _realmAwareZkClientConfig = new RealmAwareZkClient.RealmAwareZkClientConfig()
-            .setZkSerializer(new ZNRecordSerializer());
-      }
-
-      // Resolve RealmAwareZkConnectionConfig
-      if (_realmAwareZkConnectionConfig == null) {
-        // If not set, create a default one
-        _realmAwareZkConnectionConfig =
-            new RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder().build();
-      }
+      return new ZkCacheBaseDataAccessor<>(
+          createZkClient(_realmMode, _realmAwareZkConnectionConfig, _realmAwareZkClientConfig,
+              _zkAddress), _chrootPath, _wtCachePaths, _zkCachePaths);
     }
   }
 }
diff --git a/helix-core/src/main/java/org/apache/helix/tools/ClusterSetup.java b/helix-core/src/main/java/org/apache/helix/tools/ClusterSetup.java
index 4fcfc97..672d5a4 100644
--- a/helix-core/src/main/java/org/apache/helix/tools/ClusterSetup.java
+++ b/helix-core/src/main/java/org/apache/helix/tools/ClusterSetup.java
@@ -41,6 +41,7 @@ import org.apache.helix.HelixConstants;
 import org.apache.helix.HelixException;
 import org.apache.helix.PropertyKey;
 import org.apache.helix.SystemPropertyKeys;
+import org.apache.helix.manager.zk.GenericZkHelixApiBuilder;
 import org.apache.helix.manager.zk.ZKHelixAdmin;
 import org.apache.helix.manager.zk.ZKHelixDataAccessor;
 import org.apache.helix.manager.zk.ZkBaseDataAccessor;
@@ -182,29 +183,10 @@ public class ClusterSetup {
     _usesExternalZkClient = true;
   }
 
-  private ClusterSetup(Builder builder) {
-    switch (builder._realmMode) {
-      case MULTI_REALM:
-        try {
-          _zkClient = new FederatedZkClient(builder._realmAwareZkConnectionConfig,
-              builder._realmAwareZkClientConfig.setZkSerializer(new ZNRecordSerializer()));
-          break;
-        } catch (IOException | InvalidRoutingDataException | IllegalStateException e) {
-          throw new HelixException("Failed to create ClusterSetup!", e);
-        }
-      case SINGLE_REALM:
-        // Create a HelixZkClient: Use a SharedZkClient because ClusterSetup does not need to do
-        // ephemeral operations
-        _zkClient = SharedZkClientFactory.getInstance()
-            .buildZkClient(new HelixZkClient.ZkConnectionConfig(builder._zkAddress),
-                builder._realmAwareZkClientConfig.createHelixZkClientConfig()
-                    .setZkSerializer(new ZNRecordSerializer()));
-        break;
-      default:
-        throw new HelixException("Invalid RealmMode given: " + builder._realmMode);
-    }
+  private ClusterSetup(RealmAwareZkClient zkClient, boolean usesExternalZkClient) {
+    _zkClient = zkClient;
     _admin = new ZKHelixAdmin(_zkClient);
-    _usesExternalZkClient = false;
+    _usesExternalZkClient = usesExternalZkClient;
   }
 
   /**
@@ -1612,69 +1594,15 @@ public class ClusterSetup {
     System.exit(ret);
   }
 
-  public static class Builder {
-    private String _zkAddress;
-    private RealmAwareZkClient.RealmMode _realmMode;
-    private RealmAwareZkClient.RealmAwareZkConnectionConfig _realmAwareZkConnectionConfig;
-    private RealmAwareZkClient.RealmAwareZkClientConfig _realmAwareZkClientConfig;
-
+  public static class Builder extends GenericZkHelixApiBuilder<Builder> {
     public Builder() {
     }
 
-    public Builder setZkAddress(String zkAddress) {
-      _zkAddress = zkAddress;
-      return this;
-    }
-
-    public Builder setRealmMode(RealmAwareZkClient.RealmMode realmMode) {
-      _realmMode = realmMode;
-      return this;
-    }
-
-    public Builder setRealmAwareZkConnectionConfig(
-        RealmAwareZkClient.RealmAwareZkConnectionConfig realmAwareZkConnectionConfig) {
-      _realmAwareZkConnectionConfig = realmAwareZkConnectionConfig;
-      return this;
-    }
-
-    public Builder setRealmAwareZkClientConfig(
-        RealmAwareZkClient.RealmAwareZkClientConfig realmAwareZkClientConfig) {
-      _realmAwareZkClientConfig = realmAwareZkClientConfig;
-      return this;
-    }
-
     public ClusterSetup build() {
       validate();
-      return new ClusterSetup(this);
-    }
-
-    private void validate() {
-      // Resolve RealmMode based on other parameters
-      boolean isZkAddressSet = _zkAddress != null && !_zkAddress.isEmpty();
-      if (_realmMode == RealmAwareZkClient.RealmMode.SINGLE_REALM && !isZkAddressSet) {
-        throw new HelixException(
-            "ClusterSetup: RealmMode cannot be single-realm without a valid ZkAddress set!");
-      }
-      if (_realmMode == RealmAwareZkClient.RealmMode.MULTI_REALM && isZkAddressSet) {
-        throw new HelixException(
-            "ClusterSetup: You cannot set the ZkAddress on multi-realm mode!");
-      }
-      if (_realmMode == null) {
-        _realmMode = isZkAddressSet ? RealmAwareZkClient.RealmMode.SINGLE_REALM
-            : RealmAwareZkClient.RealmMode.MULTI_REALM;
-      }
-
-      // Resolve RealmAwareZkClientConfig
-      if (_realmAwareZkClientConfig == null) {
-        _realmAwareZkClientConfig = new RealmAwareZkClient.RealmAwareZkClientConfig();
-      }
-
-      // Resolve RealmAwareZkConnectionConfig
-      if (_realmAwareZkConnectionConfig == null) {
-        // If not set, create a default one
-        _realmAwareZkConnectionConfig =
-            new RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder().build();
-      }
+      return new ClusterSetup(
+          createZkClient(_realmMode, _realmAwareZkConnectionConfig, _realmAwareZkClientConfig,
+              _zkAddress), false);
     }
   }
 }
diff --git a/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/BestPossibleExternalViewVerifier.java b/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/BestPossibleExternalViewVerifier.java
index 6d601c4..12cdad4 100644
--- a/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/BestPossibleExternalViewVerifier.java
+++ b/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/BestPossibleExternalViewVerifier.java
@@ -111,19 +111,22 @@ public class BestPossibleExternalViewVerifier extends ZkHelixClusterVerifier {
     _dataProvider = new ResourceControllerDataProvider();
   }
 
-  private BestPossibleExternalViewVerifier(Builder builder) {
-    super(builder);
+  private BestPossibleExternalViewVerifier(RealmAwareZkClient zkClient, String clusterName,
+      Map<String, Map<String, String>> errStates, Set<String> resources,
+      Set<String> expectLiveInstances) {
+    super(zkClient, clusterName);
     // Deep copy data from Builder
     _errStates = new HashMap<>();
-    if (builder._errStates != null) {
-      builder._errStates.forEach((k, v) -> _errStates.put(k, new HashMap<>(v)));
+    if (errStates != null) {
+      errStates.forEach((k, v) -> _errStates.put(k, new HashMap<>(v)));
     }
-    _resources = new HashSet<>(builder._resources);
-    _expectLiveInstances = new HashSet<>(builder._expectLiveInstances);
+    _resources = resources == null ? new HashSet<>() : new HashSet<>(resources);
+    _expectLiveInstances =
+        expectLiveInstances == null ? new HashSet<>() : new HashSet<>(expectLiveInstances);
     _dataProvider = new ResourceControllerDataProvider();
   }
 
-  public static class Builder extends ZkHelixClusterVerifier.Builder {
+  public static class Builder extends ZkHelixClusterVerifier.Builder<Builder> {
     private final String _clusterName;
     private Map<String, Map<String, String>> _errStates;
     private Set<String> _resources;
@@ -151,7 +154,10 @@ public class BestPossibleExternalViewVerifier extends ZkHelixClusterVerifier {
       }
 
       validate();
-      return new BestPossibleExternalViewVerifier(this);
+      return new BestPossibleExternalViewVerifier(
+          createZkClient(RealmAwareZkClient.RealmMode.SINGLE_REALM, _realmAwareZkConnectionConfig,
+              _realmAwareZkClientConfig, _zkAddress), _clusterName, _errStates, _resources,
+          _expectLiveInstances);
     }
 
     public String getClusterName() {
@@ -193,23 +199,6 @@ public class BestPossibleExternalViewVerifier extends ZkHelixClusterVerifier {
       _zkClient = zkClient;
       return this;
     }
-
-    @Override
-    public Builder setZkAddr(String zkAddress) {
-      return (Builder) super.setZkAddr(zkAddress);
-    }
-
-    @Override
-    public Builder setRealmAwareZkConnectionConfig(
-        RealmAwareZkClient.RealmAwareZkConnectionConfig realmAwareZkConnectionConfig) {
-      return (Builder) super.setRealmAwareZkConnectionConfig(realmAwareZkConnectionConfig);
-    }
-
-    @Override
-    public Builder setRealmAwareZkClientConfig(
-        RealmAwareZkClient.RealmAwareZkClientConfig realmAwareZkClientConfig) {
-      return (Builder) super.setRealmAwareZkClientConfig(realmAwareZkClientConfig);
-    }
   }
 
   @Override
diff --git a/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/ClusterLiveNodesVerifier.java b/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/ClusterLiveNodesVerifier.java
index 8aeb64a..4a46f18 100644
--- a/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/ClusterLiveNodesVerifier.java
+++ b/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/ClusterLiveNodesVerifier.java
@@ -38,9 +38,10 @@ public class ClusterLiveNodesVerifier extends ZkHelixClusterVerifier {
     _expectLiveNodes = new HashSet<>(expectLiveNodes);
   }
 
-  private ClusterLiveNodesVerifier(Builder builder) {
-    super(builder);
-    _expectLiveNodes = new HashSet<>(builder._expectLiveNodes);
+  private ClusterLiveNodesVerifier(RealmAwareZkClient zkClient, String clusterName,
+      Set<String> expectLiveNodes) {
+    super(zkClient, clusterName);
+    _expectLiveNodes = expectLiveNodes == null ? new HashSet<>() : new HashSet<>(expectLiveNodes);
   }
 
   @Override
@@ -58,7 +59,7 @@ public class ClusterLiveNodesVerifier extends ZkHelixClusterVerifier {
     return _expectLiveNodes.equals(actualLiveNodes);
   }
 
-  public static class Builder extends ZkHelixClusterVerifier.Builder {
+  public static class Builder extends ZkHelixClusterVerifier.Builder<Builder> {
     private final String _clusterName; // This is the ZK path sharding key
     private final Set<String> _expectLiveNodes;
 
@@ -73,24 +74,9 @@ public class ClusterLiveNodesVerifier extends ZkHelixClusterVerifier {
       }
 
       validate();
-      return new ClusterLiveNodesVerifier(this);
-    }
-
-    @Override
-    public Builder setZkAddr(String zkAddress) {
-      return (Builder) super.setZkAddr(zkAddress);
-    }
-
-    @Override
-    public Builder setRealmAwareZkConnectionConfig(
-        RealmAwareZkClient.RealmAwareZkConnectionConfig realmAwareZkConnectionConfig) {
-      return (Builder) super.setRealmAwareZkConnectionConfig(realmAwareZkConnectionConfig);
-    }
-
-    @Override
-    public Builder setRealmAwareZkClientConfig(
-        RealmAwareZkClient.RealmAwareZkClientConfig realmAwareZkClientConfig) {
-      return (Builder) super.setRealmAwareZkClientConfig(realmAwareZkClientConfig);
+      return new ClusterLiveNodesVerifier(
+          createZkClient(RealmAwareZkClient.RealmMode.SINGLE_REALM, _realmAwareZkConnectionConfig,
+              _realmAwareZkClientConfig, _zkAddress), _clusterName, _expectLiveNodes);
     }
   }
 }
diff --git a/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/StrictMatchExternalViewVerifier.java b/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/StrictMatchExternalViewVerifier.java
index 76f0d09..987599e 100644
--- a/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/StrictMatchExternalViewVerifier.java
+++ b/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/StrictMatchExternalViewVerifier.java
@@ -78,23 +78,16 @@ public class StrictMatchExternalViewVerifier extends ZkHelixClusterVerifier {
     _isDeactivatedNodeAware = isDeactivatedNodeAware;
   }
 
-  @Deprecated
   private StrictMatchExternalViewVerifier(RealmAwareZkClient zkClient, String clusterName,
       Set<String> resources, Set<String> expectLiveInstances, boolean isDeactivatedNodeAware) {
     super(zkClient, clusterName);
-    _resources = resources;
-    _expectLiveInstances = expectLiveInstances;
+    _resources = resources == null ? new HashSet<>() : new HashSet<>(resources);
+    _expectLiveInstances =
+        expectLiveInstances == null ? new HashSet<>() : new HashSet<>(expectLiveInstances);
     _isDeactivatedNodeAware = isDeactivatedNodeAware;
   }
 
-  private StrictMatchExternalViewVerifier(Builder builder) {
-    super(builder);
-    _resources = new HashSet<>(builder._resources);
-    _expectLiveInstances = new HashSet<>(builder._expectLiveInstances);
-    _isDeactivatedNodeAware = builder._isDeactivatedNodeAware;
-  }
-
-  public static class Builder extends ZkHelixClusterVerifier.Builder {
+  public static class Builder extends ZkHelixClusterVerifier.Builder<Builder> {
     private final String _clusterName; // This is the ZK path sharding key
     private Set<String> _resources;
     private Set<String> _expectLiveInstances;
@@ -119,7 +112,10 @@ public class StrictMatchExternalViewVerifier extends ZkHelixClusterVerifier {
       }
 
       validate();
-      return new StrictMatchExternalViewVerifier(this);
+      return new StrictMatchExternalViewVerifier(
+          createZkClient(RealmAwareZkClient.RealmMode.SINGLE_REALM, _realmAwareZkConnectionConfig,
+              _realmAwareZkClientConfig, _zkAddress), _clusterName, _resources,
+          _expectLiveInstances, _isDeactivatedNodeAware);
     }
 
     public Builder(String clusterName) {
@@ -167,23 +163,6 @@ public class StrictMatchExternalViewVerifier extends ZkHelixClusterVerifier {
       return this;
     }
 
-    @Override
-    public Builder setZkAddr(String zkAddress) {
-      return (Builder) super.setZkAddr(zkAddress);
-    }
-
-    @Override
-    public Builder setRealmAwareZkConnectionConfig(
-        RealmAwareZkClient.RealmAwareZkConnectionConfig realmAwareZkConnectionConfig) {
-      return (Builder) super.setRealmAwareZkConnectionConfig(realmAwareZkConnectionConfig);
-    }
-
-    @Override
-    public Builder setRealmAwareZkClientConfig(
-        RealmAwareZkClient.RealmAwareZkClientConfig realmAwareZkClientConfig) {
-      return (Builder) super.setRealmAwareZkClientConfig(realmAwareZkClientConfig);
-    }
-
     protected void validate() {
       super.validate();
       if (!_clusterName.equals(_realmAwareZkConnectionConfig.getZkRealmShardingKey())) {
diff --git a/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/ZkHelixClusterVerifier.java b/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/ZkHelixClusterVerifier.java
index 449b659..e2022e7 100644
--- a/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/ZkHelixClusterVerifier.java
+++ b/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/ZkHelixClusterVerifier.java
@@ -31,6 +31,7 @@ import org.apache.helix.HelixException;
 import org.apache.helix.PropertyKey;
 import org.apache.helix.SystemPropertyKeys;
 import org.apache.helix.api.listeners.PreFetch;
+import org.apache.helix.manager.zk.GenericZkHelixApiBuilder;
 import org.apache.helix.manager.zk.ZKHelixDataAccessor;
 import org.apache.helix.manager.zk.ZkBaseDataAccessor;
 import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
@@ -93,13 +94,7 @@ public abstract class ZkHelixClusterVerifier
     }
   }
 
-  /**
-   * Deprecated because we discourage passing in a ZkClient directly.
-   * @param zkClient
-   * @param clusterName
-   */
-  @Deprecated
-  public ZkHelixClusterVerifier(RealmAwareZkClient zkClient, String clusterName) {
+  protected ZkHelixClusterVerifier(RealmAwareZkClient zkClient, String clusterName) {
     if (zkClient == null || clusterName == null) {
       throw new IllegalArgumentException("requires zkClient|clusterName");
     }
@@ -144,27 +139,6 @@ public abstract class ZkHelixClusterVerifier
     _keyBuilder = _accessor.keyBuilder();
   }
 
-  protected ZkHelixClusterVerifier(Builder builder) {
-    if (Boolean.parseBoolean(System.getProperty(SystemPropertyKeys.MULTI_ZK_ENABLED))) {
-      try {
-        // First, try to create a RealmAwareZkClient that's a DedicatedZkClient
-        _zkClient = DedicatedZkClientFactory.getInstance()
-            .buildZkClient(builder._realmAwareZkConnectionConfig,
-                builder._realmAwareZkClientConfig);
-      } catch (IOException | InvalidRoutingDataException | IllegalStateException e) {
-        throw new HelixException("ZkHelixClusterVerifier: failed to create ZkClient!", e);
-      }
-    } else {
-      _zkClient = DedicatedZkClientFactory.getInstance()
-          .buildZkClient(new HelixZkClient.ZkConnectionConfig(builder._zkAddress));
-    }
-    _usesExternalZkClient = false;
-    _zkClient.setZkSerializer(new ZNRecordSerializer());
-    _clusterName = builder._realmAwareZkConnectionConfig.getZkRealmShardingKey();
-    _accessor = new ZKHelixDataAccessor(_clusterName, new ZkBaseDataAccessor<>(_zkClient));
-    _keyBuilder = _accessor.keyBuilder();
-  }
-
   /**
    * Verify the cluster.
    * The method will be blocked at most {@code timeout}.
@@ -345,36 +319,36 @@ public abstract class ZkHelixClusterVerifier
     return _clusterName;
   }
 
-  // TODO: refactor Builders for Java APIs
-  protected abstract static class Builder {
-    // Note: ZkHelixClusterVerifier is a single-realm API, so RealmMode is assumed to be
-    // SINGLE-REALM
-    protected String _zkAddress;
-    protected RealmAwareZkClient.RealmAwareZkConnectionConfig _realmAwareZkConnectionConfig;
-    protected RealmAwareZkClient.RealmAwareZkClientConfig _realmAwareZkClientConfig;
-
+  protected abstract static class Builder<B extends Builder<B>> extends GenericZkHelixApiBuilder<B> {
     public Builder() {
+      // Note: ZkHelixClusterVerifier is a single-realm API, so RealmMode is assumed to be
+      // SINGLE-REALM
+      setRealmMode(RealmAwareZkClient.RealmMode.SINGLE_REALM);
     }
 
-    public Builder setZkAddr(String zkAddress) {
-      _zkAddress = zkAddress;
-      return this;
-    }
-
-    public Builder setRealmAwareZkConnectionConfig(
-        RealmAwareZkClient.RealmAwareZkConnectionConfig realmAwareZkConnectionConfig) {
-      _realmAwareZkConnectionConfig = realmAwareZkConnectionConfig;
-      return this;
+    /**
+     * Use setZkAddress() instead. Deprecated but left here for backward-compatibility.
+     * @param zkAddress
+     * @return
+     */
+    @Deprecated
+    public B setZkAddr(String zkAddress) {
+      return setZkAddress(zkAddress);
     }
 
-    public Builder setRealmAwareZkClientConfig(
-        RealmAwareZkClient.RealmAwareZkClientConfig realmAwareZkClientConfig) {
-      _realmAwareZkClientConfig = realmAwareZkClientConfig;
-      return this;
+    public String getClusterName() {
+      if (_realmAwareZkConnectionConfig != null && (
+          _realmAwareZkConnectionConfig.getZkRealmShardingKey() != null
+              && !_realmAwareZkConnectionConfig.getZkRealmShardingKey().isEmpty())) {
+        // Need to remove the first "/" from sharding key given
+        return _realmAwareZkConnectionConfig.getZkRealmShardingKey().substring(1);
+      }
+      throw new HelixException(
+          "Failed to get the cluster name! Either RealmAwareZkConnectionConfig is null or its sharding key is null or empty!");
     }
 
     protected void validate() {
-      // Validate that either ZkAddress or ZkRealmShardingKey is set.
+      // Validate that either ZkAddress or ZkRealmShardingKey is set
       if (_zkAddress == null || _zkAddress.isEmpty()) {
         if (_realmAwareZkConnectionConfig == null
             || _realmAwareZkConnectionConfig.getZkRealmShardingKey() == null
@@ -384,14 +358,29 @@ public abstract class ZkHelixClusterVerifier
                   + _zkAddress + " RealmAwareZkConnectionConfig: " + _realmAwareZkConnectionConfig);
         }
       }
+      initializeConfigsIfNull();
+    }
 
-      // Resolve all default values
-      if (_realmAwareZkConnectionConfig == null) {
-        _realmAwareZkConnectionConfig =
-            new RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder().build();
-      }
-      if (_realmAwareZkClientConfig == null) {
-        _realmAwareZkClientConfig = new RealmAwareZkClient.RealmAwareZkClientConfig();
+    /**
+     * Creates a RealmAwareZkClient for ZkHelixClusterVerifiers.
+     * Note that DedicatedZkClient is used whether it's multi-realm or single-realm.
+     * @return
+     */
+    @Override
+    protected RealmAwareZkClient createZkClient(RealmAwareZkClient.RealmMode realmMode,
+        RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig,
+        RealmAwareZkClient.RealmAwareZkClientConfig clientConfig, String zkAddress) {
+      if (Boolean.parseBoolean(System.getProperty(SystemPropertyKeys.MULTI_ZK_ENABLED))) {
+        try {
+          // First, try to create a RealmAwareZkClient that's a DedicatedZkClient
+          return DedicatedZkClientFactory.getInstance()
+              .buildZkClient(connectionConfig, clientConfig);
+        } catch (IOException | InvalidRoutingDataException | IllegalStateException e) {
+          throw new HelixException("ZkHelixClusterVerifier: failed to create ZkClient!", e);
+        }
+      } else {
+        return DedicatedZkClientFactory.getInstance()
+            .buildZkClient(new HelixZkClient.ZkConnectionConfig(zkAddress));
       }
     }
   }
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/RealmAwareZkClient.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/RealmAwareZkClient.java
index 40b6c54..0e461b7 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/RealmAwareZkClient.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/RealmAwareZkClient.java
@@ -419,6 +419,12 @@ public interface RealmAwareZkClient {
         return this;
       }
 
+      /**
+       * Note: this must be a valid ZK path. For example, if you are trying to create a sharding key
+       * out of a cluster name "CLUSTER", then your sharding key should be "/CLUSTER".
+       * @param shardingKey
+       * @return
+       */
       public Builder setZkRealmShardingKey(String shardingKey) {
         _zkRealmShardingKey = shardingKey;
         return this;
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/ZkClientType.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/ZkClientType.java
new file mode 100644
index 0000000..4db06a8
--- /dev/null
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/ZkClientType.java
@@ -0,0 +1,42 @@
+package org.apache.helix.zookeeper.api.client;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+public enum ZkClientType {
+  /**
+   * If a Helix API is created with the DEDICATED type, it supports ephemeral node
+   * creation, callback functionality, and session management. But note that this is more
+   * resource-heavy since it creates a dedicated ZK connection so should be used sparingly only
+   * when the aforementioned features are needed.
+   */
+  DEDICATED,
+
+  /**
+   * If a Helix API is created with the SHARED type, it only supports CRUD
+   * functionalities. This will be the default mode of creation.
+   */
+  SHARED,
+
+  /**
+   * Uses FederatedZkClient (applicable on multi-realm mode only) that queries Metadata Store
+   * Directory Service for routing data.
+   */
+  FEDERATED
+}


[helix] 38/49: Make ZkBaseDataAccessor realm-aware (#855)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit 4effd7b1cea765eeea2da836403d6950a2d917d2
Author: Huizhi Lu <ih...@gmail.com>
AuthorDate: Wed Mar 11 23:38:52 2020 -0700

    Make ZkBaseDataAccessor realm-aware (#855)
    
    This commit makes ZkBaseDataAccessor realm-aware by building according realm-aware ZkClients in the constructor. A Builder is provided to set realm-aware client config and connection config.
---
 .../helix/manager/zk/ZkBaseDataAccessor.java       | 261 ++++++++++++++++++---
 1 file changed, 233 insertions(+), 28 deletions(-)

diff --git a/helix-core/src/main/java/org/apache/helix/manager/zk/ZkBaseDataAccessor.java b/helix-core/src/main/java/org/apache/helix/manager/zk/ZkBaseDataAccessor.java
index f287c22..1d60c7b 100644
--- a/helix-core/src/main/java/org/apache/helix/manager/zk/ZkBaseDataAccessor.java
+++ b/helix-core/src/main/java/org/apache/helix/manager/zk/ZkBaseDataAccessor.java
@@ -19,6 +19,7 @@ package org.apache.helix.manager.zk;
  * under the License.
  */
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -26,16 +27,20 @@ import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.TimeUnit;
 
 import org.apache.helix.AccessOption;
 import org.apache.helix.BaseDataAccessor;
 import org.apache.helix.HelixException;
+import org.apache.helix.SystemPropertyKeys;
 import org.apache.helix.api.exceptions.HelixMetaDataAccessException;
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
 import org.apache.helix.store.zk.ZNode;
 import org.apache.helix.util.HelixUtil;
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
 import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
+import org.apache.helix.zookeeper.impl.client.FederatedZkClient;
 import org.apache.helix.zookeeper.impl.factory.DedicatedZkClientFactory;
 import org.apache.helix.zookeeper.impl.factory.SharedZkClientFactory;
 import org.apache.helix.zookeeper.zkclient.DataUpdater;
@@ -60,21 +65,23 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
   // created on SHARED mode.
   // TODO: move this to RealmAwareZkClient
   public enum ZkClientType {
-    /*
+    /**
      * When ZkBaseDataAccessor is created with the DEDICATED type, it supports ephemeral node
      * creation, callback functionality, and session management. But note that this is more
      * resource-heavy since it creates a dedicated ZK connection so should be used sparingly only
      * when the aforementioned features are needed.
      */
     DEDICATED,
-    /*
+
+    /**
      * When ZkBaseDataAccessor is created with the SHARED type, it only supports CRUD
      * functionalities. This will be the default mode of creation.
      */
     SHARED,
-    /*
+
+    /**
      * Uses FederatedZkClient (applicable on multi-realm mode only) that queries Metadata Store
-     * Directory Service for routing data
+     * Directory Service for routing data.
      */
     FEDERATED
   }
@@ -116,6 +123,13 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
   // ZkClient
   private final boolean _usesExternalZkClient;
 
+  /**
+   * @deprecated it is recommended to use the builder constructor {@link Builder}
+   * instead to avoid having to manually create and maintain a RealmAwareZkClient
+   * outside of ZkBaseDataAccessor.
+   *
+   * @param zkClient A created RealmAwareZkClient
+   */
   @Deprecated
   public ZkBaseDataAccessor(RealmAwareZkClient zkClient) {
     if (zkClient == null) {
@@ -125,13 +139,63 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
     _usesExternalZkClient = true;
   }
 
+  private ZkBaseDataAccessor(Builder builder) {
+    switch (builder.realmMode) {
+      case MULTI_REALM:
+        try {
+          if (builder.zkClientType == ZkClientType.DEDICATED) {
+            // Use a realm-aware dedicated zk client
+            _zkClient = DedicatedZkClientFactory.getInstance()
+                .buildZkClient(builder.realmAwareZkConnectionConfig,
+                    builder.realmAwareZkClientConfig);
+          } else if (builder.zkClientType == ZkClientType.SHARED) {
+            // Use a realm-aware shared zk client
+            _zkClient = SharedZkClientFactory.getInstance()
+                .buildZkClient(builder.realmAwareZkConnectionConfig,
+                    builder.realmAwareZkClientConfig);
+          } else {
+            _zkClient = new FederatedZkClient(builder.realmAwareZkConnectionConfig,
+                builder.realmAwareZkClientConfig);
+          }
+        } catch (IOException | InvalidRoutingDataException | IllegalStateException e) {
+          throw new HelixException("Not able to connect on multi-realm mode.", e);
+        }
+        break;
+
+      case SINGLE_REALM:
+        // Create a HelixZkClient: Use a SharedZkClient because ZkBaseDataAccessor does not need to
+        // do ephemeral operations.
+        if (builder.zkClientType == ZkClientType.DEDICATED) {
+          // If DEDICATED, then we use a dedicated HelixZkClient because we must support ephemeral
+          // operations
+          _zkClient = DedicatedZkClientFactory.getInstance()
+              .buildZkClient(new HelixZkClient.ZkConnectionConfig(builder.zkAddress),
+                  builder.realmAwareZkClientConfig.createHelixZkClientConfig());
+        } else {
+          _zkClient = SharedZkClientFactory.getInstance()
+              .buildZkClient(new HelixZkClient.ZkConnectionConfig(builder.zkAddress),
+                  builder.realmAwareZkClientConfig.createHelixZkClientConfig());
+          _zkClient
+              .waitUntilConnected(HelixZkClient.DEFAULT_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS);
+        }
+        break;
+      default:
+        throw new HelixException("Invalid RealmMode given: " + builder.realmMode);
+    }
+
+    _usesExternalZkClient = false;
+  }
+
   /**
    * The ZkBaseDataAccessor with custom serializer support of ZkSerializer type.
    * Note: This constructor will use a shared ZkConnection.
    * Do NOT use this for ephemeral node creation/callbacks/session management.
    * Do use this for simple CRUD operations to ZooKeeper.
    * @param zkAddress The zookeeper address
+   *
+   * @deprecated it is recommended to use the builder constructor {@link Builder}
    */
+  @Deprecated
   public ZkBaseDataAccessor(String zkAddress, ZkSerializer zkSerializer) {
     this(zkAddress, zkSerializer, ZkClientType.SHARED);
   }
@@ -142,7 +206,10 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
    * Do NOT use this for ephemeral node creation/callbacks/session management.
    * Do use this for simple CRUD operations to ZooKeeper.
    * @param zkAddress The zookeeper address
+   *
+   * @deprecated it is recommended to use the builder constructor {@link Builder}
    */
+  @Deprecated
   public ZkBaseDataAccessor(String zkAddress, PathBasedZkSerializer pathBasedZkSerializer) {
     this(zkAddress, pathBasedZkSerializer, ZkClientType.SHARED);
   }
@@ -153,7 +220,10 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
    * Does NOT support ephemeral node creation, callbacks, or session management.
    * Uses {@link ZNRecordSerializer} serializer
    * @param zkAddress The zookeeper address
+   *
+   * @deprecated it is recommended to use the builder constructor {@link Builder}
    */
+  @Deprecated
   public ZkBaseDataAccessor(String zkAddress) {
     this(zkAddress, new ZNRecordSerializer());
   }
@@ -166,7 +236,10 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
    * CRUD operations to ZooKeeper.
    * @param zkAddress
    * @param zkClientType
+   *
+   * @deprecated it is recommended to use the builder constructor {@link Builder}
    */
+  @Deprecated
   public ZkBaseDataAccessor(String zkAddress, ZkClientType zkClientType) {
     this(zkAddress, new ZNRecordSerializer(), zkClientType);
   }
@@ -179,21 +252,16 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
    * CRUD operations to ZooKeeper.
    * @param zkAddress
    * @param zkSerializer
+   *
+   * @deprecated it is recommended to use the builder constructor {@link Builder}
    */
+  @Deprecated
   public ZkBaseDataAccessor(String zkAddress, ZkSerializer zkSerializer,
       ZkClientType zkClientType) {
-    switch (zkClientType) {
-    case DEDICATED:
-      _zkClient = DedicatedZkClientFactory.getInstance().buildZkClient(
-          new HelixZkClient.ZkConnectionConfig(zkAddress),
-          new HelixZkClient.ZkClientConfig().setZkSerializer(zkSerializer));
-      break;
-    case SHARED:
-    default:
-      _zkClient = SharedZkClientFactory.getInstance().buildZkClient(
-          new HelixZkClient.ZkConnectionConfig(zkAddress),
-          new HelixZkClient.ZkClientConfig().setZkSerializer(zkSerializer));
-    }
+    RealmAwareZkClient.RealmAwareZkClientConfig clientConfig =
+        new RealmAwareZkClient.RealmAwareZkClientConfig().setZkSerializer(zkSerializer);
+
+    _zkClient = buildRealmAwareZkClient(clientConfig, zkAddress, zkClientType);
     _usesExternalZkClient = false;
   }
 
@@ -206,21 +274,16 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
    * @param zkAddress
    * @param pathBasedZkSerializer
    * @param zkClientType
+   *
+   * @deprecated it is recommended to use the builder constructor {@link Builder}
    */
+  @Deprecated
   public ZkBaseDataAccessor(String zkAddress, PathBasedZkSerializer pathBasedZkSerializer,
       ZkClientType zkClientType) {
-    switch (zkClientType) {
-    case DEDICATED:
-      _zkClient = DedicatedZkClientFactory.getInstance().buildZkClient(
-          new HelixZkClient.ZkConnectionConfig(zkAddress),
-          new HelixZkClient.ZkClientConfig().setZkSerializer(pathBasedZkSerializer));
-      break;
-    case SHARED:
-    default:
-      _zkClient = SharedZkClientFactory.getInstance().buildZkClient(
-          new HelixZkClient.ZkConnectionConfig(zkAddress),
-          new HelixZkClient.ZkClientConfig().setZkSerializer(pathBasedZkSerializer));
-    }
+    RealmAwareZkClient.RealmAwareZkClientConfig clientConfig =
+        new RealmAwareZkClient.RealmAwareZkClientConfig().setZkSerializer(pathBasedZkSerializer);
+
+    _zkClient = buildRealmAwareZkClient(clientConfig, zkAddress, zkClientType);
     _usesExternalZkClient = false;
   }
 
@@ -1256,4 +1319,146 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
       _zkClient.close();
     }
   }
+
+  // TODO: refactor Builder class to remove duplicate code with other Helix Java APIs
+  public static class Builder {
+    private String zkAddress;
+    private RealmAwareZkClient.RealmMode realmMode;
+    private ZkBaseDataAccessor.ZkClientType zkClientType;
+    private RealmAwareZkClient.RealmAwareZkConnectionConfig realmAwareZkConnectionConfig;
+    private RealmAwareZkClient.RealmAwareZkClientConfig realmAwareZkClientConfig;
+
+    public Builder() {
+    }
+
+    public ZkBaseDataAccessor.Builder setZkAddress(String zkAddress) {
+      this.zkAddress = zkAddress;
+      return this;
+    }
+
+    public ZkBaseDataAccessor.Builder setRealmMode(RealmAwareZkClient.RealmMode realmMode) {
+      this.realmMode = realmMode;
+      return this;
+    }
+
+    public ZkBaseDataAccessor.Builder setZkClientType(
+        ZkBaseDataAccessor.ZkClientType zkClientType) {
+      this.zkClientType = zkClientType;
+      return this;
+    }
+
+    public ZkBaseDataAccessor.Builder setRealmAwareZkConnectionConfig(
+        RealmAwareZkClient.RealmAwareZkConnectionConfig realmAwareZkConnectionConfig) {
+      this.realmAwareZkConnectionConfig = realmAwareZkConnectionConfig;
+      return this;
+    }
+
+    public ZkBaseDataAccessor.Builder setRealmAwareZkClientConfig(
+        RealmAwareZkClient.RealmAwareZkClientConfig realmAwareZkClientConfig) {
+      this.realmAwareZkClientConfig = realmAwareZkClientConfig;
+      return this;
+    }
+
+    /**
+     * Returns a <code>ZkBaseDataAccessor</code> instance.
+     * <p>
+     * Note: in multi-realm mode, if and only if ZK client type is set to <code>FEDERATED</code>,
+     * <code>ZkBaseDataAccessor</code> can access to multi-realm. Otherwise, it can only access to
+     * single-ream.
+     */
+    public ZkBaseDataAccessor<?> build() {
+      validate();
+      return new ZkBaseDataAccessor<>(this);
+    }
+
+    /*
+     * Validates the given parameters before building an instance of ZkBaseDataAccessor.
+     */
+    private void validate() {
+      // Resolve RealmMode based on other parameters
+      boolean isZkAddressSet = zkAddress != null && !zkAddress.isEmpty();
+      boolean isZkClientTypeSet = zkClientType != null;
+
+      // If ZkClientType is set, RealmMode must either be single-realm or not set.
+      if (isZkClientTypeSet && realmMode == RealmAwareZkClient.RealmMode.MULTI_REALM) {
+        throw new HelixException(
+            "ZkClientType cannot be set on multi-realm mode!");
+      }
+      // If ZkClientType is not set, default to SHARED
+      if (!isZkClientTypeSet) {
+        zkClientType = ZkBaseDataAccessor.ZkClientType.SHARED;
+      }
+
+      if (realmMode == RealmAwareZkClient.RealmMode.SINGLE_REALM && !isZkAddressSet) {
+        throw new HelixException(
+            "RealmMode cannot be single-realm without a valid ZkAddress set!");
+      }
+
+      if (realmMode == RealmAwareZkClient.RealmMode.MULTI_REALM && isZkAddressSet) {
+        throw new HelixException(
+            "ZkAddress cannot be set on multi-realm mode!");
+      }
+
+      if (realmMode == RealmAwareZkClient.RealmMode.SINGLE_REALM
+          && zkClientType == ZkClientType.FEDERATED) {
+        throw new HelixException(
+            "FederatedZkClient cannot be set on single-realm mode!");
+      }
+
+      if (realmMode == null) {
+        realmMode = isZkAddressSet ? RealmAwareZkClient.RealmMode.SINGLE_REALM
+            : RealmAwareZkClient.RealmMode.MULTI_REALM;
+      }
+
+      // Resolve RealmAwareZkClientConfig
+      if (realmAwareZkClientConfig == null) {
+        realmAwareZkClientConfig = new RealmAwareZkClient.RealmAwareZkClientConfig();
+      }
+
+      // Resolve RealmAwareZkConnectionConfig
+      if (realmAwareZkConnectionConfig == null) {
+        // If not set, create a default one
+        realmAwareZkConnectionConfig =
+            new RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder().build();
+      }
+    }
+  }
+
+  /*
+   * This is used for constructors that do not take a Builder in as a parameter because of
+   * keeping backward-compatibility.
+   */
+  private RealmAwareZkClient buildRealmAwareZkClient(
+      RealmAwareZkClient.RealmAwareZkClientConfig clientConfig, String zkAddress,
+      ZkClientType zkClientType) {
+    if (Boolean.getBoolean(SystemPropertyKeys.MULTI_ZK_ENABLED)) {
+      try {
+        return new FederatedZkClient(
+            new RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder().build(), clientConfig);
+      } catch (IllegalStateException | IOException | InvalidRoutingDataException e) {
+        throw new HelixException("Not able to connect on multi-realm mode.", e);
+      }
+    }
+
+    RealmAwareZkClient zkClient;
+
+    switch (zkClientType) {
+      case DEDICATED:
+        zkClient = DedicatedZkClientFactory.getInstance()
+            .buildZkClient(new HelixZkClient.ZkConnectionConfig(zkAddress),
+                clientConfig.createHelixZkClientConfig());
+        break;
+      case SHARED:
+      default:
+        zkClient = SharedZkClientFactory.getInstance()
+            .buildZkClient(new HelixZkClient.ZkConnectionConfig(zkAddress),
+                clientConfig.createHelixZkClientConfig());
+
+        zkClient
+            .waitUntilConnected(HelixZkClient.DEFAULT_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS);
+        break;
+    }
+
+    return zkClient;
+  }
 }


[helix] 21/49: Add HttpRoutingDataReader (#775)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit 93f3d92553533b36660a0880755640a582cff69c
Author: Hunter Lee <hu...@linkedin.com>
AuthorDate: Tue Feb 25 14:45:28 2020 -0800

    Add HttpRoutingDataReader (#775)
    
    HttpRoutingDataReader is a component used by new ZkClient APIs to make an HTTP read request to the metadata store directory service to retrieve routing data. ZkClient APIs will construct an internal MetadataStoreRoutingData instance based on the raw routing data retrieved from MSDS.
    
    Note: this change contains modifications to MockMSDS because the actual endpoint names changed. The methods and http server contexts have been updated.
---
 .../mock/TestMockMetadataStoreDirectoryServer.java |  84 ------------
 metadata-store-directory-common/pom.xml            |   5 +
 .../constant/MetadataStoreRoutingConstants.java    |   9 ++
 .../mock/MockMetadataStoreDirectoryServer.java     |  68 +++++++---
 .../mock/TestMockMetadataStoreDirectoryServer.java | 118 ++++++++++++++++
 zookeeper-api/pom.xml                              |   5 +
 .../zookeeper/util/HttpRoutingDataReader.java      | 149 +++++++++++++++++++++
 .../zookeeper/util/TestHttpRoutingDataReader.java  | 128 ++++++++++++++++++
 zookeeper-api/zookeeper-api-0.9.2-SNAPSHOT.ivy     |   1 +
 9 files changed, 465 insertions(+), 102 deletions(-)

diff --git a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/mock/TestMockMetadataStoreDirectoryServer.java b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/mock/TestMockMetadataStoreDirectoryServer.java
deleted file mode 100644
index 5e71089..0000000
--- a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/mock/TestMockMetadataStoreDirectoryServer.java
+++ /dev/null
@@ -1,84 +0,0 @@
-package org.apache.helix.rest.metadatastore.mock;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import com.google.common.collect.ImmutableList;
-import org.apache.http.client.methods.CloseableHttpResponse;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClients;
-import org.junit.Test;
-import org.testng.Assert;
-
-
-public class TestMockMetadataStoreDirectoryServer {
-  @Test
-  public void testMockMetadataStoreDirectoryServer()
-      throws IOException {
-    // Create fake routing data
-    Map<String, List<String>> routingData = new HashMap<>();
-    routingData.put("zk-0", ImmutableList.of("sharding-key-0", "sharding-key-1", "sharding-key-2"));
-    routingData.put("zk-1", ImmutableList.of("sharding-key-3", "sharding-key-4", "sharding-key-5"));
-    routingData.put("zk-2", ImmutableList.of("sharding-key-6", "sharding-key-7", "sharding-key-8"));
-
-    // Start MockMSDS
-    String host = "localhost";
-    int port = 11000;
-    String endpoint = "http://" + host + ":" + port;
-    String namespace = "MY-HELIX-NAMESPACE";
-    MockMetadataStoreDirectoryServer server =
-        new MockMetadataStoreDirectoryServer(host, port, namespace, routingData);
-    server.startServer();
-    CloseableHttpClient httpClient = HttpClients.createDefault();
-
-    // Send a GET request
-    String testZkRealm = "zk-0";
-    HttpGet getRequest = new HttpGet(
-        endpoint + MockMetadataStoreDirectoryServer.REST_PREFIX + namespace
-            + MockMetadataStoreDirectoryServer.ZK_REALM_ENDPOINT + testZkRealm);
-    try {
-      CloseableHttpResponse getResponse = httpClient.execute(getRequest);
-      List<String> shardingKeyList = MockMetadataStoreDirectoryServer.OBJECT_MAPPER
-          .readValue(getResponse.getEntity().getContent(), List.class);
-      Assert.assertEquals(shardingKeyList, routingData.get(testZkRealm));
-    } catch (IOException e) {
-      e.printStackTrace();
-    }
-
-    // Try sending a POST request (not supported)
-    HttpPost postRequest = new HttpPost(
-        endpoint + MockMetadataStoreDirectoryServer.REST_PREFIX + namespace
-            + MockMetadataStoreDirectoryServer.ZK_REALM_ENDPOINT + testZkRealm);
-    try {
-      CloseableHttpResponse postResponse = httpClient.execute(postRequest);
-    } catch (IOException e) {
-      e.printStackTrace();
-    }
-
-    // Shutdown
-    server.stopServer();
-  }
-}
diff --git a/metadata-store-directory-common/pom.xml b/metadata-store-directory-common/pom.xml
index cc81a33..1b0d964 100644
--- a/metadata-store-directory-common/pom.xml
+++ b/metadata-store-directory-common/pom.xml
@@ -44,6 +44,11 @@ under the License.
 
   <dependencies>
     <dependency>
+      <groupId>org.apache.httpcomponents</groupId>
+      <artifactId>httpclient</artifactId>
+      <version>4.5.8</version>
+    </dependency>
+    <dependency>
       <groupId>com.fasterxml.jackson.core</groupId>
       <artifactId>jackson-databind</artifactId>
       <version>2.10.2</version>
diff --git a/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/constant/MetadataStoreRoutingConstants.java b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/constant/MetadataStoreRoutingConstants.java
index 2358bcd..e3d541b 100644
--- a/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/constant/MetadataStoreRoutingConstants.java
+++ b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/constant/MetadataStoreRoutingConstants.java
@@ -62,4 +62,13 @@ public class MetadataStoreRoutingConstants {
    * }
    */
   public static final String SHARDING_KEY_PATH_PREFIX = "prefix";
+
+  // System Property Metadata Store Directory Server endpoint key
+  public static final String MSDS_SERVER_ENDPOINT_KEY = "metadataStoreDirectoryServerEndpoint";
+
+  // MSDS resource getAllRealms endpoint string
+  public static final String MSDS_GET_ALL_REALMS_ENDPOINT = "/metadata-store-realms";
+
+  // MSDS resource get all routing data endpoint string
+  public static final String MSDS_GET_ALL_ROUTING_DATA_ENDPOINT = "/routing-data";
 }
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/mock/MockMetadataStoreDirectoryServer.java b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/mock/MockMetadataStoreDirectoryServer.java
similarity index 61%
rename from helix-rest/src/test/java/org/apache/helix/rest/metadatastore/mock/MockMetadataStoreDirectoryServer.java
rename to metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/mock/MockMetadataStoreDirectoryServer.java
index ae0f85d..f2a59d6 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/mock/MockMetadataStoreDirectoryServer.java
+++ b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/mock/MockMetadataStoreDirectoryServer.java
@@ -1,4 +1,4 @@
-package org.apache.helix.rest.metadatastore.mock;
+package org.apache.helix.msdcommon.mock;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -22,13 +22,19 @@ package org.apache.helix.rest.metadatastore.mock;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.net.InetSocketAddress;
+import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ThreadPoolExecutor;
+import java.util.stream.Collectors;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.ImmutableMap;
+import com.sun.net.httpserver.HttpHandler;
 import com.sun.net.httpserver.HttpServer;
+import org.apache.helix.msdcommon.constant.MetadataStoreRoutingConstants;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -41,14 +47,15 @@ public class MockMetadataStoreDirectoryServer {
   private static final Logger LOG = LoggerFactory.getLogger(MockMetadataStoreDirectoryServer.class);
 
   protected static final String REST_PREFIX = "/admin/v2/namespaces/";
-  protected static final String ZK_REALM_ENDPOINT = "/METADATA_STORE_ROUTING_DATA/";
+  protected static final String ZK_REALM_ENDPOINT =
+      MetadataStoreRoutingConstants.MSDS_GET_ALL_REALMS_ENDPOINT;
   protected static final int NOT_IMPLEMENTED = 501;
   protected static final int OK = 200;
   protected static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
 
   protected final String _hostname;
   protected final int _mockServerPort;
-  protected final Map<String, List<String>> _routingDataMap;
+  protected final Map<String, Collection<String>> _routingDataMap;
   protected final String _namespace;
   protected HttpServer _server;
   protected final ThreadPoolExecutor _executor =
@@ -68,7 +75,7 @@ public class MockMetadataStoreDirectoryServer {
    * @param routingData <ZK realm, List of ZK path sharding keys>
    */
   public MockMetadataStoreDirectoryServer(String hostname, int port, String namespace,
-      Map<String, List<String>> routingData) {
+      Map<String, Collection<String>> routingData) {
     if (hostname == null || hostname.isEmpty()) {
       throw new IllegalArgumentException("hostname cannot be null or empty!");
     }
@@ -108,20 +115,45 @@ public class MockMetadataStoreDirectoryServer {
    * Dynamically generates HTTP server contexts based on the routing data given.
    */
   private void generateContexts() {
+    // Get all routing data endpoint
+    // Get the result to be in the MetadataStoreShardingKeysByRealm format
+    List<Map<String, Object>> result = _routingDataMap.entrySet().stream().map(entry -> ImmutableMap
+        .of(MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_REALM, entry.getKey(),
+            MetadataStoreRoutingConstants.SHARDING_KEYS, entry.getValue()))
+        .collect(Collectors.toList());
+    _server.createContext(
+        REST_PREFIX + _namespace + MetadataStoreRoutingConstants.MSDS_GET_ALL_ROUTING_DATA_ENDPOINT,
+        createHttpHandler(ImmutableMap
+            .of(MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_NAMESPACE, _namespace,
+                MetadataStoreRoutingConstants.ROUTING_DATA, result)));
+
+    // Get all realms endpoint
+    _server.createContext(REST_PREFIX + _namespace + ZK_REALM_ENDPOINT, createHttpHandler(
+        ImmutableMap
+            .of(MetadataStoreRoutingConstants.METADATA_STORE_REALMS, _routingDataMap.keySet())));
+
+    // Get all sharding keys for a realm endpoint
     _routingDataMap.forEach((zkRealm, shardingKeyList) -> _server
-        .createContext(REST_PREFIX + _namespace + ZK_REALM_ENDPOINT + zkRealm, httpExchange -> {
-          OutputStream outputStream = httpExchange.getResponseBody();
-          String htmlResponse;
-          if (SupportedHttpVerbs.GET.name().equals(httpExchange.getRequestMethod())) {
-            htmlResponse = OBJECT_MAPPER.writeValueAsString(shardingKeyList);
-            httpExchange.sendResponseHeaders(OK, htmlResponse.length());
-          } else {
-            htmlResponse = httpExchange.getRequestMethod() + " is not supported!\n";
-            httpExchange.sendResponseHeaders(NOT_IMPLEMENTED, htmlResponse.length());
-          }
-          outputStream.write(htmlResponse.getBytes());
-          outputStream.flush();
-          outputStream.close();
-        }));
+        .createContext(REST_PREFIX + _namespace + ZK_REALM_ENDPOINT + "/" + zkRealm,
+            createHttpHandler(ImmutableMap
+                .of(MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_REALM, zkRealm,
+                    MetadataStoreRoutingConstants.SHARDING_KEYS, shardingKeyList))));
+  }
+
+  private HttpHandler createHttpHandler(Map<String, Object> keyValuePairs) {
+    return httpExchange -> {
+      OutputStream outputStream = httpExchange.getResponseBody();
+      String htmlResponse;
+      if (SupportedHttpVerbs.GET.name().equals(httpExchange.getRequestMethod())) {
+        htmlResponse = OBJECT_MAPPER.writeValueAsString(keyValuePairs);
+        httpExchange.sendResponseHeaders(OK, htmlResponse.length());
+      } else {
+        htmlResponse = httpExchange.getRequestMethod() + " is not supported!\n";
+        httpExchange.sendResponseHeaders(NOT_IMPLEMENTED, htmlResponse.length());
+      }
+      outputStream.write(htmlResponse.getBytes());
+      outputStream.flush();
+      outputStream.close();
+    };
   }
 }
diff --git a/metadata-store-directory-common/src/test/java/org/apache/helix/msdcommon/mock/TestMockMetadataStoreDirectoryServer.java b/metadata-store-directory-common/src/test/java/org/apache/helix/msdcommon/mock/TestMockMetadataStoreDirectoryServer.java
new file mode 100644
index 0000000..1dc006a
--- /dev/null
+++ b/metadata-store-directory-common/src/test/java/org/apache/helix/msdcommon/mock/TestMockMetadataStoreDirectoryServer.java
@@ -0,0 +1,118 @@
+package org.apache.helix.msdcommon.mock;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import com.google.common.collect.ImmutableList;
+import org.apache.helix.msdcommon.constant.MetadataStoreRoutingConstants;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.junit.Test;
+import org.testng.Assert;
+
+
+public class TestMockMetadataStoreDirectoryServer {
+  @Test
+  public void testMockMetadataStoreDirectoryServer() throws IOException {
+    // Create fake routing data
+    Map<String, Collection<String>> routingData = new HashMap<>();
+    routingData.put("zk-0", ImmutableList.of("sharding-key-0", "sharding-key-1", "sharding-key-2"));
+    routingData.put("zk-1", ImmutableList.of("sharding-key-3", "sharding-key-4", "sharding-key-5"));
+    routingData.put("zk-2", ImmutableList.of("sharding-key-6", "sharding-key-7", "sharding-key-8"));
+
+    // Start MockMSDS
+    String host = "localhost";
+    int port = 11000;
+    String endpoint = "http://" + host + ":" + port;
+    String namespace = "MY-HELIX-NAMESPACE";
+
+    MockMetadataStoreDirectoryServer server =
+        new MockMetadataStoreDirectoryServer(host, port, namespace, routingData);
+    server.startServer();
+    try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+      // Send a GET request for all routing data
+      HttpGet getRequest = new HttpGet(
+          endpoint + MockMetadataStoreDirectoryServer.REST_PREFIX + namespace
+              + MetadataStoreRoutingConstants.MSDS_GET_ALL_ROUTING_DATA_ENDPOINT);
+
+      CloseableHttpResponse getResponse = httpClient.execute(getRequest);
+      Map<String, Object> resultMap = MockMetadataStoreDirectoryServer.OBJECT_MAPPER
+          .readValue(getResponse.getEntity().getContent(), Map.class);
+      List<Map<String, Object>> routingDataList =
+          (List<Map<String, Object>>) resultMap.get(MetadataStoreRoutingConstants.ROUTING_DATA);
+      Collection<String> allRealms = routingDataList.stream().map(mapEntry -> (String) mapEntry
+          .get(MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_REALM))
+          .collect(Collectors.toSet());
+      Assert.assertEquals(allRealms, routingData.keySet());
+      Map<String, List<String>> retrievedRoutingData = routingDataList.stream().collect(Collectors
+          .toMap(mapEntry -> (String) mapEntry
+                  .get(MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_REALM),
+              mapEntry -> (List<String>) mapEntry
+                  .get(MetadataStoreRoutingConstants.SHARDING_KEYS)));
+      Assert.assertEquals(retrievedRoutingData, routingData);
+
+      // Send a GET request for all realms
+      getRequest = new HttpGet(endpoint + MockMetadataStoreDirectoryServer.REST_PREFIX + namespace
+          + MockMetadataStoreDirectoryServer.ZK_REALM_ENDPOINT);
+      getResponse = httpClient.execute(getRequest);
+      Map<String, Collection<String>> allRealmsMap = MockMetadataStoreDirectoryServer.OBJECT_MAPPER
+          .readValue(getResponse.getEntity().getContent(), Map.class);
+      Assert.assertTrue(
+          allRealmsMap.containsKey(MetadataStoreRoutingConstants.METADATA_STORE_REALMS));
+      allRealms = allRealmsMap.get(MetadataStoreRoutingConstants.METADATA_STORE_REALMS);
+      Assert.assertEquals(allRealms, routingData.keySet());
+
+      // Send a GET request for testZkRealm
+      String testZkRealm = "zk-0";
+      getRequest = new HttpGet(endpoint + MockMetadataStoreDirectoryServer.REST_PREFIX + namespace
+          + MockMetadataStoreDirectoryServer.ZK_REALM_ENDPOINT + "/" + testZkRealm);
+      getResponse = httpClient.execute(getRequest);
+      Map<String, Object> shardingKeysMap = MockMetadataStoreDirectoryServer.OBJECT_MAPPER
+          .readValue(getResponse.getEntity().getContent(), Map.class);
+      Assert.assertTrue(
+          shardingKeysMap.containsKey(MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_REALM));
+      Assert.assertTrue(shardingKeysMap.containsKey(MetadataStoreRoutingConstants.SHARDING_KEYS));
+      String zkRealm =
+          (String) shardingKeysMap.get(MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_REALM);
+      Collection<String> shardingKeyList =
+          (Collection) shardingKeysMap.get(MetadataStoreRoutingConstants.SHARDING_KEYS);
+      Assert.assertEquals(zkRealm, testZkRealm);
+      Assert.assertEquals(shardingKeyList, routingData.get(testZkRealm));
+
+      // Try sending a POST request (not supported)
+      HttpPost postRequest = new HttpPost(
+          endpoint + MockMetadataStoreDirectoryServer.REST_PREFIX + namespace
+              + MockMetadataStoreDirectoryServer.ZK_REALM_ENDPOINT + "/" + testZkRealm);
+      CloseableHttpResponse postResponse = httpClient.execute(postRequest);
+    } finally {
+      // Shutdown
+      server.stopServer();
+    }
+  }
+}
diff --git a/zookeeper-api/pom.xml b/zookeeper-api/pom.xml
index 91b448f..6d3e8c7 100644
--- a/zookeeper-api/pom.xml
+++ b/zookeeper-api/pom.xml
@@ -60,6 +60,11 @@ under the License.
       </exclusions>
     </dependency>
     <dependency>
+      <groupId>org.apache.httpcomponents</groupId>
+      <artifactId>httpclient</artifactId>
+      <version>4.5.8</version>
+    </dependency>
+    <dependency>
       <groupId>org.codehaus.jackson</groupId>
       <artifactId>jackson-mapper-asl</artifactId>
       <version>1.9.13</version>
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/util/HttpRoutingDataReader.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/util/HttpRoutingDataReader.java
new file mode 100644
index 0000000..b795de0
--- /dev/null
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/util/HttpRoutingDataReader.java
@@ -0,0 +1,149 @@
+package org.apache.helix.zookeeper.util;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.helix.msdcommon.constant.MetadataStoreRoutingConstants;
+import org.apache.helix.msdcommon.datamodel.MetadataStoreRoutingData;
+import org.apache.helix.msdcommon.datamodel.TrieRoutingData;
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
+import org.apache.http.HttpEntity;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.DefaultBackoffStrategy;
+import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+
+
+public class HttpRoutingDataReader {
+  private static final String MSDS_ENDPOINT =
+      System.getProperty(MetadataStoreRoutingConstants.MSDS_SERVER_ENDPOINT_KEY);
+  private static final int HTTP_TIMEOUT_IN_MS = 5000;
+
+  /** Double-checked locking requires that the following fields be volatile */
+  private static volatile Map<String, List<String>> _rawRoutingData;
+  private static volatile MetadataStoreRoutingData _metadataStoreRoutingData;
+
+  /**
+   * This class is a Singleton.
+   */
+  private HttpRoutingDataReader() {
+  }
+
+  /**
+   * Fetches routing data from the data source via HTTP.
+   * @return a mapping from "metadata store realm addresses" to lists of
+   * "metadata store sharding keys", where the sharding keys in a value list all route to
+   * the realm address in the key disallows a meaningful mapping to be returned
+   */
+  public static Map<String, List<String>> getRawRoutingData() throws IOException {
+    if (MSDS_ENDPOINT == null || MSDS_ENDPOINT.isEmpty()) {
+      throw new IllegalStateException(
+          "HttpRoutingDataReader was unable to find a valid MSDS endpoint String in System Properties!");
+    }
+    if (_rawRoutingData == null) {
+      synchronized (HttpRoutingDataReader.class) {
+        if (_rawRoutingData == null) {
+          String routingDataJson = getAllRoutingData();
+          // Update the reference if reading routingData over HTTP is successful
+          _rawRoutingData = parseRoutingData(routingDataJson);
+        }
+      }
+    }
+    return _rawRoutingData;
+  }
+
+  /**
+   * Returns the routing data read from MSDS in a MetadataStoreRoutingData format.
+   * @return
+   * @throws IOException if there is an issue connecting to MSDS
+   * @throws InvalidRoutingDataException if the raw routing data is not valid
+   */
+  public static MetadataStoreRoutingData getMetadataStoreRoutingData()
+      throws IOException, InvalidRoutingDataException {
+    if (_metadataStoreRoutingData == null) {
+      synchronized (HttpRoutingDataReader.class) {
+        if (_metadataStoreRoutingData == null) {
+          _metadataStoreRoutingData = new TrieRoutingData(getRawRoutingData());
+        }
+      }
+    }
+    return _metadataStoreRoutingData;
+  }
+
+  /**
+   * Makes an HTTP call to fetch all routing data.
+   * @return
+   * @throws IOException
+   */
+  private static String getAllRoutingData() throws IOException {
+    // Note that MSDS_ENDPOINT should provide high-availability - it risks becoming a single point of failure if it's backed by a single IP address/host
+    // Retry count is 3 by default.
+    HttpGet requestAllData = new HttpGet(
+        MSDS_ENDPOINT + MetadataStoreRoutingConstants.MSDS_GET_ALL_ROUTING_DATA_ENDPOINT);
+
+    // Define timeout configs
+    RequestConfig config = RequestConfig.custom().setConnectTimeout(HTTP_TIMEOUT_IN_MS)
+        .setConnectionRequestTimeout(HTTP_TIMEOUT_IN_MS).setSocketTimeout(HTTP_TIMEOUT_IN_MS)
+        .build();
+
+    try (CloseableHttpClient httpClient = HttpClients.custom().setDefaultRequestConfig(config)
+        .setConnectionBackoffStrategy(new DefaultBackoffStrategy())
+        .setRetryHandler(new DefaultHttpRequestRetryHandler()).build()) {
+      // Return the JSON because try-resources clause closes the CloseableHttpResponse
+      HttpEntity entity = httpClient.execute(requestAllData).getEntity();
+      if (entity == null) {
+        throw new IOException("Response's entity is null!");
+      }
+      return EntityUtils.toString(entity, "UTF-8");
+    }
+  }
+
+  /**
+   * Returns the raw routing data in a Map< ZkRealm, List of shardingKeys > format.
+   * @param routingDataJson
+   * @return
+   */
+  private static Map<String, List<String>> parseRoutingData(String routingDataJson)
+      throws IOException {
+    if (routingDataJson != null) {
+      @SuppressWarnings("unchecked")
+      Map<String, Object> resultMap = new ObjectMapper().readValue(routingDataJson, Map.class);
+      @SuppressWarnings("unchecked")
+      List<Map<String, Object>> routingDataList =
+          (List<Map<String, Object>>) resultMap.get(MetadataStoreRoutingConstants.ROUTING_DATA);
+      @SuppressWarnings("unchecked")
+      Map<String, List<String>> routingData = routingDataList.stream().collect(Collectors.toMap(
+          realmKeyPair -> (String) realmKeyPair
+              .get(MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_REALM),
+          mapEntry -> (List<String>) mapEntry.get(MetadataStoreRoutingConstants.SHARDING_KEYS)));
+      return routingData;
+    }
+    return Collections.emptyMap();
+  }
+}
\ No newline at end of file
diff --git a/zookeeper-api/src/test/java/org/apache/helix/zookeeper/util/TestHttpRoutingDataReader.java b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/util/TestHttpRoutingDataReader.java
new file mode 100644
index 0000000..83ee471
--- /dev/null
+++ b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/util/TestHttpRoutingDataReader.java
@@ -0,0 +1,128 @@
+package org.apache.helix.zookeeper.util;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import com.google.common.collect.ImmutableSet;
+import org.apache.helix.msdcommon.constant.MetadataStoreRoutingConstants;
+import org.apache.helix.msdcommon.datamodel.MetadataStoreRoutingData;
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
+import org.apache.helix.msdcommon.mock.MockMetadataStoreDirectoryServer;
+import org.apache.helix.zookeeper.impl.ZkTestBase;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+
+public class TestHttpRoutingDataReader extends ZkTestBase {
+  private MockMetadataStoreDirectoryServer _msdsServer;
+  private Map<String, Collection<String>> _testRawRoutingData;
+  private final String _host = "localhost";
+  private final int _port = 1991;
+  private final String _namespace = "TestHttpRoutingDataReader";
+
+  @BeforeClass
+  public void beforeClass() throws IOException {
+    // Create fake routing data
+    _testRawRoutingData = new HashMap<>();
+    _testRawRoutingData
+        .put("zk-0", ImmutableSet.of("/sharding-key-0", "/sharding-key-1", "/sharding-key-2"));
+    _testRawRoutingData
+        .put("zk-1", ImmutableSet.of("/sharding-key-3", "/sharding-key-4", "/sharding-key-5"));
+    _testRawRoutingData
+        .put("zk-2", ImmutableSet.of("/sharding-key-6", "/sharding-key-7", "/sharding-key-8"));
+
+    // Start MockMSDS
+    _msdsServer =
+        new MockMetadataStoreDirectoryServer(_host, _port, _namespace, _testRawRoutingData);
+    _msdsServer.startServer();
+
+    // Register the endpoint as a System property
+    String msdsEndpoint = "http://" + _host + ":" + _port + "/admin/v2/namespaces/" + _namespace;
+    System.setProperty(MetadataStoreRoutingConstants.MSDS_SERVER_ENDPOINT_KEY, msdsEndpoint);
+  }
+
+  @AfterClass
+  public void afterClass() {
+    _msdsServer.stopServer();
+  }
+
+  @Test
+  public void testGetRawRoutingData() throws IOException {
+    Map<String, List<String>> rawRoutingData = HttpRoutingDataReader.getRawRoutingData();
+    _testRawRoutingData.forEach((realm, keys) -> Assert
+        .assertEquals(new HashSet(rawRoutingData.get(realm)), new HashSet(keys)));
+  }
+
+  @Test(dependsOnMethods = "testGetRawRoutingData")
+  public void testGetMetadataStoreRoutingData() throws IOException, InvalidRoutingDataException {
+    MetadataStoreRoutingData data = HttpRoutingDataReader.getMetadataStoreRoutingData();
+    Map<String, String> allMappings = data.getAllMappingUnderPath("/");
+    Map<String, Set<String>> groupedMappings = allMappings.entrySet().stream().collect(Collectors
+        .groupingBy(Map.Entry::getValue,
+            Collectors.mapping(Map.Entry::getKey, Collectors.toSet())));
+    _testRawRoutingData.forEach((realm, keys) -> {
+      Assert.assertEquals(groupedMappings.get(realm), new HashSet(keys));
+    });
+  }
+
+  /**
+   * Test that the static methods in HttpRoutingDataReader returns consistent results even though MSDS's data have been updated.
+   */
+  @Test(dependsOnMethods = "testGetMetadataStoreRoutingData")
+  public void testStaticMapping() throws IOException, InvalidRoutingDataException {
+    // Modify routing data
+    String newRealm = "newRealm";
+    _testRawRoutingData.put(newRealm, ImmutableSet.of("/newKey"));
+
+    // Kill MSDS and restart with a new mapping
+    _msdsServer.stopServer();
+    _msdsServer =
+        new MockMetadataStoreDirectoryServer(_host, _port, _namespace, _testRawRoutingData);
+    _msdsServer.startServer();
+
+    // HttpRoutingDataReader should still return old data because it's static
+    // Make sure the results don't contain the new realm
+    Map<String, List<String>> rawRoutingData = HttpRoutingDataReader.getRawRoutingData();
+    Assert.assertFalse(rawRoutingData.containsKey(newRealm));
+
+    // Remove newRealm and check for equality
+    _testRawRoutingData.remove(newRealm);
+    Assert.assertEquals(rawRoutingData.keySet(), _testRawRoutingData.keySet());
+    _testRawRoutingData.forEach((realm, keys) -> Assert
+        .assertEquals(new HashSet(rawRoutingData.get(realm)), new HashSet(keys)));
+
+    MetadataStoreRoutingData data = HttpRoutingDataReader.getMetadataStoreRoutingData();
+    Map<String, String> allMappings = data.getAllMappingUnderPath("/");
+    Map<String, Set<String>> groupedMappings = allMappings.entrySet().stream().collect(Collectors
+        .groupingBy(Map.Entry::getValue,
+            Collectors.mapping(Map.Entry::getKey, Collectors.toSet())));
+    Assert.assertFalse(groupedMappings.containsKey(newRealm));
+  }
+}
diff --git a/zookeeper-api/zookeeper-api-0.9.2-SNAPSHOT.ivy b/zookeeper-api/zookeeper-api-0.9.2-SNAPSHOT.ivy
index 19a9ed8..2f7b6cb 100644
--- a/zookeeper-api/zookeeper-api-0.9.2-SNAPSHOT.ivy
+++ b/zookeeper-api/zookeeper-api-0.9.2-SNAPSHOT.ivy
@@ -46,5 +46,6 @@ under the License.
 		<dependency org="org.codehaus.jackson" name="jackson-core-asl" rev="1.8.5" force="true" conf="compile->compile(*),master(*);runtime->runtime(*)"/>
 		<dependency org="org.codehaus.jackson" name="jackson-mapper-asl" rev="1.8.5" force="true" conf="compile->compile(*),master(*);runtime->runtime(*)"/>
 		<dependency org="commons-cli" name="commons-cli" rev="1.2" force="true" conf="compile->compile(*),master(*);runtime->runtime(*)"/>
+		<dependency org="org.apache.httpcomponents" name="httpclient" rev="4.5.8" force="true" conf="compile->compile(*),master(*);runtime->runtime(*)"/>
 	</dependencies>
 </ivy-module>


[helix] 35/49: Make ClusterSetup realm-aware (#861)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit dd4c3838ba0ad92cedf6f3a16132d17354843be8
Author: Hunter Lee <hu...@linkedin.com>
AuthorDate: Wed Mar 11 19:35:42 2020 -0700

    Make ClusterSetup realm-aware (#861)
    
    We make ClusterSetup, a Helix Java API, realm-aware so that this could be used in a multi-ZK environment.
    
    Changelist:
    Add a Builder to enable users to set internal ZkClient parameters
    Add the realm-aware behavior in existing constructors
    Update ConfigAccessor to reflect the change in the logic
---
 .../java/org/apache/helix/SystemPropertyKeys.java  |   6 +
 .../main/java/org/apache/helix/ConfigAccessor.java |  66 ++++++----
 .../java/org/apache/helix/SystemPropertyKeys.java  |  62 ---------
 .../org/apache/helix/manager/zk/ZKHelixAdmin.java  |   5 +-
 .../helix/manager/zk/ZkBaseDataAccessor.java       |   5 +-
 .../java/org/apache/helix/tools/ClusterSetup.java  | 143 ++++++++++++++++++---
 .../zookeeper/api/client/RealmAwareZkClient.java   |   5 +-
 7 files changed, 178 insertions(+), 114 deletions(-)

diff --git a/helix-common/src/main/java/org/apache/helix/SystemPropertyKeys.java b/helix-common/src/main/java/org/apache/helix/SystemPropertyKeys.java
index bcb8405..a40dbe9 100644
--- a/helix-common/src/main/java/org/apache/helix/SystemPropertyKeys.java
+++ b/helix-common/src/main/java/org/apache/helix/SystemPropertyKeys.java
@@ -26,6 +26,9 @@ public class SystemPropertyKeys {
   // ZKHelixManager
   public static final String CLUSTER_MANAGER_VERSION = "cluster-manager-version.properties";
 
+  // soft constraints weight definitions
+  public static final String SOFT_CONSTRAINT_WEIGHTS = "soft-constraint-weight.properties";
+
   public static final String FLAPPING_TIME_WINDOW = "helixmanager.flappingTimeWindow";
 
   // max disconnect count during the flapping time window to trigger HelixManager flapping handling
@@ -57,4 +60,7 @@ public class SystemPropertyKeys {
 
   // MBean monitor for helix.
   public static final String HELIX_MONITOR_TIME_WINDOW_LENGTH_MS = "helix.monitor.slidingTimeWindow.ms";
+
+  // Multi-ZK mode enable/disable flag
+  public static final String MULTI_ZK_ENABLED = "helix.multiZkEnabled";
 }
diff --git a/helix-core/src/main/java/org/apache/helix/ConfigAccessor.java b/helix-core/src/main/java/org/apache/helix/ConfigAccessor.java
index b0c1add..d0b3bba 100644
--- a/helix-core/src/main/java/org/apache/helix/ConfigAccessor.java
+++ b/helix-core/src/main/java/org/apache/helix/ConfigAccessor.java
@@ -29,7 +29,6 @@ import java.util.Map;
 import java.util.TreeMap;
 
 import org.apache.helix.manager.zk.ZKUtil;
-import org.apache.helix.manager.zk.ZNRecordSerializer;
 import org.apache.helix.model.ClusterConfig;
 import org.apache.helix.model.ConfigScope;
 import org.apache.helix.model.HelixConfigScope;
@@ -44,6 +43,7 @@ import org.apache.helix.util.StringTemplate;
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
 import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
+import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
 import org.apache.helix.zookeeper.impl.client.FederatedZkClient;
 import org.apache.helix.zookeeper.impl.factory.SharedZkClientFactory;
 import org.slf4j.Logger;
@@ -86,18 +86,23 @@ public class ConfigAccessor {
    * Constructor that creates a realm-aware ConfigAccessor using a builder.
    * @param builder
    */
-  private ConfigAccessor(Builder builder) throws IOException, InvalidRoutingDataException {
+  private ConfigAccessor(Builder builder) {
     switch (builder._realmMode) {
       case MULTI_REALM:
-        _zkClient = new FederatedZkClient(builder._realmAwareZkConnectionConfig,
-            builder._realmAwareZkClientConfig);
+        try {
+          _zkClient = new FederatedZkClient(builder._realmAwareZkConnectionConfig,
+              builder._realmAwareZkClientConfig.setZkSerializer(new ZNRecordSerializer()));
+        } catch (IOException | InvalidRoutingDataException | IllegalStateException e) {
+          throw new HelixException("Failed to create ConfigAccessor!", e);
+        }
         break;
       case SINGLE_REALM:
         // Create a HelixZkClient: Use a SharedZkClient because ConfigAccessor does not need to do
         // ephemeral operations
         _zkClient = SharedZkClientFactory.getInstance()
-            .buildZkClient(builder._realmAwareZkConnectionConfig.createZkConnectionConfig(),
-                builder._realmAwareZkClientConfig.createHelixZkClientConfig());
+            .buildZkClient(new HelixZkClient.ZkConnectionConfig(builder._zkAddress),
+                builder._realmAwareZkClientConfig.createHelixZkClientConfig()
+                    .setZkSerializer(new ZNRecordSerializer()));
         break;
       default:
         throw new HelixException("Invalid RealmMode given: " + builder._realmMode);
@@ -123,24 +128,26 @@ public class ConfigAccessor {
    * ConfigAccessor only deals with Helix's data models like ResourceConfig.
    * @param zkAddress
    */
+  @Deprecated
   public ConfigAccessor(String zkAddress) {
-    // First, attempt to connect on multi-realm mode using FederatedZkClient
-    RealmAwareZkClient zkClient;
-    try {
-      zkClient = new FederatedZkClient(
-          new RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder().build(),
-          new RealmAwareZkClient.RealmAwareZkClientConfig());
-    } catch (IOException | InvalidRoutingDataException | IllegalStateException e) {
-      // Connecting multi-realm failed - fall back to creating it on single-realm mode using the given ZK address
-      LOG.info(
-          "ConfigAccessor: not able to connect on multi-realm mode; connecting single-realm mode to ZK: {}",
-          zkAddress, e);
-      zkClient = SharedZkClientFactory.getInstance()
-          .buildZkClient(new HelixZkClient.ZkConnectionConfig(zkAddress),
-              new HelixZkClient.ZkClientConfig().setZkSerializer(new ZNRecordSerializer()));
-    }
-    _zkClient = zkClient;
     _usesExternalZkClient = false;
+
+    // If the multi ZK config is enabled, use FederatedZkClient on multi-realm mode
+    if (Boolean.parseBoolean(System.getProperty(SystemPropertyKeys.MULTI_ZK_ENABLED))) {
+      try {
+        _zkClient = new FederatedZkClient(
+            new RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder().build(),
+            new RealmAwareZkClient.RealmAwareZkClientConfig()
+                .setZkSerializer(new ZNRecordSerializer()));
+        return;
+      } catch (IOException | InvalidRoutingDataException | IllegalStateException e) {
+        throw new HelixException("Failed to create ConfigAccessor!", e);
+      }
+    }
+
+    _zkClient = SharedZkClientFactory.getInstance()
+        .buildZkClient(new HelixZkClient.ZkConnectionConfig(zkAddress),
+            new HelixZkClient.ZkClientConfig().setZkSerializer(new ZNRecordSerializer()));
   }
 
   /**
@@ -956,7 +963,7 @@ public class ConfigAccessor {
       return this;
     }
 
-    public ConfigAccessor build() throws Exception {
+    public ConfigAccessor build() {
       validate();
       return new ConfigAccessor(this);
     }
@@ -971,17 +978,20 @@ public class ConfigAccessor {
         throw new HelixException(
             "ConfigAccessor: RealmMode cannot be single-realm without a valid ZkAddress set!");
       }
+      if (_realmMode == RealmAwareZkClient.RealmMode.MULTI_REALM && isZkAddressSet) {
+        throw new HelixException(
+            "ConfigAccessor: You cannot set the ZkAddress on multi-realm mode!");
+      }
+
       if (_realmMode == null) {
         _realmMode = isZkAddressSet ? RealmAwareZkClient.RealmMode.SINGLE_REALM
             : RealmAwareZkClient.RealmMode.MULTI_REALM;
       }
 
       // Resolve RealmAwareZkClientConfig
-      boolean isZkClientConfigSet = _realmAwareZkClientConfig != null;
-      // Resolve which clientConfig to use
-      _realmAwareZkClientConfig =
-          isZkClientConfigSet ? _realmAwareZkClientConfig.createHelixZkClientConfig()
-              : new HelixZkClient.ZkClientConfig().setZkSerializer(new ZNRecordSerializer());
+      if (_realmAwareZkClientConfig == null) {
+        _realmAwareZkClientConfig = new RealmAwareZkClient.RealmAwareZkClientConfig();
+      }
 
       // Resolve RealmAwareZkConnectionConfig
       if (_realmAwareZkConnectionConfig == null) {
diff --git a/helix-core/src/main/java/org/apache/helix/SystemPropertyKeys.java b/helix-core/src/main/java/org/apache/helix/SystemPropertyKeys.java
deleted file mode 100644
index 2d824cb..0000000
--- a/helix-core/src/main/java/org/apache/helix/SystemPropertyKeys.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package org.apache.helix;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-public class SystemPropertyKeys {
-  // Task Driver
-  public static final String TASK_CONFIG_LIMITATION = "helixTask.configsLimitation";
-
-  // ZKHelixManager
-  public static final String CLUSTER_MANAGER_VERSION = "cluster-manager-version.properties";
-  // soft constraints weight definitions
-  public static final String SOFT_CONSTRAINT_WEIGHTS = "soft-constraint-weight.properties";
-
-  public static final String FLAPPING_TIME_WINDOW = "helixmanager.flappingTimeWindow";
-
-  // max disconnect count during the flapping time window to trigger HelixManager flapping handling
-  public static final String MAX_DISCONNECT_THRESHOLD = "helixmanager.maxDisconnectThreshold";
-
-  public static final String ZK_SESSION_TIMEOUT = "zk.session.timeout";
-
-  public static final String ZK_CONNECTION_TIMEOUT = "zk.connection.timeout";
-
-  @Deprecated
-  public static final String ZK_REESTABLISHMENT_CONNECTION_TIMEOUT =
-      "zk.connectionReEstablishment.timeout";
-
-  public static final String ZK_WAIT_CONNECTED_TIMEOUT = "helixmanager.waitForConnectedTimeout";
-
-  public static final String PARTICIPANT_HEALTH_REPORT_LATENCY =
-      "helixmanager.participantHealthReport.reportLatency";
-
-  // Indicate monitoring level of the HelixManager metrics
-  public static final String MONITOR_LEVEL = "helixmanager.monitorLevel";
-
-  // CallbackHandler
-  public static final String ASYNC_BATCH_MODE_ENABLED = "helix.callbackhandler.isAsyncBatchModeEnabled";
-
-  public static final String LEGACY_ASYNC_BATCH_MODE_ENABLED = "isAsyncBatchModeEnabled";
-
-  // Controller
-  public static final String CONTROLLER_MESSAGE_PURGE_DELAY = "helix.controller.stages.MessageGenerationPhase.messagePurgeDelay";
-
-  // MBean monitor for helix.
-  public static final String HELIX_MONITOR_TIME_WINDOW_LENGTH_MS = "helix.monitor.slidingTimeWindow.ms";
-}
diff --git a/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixAdmin.java b/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixAdmin.java
index 77d8103..d7c40ff 100644
--- a/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixAdmin.java
+++ b/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixAdmin.java
@@ -78,6 +78,7 @@ import org.apache.helix.tools.DefaultIdealStateCalculator;
 import org.apache.helix.util.HelixUtil;
 import org.apache.helix.util.RebalanceUtil;
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.apache.helix.zookeeper.impl.factory.SharedZkClientFactory;
 import org.apache.helix.zookeeper.zkclient.DataUpdater;
@@ -93,7 +94,7 @@ public class ZKHelixAdmin implements HelixAdmin {
   private static final String MAINTENANCE_ZNODE_ID = "maintenance";
   private static final int DEFAULT_SUPERCLUSTER_REPLICA = 3;
 
-  private final HelixZkClient _zkClient;
+  private final RealmAwareZkClient _zkClient;
   private final ConfigAccessor _configAccessor;
   // true if ZKHelixAdmin was instantiated with a HelixZkClient, false otherwise
   // This is used for close() to determine how ZKHelixAdmin should close the underlying ZkClient
@@ -102,7 +103,7 @@ public class ZKHelixAdmin implements HelixAdmin {
   private static Logger logger = LoggerFactory.getLogger(ZKHelixAdmin.class);
 
   @Deprecated
-  public ZKHelixAdmin(HelixZkClient zkClient) {
+  public ZKHelixAdmin(RealmAwareZkClient zkClient) {
     _zkClient = zkClient;
     _configAccessor = new ConfigAccessor(zkClient);
     _usesExternalZkClient = true;
diff --git a/helix-core/src/main/java/org/apache/helix/manager/zk/ZkBaseDataAccessor.java b/helix-core/src/main/java/org/apache/helix/manager/zk/ZkBaseDataAccessor.java
index bc84a1d..f08ba55 100644
--- a/helix-core/src/main/java/org/apache/helix/manager/zk/ZkBaseDataAccessor.java
+++ b/helix-core/src/main/java/org/apache/helix/manager/zk/ZkBaseDataAccessor.java
@@ -34,6 +34,7 @@ import org.apache.helix.api.exceptions.HelixMetaDataAccessException;
 import org.apache.helix.store.zk.ZNode;
 import org.apache.helix.util.HelixUtil;
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.apache.helix.zookeeper.impl.factory.DedicatedZkClientFactory;
 import org.apache.helix.zookeeper.impl.factory.SharedZkClientFactory;
@@ -102,14 +103,14 @@ public class ZkBaseDataAccessor<T> implements BaseDataAccessor<T> {
 
   private static Logger LOG = LoggerFactory.getLogger(ZkBaseDataAccessor.class);
 
-  private final HelixZkClient _zkClient;
+  private final RealmAwareZkClient _zkClient;
   // true if ZkBaseDataAccessor was instantiated with a HelixZkClient, false otherwise
   // This is used for close() to determine how ZkBaseDataAccessor should close the underlying
   // ZkClient
   private final boolean _usesExternalZkClient;
 
   @Deprecated
-  public ZkBaseDataAccessor(HelixZkClient zkClient) {
+  public ZkBaseDataAccessor(RealmAwareZkClient zkClient) {
     if (zkClient == null) {
       throw new NullPointerException("zkclient is null");
     }
diff --git a/helix-core/src/main/java/org/apache/helix/tools/ClusterSetup.java b/helix-core/src/main/java/org/apache/helix/tools/ClusterSetup.java
index 7ac20a1..4fcfc97 100644
--- a/helix-core/src/main/java/org/apache/helix/tools/ClusterSetup.java
+++ b/helix-core/src/main/java/org/apache/helix/tools/ClusterSetup.java
@@ -39,14 +39,11 @@ import org.apache.commons.cli.ParseException;
 import org.apache.helix.HelixAdmin;
 import org.apache.helix.HelixConstants;
 import org.apache.helix.HelixException;
-import org.apache.helix.PropertyKey.Builder;
-import org.apache.helix.zookeeper.datamodel.ZNRecord;
+import org.apache.helix.PropertyKey;
+import org.apache.helix.SystemPropertyKeys;
 import org.apache.helix.manager.zk.ZKHelixAdmin;
 import org.apache.helix.manager.zk.ZKHelixDataAccessor;
-import org.apache.helix.manager.zk.ZNRecordSerializer;
 import org.apache.helix.manager.zk.ZkBaseDataAccessor;
-import org.apache.helix.zookeeper.api.client.HelixZkClient;
-import org.apache.helix.zookeeper.impl.factory.SharedZkClientFactory;
 import org.apache.helix.model.BuiltInStateModelDefinitions;
 import org.apache.helix.model.ClusterConfig;
 import org.apache.helix.model.ClusterConstraints;
@@ -62,7 +59,14 @@ import org.apache.helix.model.LiveInstance;
 import org.apache.helix.model.StateModelDefinition;
 import org.apache.helix.model.builder.ConstraintItemBuilder;
 import org.apache.helix.model.builder.HelixConfigScopeBuilder;
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
 import org.apache.helix.util.HelixUtil;
+import org.apache.helix.zookeeper.api.client.HelixZkClient;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
+import org.apache.helix.zookeeper.datamodel.ZNRecord;
+import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
+import org.apache.helix.zookeeper.impl.client.FederatedZkClient;
+import org.apache.helix.zookeeper.impl.factory.SharedZkClientFactory;
 import org.apache.helix.zookeeper.zkclient.DataUpdater;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -135,37 +139,74 @@ public class ClusterSetup {
   public static final String removeConstraint = "removeConstraint";
 
   private static final Logger _logger = LoggerFactory.getLogger(ClusterSetup.class);
-  private final String _zkServerAddress;
-  private final HelixZkClient _zkClient;
-  // true if ZkBaseDataAccessor was instantiated with a HelixZkClient, false otherwise
+  private final RealmAwareZkClient _zkClient;
+  // true if ZkBaseDataAccessor was instantiated with a RealmAwareZkClient, false otherwise
   // This is used for close() to determine how ZkBaseDataAccessor should close the underlying
   // ZkClient
   private final boolean _usesExternalZkClient;
   private final HelixAdmin _admin;
 
+  @Deprecated
   public ClusterSetup(String zkServerAddress) {
-    _zkServerAddress = zkServerAddress;
-    _zkClient = SharedZkClientFactory.getInstance()
-        .buildZkClient(new HelixZkClient.ZkConnectionConfig(_zkServerAddress));
-    _zkClient.setZkSerializer(new ZNRecordSerializer());
+    // If the multi ZK config is enabled, use FederatedZkClient on multi-realm mode
+    if (Boolean.parseBoolean(System.getProperty(SystemPropertyKeys.MULTI_ZK_ENABLED))) {
+      try {
+        _zkClient = new FederatedZkClient(
+            new RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder().build(),
+            new RealmAwareZkClient.RealmAwareZkClientConfig()
+                .setZkSerializer(new ZNRecordSerializer()));
+      } catch (IOException | InvalidRoutingDataException | IllegalStateException e) {
+        throw new HelixException("Failed to create ConfigAccessor!", e);
+      }
+    } else {
+      _zkClient = SharedZkClientFactory.getInstance()
+          .buildZkClient(new HelixZkClient.ZkConnectionConfig(zkServerAddress));
+      _zkClient.setZkSerializer(new ZNRecordSerializer());
+    }
+
     _admin = new ZKHelixAdmin(_zkClient);
     _usesExternalZkClient = false;
   }
 
-  public ClusterSetup(HelixZkClient zkClient) {
-    _zkServerAddress = zkClient.getServers();
+  @Deprecated
+  public ClusterSetup(RealmAwareZkClient zkClient) {
     _zkClient = zkClient;
     _admin = new ZKHelixAdmin(_zkClient);
     _usesExternalZkClient = true;
   }
 
-  public ClusterSetup(HelixZkClient zkClient, HelixAdmin zkHelixAdmin) {
-    _zkServerAddress = zkClient.getServers();
+  @Deprecated
+  public ClusterSetup(RealmAwareZkClient zkClient, HelixAdmin zkHelixAdmin) {
     _zkClient = zkClient;
     _admin = zkHelixAdmin;
     _usesExternalZkClient = true;
   }
 
+  private ClusterSetup(Builder builder) {
+    switch (builder._realmMode) {
+      case MULTI_REALM:
+        try {
+          _zkClient = new FederatedZkClient(builder._realmAwareZkConnectionConfig,
+              builder._realmAwareZkClientConfig.setZkSerializer(new ZNRecordSerializer()));
+          break;
+        } catch (IOException | InvalidRoutingDataException | IllegalStateException e) {
+          throw new HelixException("Failed to create ClusterSetup!", e);
+        }
+      case SINGLE_REALM:
+        // Create a HelixZkClient: Use a SharedZkClient because ClusterSetup does not need to do
+        // ephemeral operations
+        _zkClient = SharedZkClientFactory.getInstance()
+            .buildZkClient(new HelixZkClient.ZkConnectionConfig(builder._zkAddress),
+                builder._realmAwareZkClientConfig.createHelixZkClientConfig()
+                    .setZkSerializer(new ZNRecordSerializer()));
+        break;
+      default:
+        throw new HelixException("Invalid RealmMode given: " + builder._realmMode);
+    }
+    _admin = new ZKHelixAdmin(_zkClient);
+    _usesExternalZkClient = false;
+  }
+
   /**
    * Closes any stateful resources in ClusterSetup.
    */
@@ -225,7 +266,7 @@ public class ClusterSetup {
   public void dropInstanceFromCluster(String clusterName, String instanceId) {
     ZKHelixDataAccessor accessor =
         new ZKHelixDataAccessor(clusterName, new ZkBaseDataAccessor<ZNRecord>(_zkClient));
-    Builder keyBuilder = accessor.keyBuilder();
+    PropertyKey.Builder keyBuilder = accessor.keyBuilder();
 
     InstanceConfig instanceConfig = InstanceConfig.toInstanceConfig(instanceId);
     instanceId = instanceConfig.getInstanceName();
@@ -276,7 +317,7 @@ public class ClusterSetup {
 
     ZKHelixDataAccessor accessor =
         new ZKHelixDataAccessor(clusterName, new ZkBaseDataAccessor<ZNRecord>(_zkClient));
-    Builder keyBuilder = accessor.keyBuilder();
+    PropertyKey.Builder keyBuilder = accessor.keyBuilder();
 
     // If new instance config is missing, new instance is not in good state and therefore
     // should not perform swap.
@@ -1570,4 +1611,70 @@ public class ClusterSetup {
     int ret = processCommandLineArgs(args);
     System.exit(ret);
   }
+
+  public static class Builder {
+    private String _zkAddress;
+    private RealmAwareZkClient.RealmMode _realmMode;
+    private RealmAwareZkClient.RealmAwareZkConnectionConfig _realmAwareZkConnectionConfig;
+    private RealmAwareZkClient.RealmAwareZkClientConfig _realmAwareZkClientConfig;
+
+    public Builder() {
+    }
+
+    public Builder setZkAddress(String zkAddress) {
+      _zkAddress = zkAddress;
+      return this;
+    }
+
+    public Builder setRealmMode(RealmAwareZkClient.RealmMode realmMode) {
+      _realmMode = realmMode;
+      return this;
+    }
+
+    public Builder setRealmAwareZkConnectionConfig(
+        RealmAwareZkClient.RealmAwareZkConnectionConfig realmAwareZkConnectionConfig) {
+      _realmAwareZkConnectionConfig = realmAwareZkConnectionConfig;
+      return this;
+    }
+
+    public Builder setRealmAwareZkClientConfig(
+        RealmAwareZkClient.RealmAwareZkClientConfig realmAwareZkClientConfig) {
+      _realmAwareZkClientConfig = realmAwareZkClientConfig;
+      return this;
+    }
+
+    public ClusterSetup build() {
+      validate();
+      return new ClusterSetup(this);
+    }
+
+    private void validate() {
+      // Resolve RealmMode based on other parameters
+      boolean isZkAddressSet = _zkAddress != null && !_zkAddress.isEmpty();
+      if (_realmMode == RealmAwareZkClient.RealmMode.SINGLE_REALM && !isZkAddressSet) {
+        throw new HelixException(
+            "ClusterSetup: RealmMode cannot be single-realm without a valid ZkAddress set!");
+      }
+      if (_realmMode == RealmAwareZkClient.RealmMode.MULTI_REALM && isZkAddressSet) {
+        throw new HelixException(
+            "ClusterSetup: You cannot set the ZkAddress on multi-realm mode!");
+      }
+      if (_realmMode == null) {
+        _realmMode = isZkAddressSet ? RealmAwareZkClient.RealmMode.SINGLE_REALM
+            : RealmAwareZkClient.RealmMode.MULTI_REALM;
+      }
+
+      // Resolve RealmAwareZkClientConfig
+      if (_realmAwareZkClientConfig == null) {
+        _realmAwareZkClientConfig = new RealmAwareZkClient.RealmAwareZkClientConfig();
+      }
+
+      // Resolve RealmAwareZkConnectionConfig
+      if (_realmAwareZkConnectionConfig == null) {
+        // If not set, create a default one
+        _realmAwareZkConnectionConfig =
+            new RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder().build();
+      }
+    }
+  }
 }
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/RealmAwareZkClient.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/RealmAwareZkClient.java
index fb10073..40b6c54 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/RealmAwareZkClient.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/RealmAwareZkClient.java
@@ -59,7 +59,8 @@ public interface RealmAwareZkClient {
    * MULTI_REALM: CRUD and change subscription are supported. Operations involving EPHEMERAL CreateMode will throw an UnsupportedOperationException.
    */
   enum RealmMode {
-    SINGLE_REALM, MULTI_REALM
+    SINGLE_REALM,
+    MULTI_REALM
   }
 
   int DEFAULT_OPERATION_TIMEOUT = Integer.MAX_VALUE;
@@ -465,7 +466,7 @@ public interface RealmAwareZkClient {
     // Data access configs
     protected long _operationRetryTimeout = DEFAULT_OPERATION_TIMEOUT;
 
-    // Others
+    // Serializer
     protected PathBasedZkSerializer _zkSerializer;
 
     // Monitoring


[helix] 12/49: Implement getAllMappingUnderPath and getMetadataStoreRealm in ZkMetadataStoreDirectory

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit ba99639c62f7f50e36e96a4b0edeee4148aa1929
Author: Neal Sun <ne...@gmail.com>
AuthorDate: Wed Feb 12 15:16:28 2020 -0800

    Implement getAllMappingUnderPath and getMetadataStoreRealm in ZkMetadataStoreDirectory
    
    This PR implements getAllMappingUnderPath and getMetadataStoreRealm in ZkMetadataStoreDirectory. Also, getRoutingData in ZkRoutingDataReader is updated : not having realm addresses and not having sharding keys for some realm addresses no longer raise exceptions. We now allow realms to not have sharding keys and return them as a part of routing data.
---
 .../helix/rest/metadatastore/TrieRoutingData.java  |  6 +---
 .../metadatastore/ZkMetadataStoreDirectory.java    | 16 +++++++---
 .../accessor/ZkRoutingDataReader.java              | 27 ++++++-----------
 .../rest/metadatastore/TestTrieRoutingData.java    | 13 --------
 .../TestZkMetadataStoreDirectory.java              | 35 ++++++++++++++++++++++
 .../accessor/TestZkRoutingDataReader.java          | 18 +++++------
 6 files changed, 64 insertions(+), 51 deletions(-)

diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/TrieRoutingData.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/TrieRoutingData.java
index 28add4c..923f818 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/TrieRoutingData.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/TrieRoutingData.java
@@ -162,15 +162,11 @@ public class TrieRoutingData implements MetadataStoreRoutingData {
    * @throws InvalidRoutingDataException - when there is an empty sharding key (edge case that
    *           always renders the routing data invalid); when there is a sharding key which already
    *           contains a sharding key (invalid); when there is a sharding key that is a part of
-   *           another sharding key (invalid)
+   *           another sharding key (invalid); when a sharding key doesn't have a leading delimiter
    */
   private void constructTrie(Map<String, List<String>> routingData)
       throws InvalidRoutingDataException {
     for (Map.Entry<String, List<String>> entry : routingData.entrySet()) {
-      if (entry.getValue().isEmpty()) {
-        throw new InvalidRoutingDataException(
-            "Realm address does not have associating sharding keys: " + entry.getKey());
-      }
       for (String shardingKey : entry.getValue()) {
         // Missing leading delimiter is invalid
         if (shardingKey.isEmpty() || !shardingKey.substring(0, 1).equals(DELIMITER)) {
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java
index 536d058..3be9b72 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java
@@ -79,6 +79,8 @@ public class ZkMetadataStoreDirectory implements MetadataStoreDirectory, Routing
       // Populate realmToShardingKeys with ZkRoutingDataReader
       _realmToShardingKeysMap.put(routingEntry.getKey(),
           _routingDataReaderMap.get(routingEntry.getKey()).getRoutingData());
+      _routingDataMap.put(routingEntry.getKey(),
+          new TrieRoutingData(_realmToShardingKeysMap.get(routingEntry.getKey())));
     }
   }
 
@@ -119,14 +121,20 @@ public class ZkMetadataStoreDirectory implements MetadataStoreDirectory, Routing
 
   @Override
   public Map<String, String> getAllMappingUnderPath(String namespace, String path) {
-    // TODO: get it from routingData
-    throw new UnsupportedOperationException();
+    if (!_routingDataMap.containsKey(namespace)) {
+      throw new NoSuchElementException(
+          "Failed to get all mapping under path: Namespace " + namespace + " is not found!");
+    }
+    return _routingDataMap.get(namespace).getAllMappingUnderPath(path);
   }
 
   @Override
   public String getMetadataStoreRealm(String namespace, String shardingKey) {
-    // TODO: get it from routingData
-    throw new UnsupportedOperationException();
+    if (!_routingDataMap.containsKey(namespace)) {
+      throw new NoSuchElementException(
+          "Failed to get metadata store realm: Namespace " + namespace + " is not found!");
+    }
+    return _routingDataMap.get(namespace).getMetadataStoreRealm(shardingKey);
   }
 
   @Override
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataReader.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataReader.java
index 9decf23..44ce110 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataReader.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataReader.java
@@ -72,36 +72,27 @@ public class ZkRoutingDataReader implements MetadataStoreRoutingDataReader, IZkD
   /**
    * Returns (realm, list of ZK path sharding keys) mappings.
    * @return Map <realm, list of ZK path sharding keys>
-   * @throws InvalidRoutingDataException
+   * @throws InvalidRoutingDataException - when the node on
+   *           MetadataStoreRoutingConstants.ROUTING_DATA_PATH is missing
    */
-  public Map<String, List<String>> getRoutingData()
-      throws InvalidRoutingDataException {
+  public Map<String, List<String>> getRoutingData() throws InvalidRoutingDataException {
     Map<String, List<String>> routingData = new HashMap<>();
-    List<String> children;
+    List<String> allRealmAddresses;
     try {
-      children = _zkClient.getChildren(MetadataStoreRoutingConstants.ROUTING_DATA_PATH);
+      allRealmAddresses = _zkClient.getChildren(MetadataStoreRoutingConstants.ROUTING_DATA_PATH);
     } catch (ZkNoNodeException e) {
       throw new InvalidRoutingDataException(
           "Routing data directory ZNode " + MetadataStoreRoutingConstants.ROUTING_DATA_PATH
               + " does not exist. Routing ZooKeeper address: " + _zkAddress);
     }
-    if (children == null || children.isEmpty()) {
-      throw new InvalidRoutingDataException(
-          "There are no metadata store realms defined. Routing ZooKeeper address: " + _zkAddress);
-    }
-    for (String child : children) {
+    for (String realmAddress : allRealmAddresses) {
       ZNRecord record =
-          _zkClient.readData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + child);
+          _zkClient.readData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + realmAddress);
       List<String> shardingKeys =
           record.getListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY);
-      if (shardingKeys == null || shardingKeys.isEmpty()) {
-        throw new InvalidRoutingDataException(
-            "Realm address ZNode " + MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + child
-                + " does not have a value for key "
-                + MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY
-                + ". Routing ZooKeeper address: " + _zkAddress);
+      if (shardingKeys != null) {
+        routingData.put(realmAddress, shardingKeys);
       }
-      routingData.put(child, shardingKeys);
     }
     return routingData;
   }
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestTrieRoutingData.java b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestTrieRoutingData.java
index bf71456..4de68a6 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestTrieRoutingData.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestTrieRoutingData.java
@@ -67,19 +67,6 @@ public class TestTrieRoutingData {
   }
 
   @Test
-  public void testConstructionEmptyShardingKeys() {
-    Map<String, List<String>> routingData = new HashMap<>();
-    routingData.put("realmAddress1", Collections.emptyList());
-    try {
-      new TrieRoutingData(routingData);
-      Assert.fail("Expecting InvalidRoutingDataException");
-    } catch (InvalidRoutingDataException e) {
-      Assert.assertTrue(e.getMessage()
-          .contains("Realm address does not have associating sharding keys: realmAddress1"));
-    }
-  }
-
-  @Test
   public void testConstructionShardingKeyNoLeadingSlash() {
     Map<String, List<String>> routingData = new HashMap<>();
     routingData.put("realmAddress1", Arrays.asList("/g", "/h/i", "/h/j"));
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java
index c0741ee..68b93b3 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java
@@ -21,6 +21,7 @@ package org.apache.helix.rest.metadatastore;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -152,6 +153,40 @@ public class TestZkMetadataStoreDirectory extends AbstractTestClass {
   }
 
   @Test(dependsOnMethods = "testGetAllShardingKeysInRealm")
+  public void testGetAllMappingUnderPath() {
+    Map<String, String> mappingFromRoot = new HashMap<>();
+    TEST_SHARDING_KEYS_1.forEach(shardingKey -> {
+      mappingFromRoot.put(shardingKey, TEST_REALM_1);
+    });
+    TEST_SHARDING_KEYS_2.forEach(shardingKey -> {
+      mappingFromRoot.put(shardingKey, TEST_REALM_2);
+    });
+
+    Map<String, String> mappingFromLeaf = new HashMap<>(1);
+    mappingFromLeaf.put("/sharding/key/1/a", TEST_REALM_1);
+
+    for (String namespace : _routingZkAddrMap.keySet()) {
+      Assert.assertEquals(_metadataStoreDirectory.getAllMappingUnderPath(namespace, "/"),
+          mappingFromRoot);
+      Assert.assertEquals(
+          _metadataStoreDirectory.getAllMappingUnderPath(namespace, "/sharding/key/1/a"),
+          mappingFromLeaf);
+    }
+  }
+
+  @Test(dependsOnMethods = "testGetAllMappingUnderPath")
+  public void testGetMetadataStoreRealm() {
+    for (String namespace : _routingZkAddrMap.keySet()) {
+      Assert.assertEquals(
+          _metadataStoreDirectory.getMetadataStoreRealm(namespace, "/sharding/key/1/a/x/y/z"),
+          TEST_REALM_1);
+      Assert.assertEquals(
+          _metadataStoreDirectory.getMetadataStoreRealm(namespace, "/sharding/key/1/d/x/y/z"),
+          TEST_REALM_2);
+    }
+  }
+
+  @Test(dependsOnMethods = "testGetMetadataStoreRealm")
   public void testDataChangeCallback()
       throws Exception {
     // For all namespaces (Routing ZKs), add an extra sharding key to TEST_REALM_1
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataReader.java b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataReader.java
index aa46429..5781a85 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataReader.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataReader.java
@@ -104,11 +104,10 @@ public class TestZkRoutingDataReader extends AbstractTestClass {
     _baseAccessor.create(MetadataStoreRoutingConstants.ROUTING_DATA_PATH, new ZNRecord("test"),
         AccessOption.PERSISTENT);
     try {
-      _zkRoutingDataReader.getRoutingData();
-      Assert.fail("Expecting InvalidRoutingDataException");
+      Map<String, List<String>> routingData = _zkRoutingDataReader.getRoutingData();
+      Assert.assertEquals(routingData.size(), 0);
     } catch (InvalidRoutingDataException e) {
-      Assert.assertTrue(e.getMessage().contains(
-          "There are no metadata store realms defined. Routing ZooKeeper address: " + ZK_ADDR));
+      Assert.fail("Not expecting InvalidRoutingDataException");
     }
   }
 
@@ -120,14 +119,11 @@ public class TestZkRoutingDataReader extends AbstractTestClass {
     _baseAccessor.create(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/testRealmAddress1",
         testZnRecord1, AccessOption.PERSISTENT);
     try {
-      _zkRoutingDataReader.getRoutingData();
-      Assert.fail("Expecting InvalidRoutingDataException");
+      Map<String, List<String>> routingData = _zkRoutingDataReader.getRoutingData();
+      Assert.assertEquals(routingData.size(), 1);
+      Assert.assertTrue(routingData.get("testRealmAddress1").isEmpty());
     } catch (InvalidRoutingDataException e) {
-      Assert.assertTrue(e.getMessage().contains(
-          "Realm address ZNode " + MetadataStoreRoutingConstants.ROUTING_DATA_PATH
-              + "/testRealmAddress1 does not have a value for key "
-              + MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY
-              + ". Routing ZooKeeper address: " + ZK_ADDR));
+      Assert.fail("Not expecting InvalidRoutingDataException");
     }
   }
 }


[helix] 20/49: [helix-rest] Add endpoint to get namespace routing data (#799)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit 7cc4841ccf6e8bcdd624ba36ca3c405269a950be
Author: Huizhi Lu <ih...@gmail.com>
AuthorDate: Sun Feb 23 15:19:37 2020 -0800

    [helix-rest] Add endpoint to get namespace routing data (#799)
    
    RealmAwareZkClient construction needs a REST endpoint to get routing data in a namespace. This endpoint will help with reducing REST calls down to one single call to read all raw routing data in ZK.
    
    This commit adds Java API getNamespaceRoutingData() and an endpoint "GET /routing-data".
---
 .../rest/metadatastore/MetadataStoreDirectory.java |  9 ++++
 .../metadatastore/ZkMetadataStoreDirectory.java    | 10 ++++
 .../MetadataStoreShardingKeysByRealm.java          | 56 ++++++++++++++++++++++
 .../MetadataStoreDirectoryAccessor.java            | 38 +++++++++++++++
 .../server/TestMetadataStoreDirectoryAccessor.java | 53 +++++++++++++++++++-
 .../constant/MetadataStoreRoutingConstants.java    |  3 ++
 6 files changed, 168 insertions(+), 1 deletion(-)

diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/MetadataStoreDirectory.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/MetadataStoreDirectory.java
index 032362a..4630d50 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/MetadataStoreDirectory.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/MetadataStoreDirectory.java
@@ -20,6 +20,7 @@ package org.apache.helix.rest.metadatastore;
  */
 
 import java.util.Collection;
+import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
 
@@ -52,6 +53,14 @@ public interface MetadataStoreDirectory extends AutoCloseable {
   Collection<String> getAllShardingKeys(String namespace);
 
   /**
+   * Returns routing data in the given namespace.
+   *
+   * @param namespace namespace in metadata store directory.
+   * @return Routing data map: realm -> List of sharding keys
+   */
+  Map<String, List<String>> getNamespaceRoutingData(String namespace);
+
+  /**
    * Returns all path-based sharding keys in the given namespace and the realm.
    * @param namespace
    * @param realm
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java
index 7e972ab..5b64f7b 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java
@@ -112,6 +112,16 @@ public class ZkMetadataStoreDirectory implements MetadataStoreDirectory, Routing
   }
 
   @Override
+  public Map<String, List<String>> getNamespaceRoutingData(String namespace) {
+    Map<String, List<String>> routingData = _realmToShardingKeysMap.get(namespace);
+    if (routingData == null) {
+      throw new NoSuchElementException("Namespace " + namespace + " does not exist!");
+    }
+
+    return routingData;
+  }
+
+  @Override
   public Collection<String> getAllShardingKeysInRealm(String namespace, String realm) {
     if (!_realmToShardingKeysMap.containsKey(namespace)) {
       throw new NoSuchElementException("Namespace " + namespace + " does not exist!");
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/datamodel/MetadataStoreShardingKeysByRealm.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/datamodel/MetadataStoreShardingKeysByRealm.java
new file mode 100644
index 0000000..0f54350
--- /dev/null
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/datamodel/MetadataStoreShardingKeysByRealm.java
@@ -0,0 +1,56 @@
+package org.apache.helix.rest.metadatastore.datamodel;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Collection;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+
+
+@JsonPropertyOrder({"realm", "shardingKeys"})
+public class MetadataStoreShardingKeysByRealm {
+  private String realm;
+  private Collection<String> shardingKeys;
+
+  @JsonCreator
+  public MetadataStoreShardingKeysByRealm(@JsonProperty String realm,
+      @JsonProperty Collection<String> shardingKeys) {
+    this.realm = realm;
+    this.shardingKeys = shardingKeys;
+  }
+
+  @JsonProperty
+  public String getRealm() {
+    return realm;
+  }
+
+  @JsonProperty
+  public Collection<String> getShardingKeys() {
+    return shardingKeys;
+  }
+
+  @Override
+  public String toString() {
+    return "MetadataStoreShardingKeysByRealm{" + "realm='" + realm + '\'' + ", shardingKeys="
+        + shardingKeys + '}';
+  }
+}
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/server/resources/metadatastore/MetadataStoreDirectoryAccessor.java b/helix-rest/src/main/java/org/apache/helix/rest/server/resources/metadatastore/MetadataStoreDirectoryAccessor.java
index bc6571a..0f22d81 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/server/resources/metadatastore/MetadataStoreDirectoryAccessor.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/server/resources/metadatastore/MetadataStoreDirectoryAccessor.java
@@ -43,6 +43,7 @@ import org.apache.helix.rest.common.HelixRestUtils;
 import org.apache.helix.rest.metadatastore.MetadataStoreDirectory;
 import org.apache.helix.rest.metadatastore.ZkMetadataStoreDirectory;
 import org.apache.helix.rest.metadatastore.datamodel.MetadataStoreShardingKey;
+import org.apache.helix.rest.metadatastore.datamodel.MetadataStoreShardingKeysByRealm;
 import org.apache.helix.rest.server.resources.AbstractResource;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -181,6 +182,43 @@ public class MetadataStoreDirectoryAccessor extends AbstractResource {
   }
 
   /**
+   * Gets routing data in current namespace.
+   *
+   * - "HTTP GET /routing-data"
+   * -- Response example:
+   * {
+   *   "namespace" : "my-namespace",
+   *   "routingData" : [ {
+   *     "realm" : "realm-1",
+   *     "shardingKeys" : [ "/sharding/key/1/d", "/sharding/key/1/e", "/sharding/key/1/f" ]
+   *   }, {
+   *     "realm" : "realm-2",
+   *     "shardingKeys" : [ "/sharding/key/1/a", "/sharding/key/1/b", "/sharding/key/1/c" ]
+   *   } ]
+   * }
+   */
+  @GET
+  @Path("/routing-data")
+  public Response getRoutingData() {
+    Map<String, List<String>> rawRoutingData;
+    try {
+      rawRoutingData = _metadataStoreDirectory.getNamespaceRoutingData(_namespace);
+    } catch (NoSuchElementException ex) {
+      return notFound(ex.getMessage());
+    }
+
+    List<MetadataStoreShardingKeysByRealm> shardingKeysByRealm = rawRoutingData.entrySet().stream()
+        .map(entry -> new MetadataStoreShardingKeysByRealm(entry.getKey(), entry.getValue()))
+        .collect(Collectors.toList());
+
+    Map<String, Object> responseMap = ImmutableMap
+        .of(MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_NAMESPACE, _namespace,
+            MetadataStoreRoutingConstants.ROUTING_DATA, shardingKeysByRealm);
+
+    return JSONRepresentation(responseMap);
+  }
+
+  /**
    * Gets all path-based sharding keys for a queried realm at endpoint:
    * "GET /metadata-store-realms/{realm}/sharding-keys"
    * <p>
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/server/TestMetadataStoreDirectoryAccessor.java b/helix-rest/src/test/java/org/apache/helix/rest/server/TestMetadataStoreDirectoryAccessor.java
index 6a9c598..ee49239 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/server/TestMetadataStoreDirectoryAccessor.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/server/TestMetadataStoreDirectoryAccessor.java
@@ -282,9 +282,60 @@ public class TestMetadataStoreDirectoryAccessor extends AbstractTestClass {
   }
 
   /*
-   * Tests REST endpoint: "GET /metadata-store-realms/{realm}/sharding-keys"
+   * Tests REST endpoint: "GET /routing-data"
    */
   @Test(dependsOnMethods = "testGetShardingKeysInNamespace")
+  public void testGetRoutingData() throws IOException {
+    /*
+     * responseBody:
+     * {
+     *   "namespace" : "test-namespace",
+     *   "routingData" : [ {
+     *     "realm" : "testRealm2",
+     *     "shardingKeys" : [ "/sharding/key/1/d", "/sharding/key/1/e", "/sharding/key/1/f" ]
+     *   }, {
+     *     "realm" : "testRealm1",
+     *     "shardingKeys" : [ "/sharding/key/1/a", "/sharding/key/1/b", "/sharding/key/1/c" ]
+     *   } ]
+     * }
+     */
+    String responseBody =
+        new JerseyUriRequestBuilder(TEST_NAMESPACE_URI_PREFIX + "/routing-data")
+            .isBodyReturnExpected(true).get(this);
+
+    // It is safe to cast the object and suppress warnings.
+    @SuppressWarnings("unchecked")
+    Map<String, Object> queriedShardingKeysMap = OBJECT_MAPPER.readValue(responseBody, Map.class);
+
+    // Check fields.
+    Assert.assertEquals(queriedShardingKeysMap.keySet(), ImmutableSet
+        .of(MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_NAMESPACE,
+            MetadataStoreRoutingConstants.ROUTING_DATA));
+
+    // Check namespace in json response.
+    Assert.assertEquals(
+        queriedShardingKeysMap.get(MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_NAMESPACE),
+        TEST_NAMESPACE);
+
+    @SuppressWarnings("unchecked")
+    List<Map<String, Object>> queriedShardingKeys =
+        (List<Map<String, Object>>) queriedShardingKeysMap
+            .get(MetadataStoreRoutingConstants.ROUTING_DATA);
+
+    Set<Map<String, Object>> queriedShardingKeysSet = new HashSet<>(queriedShardingKeys);
+    Set<Map<String, Object>> expectedShardingKeysSet = ImmutableSet.of(ImmutableMap
+        .of(MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_REALM, TEST_REALM_1,
+            MetadataStoreRoutingConstants.SHARDING_KEYS, TEST_SHARDING_KEYS_1), ImmutableMap
+        .of(MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_REALM, TEST_REALM_2,
+            MetadataStoreRoutingConstants.SHARDING_KEYS, TEST_SHARDING_KEYS_2));
+
+    Assert.assertEquals(queriedShardingKeysSet, expectedShardingKeysSet);
+  }
+
+  /*
+   * Tests REST endpoint: "GET /metadata-store-realms/{realm}/sharding-keys"
+   */
+  @Test(dependsOnMethods = "testGetRoutingData")
   public void testGetShardingKeysInRealm() throws IOException {
     // Test NOT_FOUND response for a non existed realm.
     new JerseyUriRequestBuilder(
diff --git a/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/constant/MetadataStoreRoutingConstants.java b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/constant/MetadataStoreRoutingConstants.java
index 13e78b0..2358bcd 100644
--- a/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/constant/MetadataStoreRoutingConstants.java
+++ b/metadata-store-directory-common/src/main/java/org/apache/helix/msdcommon/constant/MetadataStoreRoutingConstants.java
@@ -43,6 +43,9 @@ public class MetadataStoreRoutingConstants {
   /** Field name in JSON REST response of getting sharding keys. */
   public static final String SHARDING_KEYS = "shardingKeys";
 
+  /** Field name in JSON REST response of getting routing data. */
+  public static final String ROUTING_DATA = "routingData";
+
   /** Field name in JSON REST response related to one single sharding key. */
   public static final String SINGLE_SHARDING_KEY = "shardingKey";
 


[helix] 43/49: Make ZkUtil realm-aware (#896)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit 873a2a4baa7b5eae209b0bd51974c6fd3ad5aace
Author: Hunter Lee <hu...@linkedin.com>
AuthorDate: Fri Mar 13 17:33:11 2020 -0700

    Make ZkUtil realm-aware (#896)
    
    There were some places in the code that were missed in previous PRs. This makes ZkUtil realm-aware by replacing HelixZkClient with RealmAwareZkClient.
---
 .../java/org/apache/helix/manager/zk/ZKUtil.java   | 49 +++++++++++++++-------
 1 file changed, 34 insertions(+), 15 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 70042ff..d2c80c8 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
@@ -19,6 +19,7 @@ package org.apache.helix.manager.zk;
  * under the License.
  */
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -26,9 +27,12 @@ import java.util.List;
 import org.apache.helix.HelixException;
 import org.apache.helix.InstanceType;
 import org.apache.helix.PropertyPathBuilder;
+import org.apache.helix.SystemPropertyKeys;
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
 import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
+import org.apache.helix.zookeeper.impl.client.FederatedZkClient;
 import org.apache.helix.zookeeper.impl.factory.DedicatedZkClientFactory;
 import org.apache.helix.zookeeper.zkclient.DataUpdater;
 import org.apache.zookeeper.CreateMode;
@@ -40,6 +44,8 @@ import org.slf4j.LoggerFactory;
 /**
  * Using this ZKUtil class for production purposes is NOT recommended since a lot of the static
  * methods require a ZkClient instance to be passed in.
+ *
+ * NOTE: Ephemeral operations will not be supported on multi-zk mode!
  */
 public final class ZKUtil {
   private static Logger logger = LoggerFactory.getLogger(ZKUtil.class);
@@ -56,7 +62,7 @@ public final class ZKUtil {
    * @return
    */
   public static boolean isClusterSetup(String clusterName, String zkAddress) {
-    HelixZkClient zkClient = getHelixZkClient(zkAddress);
+    RealmAwareZkClient zkClient = getHelixZkClient(zkAddress);
     boolean result;
     try {
       result = isClusterSetup(clusterName, zkClient);
@@ -129,7 +135,7 @@ public final class ZKUtil {
    */
   public static boolean isInstanceSetup(String zkAddress, String clusterName, String instanceName,
       InstanceType type) {
-    HelixZkClient zkClient = getHelixZkClient(zkAddress);
+    RealmAwareZkClient zkClient = getHelixZkClient(zkAddress);
     boolean result;
     try {
       result = isInstanceSetup(zkClient, clusterName, instanceName, type);
@@ -179,7 +185,7 @@ public final class ZKUtil {
    * @param list
    */
   public static void createChildren(String zkAddress, String parentPath, List<ZNRecord> list) {
-    HelixZkClient zkClient = getHelixZkClient(zkAddress);
+    RealmAwareZkClient zkClient = getHelixZkClient(zkAddress);
     try {
       createChildren(zkClient, parentPath, list);
     } finally {
@@ -205,7 +211,7 @@ public final class ZKUtil {
    * @param nodeRecord
    */
   public static void createChildren(String zkAddress, String parentPath, ZNRecord nodeRecord) {
-    HelixZkClient zkClient = getHelixZkClient(zkAddress);
+    RealmAwareZkClient zkClient = getHelixZkClient(zkAddress);
     try {
       createChildren(zkClient, parentPath, nodeRecord);
     } finally {
@@ -230,7 +236,7 @@ public final class ZKUtil {
    * @param list
    */
   public static void dropChildren(String zkAddress, String parentPath, List<ZNRecord> list) {
-    HelixZkClient zkClient = getHelixZkClient(zkAddress);
+    RealmAwareZkClient zkClient = getHelixZkClient(zkAddress);
     try {
       dropChildren(zkClient, parentPath, list);
     } finally {
@@ -256,7 +262,7 @@ public final class ZKUtil {
    * @param nodeRecord
    */
   public static void dropChildren(String zkAddress, String parentPath, ZNRecord nodeRecord) {
-    HelixZkClient zkClient = getHelixZkClient(zkAddress);
+    RealmAwareZkClient zkClient = getHelixZkClient(zkAddress);
     try {
       dropChildren(zkClient, parentPath, nodeRecord);
     } finally {
@@ -280,7 +286,7 @@ public final class ZKUtil {
    * @return
    */
   public static List<ZNRecord> getChildren(String zkAddress, String path) {
-    HelixZkClient zkClient = getHelixZkClient(zkAddress);
+    RealmAwareZkClient zkClient = getHelixZkClient(zkAddress);
     List<ZNRecord> result;
     try {
       result = getChildren(zkClient, path);
@@ -323,7 +329,7 @@ public final class ZKUtil {
    */
   public static void updateIfExists(String zkAddress, String path, final ZNRecord record,
       boolean mergeOnUpdate) {
-    HelixZkClient zkClient = getHelixZkClient(zkAddress);
+    RealmAwareZkClient zkClient = getHelixZkClient(zkAddress);
     try {
       updateIfExists(zkClient, path, record, mergeOnUpdate);
     } finally {
@@ -355,7 +361,7 @@ public final class ZKUtil {
    */
   public static void createOrMerge(String zkAddress, String path, final ZNRecord record,
       final boolean persistent, final boolean mergeOnUpdate) {
-    HelixZkClient zkClient = getHelixZkClient(zkAddress);
+    RealmAwareZkClient zkClient = getHelixZkClient(zkAddress);
     try {
       createOrMerge(zkClient, path, record, persistent, mergeOnUpdate);
     } finally {
@@ -410,7 +416,7 @@ public final class ZKUtil {
    */
   public static void createOrUpdate(String zkAddress, String path, final ZNRecord record,
       final boolean persistent, final boolean mergeOnUpdate) {
-    HelixZkClient zkClient = getHelixZkClient(zkAddress);
+    RealmAwareZkClient zkClient = getHelixZkClient(zkAddress);
     try {
       createOrUpdate(zkClient, path, record, persistent, mergeOnUpdate);
     } finally {
@@ -459,7 +465,7 @@ public final class ZKUtil {
    */
   public static void asyncCreateOrMerge(String zkAddress, String path, final ZNRecord record,
       final boolean persistent, final boolean mergeOnUpdate) {
-    HelixZkClient zkClient = getHelixZkClient(zkAddress);
+    RealmAwareZkClient zkClient = getHelixZkClient(zkAddress);
     try {
       asyncCreateOrMerge(zkClient, path, record, persistent, mergeOnUpdate);
     } finally {
@@ -512,7 +518,7 @@ public final class ZKUtil {
    */
   public static void createOrReplace(String zkAddress, String path, final ZNRecord record,
       final boolean persistent) {
-    HelixZkClient zkClient = getHelixZkClient(zkAddress);
+    RealmAwareZkClient zkClient = getHelixZkClient(zkAddress);
     try {
       createOrReplace(zkClient, path, record, persistent);
     } finally {
@@ -555,7 +561,7 @@ public final class ZKUtil {
    */
   public static void subtract(String zkAddress, final String path,
       final ZNRecord recordTosubtract) {
-    HelixZkClient zkClient = getHelixZkClient(zkAddress);
+    RealmAwareZkClient zkClient = getHelixZkClient(zkAddress);
     try {
       subtract(zkClient, path, recordTosubtract);
     } finally {
@@ -601,10 +607,23 @@ public final class ZKUtil {
   }
 
   /**
-   * Returns a dedicated ZkClient.
+   * Returns a dedicated ZkClient. A federatedZkClient will be used on multi-zk mode.
+   * WARNING: ephemeral operations will not be supported on multi-zk mode!
    * @return
    */
-  private static HelixZkClient getHelixZkClient(String zkAddr) {
+  private static RealmAwareZkClient getHelixZkClient(String zkAddr) {
+    if (Boolean.getBoolean(SystemPropertyKeys.MULTI_ZK_ENABLED)) {
+      try {
+        // Create realm-aware ZkClient.
+        RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig =
+            new RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder().build();
+        RealmAwareZkClient.RealmAwareZkClientConfig clientConfig =
+            new RealmAwareZkClient.RealmAwareZkClientConfig();
+        return new FederatedZkClient(connectionConfig, clientConfig);
+      } catch (IllegalArgumentException | IOException | InvalidRoutingDataException e) {
+        throw new HelixException("Not able to connect on realm-aware mode", e);
+      }
+    }
     if (zkAddr == null || zkAddr.isEmpty()) {
       throw new HelixException("ZK Address given is either null or empty!");
     }


[helix] 14/49: Add RealmAwareZkClient and RealmAwareZkClientFactory interfaces (#745)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit e429c520c33a3d628fc3c3241251ec55fde8a268
Author: Hunter Lee <hu...@linkedin.com>
AuthorDate: Thu Feb 13 17:27:10 2020 -0800

    Add RealmAwareZkClient and RealmAwareZkClientFactory interfaces (#745)
    
    RealmAwareZkClient and RealmAwareZkClientFactory are going to be the top-level interfaces for other realm-aware ZkClient APIs (think FederatedZkClient, DedicatedZkClient, etc.).
    
    RealmAwareZkClient will support all the existing interface methods that HelixZkClient supports.
    RealmAwareZkClientFactory will be the interface implemented by SharedZkClientFactory and DedicatedZkClientFactory.
---
 .../apache/helix/manager/zk/ZKHelixManager.java    |   4 +-
 .../java/org/apache/helix/common/ZkTestBase.java   |   3 +-
 .../helix/zookeeper/api/client/HelixZkClient.java  | 173 +++++++++++++++++++++
 .../zookeeper/api/client/RealmAwareZkClient.java   |  94 ++++++-----
 .../api/factory/RealmAwareZkClientFactory.java     |  22 +++
 .../impl/factory/DedicatedZkClientFactory.java     |  17 ++
 .../impl/factory/HelixZkClientFactory.java         |   3 +-
 .../impl/factory/SharedZkClientFactory.java        |  17 ++
 8 files changed, 292 insertions(+), 41 deletions(-)

diff --git a/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixManager.java b/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixManager.java
index 532ef44..5fa307b 100644
--- a/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixManager.java
+++ b/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixManager.java
@@ -1145,11 +1145,11 @@ public class ZKHelixManager implements HelixManager, IZkStateListener {
   @Override
   public void handleNewSession(String sessionId) throws Exception {
     /*
-     * TODO: after removing I0ItecIZkStateListenerHelixImpl, null session should be checked and
+     * TODO: after removing I0ItecIZkStateListenerImpl, null session should be checked and
      *  discarded.
      * Null session is still a special case here, which is treated as non-session aware operation.
      * This special case could still potentially cause race condition, so null session should NOT
-     * be acceptable, once I0ItecIZkStateListenerHelixImpl is removed. Currently this special case
+     * be acceptable, once I0ItecIZkStateListenerImpl is removed. Currently this special case
      * is kept for backward compatibility.
      */
 
diff --git a/helix-core/src/test/java/org/apache/helix/common/ZkTestBase.java b/helix-core/src/test/java/org/apache/helix/common/ZkTestBase.java
index f1fdf53..88e9ea4 100644
--- a/helix-core/src/test/java/org/apache/helix/common/ZkTestBase.java
+++ b/helix-core/src/test/java/org/apache/helix/common/ZkTestBase.java
@@ -149,8 +149,9 @@ public class ZkTestBase {
         } catch (Exception e) {
           Assert.fail("Failed to parse the number of ZKs from config!");
         }
+      } else {
+        Assert.fail("multiZk config is set but numZk config is missing!");
       }
-      Assert.fail("multiZk config is set but numZk config is missing!");
     }
 
     // Start "numZkFromConfigInt" ZooKeepers
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/HelixZkClient.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/HelixZkClient.java
index bbb8a98..9a1a69d 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/HelixZkClient.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/HelixZkClient.java
@@ -19,8 +19,181 @@ package org.apache.helix.zookeeper.api.client;
  * under the License.
  */
 
+import org.apache.helix.zookeeper.zkclient.serialize.BasicZkSerializer;
+import org.apache.helix.zookeeper.zkclient.serialize.PathBasedZkSerializer;
+import org.apache.helix.zookeeper.zkclient.serialize.SerializableSerializer;
+import org.apache.helix.zookeeper.zkclient.serialize.ZkSerializer;
+
+
 /**
+ * Deprecated - please use RealmAwareZkClient instead.
+ *
  * HelixZkClient interface that follows the supported API structure of RealmAwareZkClient.
  */
+@Deprecated
 public interface HelixZkClient extends RealmAwareZkClient {
+
+  /**
+   * Deprecated - please use RealmAwareZkClient and RealmAwareZkConnectionConfig instead.
+   *
+   * Configuration for creating a new ZkConnection.
+   */
+  @Deprecated
+  class ZkConnectionConfig {
+    // Connection configs
+    private final String _zkServers;
+    private int _sessionTimeout = HelixZkClient.DEFAULT_SESSION_TIMEOUT;
+
+    public ZkConnectionConfig(String zkServers) {
+      _zkServers = zkServers;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (obj == this) {
+        return true;
+      }
+      if (!(obj instanceof HelixZkClient.ZkConnectionConfig)) {
+        return false;
+      }
+      HelixZkClient.ZkConnectionConfig configObj = (HelixZkClient.ZkConnectionConfig) obj;
+      return (_zkServers == null && configObj._zkServers == null || _zkServers != null && _zkServers
+          .equals(configObj._zkServers)) && _sessionTimeout == configObj._sessionTimeout;
+    }
+
+    @Override
+    public int hashCode() {
+      return _sessionTimeout * 31 + _zkServers.hashCode();
+    }
+
+    @Override
+    public String toString() {
+      return (_zkServers + "_" + _sessionTimeout).replaceAll("[\\W]", "_");
+    }
+
+    public HelixZkClient.ZkConnectionConfig setSessionTimeout(Integer sessionTimeout) {
+      this._sessionTimeout = sessionTimeout;
+      return this;
+    }
+
+    public String getZkServers() {
+      return _zkServers;
+    }
+
+    public int getSessionTimeout() {
+      return _sessionTimeout;
+    }
+  }
+
+  /**
+   * Deprecated - please use RealmAwareZkClient and RealmAwareZkClientConfig instead.
+   *
+   * Configuration for creating a new HelixZkClient with serializer and monitor.
+   *
+   * TODO: If possible, try to merge with RealmAwareZkClient's RealmAwareZkClientConfig to reduce duplicate logic/code (without breaking backward-compatibility).
+   * Simply making this a subclass of RealmAwareZkClientConfig will break backward-compatiblity.
+   */
+  @Deprecated
+  class ZkClientConfig {
+    // For client to init the connection
+    private long _connectInitTimeout = DEFAULT_CONNECTION_TIMEOUT;
+
+    // Data access configs
+    private long _operationRetryTimeout = DEFAULT_OPERATION_TIMEOUT;
+
+    // Others
+    private PathBasedZkSerializer _zkSerializer;
+
+    // Monitoring
+    private String _monitorType;
+    private String _monitorKey;
+    private String _monitorInstanceName = null;
+    private boolean _monitorRootPathOnly = true;
+
+    public ZkClientConfig setZkSerializer(PathBasedZkSerializer zkSerializer) {
+      this._zkSerializer = zkSerializer;
+      return this;
+    }
+
+    public ZkClientConfig setZkSerializer(ZkSerializer zkSerializer) {
+      this._zkSerializer = new BasicZkSerializer(zkSerializer);
+      return this;
+    }
+
+    /**
+     * Used as part of the MBean ObjectName. This item is required for enabling monitoring.
+     *
+     * @param monitorType
+     */
+    public ZkClientConfig setMonitorType(String monitorType) {
+      this._monitorType = monitorType;
+      return this;
+    }
+
+    /**
+     * Used as part of the MBean ObjectName. This item is required for enabling monitoring.
+     *
+     * @param monitorKey
+     */
+    public ZkClientConfig setMonitorKey(String monitorKey) {
+      this._monitorKey = monitorKey;
+      return this;
+    }
+
+    /**
+     * Used as part of the MBean ObjectName. This item is optional.
+     *
+     * @param instanceName
+     */
+    public ZkClientConfig setMonitorInstanceName(String instanceName) {
+      this._monitorInstanceName = instanceName;
+      return this;
+    }
+
+    public ZkClientConfig setMonitorRootPathOnly(Boolean monitorRootPathOnly) {
+      this._monitorRootPathOnly = monitorRootPathOnly;
+      return this;
+    }
+
+    public ZkClientConfig setOperationRetryTimeout(Long operationRetryTimeout) {
+      this._operationRetryTimeout = operationRetryTimeout;
+      return this;
+    }
+
+    public ZkClientConfig setConnectInitTimeout(long _connectInitTimeout) {
+      this._connectInitTimeout = _connectInitTimeout;
+      return this;
+    }
+
+    public PathBasedZkSerializer getZkSerializer() {
+      if (_zkSerializer == null) {
+        _zkSerializer = new BasicZkSerializer(new SerializableSerializer());
+      }
+      return _zkSerializer;
+    }
+
+    public long getOperationRetryTimeout() {
+      return _operationRetryTimeout;
+    }
+
+    public String getMonitorType() {
+      return _monitorType;
+    }
+
+    public String getMonitorKey() {
+      return _monitorKey;
+    }
+
+    public String getMonitorInstanceName() {
+      return _monitorInstanceName;
+    }
+
+    public boolean isMonitorRootPathOnly() {
+      return _monitorRootPathOnly;
+    }
+
+    public long getConnectInitTimeout() {
+      return _connectInitTimeout;
+    }
+  }
 }
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/RealmAwareZkClient.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/RealmAwareZkClient.java
index f2345f6..aa8bf7e 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/RealmAwareZkClient.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/client/RealmAwareZkClient.java
@@ -40,7 +40,25 @@ import org.apache.zookeeper.data.ACL;
 import org.apache.zookeeper.data.Stat;
 
 
+/**
+ * The Realm-aware ZkClient interface.
+ * NOTE: "Realm-aware" does not necessarily mean that the RealmAwareZkClient instance will be connecting to multiple ZK realms.
+ * On single-realm mode, RealmAwareZkClient will reject requests going out to other ZK realms than the one set at initialization.
+ * On multi-realm mode, RealmAwareZkClient will connect to multiple ZK realms but will reject EPHEMERAL AccessMode operations.
+ */
 public interface RealmAwareZkClient {
+
+  /**
+   * Specifies which mode to run this RealmAwareZkClient on.
+   *
+   * SINGLE_REALM: CRUD, change subscription, and EPHEMERAL CreateMode are supported.
+   * MULTI_REALM: CRUD and change subscription are supported. Operations involving EPHEMERAL CreateMode will throw an UnsupportedOperationException.
+   */
+  enum MODE {
+    SINGLE_REALM,
+    MULTI_REALM
+  }
+
   int DEFAULT_OPERATION_TIMEOUT = Integer.MAX_VALUE;
   int DEFAULT_CONNECTION_TIMEOUT = 60 * 1000;
   int DEFAULT_SESSION_TIMEOUT = 30 * 1000;
@@ -60,7 +78,7 @@ public interface RealmAwareZkClient {
    * TODO: remove below default implementation when getting rid of I0Itec in the new zk client.
    */
   default void subscribeStateChanges(final IZkStateListener listener) {
-    subscribeStateChanges(new HelixZkClient.I0ItecIZkStateListenerHelixImpl(listener));
+    subscribeStateChanges(new I0ItecIZkStateListenerImpl(listener));
   }
 
   /*
@@ -69,7 +87,7 @@ public interface RealmAwareZkClient {
    * TODO: remove below default implementation when getting rid of I0Itec in the new zk client.
    */
   default void unsubscribeStateChanges(IZkStateListener listener) {
-    unsubscribeStateChanges(new HelixZkClient.I0ItecIZkStateListenerHelixImpl(listener));
+    unsubscribeStateChanges(new I0ItecIZkStateListenerImpl(listener));
   }
 
   /**
@@ -250,11 +268,10 @@ public interface RealmAwareZkClient {
    * This is for backward compatibility and to avoid breaking the original implementation of
    * {@link org.apache.helix.zookeeper.zkclient.deprecated.IZkStateListener}.
    */
-  class I0ItecIZkStateListenerHelixImpl
-      implements org.apache.helix.zookeeper.zkclient.deprecated.IZkStateListener {
+  class I0ItecIZkStateListenerImpl implements org.apache.helix.zookeeper.zkclient.deprecated.IZkStateListener {
     private IZkStateListener _listener;
 
-    I0ItecIZkStateListenerHelixImpl(IZkStateListener listener) {
+    I0ItecIZkStateListenerImpl(IZkStateListener listener) {
       _listener = listener;
     }
 
@@ -282,15 +299,14 @@ public interface RealmAwareZkClient {
       if (obj == this) {
         return true;
       }
-      if (!(obj instanceof HelixZkClient.I0ItecIZkStateListenerHelixImpl)) {
+      if (!(obj instanceof I0ItecIZkStateListenerImpl)) {
         return false;
       }
       if (_listener == null) {
         return false;
       }
 
-      HelixZkClient.I0ItecIZkStateListenerHelixImpl
-          defaultListener = (HelixZkClient.I0ItecIZkStateListenerHelixImpl) obj;
+      I0ItecIZkStateListenerImpl defaultListener = (I0ItecIZkStateListenerImpl) obj;
 
       return _listener.equals(defaultListener._listener);
     }
@@ -306,15 +322,19 @@ public interface RealmAwareZkClient {
   }
 
   /**
-   * Configuration for creating a new ZkConnection.
+   * ZkConnection-related configs for creating an instance of RealmAwareZkClient.
    */
-  class ZkConnectionConfig {
-    // Connection configs
-    private final String _zkServers;
-    private int _sessionTimeout = HelixZkClient.DEFAULT_SESSION_TIMEOUT;
+  class RealmAwareZkConnectionConfig {
+
+    /**
+     * zkRealmShardingKey: used to deduce which ZK realm this RealmAwareZkClientConfig should connect to.
+     * NOTE: this field will be ignored if MODE is MULTI_REALM!
+     */
+    private final String _zkRealmShardingKey;
+    private int _sessionTimeout = DEFAULT_SESSION_TIMEOUT;
 
-    public ZkConnectionConfig(String zkServers) {
-      _zkServers = zkServers;
+    public RealmAwareZkConnectionConfig(String zkRealmShardingKey) {
+      _zkRealmShardingKey = zkRealmShardingKey;
     }
 
     @Override
@@ -322,32 +342,32 @@ public interface RealmAwareZkClient {
       if (obj == this) {
         return true;
       }
-      if (!(obj instanceof HelixZkClient.ZkConnectionConfig)) {
+      if (!(obj instanceof RealmAwareZkConnectionConfig)) {
         return false;
       }
-      HelixZkClient.ZkConnectionConfig configObj = (HelixZkClient.ZkConnectionConfig) obj;
-      return (_zkServers == null && configObj._zkServers == null ||
-          _zkServers != null && _zkServers.equals(configObj._zkServers)) &&
-          _sessionTimeout == configObj._sessionTimeout;
+      RealmAwareZkConnectionConfig configObj = (RealmAwareZkConnectionConfig) obj;
+      return (_zkRealmShardingKey == null && configObj._zkRealmShardingKey == null
+          || _zkRealmShardingKey != null && _zkRealmShardingKey
+          .equals(configObj._zkRealmShardingKey)) && _sessionTimeout == configObj._sessionTimeout;
     }
 
     @Override
     public int hashCode() {
-      return _sessionTimeout * 31 + _zkServers.hashCode();
+      return _sessionTimeout * 31 + _zkRealmShardingKey.hashCode();
     }
 
     @Override
     public String toString() {
-      return (_zkServers + "_" + _sessionTimeout).replaceAll("[\\W]", "_");
+      return (_zkRealmShardingKey + "_" + _sessionTimeout).replaceAll("[\\W]", "_");
     }
 
-    public HelixZkClient.ZkConnectionConfig setSessionTimeout(Integer sessionTimeout) {
+    public RealmAwareZkConnectionConfig setSessionTimeout(int sessionTimeout) {
       this._sessionTimeout = sessionTimeout;
       return this;
     }
 
-    public String getZkServers() {
-      return _zkServers;
+    public String getZkRealmShardingKey() {
+      return _zkRealmShardingKey;
     }
 
     public int getSessionTimeout() {
@@ -356,14 +376,14 @@ public interface RealmAwareZkClient {
   }
 
   /**
-   * Configuration for creating a new RealmAwareZkClient with serializer and monitor.
+   * ZkClient-related configs for creating an instance of RealmAwareZkClient.
    */
-  class ZkClientConfig {
+  class RealmAwareZkClientConfig {
     // For client to init the connection
-    private long _connectInitTimeout = HelixZkClient.DEFAULT_CONNECTION_TIMEOUT;
+    private long _connectInitTimeout = DEFAULT_CONNECTION_TIMEOUT;
 
     // Data access configs
-    private long _operationRetryTimeout = HelixZkClient.DEFAULT_OPERATION_TIMEOUT;
+    private long _operationRetryTimeout = DEFAULT_OPERATION_TIMEOUT;
 
     // Others
     private PathBasedZkSerializer _zkSerializer;
@@ -374,12 +394,12 @@ public interface RealmAwareZkClient {
     private String _monitorInstanceName = null;
     private boolean _monitorRootPathOnly = true;
 
-    public HelixZkClient.ZkClientConfig setZkSerializer(PathBasedZkSerializer zkSerializer) {
+    public RealmAwareZkClientConfig setZkSerializer(PathBasedZkSerializer zkSerializer) {
       this._zkSerializer = zkSerializer;
       return this;
     }
 
-    public HelixZkClient.ZkClientConfig setZkSerializer(ZkSerializer zkSerializer) {
+    public RealmAwareZkClientConfig setZkSerializer(ZkSerializer zkSerializer) {
       this._zkSerializer = new BasicZkSerializer(zkSerializer);
       return this;
     }
@@ -389,7 +409,7 @@ public interface RealmAwareZkClient {
      *
      * @param monitorType
      */
-    public HelixZkClient.ZkClientConfig setMonitorType(String monitorType) {
+    public RealmAwareZkClientConfig setMonitorType(String monitorType) {
       this._monitorType = monitorType;
       return this;
     }
@@ -399,7 +419,7 @@ public interface RealmAwareZkClient {
      *
      * @param monitorKey
      */
-    public HelixZkClient.ZkClientConfig setMonitorKey(String monitorKey) {
+    public RealmAwareZkClientConfig setMonitorKey(String monitorKey) {
       this._monitorKey = monitorKey;
       return this;
     }
@@ -409,22 +429,22 @@ public interface RealmAwareZkClient {
      *
      * @param instanceName
      */
-    public HelixZkClient.ZkClientConfig setMonitorInstanceName(String instanceName) {
+    public RealmAwareZkClientConfig setMonitorInstanceName(String instanceName) {
       this._monitorInstanceName = instanceName;
       return this;
     }
 
-    public HelixZkClient.ZkClientConfig setMonitorRootPathOnly(Boolean monitorRootPathOnly) {
+    public RealmAwareZkClientConfig setMonitorRootPathOnly(Boolean monitorRootPathOnly) {
       this._monitorRootPathOnly = monitorRootPathOnly;
       return this;
     }
 
-    public HelixZkClient.ZkClientConfig setOperationRetryTimeout(Long operationRetryTimeout) {
+    public RealmAwareZkClientConfig setOperationRetryTimeout(Long operationRetryTimeout) {
       this._operationRetryTimeout = operationRetryTimeout;
       return this;
     }
 
-    public HelixZkClient.ZkClientConfig setConnectInitTimeout(long _connectInitTimeout) {
+    public RealmAwareZkClientConfig setConnectInitTimeout(long _connectInitTimeout) {
       this._connectInitTimeout = _connectInitTimeout;
       return this;
     }
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/factory/RealmAwareZkClientFactory.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/factory/RealmAwareZkClientFactory.java
index 9fbf259..f68ffe4 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/factory/RealmAwareZkClientFactory.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/api/factory/RealmAwareZkClientFactory.java
@@ -19,5 +19,27 @@ package org.apache.helix.zookeeper.api.factory;
  * under the License.
  */
 
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
+
+
+/**
+ * Creates an instance of RealmAwareZkClient.
+ */
 public interface RealmAwareZkClientFactory {
+  /**
+   * Build a RealmAwareZkClient using specified connection config and client config.
+   * @param connectionConfig
+   * @param clientConfig
+   * @return HelixZkClient
+   */
+  RealmAwareZkClient buildZkClient(RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig,
+      RealmAwareZkClient.RealmAwareZkClientConfig clientConfig);
+
+  /**
+   * Builds a RealmAwareZkClient using specified connection config and default client config.
+   * @param connectionConfig
+   * @return RealmAwareZkClient
+   */
+  RealmAwareZkClient buildZkClient(
+      RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig);
 }
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/DedicatedZkClientFactory.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/DedicatedZkClientFactory.java
index 90ecf9b..2695a5d 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/DedicatedZkClientFactory.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/DedicatedZkClientFactory.java
@@ -20,6 +20,7 @@ package org.apache.helix.zookeeper.impl.factory;
  */
 
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 import org.apache.helix.zookeeper.impl.client.ZkClient;
 
 
@@ -31,6 +32,22 @@ public class DedicatedZkClientFactory extends HelixZkClientFactory {
   protected DedicatedZkClientFactory() {
   }
 
+  @Override
+  public RealmAwareZkClient buildZkClient(
+      RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig,
+      RealmAwareZkClient.RealmAwareZkClientConfig clientConfig) {
+    // TODO: Implement the logic
+    // Return an instance of DedicatedZkClient
+    return null;
+  }
+
+  @Override
+  public RealmAwareZkClient buildZkClient(
+      RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig) {
+    // TODO: Implement the logic
+    return null;
+  }
+
   private static class SingletonHelper {
     private static final DedicatedZkClientFactory INSTANCE = new DedicatedZkClientFactory();
   }
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/HelixZkClientFactory.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/HelixZkClientFactory.java
index 9e1ca6e..9584c57 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/HelixZkClientFactory.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/HelixZkClientFactory.java
@@ -20,6 +20,7 @@ package org.apache.helix.zookeeper.impl.factory;
  */
 
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
+import org.apache.helix.zookeeper.api.factory.RealmAwareZkClientFactory;
 import org.apache.helix.zookeeper.exception.ZkClientException;
 import org.apache.helix.zookeeper.zkclient.IZkConnection;
 import org.apache.helix.zookeeper.zkclient.ZkConnection;
@@ -28,7 +29,7 @@ import org.apache.helix.zookeeper.zkclient.ZkConnection;
 /**
  * Abstract class of the ZkClient factory.
  */
-abstract class HelixZkClientFactory {
+abstract class HelixZkClientFactory implements RealmAwareZkClientFactory {
 
   /**
    * Build a ZkClient using specified connection config and client config
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/SharedZkClientFactory.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/SharedZkClientFactory.java
index bf9d9a1..a9b8e33 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/SharedZkClientFactory.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/SharedZkClientFactory.java
@@ -22,6 +22,7 @@ package org.apache.helix.zookeeper.impl.factory;
 import java.util.HashMap;
 
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 import org.apache.helix.zookeeper.exception.ZkClientException;
 import org.apache.helix.zookeeper.impl.client.SharedZkClient;
 import org.slf4j.Logger;
@@ -40,6 +41,22 @@ public class SharedZkClientFactory extends HelixZkClientFactory {
   protected SharedZkClientFactory() {
   }
 
+  @Override
+  public RealmAwareZkClient buildZkClient(
+      RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig,
+      RealmAwareZkClient.RealmAwareZkClientConfig clientConfig) {
+    // TODO: Implement the logic
+    // Return an instance of SharedZkClient
+    return null;
+  }
+
+  @Override
+  public RealmAwareZkClient buildZkClient(
+      RealmAwareZkClient.RealmAwareZkConnectionConfig connectionConfig) {
+    // TODO: Implement the logic
+    return null;
+  }
+
   private static class SingletonHelper {
     private static final SharedZkClientFactory INSTANCE = new SharedZkClientFactory();
   }


[helix] 28/49: Make MSDS endpoint configurable for HttpRoutingDataReader (#836)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit 3c4c248050c5161c6e94ea9b7315f35b350c6f9c
Author: Hunter Lee <hu...@linkedin.com>
AuthorDate: Fri Feb 28 18:47:41 2020 -0800

    Make MSDS endpoint configurable for HttpRoutingDataReader (#836)
    
    We need to add a few more constructors that allows the users to configure which MSDS to talk to. Applications may wish to create RealmAwareZkClients connecting to different regions or namespaces.
---
 .../zookeeper/util/HttpRoutingDataReader.java      | 77 ++++++++++++++++------
 .../zookeeper/impl/client/TestSharedZkClient.java  | 20 +++++-
 .../zookeeper/util/TestHttpRoutingDataReader.java  |  5 +-
 3 files changed, 79 insertions(+), 23 deletions(-)

diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/util/HttpRoutingDataReader.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/util/HttpRoutingDataReader.java
index b795de0..04f6c44 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/util/HttpRoutingDataReader.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/util/HttpRoutingDataReader.java
@@ -21,6 +21,7 @@ package org.apache.helix.zookeeper.util;
 
 import java.io.IOException;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
@@ -41,13 +42,17 @@ import org.apache.http.util.EntityUtils;
 
 
 public class HttpRoutingDataReader {
-  private static final String MSDS_ENDPOINT =
+  private static final String SYSTEM_MSDS_ENDPOINT =
       System.getProperty(MetadataStoreRoutingConstants.MSDS_SERVER_ENDPOINT_KEY);
   private static final int HTTP_TIMEOUT_IN_MS = 5000;
 
   /** Double-checked locking requires that the following fields be volatile */
-  private static volatile Map<String, List<String>> _rawRoutingData;
-  private static volatile MetadataStoreRoutingData _metadataStoreRoutingData;
+  // The following map stands for (MSDS endpoint, Raw Routing Data)
+  private static volatile Map<String, Map<String, List<String>>> _rawRoutingDataMap =
+      new HashMap<>();
+  // The following map stands for (MSDS endpoint, MetadataStoreRoutingData)
+  private static volatile Map<String, MetadataStoreRoutingData> _metadataStoreRoutingDataMap =
+      new HashMap<>();
 
   /**
    * This class is a Singleton.
@@ -56,44 +61,78 @@ public class HttpRoutingDataReader {
   }
 
   /**
-   * Fetches routing data from the data source via HTTP.
-   * @return a mapping from "metadata store realm addresses" to lists of
-   * "metadata store sharding keys", where the sharding keys in a value list all route to
-   * the realm address in the key disallows a meaningful mapping to be returned
+   * Fetches routing data from the data source via HTTP by querying the MSDS configured in the JVM config.
+   * @return
+   * @throws IOException
    */
   public static Map<String, List<String>> getRawRoutingData() throws IOException {
-    if (MSDS_ENDPOINT == null || MSDS_ENDPOINT.isEmpty()) {
+    if (SYSTEM_MSDS_ENDPOINT == null || SYSTEM_MSDS_ENDPOINT.isEmpty()) {
       throw new IllegalStateException(
           "HttpRoutingDataReader was unable to find a valid MSDS endpoint String in System Properties!");
     }
-    if (_rawRoutingData == null) {
+    return getRawRoutingData(SYSTEM_MSDS_ENDPOINT);
+  }
+
+  /**
+   * Fetches routing data from the data source via HTTP.
+   * @return a mapping from "metadata store realm addresses" to lists of
+   * "metadata store sharding keys", where the sharding keys in a value list all route to
+   * the realm address in the key disallows a meaningful mapping to be returned.
+   * @param msdsEndpoint Metadata Store Directory Store endpoint to query from
+   */
+  public static Map<String, List<String>> getRawRoutingData(String msdsEndpoint)
+      throws IOException {
+    Map<String, List<String>> rawRoutingData = _rawRoutingDataMap.get(msdsEndpoint);
+    if (rawRoutingData == null) {
       synchronized (HttpRoutingDataReader.class) {
-        if (_rawRoutingData == null) {
+        rawRoutingData = _rawRoutingDataMap.get(msdsEndpoint);
+        if (rawRoutingData == null) {
           String routingDataJson = getAllRoutingData();
           // Update the reference if reading routingData over HTTP is successful
-          _rawRoutingData = parseRoutingData(routingDataJson);
+          rawRoutingData = parseRoutingData(routingDataJson);
+          _rawRoutingDataMap.put(msdsEndpoint, rawRoutingData);
         }
       }
     }
-    return _rawRoutingData;
+    return rawRoutingData;
+  }
+
+  /**
+   * Returns the routing data read from MSDS in a MetadataStoreRoutingData format by querying the MSDS configured in the JVM config.
+   * @return MetadataStoreRoutingData
+   * @throws IOException
+   * @throws InvalidRoutingDataException
+   */
+  public static MetadataStoreRoutingData getMetadataStoreRoutingData()
+      throws IOException, InvalidRoutingDataException {
+    if (SYSTEM_MSDS_ENDPOINT == null || SYSTEM_MSDS_ENDPOINT.isEmpty()) {
+      throw new IllegalStateException(
+          "HttpRoutingDataReader was unable to find a valid MSDS endpoint String in System Properties!");
+    }
+    return getMetadataStoreRoutingData(SYSTEM_MSDS_ENDPOINT);
   }
 
   /**
    * Returns the routing data read from MSDS in a MetadataStoreRoutingData format.
-   * @return
+   * @param msdsEndpoint Metadata Store Directory Store endpoint to query from
+   * @return MetadataStoreRoutingData
    * @throws IOException if there is an issue connecting to MSDS
    * @throws InvalidRoutingDataException if the raw routing data is not valid
    */
-  public static MetadataStoreRoutingData getMetadataStoreRoutingData()
+  public static MetadataStoreRoutingData getMetadataStoreRoutingData(String msdsEndpoint)
       throws IOException, InvalidRoutingDataException {
-    if (_metadataStoreRoutingData == null) {
+    MetadataStoreRoutingData metadataStoreRoutingData =
+        _metadataStoreRoutingDataMap.get(msdsEndpoint);
+    if (metadataStoreRoutingData == null) {
       synchronized (HttpRoutingDataReader.class) {
-        if (_metadataStoreRoutingData == null) {
-          _metadataStoreRoutingData = new TrieRoutingData(getRawRoutingData());
+        metadataStoreRoutingData = _metadataStoreRoutingDataMap.get(msdsEndpoint);
+        if (metadataStoreRoutingData == null) {
+          metadataStoreRoutingData = new TrieRoutingData(getRawRoutingData(msdsEndpoint));
+          _metadataStoreRoutingDataMap.put(msdsEndpoint, metadataStoreRoutingData);
         }
       }
     }
-    return _metadataStoreRoutingData;
+    return metadataStoreRoutingData;
   }
 
   /**
@@ -105,7 +144,7 @@ public class HttpRoutingDataReader {
     // Note that MSDS_ENDPOINT should provide high-availability - it risks becoming a single point of failure if it's backed by a single IP address/host
     // Retry count is 3 by default.
     HttpGet requestAllData = new HttpGet(
-        MSDS_ENDPOINT + MetadataStoreRoutingConstants.MSDS_GET_ALL_ROUTING_DATA_ENDPOINT);
+        SYSTEM_MSDS_ENDPOINT + MetadataStoreRoutingConstants.MSDS_GET_ALL_ROUTING_DATA_ENDPOINT);
 
     // Define timeout configs
     RequestConfig config = RequestConfig.custom().setConnectTimeout(HTTP_TIMEOUT_IN_MS)
diff --git a/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/TestSharedZkClient.java b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/TestSharedZkClient.java
index 364f66f..1dd44f6 100644
--- a/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/TestSharedZkClient.java
+++ b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/TestSharedZkClient.java
@@ -1,8 +1,26 @@
 package org.apache.helix.zookeeper.impl.client;
 
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
 import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
-import org.apache.helix.zookeeper.impl.factory.DedicatedZkClientFactory;
 import org.apache.helix.zookeeper.impl.factory.SharedZkClientFactory;
 import org.apache.zookeeper.CreateMode;
 import org.testng.Assert;
diff --git a/zookeeper-api/src/test/java/org/apache/helix/zookeeper/util/TestHttpRoutingDataReader.java b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/util/TestHttpRoutingDataReader.java
index 83ee471..1eccd1a 100644
--- a/zookeeper-api/src/test/java/org/apache/helix/zookeeper/util/TestHttpRoutingDataReader.java
+++ b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/util/TestHttpRoutingDataReader.java
@@ -87,9 +87,8 @@ public class TestHttpRoutingDataReader extends ZkTestBase {
     Map<String, Set<String>> groupedMappings = allMappings.entrySet().stream().collect(Collectors
         .groupingBy(Map.Entry::getValue,
             Collectors.mapping(Map.Entry::getKey, Collectors.toSet())));
-    _testRawRoutingData.forEach((realm, keys) -> {
-      Assert.assertEquals(groupedMappings.get(realm), new HashSet(keys));
-    });
+    _testRawRoutingData.forEach(
+        (realm, keys) -> Assert.assertEquals(groupedMappings.get(realm), new HashSet(keys)));
   }
 
   /**


[helix] 41/49: Update listClusters() in ZkHelixAdmin (#895)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit 4565d88a943fafcfe694e76c9e1381249a214d71
Author: Hunter Lee <hu...@linkedin.com>
AuthorDate: Fri Mar 13 17:28:01 2020 -0700

    Update listClusters() in ZkHelixAdmin (#895)
    
    This PR updates getClusters() in ZkHelixAdmin in realm-aware mode. It is difficult to reason about getting all clusters - and this method, on single-realm mode, is also broken anyways because in the case users create nested clusters, this method will end up returning nothing. This update is backward-compatible for single-realm users.
    
    Ideally, users should use Metadata Store Directory Service to get the list of all clusters.
---
 .../main/java/org/apache/helix/manager/zk/ZKHelixAdmin.java   | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixAdmin.java b/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixAdmin.java
index ea9b55a..5804dd9 100644
--- a/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixAdmin.java
+++ b/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixAdmin.java
@@ -954,6 +954,12 @@ public class ZKHelixAdmin implements HelixAdmin {
 
   @Override
   public List<String> getClusters() {
+    if (Boolean.getBoolean(SystemPropertyKeys.MULTI_ZK_ENABLED)
+        || _zkClient instanceof FederatedZkClient) {
+      throw new UnsupportedOperationException(
+          "getClusters() is not supported in multi-realm mode! Use Metadata Store Directory Service instead!");
+    }
+
     List<String> zkToplevelPathes = _zkClient.getChildren("/");
     List<String> result = new ArrayList<String>();
     for (String pathName : zkToplevelPathes) {
@@ -1925,7 +1931,10 @@ public class ZKHelixAdmin implements HelixAdmin {
 
       // Resolve RealmAwareZkClientConfig
       if (realmAwareZkClientConfig == null) {
-        realmAwareZkClientConfig = new RealmAwareZkClient.RealmAwareZkClientConfig();
+        // ZkHelixAdmin should have ZNRecordSerializer set by default
+        realmAwareZkClientConfig = new RealmAwareZkClient.RealmAwareZkClientConfig()
+            .setZkSerializer(
+                new org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer());
       }
 
       // Resolve RealmAwareZkConnectionConfig


[helix] 27/49: Improve MetadataStoreDirectoryAccessor endpoints and fix bugs in ZkRoutingDataReader/Writer

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit 2a0214a65bcd7208cd578b6635d8acbbb26f6fa0
Author: Neal Sun <ne...@gmail.com>
AuthorDate: Thu Feb 27 16:41:39 2020 -0800

    Improve MetadataStoreDirectoryAccessor endpoints and fix bugs in ZkRoutingDataReader/Writer
    
    Improve the current status code design of MetadataStoreDirectoryAccessor endpoints by more clearly translate underlying exceptions to status codes. Fix 4 bugs in Reader/Writer.
---
 .../apache/helix/rest/common/HttpConstants.java    |   2 +
 .../metadatastore/ZkMetadataStoreDirectory.java    |  31 ++++-
 .../accessor/ZkRoutingDataReader.java              |  72 +++++-----
 .../accessor/ZkRoutingDataWriter.java              |  11 +-
 .../MetadataStoreDirectoryAccessor.java            |  10 +-
 .../TestZkMetadataStoreDirectory.java              |   2 +-
 .../accessor/TestZkRoutingDataWriter.java          |   2 +-
 .../MetadataStoreDirectoryAccessorTestBase.java    |   3 +-
 .../rest/server/TestMSDAccessorLeaderElection.java |   5 +-
 .../server/TestMetadataStoreDirectoryAccessor.java | 152 +++++++++++++--------
 10 files changed, 193 insertions(+), 97 deletions(-)

diff --git a/helix-rest/src/main/java/org/apache/helix/rest/common/HttpConstants.java b/helix-rest/src/main/java/org/apache/helix/rest/common/HttpConstants.java
index 369f1a4..d5f3bd9 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/common/HttpConstants.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/common/HttpConstants.java
@@ -26,4 +26,6 @@ public class HttpConstants {
     PUT,
     DELETE
   }
+
+  public static final String HTTP_PROTOCOL_PREFIX = "http://";
 }
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java
index 9d54c82..28a4afe 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/ZkMetadataStoreDirectory.java
@@ -30,6 +30,7 @@ import java.util.concurrent.ConcurrentHashMap;
 
 import com.google.common.annotations.VisibleForTesting;
 import org.apache.helix.msdcommon.callback.RoutingDataListener;
+import org.apache.helix.msdcommon.constant.MetadataStoreRoutingConstants;
 import org.apache.helix.msdcommon.datamodel.MetadataStoreRoutingData;
 import org.apache.helix.msdcommon.datamodel.TrieRoutingData;
 import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
@@ -37,6 +38,10 @@ import org.apache.helix.rest.metadatastore.accessor.MetadataStoreRoutingDataRead
 import org.apache.helix.rest.metadatastore.accessor.MetadataStoreRoutingDataWriter;
 import org.apache.helix.rest.metadatastore.accessor.ZkRoutingDataReader;
 import org.apache.helix.rest.metadatastore.accessor.ZkRoutingDataWriter;
+import org.apache.helix.zookeeper.api.client.HelixZkClient;
+import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
+import org.apache.helix.zookeeper.impl.factory.DedicatedZkClientFactory;
+import org.apache.helix.zookeeper.zkclient.exception.ZkNodeExistsException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -93,6 +98,16 @@ public class ZkMetadataStoreDirectory implements MetadataStoreDirectory, Routing
     if (!_routingZkAddressMap.containsKey(namespace)) {
       synchronized (_routingZkAddressMap) {
         if (!_routingZkAddressMap.containsKey(namespace)) {
+          // Ensure that ROUTING_DATA_PATH exists in ZK.
+          HelixZkClient zkClient = DedicatedZkClientFactory.getInstance()
+              .buildZkClient(new HelixZkClient.ZkConnectionConfig(zkAddress),
+                  new HelixZkClient.ZkClientConfig().setZkSerializer(new ZNRecordSerializer()));
+          try {
+            zkClient.createPersistent(MetadataStoreRoutingConstants.ROUTING_DATA_PATH, true);
+          } catch (ZkNodeExistsException e) {
+            // The node already exists and it's okay
+          }
+
           _routingZkAddressMap.put(namespace, zkAddress);
           _routingDataReaderMap.put(namespace, new ZkRoutingDataReader(namespace, zkAddress, this));
           _routingDataWriterMap.put(namespace, new ZkRoutingDataWriter(namespace, zkAddress));
@@ -173,7 +188,9 @@ public class ZkMetadataStoreDirectory implements MetadataStoreDirectory, Routing
   @Override
   public boolean addMetadataStoreRealm(String namespace, String realm) {
     if (!_routingDataWriterMap.containsKey(namespace)) {
-      throw new IllegalArgumentException(
+      // throwing NoSuchElementException instead of IllegalArgumentException to differentiate the
+      // status code in the Accessor level
+      throw new NoSuchElementException(
           "Failed to add metadata store realm: Namespace " + namespace + " is not found!");
     }
     return _routingDataWriterMap.get(namespace).addMetadataStoreRealm(realm);
@@ -182,7 +199,9 @@ public class ZkMetadataStoreDirectory implements MetadataStoreDirectory, Routing
   @Override
   public boolean deleteMetadataStoreRealm(String namespace, String realm) {
     if (!_routingDataWriterMap.containsKey(namespace)) {
-      throw new IllegalArgumentException(
+      // throwing NoSuchElementException instead of IllegalArgumentException to differentiate the
+      // status code in the Accessor level
+      throw new NoSuchElementException(
           "Failed to delete metadata store realm: Namespace " + namespace + " is not found!");
     }
     return _routingDataWriterMap.get(namespace).deleteMetadataStoreRealm(realm);
@@ -191,7 +210,9 @@ public class ZkMetadataStoreDirectory implements MetadataStoreDirectory, Routing
   @Override
   public boolean addShardingKey(String namespace, String realm, String shardingKey) {
     if (!_routingDataWriterMap.containsKey(namespace) || !_routingDataMap.containsKey(namespace)) {
-      throw new IllegalArgumentException(
+      // throwing NoSuchElementException instead of IllegalArgumentException to differentiate the
+      // status code in the Accessor level
+      throw new NoSuchElementException(
           "Failed to add sharding key: Namespace " + namespace + " is not found!");
     }
     if (_routingDataMap.get(namespace).containsKeyRealmPair(shardingKey, realm)) {
@@ -208,7 +229,9 @@ public class ZkMetadataStoreDirectory implements MetadataStoreDirectory, Routing
   @Override
   public boolean deleteShardingKey(String namespace, String realm, String shardingKey) {
     if (!_routingDataWriterMap.containsKey(namespace)) {
-      throw new IllegalArgumentException(
+      // throwing NoSuchElementException instead of IllegalArgumentException to differentiate the
+      // status code in the Accessor level
+      throw new NoSuchElementException(
           "Failed to delete sharding key: Namespace " + namespace + " is not found!");
     }
     return _routingDataWriterMap.get(namespace).deleteShardingKey(realm, shardingKey);
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataReader.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataReader.java
index 76465f9..9251571 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataReader.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataReader.java
@@ -35,6 +35,7 @@ import org.apache.helix.zookeeper.zkclient.IZkChildListener;
 import org.apache.helix.zookeeper.zkclient.IZkDataListener;
 import org.apache.helix.zookeeper.zkclient.IZkStateListener;
 import org.apache.helix.zookeeper.zkclient.exception.ZkNoNodeException;
+import org.apache.helix.zookeeper.zkclient.exception.ZkNodeExistsException;
 import org.apache.zookeeper.Watcher;
 
 
@@ -57,6 +58,15 @@ public class ZkRoutingDataReader implements MetadataStoreRoutingDataReader, IZkD
     _zkClient = DedicatedZkClientFactory.getInstance()
         .buildZkClient(new HelixZkClient.ZkConnectionConfig(zkAddress),
             new HelixZkClient.ZkClientConfig().setZkSerializer(new ZNRecordSerializer()));
+
+    // Ensure that ROUTING_DATA_PATH exists in ZK. If not, create
+    // create() semantic will fail if it already exists
+    try {
+      _zkClient.createPersistent(MetadataStoreRoutingConstants.ROUTING_DATA_PATH, true);
+    } catch (ZkNodeExistsException e) {
+      // This is okay
+    }
+
     _routingDataListener = routingDataListener;
     if (_routingDataListener != null) {
       // Subscribe child changes
@@ -76,8 +86,7 @@ public class ZkRoutingDataReader implements MetadataStoreRoutingDataReader, IZkD
    * @throws InvalidRoutingDataException - when the node on
    *           MetadataStoreRoutingConstants.ROUTING_DATA_PATH is missing
    */
-  public Map<String, List<String>> getRoutingData()
-      throws InvalidRoutingDataException {
+  public Map<String, List<String>> getRoutingData() throws InvalidRoutingDataException {
     Map<String, List<String>> routingData = new HashMap<>();
     List<String> allRealmAddresses;
     try {
@@ -87,13 +96,17 @@ public class ZkRoutingDataReader implements MetadataStoreRoutingDataReader, IZkD
           "Routing data directory ZNode " + MetadataStoreRoutingConstants.ROUTING_DATA_PATH
               + " does not exist. Routing ZooKeeper address: " + _zkAddress);
     }
-    for (String realmAddress : allRealmAddresses) {
-      ZNRecord record =
-          _zkClient.readData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + realmAddress);
-      List<String> shardingKeys =
-          record.getListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY);
-      routingData
-          .put(realmAddress, shardingKeys != null ? shardingKeys : Collections.emptyList());
+    if (allRealmAddresses != null) {
+      for (String realmAddress : allRealmAddresses) {
+        ZNRecord record = _zkClient
+            .readData(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + realmAddress);
+        if (record != null) {
+          List<String> shardingKeys =
+              record.getListField(MetadataStoreRoutingConstants.ZNRECORD_LIST_FIELD_KEY);
+          routingData
+              .put(realmAddress, shardingKeys != null ? shardingKeys : Collections.emptyList());
+        }
+      }
     }
     return routingData;
   }
@@ -113,32 +126,14 @@ public class ZkRoutingDataReader implements MetadataStoreRoutingDataReader, IZkD
 
   @Override
   public synchronized void handleDataDeleted(String s) {
-    if (_zkClient.isClosed()) {
-      return;
-    }
-
-    // Renew subscription
-    _zkClient.subscribeChildChanges(MetadataStoreRoutingConstants.ROUTING_DATA_PATH, this);
-    for (String child : _zkClient.getChildren(MetadataStoreRoutingConstants.ROUTING_DATA_PATH)) {
-      _zkClient.subscribeDataChanges(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + child,
-          this);
-    }
-    _routingDataListener.refreshRoutingData(_namespace);
+    // When a child node is deleted, this and handleChildChange will both be triggered, but the
+    // behavior is safe
+    handleResubscription();
   }
 
   @Override
   public synchronized void handleChildChange(String s, List<String> list) {
-    if (_zkClient.isClosed()) {
-      return;
-    }
-
-    // Subscribe data changes again because some children might have been deleted or added
-    _zkClient.unsubscribeAll();
-    for (String child : _zkClient.getChildren(MetadataStoreRoutingConstants.ROUTING_DATA_PATH)) {
-      _zkClient.subscribeDataChanges(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + child,
-          this);
-    }
-    _routingDataListener.refreshRoutingData(_namespace);
+    handleResubscription();
   }
 
   @Override
@@ -164,4 +159,19 @@ public class ZkRoutingDataReader implements MetadataStoreRoutingDataReader, IZkD
     }
     _routingDataListener.refreshRoutingData(_namespace);
   }
+
+  private void handleResubscription() {
+    if (_zkClient.isClosed()) {
+      return;
+    }
+
+    // Renew subscription
+    _zkClient.unsubscribeAll();
+    _zkClient.subscribeChildChanges(MetadataStoreRoutingConstants.ROUTING_DATA_PATH, this);
+    for (String child : _zkClient.getChildren(MetadataStoreRoutingConstants.ROUTING_DATA_PATH)) {
+      _zkClient.subscribeDataChanges(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + child,
+          this);
+    }
+    _routingDataListener.refreshRoutingData(_namespace);
+  }
 }
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataWriter.java b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataWriter.java
index 061372c..74cc14c 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataWriter.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/metadatastore/accessor/ZkRoutingDataWriter.java
@@ -90,7 +90,7 @@ public class ZkRoutingDataWriter implements MetadataStoreRoutingDataWriter {
     if (hostName.charAt(hostName.length() - 1) == '/') {
       hostName = hostName.substring(0, hostName.length() - 1);
     }
-    _myHostName = hostName;
+    _myHostName = HttpConstants.HTTP_PROTOCOL_PREFIX + hostName;
     ZNRecord myServerInfo = new ZNRecord(_myHostName);
 
     _leaderElection = new ZkDistributedLeaderElection(_zkClient,
@@ -247,6 +247,12 @@ public class ZkRoutingDataWriter implements MetadataStoreRoutingDataWriter {
   }
 
   protected boolean deleteZkRealm(String realm) {
+    if (!_zkClient.exists(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + realm)) {
+      LOG.warn(
+          "deleteZkRealm() called for realm: {}, but this realm already doesn't exist! Namespace: {}",
+          realm, _namespace);
+      return true;
+    }
     return _zkClient.delete(MetadataStoreRoutingConstants.ROUTING_DATA_PATH + "/" + realm);
   }
 
@@ -339,7 +345,8 @@ public class ZkRoutingDataWriter implements MetadataStoreRoutingDataWriter {
         request = new HttpDelete(url);
         break;
       default:
-        throw new IllegalArgumentException("Unsupported request_method: " + request_method);
+        LOG.error("Unsupported request_method: " + request_method.name());
+        return false;
     }
 
     return sendRequestToLeader(request, expectedResponseCode, leaderHostName);
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/server/resources/metadatastore/MetadataStoreDirectoryAccessor.java b/helix-rest/src/main/java/org/apache/helix/rest/server/resources/metadatastore/MetadataStoreDirectoryAccessor.java
index 38b764d..0763ec1 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/server/resources/metadatastore/MetadataStoreDirectoryAccessor.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/server/resources/metadatastore/MetadataStoreDirectoryAccessor.java
@@ -108,6 +108,8 @@ public class MetadataStoreDirectoryAccessor extends AbstractResource {
       return JSONRepresentation(new MetadataStoreShardingKey(shardingKey, realm));
     } catch (NoSuchElementException ex) {
       return notFound(ex.getMessage());
+    } catch (IllegalArgumentException e) {
+      return badRequest(e.getMessage());
     }
   }
 
@@ -176,6 +178,8 @@ public class MetadataStoreDirectoryAccessor extends AbstractResource {
       return getAllShardingKeysUnderPath(prefix);
     } catch (NoSuchElementException ex) {
       return notFound(ex.getMessage());
+    } catch (IllegalArgumentException e) {
+      return badRequest(e.getMessage());
     }
   }
 
@@ -240,6 +244,8 @@ public class MetadataStoreDirectoryAccessor extends AbstractResource {
       return getRealmShardingKeysUnderPath(realm, prefix);
     } catch (NoSuchElementException ex) {
       return notFound(ex.getMessage());
+    } catch (IllegalArgumentException e) {
+      return badRequest(e.getMessage());
     }
   }
 
@@ -252,8 +258,10 @@ public class MetadataStoreDirectoryAccessor extends AbstractResource {
       if (!_metadataStoreDirectory.addShardingKey(_namespace, realm, shardingKey)) {
         return serverError();
       }
-    } catch (IllegalArgumentException ex) {
+    } catch (NoSuchElementException ex) {
       return notFound(ex.getMessage());
+    } catch (IllegalArgumentException e) {
+      return badRequest(e.getMessage());
     }
 
     return created();
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java
index 00a328f..8eddcea 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/TestZkMetadataStoreDirectory.java
@@ -101,7 +101,7 @@ public class TestZkMetadataStoreDirectory extends AbstractTestClass {
     });
 
     System.setProperty(MetadataStoreRoutingConstants.MSDS_SERVER_HOSTNAME_KEY,
-        getBaseUri().toString());
+        getBaseUri().getHost() + ":" + getBaseUri().getPort());
 
     // Create metadataStoreDirectory
     for (Map.Entry<String, String> entry : _routingZkAddrMap.entrySet()) {
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataWriter.java b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataWriter.java
index e61e905..069931f 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataWriter.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/metadatastore/accessor/TestZkRoutingDataWriter.java
@@ -63,7 +63,7 @@ public class TestZkRoutingDataWriter extends AbstractTestClass {
   public void beforeClass() {
     _baseAccessor.remove(MetadataStoreRoutingConstants.ROUTING_DATA_PATH, AccessOption.PERSISTENT);
     System.setProperty(MetadataStoreRoutingConstants.MSDS_SERVER_HOSTNAME_KEY,
-        getBaseUri().toString());
+        getBaseUri().getHost() + ":" + getBaseUri().getPort());
     _zkRoutingDataWriter = new ZkRoutingDataWriter(DUMMY_NAMESPACE, ZK_ADDR);
   }
 
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/server/MetadataStoreDirectoryAccessorTestBase.java b/helix-rest/src/test/java/org/apache/helix/rest/server/MetadataStoreDirectoryAccessorTestBase.java
index f234795..7cebbf3 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/server/MetadataStoreDirectoryAccessorTestBase.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/server/MetadataStoreDirectoryAccessorTestBase.java
@@ -52,6 +52,7 @@ public class MetadataStoreDirectoryAccessorTestBase extends AbstractTestClass {
       Arrays.asList("/sharding/key/1/d", "/sharding/key/1/e", "/sharding/key/1/f");
   protected static final String TEST_REALM_3 = "testRealm3";
   protected static final String TEST_SHARDING_KEY = "/sharding/key/1/x";
+  protected static final String INVALID_TEST_SHARDING_KEY = "sharding/key/1/x";
 
   // List of all ZK addresses, each of which corresponds to a namespace/routing ZK
   protected List<String> _zkList;
@@ -93,7 +94,7 @@ public class MetadataStoreDirectoryAccessorTestBase extends AbstractTestClass {
     _routingDataReader = new ZkRoutingDataReader(TEST_NAMESPACE, _zkAddrTestNS, null);
 
     System.setProperty(MetadataStoreRoutingConstants.MSDS_SERVER_HOSTNAME_KEY,
-        getBaseUri().toString());
+        getBaseUri().getHost() + ":" + getBaseUri().getPort());
   }
 
   @AfterClass
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/server/TestMSDAccessorLeaderElection.java b/helix-rest/src/test/java/org/apache/helix/rest/server/TestMSDAccessorLeaderElection.java
index 9a122a9..951015b 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/server/TestMSDAccessorLeaderElection.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/server/TestMSDAccessorLeaderElection.java
@@ -74,7 +74,7 @@ public class TestMSDAccessorLeaderElection extends MetadataStoreDirectoryAccesso
     int newPort = getBaseUri().getPort() + 1;
 
     // Start a second server for testing Distributed Leader Election for writes
-    _mockBaseUri = getBaseUri().getScheme() + "://" + getBaseUri().getHost() + ":" + newPort;
+    _mockBaseUri = HttpConstants.HTTP_PROTOCOL_PREFIX + getBaseUri().getHost() + ":" + newPort;
     try {
       List<HelixRestNamespace> namespaces = new ArrayList<>();
       // Add test namespace
@@ -93,7 +93,8 @@ public class TestMSDAccessorLeaderElection extends MetadataStoreDirectoryAccesso
         Response.Status.OK.getStatusCode(), true);
 
     // Set the new uri to be used in leader election
-    System.setProperty(MetadataStoreRoutingConstants.MSDS_SERVER_HOSTNAME_KEY, _mockBaseUri);
+    System.setProperty(MetadataStoreRoutingConstants.MSDS_SERVER_HOSTNAME_KEY,
+        getBaseUri().getHost() + ":" + newPort);
 
     // Start http client for testing
     _httpClient = HttpClients.createDefault();
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/server/TestMetadataStoreDirectoryAccessor.java b/helix-rest/src/test/java/org/apache/helix/rest/server/TestMetadataStoreDirectoryAccessor.java
index b6179aa..94641ff 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/server/TestMetadataStoreDirectoryAccessor.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/server/TestMetadataStoreDirectoryAccessor.java
@@ -99,6 +99,14 @@ public class TestMetadataStoreDirectoryAccessor extends MetadataStoreDirectoryAc
         NON_EXISTING_NAMESPACE_URI_PREFIX + "metadata-store-realms?sharding-key=" + shardingKey)
         .expectedReturnStatusCode(Response.Status.NOT_FOUND.getStatusCode()).get(this);
 
+    new JerseyUriRequestBuilder(
+        TEST_NAMESPACE_URI_PREFIX + "/metadata-store-realms?sharding-key=" + TEST_SHARDING_KEY)
+        .expectedReturnStatusCode(Response.Status.NOT_FOUND.getStatusCode()).get(this);
+
+    new JerseyUriRequestBuilder(TEST_NAMESPACE_URI_PREFIX + "/metadata-store-realms?sharding-key="
+        + INVALID_TEST_SHARDING_KEY)
+        .expectedReturnStatusCode(Response.Status.BAD_REQUEST.getStatusCode()).get(this);
+
     String responseBody = new JerseyUriRequestBuilder(
         TEST_NAMESPACE_URI_PREFIX + "/metadata-store-realms?sharding-key=" + shardingKey)
         .isBodyReturnExpected(true).get(this);
@@ -133,6 +141,11 @@ public class TestMetadataStoreDirectoryAccessor extends MetadataStoreDirectoryAc
         Entity.entity("", MediaType.APPLICATION_JSON_TYPE),
         Response.Status.CREATED.getStatusCode());
 
+    // Second addition also succeeds.
+    put(TEST_NAMESPACE_URI_PREFIX + "/metadata-store-realms/" + TEST_REALM_3, null,
+        Entity.entity("", MediaType.APPLICATION_JSON_TYPE),
+        Response.Status.CREATED.getStatusCode());
+
     expectedRealmsSet.add(TEST_REALM_3);
     Assert.assertEquals(getAllRealms(), expectedRealmsSet);
   }
@@ -151,6 +164,10 @@ public class TestMetadataStoreDirectoryAccessor extends MetadataStoreDirectoryAc
     delete(TEST_NAMESPACE_URI_PREFIX + "/metadata-store-realms/" + TEST_REALM_3,
         Response.Status.OK.getStatusCode());
 
+    // Second deletion also succeeds.
+    delete(TEST_NAMESPACE_URI_PREFIX + "/metadata-store-realms/" + TEST_REALM_3,
+        Response.Status.OK.getStatusCode());
+
     Set<String> updateRealmsSet = getAllRealms();
     expectedRealmsSet.remove(TEST_REALM_3);
     Assert.assertEquals(updateRealmsSet, expectedRealmsSet);
@@ -190,9 +207,65 @@ public class TestMetadataStoreDirectoryAccessor extends MetadataStoreDirectoryAc
   }
 
   /*
-   * Tests REST endpoint: "GET /routing-data"
+   * Tests REST endpoint: "GET /sharding-keys?prefix={prefix}"
    */
+  @SuppressWarnings("unchecked")
   @Test(dependsOnMethods = "testGetShardingKeysInNamespace")
+  public void testGetShardingKeysUnderPath() throws IOException {
+    new JerseyUriRequestBuilder(
+        TEST_NAMESPACE_URI_PREFIX + "/sharding-keys?prefix=" + INVALID_TEST_SHARDING_KEY)
+        .expectedReturnStatusCode(Response.Status.BAD_REQUEST.getStatusCode()).get(this);
+
+    // Test non existed prefix and empty sharding keys in response.
+    String responseBody = new JerseyUriRequestBuilder(
+        TEST_NAMESPACE_URI_PREFIX + "/sharding-keys?prefix=/non/Existed/Prefix")
+        .isBodyReturnExpected(true).get(this);
+
+    Map<String, Object> queriedShardingKeysMap = OBJECT_MAPPER.readValue(responseBody, Map.class);
+    Collection<Map<String, String>> emptyKeysList =
+        (Collection<Map<String, String>>) queriedShardingKeysMap
+            .get(MetadataStoreRoutingConstants.SHARDING_KEYS);
+    Assert.assertTrue(emptyKeysList.isEmpty());
+
+    // Success response with non empty sharding keys.
+    String shardingKeyPrefix = "/sharding/key";
+    responseBody = new JerseyUriRequestBuilder(
+        TEST_NAMESPACE_URI_PREFIX + "/sharding-keys?prefix=" + shardingKeyPrefix)
+        .isBodyReturnExpected(true).get(this);
+
+    queriedShardingKeysMap = OBJECT_MAPPER.readValue(responseBody, Map.class);
+
+    // Check fields.
+    Assert.assertEquals(queriedShardingKeysMap.keySet(), ImmutableSet
+        .of(MetadataStoreRoutingConstants.SHARDING_KEY_PATH_PREFIX,
+            MetadataStoreRoutingConstants.SHARDING_KEYS));
+
+    // Check sharding key prefix in json response.
+    Assert.assertEquals(
+        queriedShardingKeysMap.get(MetadataStoreRoutingConstants.SHARDING_KEY_PATH_PREFIX),
+        shardingKeyPrefix);
+
+    Collection<Map<String, String>> queriedShardingKeys =
+        (Collection<Map<String, String>>) queriedShardingKeysMap
+            .get(MetadataStoreRoutingConstants.SHARDING_KEYS);
+    Set<Map<String, String>> queriedShardingKeysSet = new HashSet<>(queriedShardingKeys);
+    Set<Map<String, String>> expectedShardingKeysSet = new HashSet<>();
+
+    TEST_SHARDING_KEYS_1.forEach(key -> expectedShardingKeysSet.add(ImmutableMap
+        .of(MetadataStoreRoutingConstants.SINGLE_SHARDING_KEY, key,
+            MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_REALM, TEST_REALM_1)));
+
+    TEST_SHARDING_KEYS_2.forEach(key -> expectedShardingKeysSet.add(ImmutableMap
+        .of(MetadataStoreRoutingConstants.SINGLE_SHARDING_KEY, key,
+            MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_REALM, TEST_REALM_2)));
+
+    Assert.assertEquals(queriedShardingKeysSet, expectedShardingKeysSet);
+  }
+
+  /*
+   * Tests REST endpoint: "GET /routing-data"
+   */
+  @Test(dependsOnMethods = "testGetShardingKeysUnderPath")
   public void testGetRoutingData() throws IOException {
     /*
      * responseBody:
@@ -258,63 +331,15 @@ public class TestMetadataStoreDirectoryAccessor extends MetadataStoreDirectoryAc
   }
 
   /*
-   * Tests REST endpoint: "GET /sharding-keys?prefix={prefix}"
-   */
-  @SuppressWarnings("unchecked")
-  @Test(dependsOnMethods = "testGetShardingKeysInRealm")
-  public void testGetShardingKeysUnderPath() throws IOException {
-    // Test non existed prefix and empty sharding keys in response.
-    String responseBody = new JerseyUriRequestBuilder(
-        TEST_NAMESPACE_URI_PREFIX + "/sharding-keys?prefix=/non/Existed/Prefix")
-        .isBodyReturnExpected(true).get(this);
-
-    Map<String, Object> queriedShardingKeysMap = OBJECT_MAPPER.readValue(responseBody, Map.class);
-    Collection<Map<String, String>> emptyKeysList =
-        (Collection<Map<String, String>>) queriedShardingKeysMap
-            .get(MetadataStoreRoutingConstants.SHARDING_KEYS);
-    Assert.assertTrue(emptyKeysList.isEmpty());
-
-    // Success response with non empty sharding keys.
-    String shardingKeyPrefix = "/sharding/key";
-    responseBody = new JerseyUriRequestBuilder(
-        TEST_NAMESPACE_URI_PREFIX + "/sharding-keys?prefix=" + shardingKeyPrefix)
-        .isBodyReturnExpected(true).get(this);
-
-    queriedShardingKeysMap = OBJECT_MAPPER.readValue(responseBody, Map.class);
-
-    // Check fields.
-    Assert.assertEquals(queriedShardingKeysMap.keySet(), ImmutableSet
-        .of(MetadataStoreRoutingConstants.SHARDING_KEY_PATH_PREFIX,
-            MetadataStoreRoutingConstants.SHARDING_KEYS));
-
-    // Check sharding key prefix in json response.
-    Assert.assertEquals(
-        queriedShardingKeysMap.get(MetadataStoreRoutingConstants.SHARDING_KEY_PATH_PREFIX),
-        shardingKeyPrefix);
-
-    Collection<Map<String, String>> queriedShardingKeys =
-        (Collection<Map<String, String>>) queriedShardingKeysMap
-            .get(MetadataStoreRoutingConstants.SHARDING_KEYS);
-    Set<Map<String, String>> queriedShardingKeysSet = new HashSet<>(queriedShardingKeys);
-    Set<Map<String, String>> expectedShardingKeysSet = new HashSet<>();
-
-    TEST_SHARDING_KEYS_1.forEach(key -> expectedShardingKeysSet.add(ImmutableMap
-        .of(MetadataStoreRoutingConstants.SINGLE_SHARDING_KEY, key,
-            MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_REALM, TEST_REALM_1)));
-
-    TEST_SHARDING_KEYS_2.forEach(key -> expectedShardingKeysSet.add(ImmutableMap
-        .of(MetadataStoreRoutingConstants.SINGLE_SHARDING_KEY, key,
-            MetadataStoreRoutingConstants.SINGLE_METADATA_STORE_REALM, TEST_REALM_2)));
-
-    Assert.assertEquals(queriedShardingKeysSet, expectedShardingKeysSet);
-  }
-
-  /*
    * Tests REST endpoint: "GET /metadata-store-realms/{realm}/sharding-keys?prefix={prefix}"
    */
   @SuppressWarnings("unchecked")
-  @Test(dependsOnMethods = "testGetShardingKeysUnderPath")
+  @Test(dependsOnMethods = "testGetShardingKeysInRealm")
   public void testGetRealmShardingKeysUnderPath() throws IOException {
+    new JerseyUriRequestBuilder(TEST_NAMESPACE_URI_PREFIX + "/metadata-store-realms/" + TEST_REALM_1
+        + "/sharding-keys?prefix=" + INVALID_TEST_SHARDING_KEY)
+        .expectedReturnStatusCode(Response.Status.BAD_REQUEST.getStatusCode()).get(this);
+
     // Test non existed prefix and empty sharding keys in response.
     String responseBody = new JerseyUriRequestBuilder(
         TEST_NAMESPACE_URI_PREFIX + "/metadata-store-realms/" + TEST_REALM_1
@@ -380,11 +405,26 @@ public class TestMetadataStoreDirectoryAccessor extends MetadataStoreDirectoryAc
         null, Entity.entity("", MediaType.APPLICATION_JSON_TYPE),
         Response.Status.NOT_FOUND.getStatusCode());
 
+    put(TEST_NAMESPACE_URI_PREFIX + "/metadata-store-realms/" + TEST_REALM_1 + "/sharding-keys"
+            + "//" + INVALID_TEST_SHARDING_KEY, null,
+        Entity.entity("", MediaType.APPLICATION_JSON_TYPE),
+        Response.Status.BAD_REQUEST.getStatusCode());
+
     // Successful request.
     put(TEST_NAMESPACE_URI_PREFIX + "/metadata-store-realms/" + TEST_REALM_1 + "/sharding-keys"
             + TEST_SHARDING_KEY, null, Entity.entity("", MediaType.APPLICATION_JSON_TYPE),
         Response.Status.CREATED.getStatusCode());
 
+    // Idempotency
+    put(TEST_NAMESPACE_URI_PREFIX + "/metadata-store-realms/" + TEST_REALM_1 + "/sharding-keys"
+            + TEST_SHARDING_KEY, null, Entity.entity("", MediaType.APPLICATION_JSON_TYPE),
+        Response.Status.CREATED.getStatusCode());
+
+    // Invalid
+    put(TEST_NAMESPACE_URI_PREFIX + "/metadata-store-realms/" + TEST_REALM_2 + "/sharding-keys"
+            + TEST_SHARDING_KEY, null, Entity.entity("", MediaType.APPLICATION_JSON_TYPE),
+        Response.Status.BAD_REQUEST.getStatusCode());
+
     expectedShardingKeysSet.add(TEST_SHARDING_KEY);
     Assert.assertEquals(getAllShardingKeysInTestRealm1(), expectedShardingKeysSet);
   }
@@ -404,6 +444,10 @@ public class TestMetadataStoreDirectoryAccessor extends MetadataStoreDirectoryAc
     delete(TEST_NAMESPACE_URI_PREFIX + "/metadata-store-realms/" + TEST_REALM_1 + "/sharding-keys"
         + TEST_SHARDING_KEY, Response.Status.OK.getStatusCode());
 
+    // Idempotency
+    delete(TEST_NAMESPACE_URI_PREFIX + "/metadata-store-realms/" + TEST_REALM_1 + "/sharding-keys"
+        + TEST_SHARDING_KEY, Response.Status.OK.getStatusCode());
+
     expectedShardingKeysSet.remove(TEST_SHARDING_KEY);
     Assert.assertEquals(getAllShardingKeysInTestRealm1(), expectedShardingKeysSet);
   }


[helix] 29/49: Add FederatedZkClient (#789)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit 4b28762b23f2d4ec81eca0f84c9a03c61e476cdb
Author: Huizhi Lu <ih...@gmail.com>
AuthorDate: Sat Feb 29 09:45:23 2020 -0800

    Add FederatedZkClient (#789)
    
    As part of ZkClient API enhancement, we wish to add FederatedZkClient, which is a wrapper of the raw ZkClient, that provides realm-aware access to ZooKeeper.
    FederatedZkClient will internally maintain multiple ZooKeeper sessions connecting to different ZooKeeper realms on an as-needed basis and route requests to the appropriate ZooKeeper based on the ZK path sharding key. Ephemeral node creation is not supported.
---
 .../zookeeper/impl/client/FederatedZkClient.java   | 322 ++++++++++++++++-----
 .../apache/helix/zookeeper/impl/ZkTestBase.java    |  13 +-
 .../impl/client/TestFederatedZkClient.java         | 312 ++++++++++++++++++++
 3 files changed, 573 insertions(+), 74 deletions(-)

diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/client/FederatedZkClient.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/client/FederatedZkClient.java
index 3925a6d..5f63408 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/client/FederatedZkClient.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/client/FederatedZkClient.java
@@ -19,345 +19,533 @@ package org.apache.helix.zookeeper.impl.client;
  * under the License.
  */
 
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.TimeUnit;
 
+import org.apache.helix.msdcommon.datamodel.MetadataStoreRoutingData;
 import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
+import org.apache.helix.zookeeper.impl.factory.DedicatedZkClientFactory;
 import org.apache.helix.zookeeper.zkclient.DataUpdater;
 import org.apache.helix.zookeeper.zkclient.IZkChildListener;
 import org.apache.helix.zookeeper.zkclient.IZkDataListener;
+import org.apache.helix.zookeeper.zkclient.ZkConnection;
 import org.apache.helix.zookeeper.zkclient.callback.ZkAsyncCallbacks;
-import org.apache.helix.zookeeper.zkclient.deprecated.IZkStateListener;
+import org.apache.helix.zookeeper.zkclient.IZkStateListener;
+import org.apache.helix.zookeeper.zkclient.serialize.BasicZkSerializer;
 import org.apache.helix.zookeeper.zkclient.serialize.PathBasedZkSerializer;
 import org.apache.helix.zookeeper.zkclient.serialize.ZkSerializer;
 import org.apache.zookeeper.CreateMode;
 import org.apache.zookeeper.Op;
 import org.apache.zookeeper.OpResult;
+import org.apache.zookeeper.ZooDefs;
 import org.apache.zookeeper.data.ACL;
 import org.apache.zookeeper.data.Stat;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Implements and supports all ZK operations defined in interface {@link RealmAwareZkClient},
+ * except for session-aware operations such as creating ephemeral nodes, for which
+ * an {@link UnsupportedOperationException} will be thrown.
+ * <p>
+ * It acts as a single ZK client but will automatically route read/write/change subscription
+ * requests to the corresponding ZkClient with the help of metadata store directory service.
+ * It could connect to multiple ZK addresses and maintain a {@link ZkClient} for each ZK address.
+ * <p>
+ * Note: each Zk realm has its own event queue to handle listeners. So listeners from different ZK
+ * realms could be handled concurrently because listeners of a ZK realm are handled in its own
+ * queue. The concurrency of listeners should be aware of when implementing listeners for different
+ * ZK realms. The users should use thread-safe data structures if they wish to handle change
+ * callbacks.
+ */
+public class FederatedZkClient implements RealmAwareZkClient {
+  private static final Logger LOG = LoggerFactory.getLogger(FederatedZkClient.class);
 
+  private static final String FEDERATED_ZK_CLIENT = FederatedZkClient.class.getSimpleName();
+  private static final String DEDICATED_ZK_CLIENT_FACTORY =
+      DedicatedZkClientFactory.class.getSimpleName();
+
+  private final MetadataStoreRoutingData _metadataStoreRoutingData;
+  private final RealmAwareZkClient.RealmAwareZkClientConfig _clientConfig;
+
+  // ZK realm -> ZkClient
+  private final Map<String, ZkClient> _zkRealmToZkClientMap;
+
+  private volatile boolean _isClosed;
+  private PathBasedZkSerializer _pathBasedZkSerializer;
+
+  // TODO: support capacity of ZkClient number in one FederatedZkClient and do garbage collection.
+  public FederatedZkClient(RealmAwareZkClient.RealmAwareZkClientConfig clientConfig,
+      MetadataStoreRoutingData metadataStoreRoutingData) {
+    if (metadataStoreRoutingData == null) {
+      throw new IllegalArgumentException("MetadataStoreRoutingData cannot be null!");
+    }
+    if (clientConfig == null) {
+      throw new IllegalArgumentException("Client config cannot be null!");
+    }
+
+    _isClosed = false;
+    _clientConfig = clientConfig;
+    _pathBasedZkSerializer = clientConfig.getZkSerializer();
+    _metadataStoreRoutingData = metadataStoreRoutingData;
+    _zkRealmToZkClientMap = new ConcurrentHashMap<>();
+  }
 
-public class FederatedZkClient implements RealmAwareZkClient {
   @Override
   public List<String> subscribeChildChanges(String path, IZkChildListener listener) {
-    return null;
+    return getZkClient(path).subscribeChildChanges(path, listener);
   }
 
   @Override
   public void unsubscribeChildChanges(String path, IZkChildListener listener) {
-
+    getZkClient(path).unsubscribeChildChanges(path, listener);
   }
 
   @Override
   public void subscribeDataChanges(String path, IZkDataListener listener) {
-
+    getZkClient(path).subscribeDataChanges(path, listener);
   }
 
   @Override
   public void unsubscribeDataChanges(String path, IZkDataListener listener) {
-
+    getZkClient(path).unsubscribeDataChanges(path, listener);
   }
 
   @Override
   public void subscribeStateChanges(IZkStateListener listener) {
-
+    throwUnsupportedOperationException();
   }
 
   @Override
   public void unsubscribeStateChanges(IZkStateListener listener) {
+    throwUnsupportedOperationException();
+  }
 
+  @Override
+  public void subscribeStateChanges(
+      org.apache.helix.zookeeper.zkclient.deprecated.IZkStateListener listener) {
+    throwUnsupportedOperationException();
   }
 
   @Override
-  public void unsubscribeAll() {
+  public void unsubscribeStateChanges(
+      org.apache.helix.zookeeper.zkclient.deprecated.IZkStateListener listener) {
+    throwUnsupportedOperationException();
+  }
 
+  @Override
+  public void unsubscribeAll() {
+    _zkRealmToZkClientMap.values().forEach(ZkClient::unsubscribeAll);
   }
 
   @Override
   public void createPersistent(String path) {
-
+    createPersistent(path, false);
   }
 
   @Override
   public void createPersistent(String path, boolean createParents) {
-
+    createPersistent(path, createParents, ZooDefs.Ids.OPEN_ACL_UNSAFE);
   }
 
   @Override
   public void createPersistent(String path, boolean createParents, List<ACL> acl) {
-
+    getZkClient(path).createPersistent(path, createParents, acl);
   }
 
   @Override
   public void createPersistent(String path, Object data) {
-
+    create(path, data, CreateMode.PERSISTENT);
   }
 
   @Override
   public void createPersistent(String path, Object data, List<ACL> acl) {
-
+    create(path, data, acl, CreateMode.PERSISTENT);
   }
 
   @Override
   public String createPersistentSequential(String path, Object data) {
-    return null;
+    return create(path, data, CreateMode.PERSISTENT_SEQUENTIAL);
   }
 
   @Override
   public String createPersistentSequential(String path, Object data, List<ACL> acl) {
-    return null;
+    return create(path, data, acl, CreateMode.PERSISTENT_SEQUENTIAL);
   }
 
   @Override
   public void createEphemeral(String path) {
-
+    create(path, null, CreateMode.EPHEMERAL);
   }
 
   @Override
   public void createEphemeral(String path, String sessionId) {
-
+    createEphemeral(path, null, sessionId);
   }
 
   @Override
   public void createEphemeral(String path, List<ACL> acl) {
-
+    create(path, null, acl, CreateMode.EPHEMERAL);
   }
 
   @Override
   public void createEphemeral(String path, List<ACL> acl, String sessionId) {
-
+    create(path, null, acl, CreateMode.EPHEMERAL, sessionId);
   }
 
   @Override
   public String create(String path, Object data, CreateMode mode) {
-    return null;
+    return create(path, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, mode);
   }
 
   @Override
-  public String create(String path, Object datat, List<ACL> acl, CreateMode mode) {
-    return null;
+  public String create(String path, Object data, List<ACL> acl, CreateMode mode) {
+    return create(path, data, acl, mode, null);
   }
 
   @Override
   public void createEphemeral(String path, Object data) {
-
+    create(path, data, CreateMode.EPHEMERAL);
   }
 
   @Override
   public void createEphemeral(String path, Object data, String sessionId) {
-
+    create(path, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL, sessionId);
   }
 
   @Override
   public void createEphemeral(String path, Object data, List<ACL> acl) {
-
+    create(path, data, acl, CreateMode.EPHEMERAL);
   }
 
   @Override
   public void createEphemeral(String path, Object data, List<ACL> acl, String sessionId) {
-
+    create(path, data, acl, CreateMode.EPHEMERAL, sessionId);
   }
 
   @Override
   public String createEphemeralSequential(String path, Object data) {
-    return null;
+    return create(path, data, CreateMode.EPHEMERAL_SEQUENTIAL);
   }
 
   @Override
   public String createEphemeralSequential(String path, Object data, List<ACL> acl) {
-    return null;
+    return create(path, data, acl, CreateMode.EPHEMERAL_SEQUENTIAL);
   }
 
   @Override
   public String createEphemeralSequential(String path, Object data, String sessionId) {
-    return null;
+    return create(path, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL,
+        sessionId);
   }
 
   @Override
   public String createEphemeralSequential(String path, Object data, List<ACL> acl,
       String sessionId) {
-    return null;
+    return create(path, data, acl, CreateMode.EPHEMERAL_SEQUENTIAL, sessionId);
   }
 
   @Override
   public List<String> getChildren(String path) {
-    return null;
+    return getZkClient(path).getChildren(path);
   }
 
   @Override
   public int countChildren(String path) {
-    return 0;
+    return getZkClient(path).countChildren(path);
   }
 
   @Override
   public boolean exists(String path) {
-    return false;
+    return getZkClient(path).exists(path);
   }
 
   @Override
   public Stat getStat(String path) {
-    return null;
+    return getZkClient(path).getStat(path);
   }
 
   @Override
   public boolean waitUntilExists(String path, TimeUnit timeUnit, long time) {
-    return false;
+    return getZkClient(path).waitUntilExists(path, timeUnit, time);
   }
 
   @Override
   public void deleteRecursively(String path) {
-
+    getZkClient(path).deleteRecursively(path);
   }
 
   @Override
   public boolean delete(String path) {
-    return false;
+    return getZkClient(path).delete(path);
   }
 
   @Override
+  @SuppressWarnings("unchecked")
   public <T> T readData(String path) {
-    return null;
+    return (T) readData(path, false);
   }
 
   @Override
   public <T> T readData(String path, boolean returnNullIfPathNotExists) {
-    return null;
+    return getZkClient(path).readData(path, returnNullIfPathNotExists);
   }
 
   @Override
   public <T> T readData(String path, Stat stat) {
-    return null;
+    return getZkClient(path).readData(path, stat);
   }
 
   @Override
   public <T> T readData(String path, Stat stat, boolean watch) {
-    return null;
+    return getZkClient(path).readData(path, stat, watch);
   }
 
   @Override
   public <T> T readDataAndStat(String path, Stat stat, boolean returnNullIfPathNotExists) {
-    return null;
+    return getZkClient(path).readData(path, stat, returnNullIfPathNotExists);
   }
 
   @Override
   public void writeData(String path, Object object) {
-
+    writeData(path, object, -1);
   }
 
   @Override
   public <T> void updateDataSerialized(String path, DataUpdater<T> updater) {
-
+    getZkClient(path).updateDataSerialized(path, updater);
   }
 
   @Override
-  public void writeData(String path, Object datat, int expectedVersion) {
-
+  public void writeData(String path, Object data, int expectedVersion) {
+    writeDataReturnStat(path, data, expectedVersion);
   }
 
   @Override
-  public Stat writeDataReturnStat(String path, Object datat, int expectedVersion) {
-    return null;
+  public Stat writeDataReturnStat(String path, Object data, int expectedVersion) {
+    return getZkClient(path).writeDataReturnStat(path, data, expectedVersion);
   }
 
   @Override
-  public Stat writeDataGetStat(String path, Object datat, int expectedVersion) {
-    return null;
+  public Stat writeDataGetStat(String path, Object data, int expectedVersion) {
+    return writeDataReturnStat(path, data, expectedVersion);
   }
 
   @Override
-  public void asyncCreate(String path, Object datat, CreateMode mode,
+  public void asyncCreate(String path, Object data, CreateMode mode,
       ZkAsyncCallbacks.CreateCallbackHandler cb) {
-
+    getZkClient(path).asyncCreate(path, data, mode, cb);
   }
 
   @Override
-  public void asyncSetData(String path, Object datat, int version,
+  public void asyncSetData(String path, Object data, int version,
       ZkAsyncCallbacks.SetDataCallbackHandler cb) {
-
+    getZkClient(path).asyncSetData(path, data, version, cb);
   }
 
   @Override
   public void asyncGetData(String path, ZkAsyncCallbacks.GetDataCallbackHandler cb) {
-
+    getZkClient(path).asyncGetData(path, cb);
   }
 
   @Override
   public void asyncExists(String path, ZkAsyncCallbacks.ExistsCallbackHandler cb) {
-
+    getZkClient(path).asyncExists(path, cb);
   }
 
   @Override
   public void asyncDelete(String path, ZkAsyncCallbacks.DeleteCallbackHandler cb) {
-
+    getZkClient(path).asyncDelete(path, cb);
   }
 
   @Override
   public void watchForData(String path) {
-
+    getZkClient(path).watchForData(path);
   }
 
   @Override
   public List<String> watchForChilds(String path) {
-    return null;
+    return getZkClient(path).watchForChilds(path);
   }
 
   @Override
   public long getCreationTime(String path) {
-    return 0;
+    return getZkClient(path).getCreationTime(path);
   }
 
   @Override
   public List<OpResult> multi(Iterable<Op> ops) {
+    throwUnsupportedOperationException();
     return null;
   }
 
   @Override
   public boolean waitUntilConnected(long time, TimeUnit timeUnit) {
+    throwUnsupportedOperationException();
     return false;
   }
 
   @Override
   public String getServers() {
+    throwUnsupportedOperationException();
     return null;
   }
 
   @Override
   public long getSessionId() {
-    return 0;
+    // Session-aware is unsupported.
+    throwUnsupportedOperationException();
+    return 0L;
   }
 
   @Override
   public void close() {
+    if (isClosed()) {
+      return;
+    }
 
+    _isClosed = true;
+
+    synchronized (_zkRealmToZkClientMap) {
+      Iterator<Map.Entry<String, ZkClient>> iterator = _zkRealmToZkClientMap.entrySet().iterator();
+
+      while (iterator.hasNext()) {
+        Map.Entry<String, ZkClient> entry = iterator.next();
+        String zkRealm = entry.getKey();
+        ZkClient zkClient = entry.getValue();
+
+        // Catch any exception from ZkClient's close() to avoid that there is leakage of
+        // remaining unclosed ZkClient.
+        try {
+          zkClient.close();
+        } catch (Exception e) {
+          LOG.error("Exception thrown when closing ZkClient for ZkRealm: {}!", zkRealm, e);
+        }
+        iterator.remove();
+      }
+    }
+
+    LOG.info("{} is successfully closed.", FEDERATED_ZK_CLIENT);
   }
 
   @Override
   public boolean isClosed() {
-    return false;
+    return _isClosed;
   }
 
   @Override
   public byte[] serialize(Object data, String path) {
-    return new byte[0];
+    return getZkClient(path).serialize(data, path);
   }
 
   @Override
   public <T> T deserialize(byte[] data, String path) {
-    return null;
+    return getZkClient(path).deserialize(data, path);
   }
 
   @Override
   public void setZkSerializer(ZkSerializer zkSerializer) {
-
+    _pathBasedZkSerializer = new BasicZkSerializer(zkSerializer);
+    _zkRealmToZkClientMap.values()
+        .forEach(zkClient -> zkClient.setZkSerializer(_pathBasedZkSerializer));
   }
 
   @Override
   public void setZkSerializer(PathBasedZkSerializer zkSerializer) {
-
+    _pathBasedZkSerializer = zkSerializer;
+    _zkRealmToZkClientMap.values().forEach(zkClient -> zkClient.setZkSerializer(zkSerializer));
   }
 
   @Override
   public PathBasedZkSerializer getZkSerializer() {
-    return null;
+    return _pathBasedZkSerializer;
+  }
+
+  private String create(final String path, final Object dataObject, final List<ACL> acl,
+      final CreateMode mode, final String expectedSessionId) {
+    if (mode.isEphemeral()) {
+      throwUnsupportedOperationException();
+    }
+
+    // Create mode is not session-aware, so the node does not have to be created
+    // by the expectedSessionId.
+    return getZkClient(path).create(path, dataObject, acl, mode);
+  }
+
+  private ZkClient getZkClient(String path) {
+    // If FederatedZkClient is closed, should not return ZkClient.
+    checkClosedState();
+
+    String zkRealm = getZkRealm(path);
+
+    // Use this zkClient reference to protect the returning zkClient from being null because of
+    // race condition. Once we get the reference, even _zkRealmToZkClientMap is cleared by closed(),
+    // this zkClient is not null which guarantees the returned value not null.
+    ZkClient zkClient = _zkRealmToZkClientMap.get(zkRealm);
+
+    if (zkClient == null) {
+      // 1. Synchronized to avoid creating duplicate ZkClient for the same ZkRealm.
+      // 2. Synchronized with close() to avoid creating new ZkClient when all ZkClients are
+      // being closed and _zkRealmToZkClientMap is being cleared.
+      synchronized (_zkRealmToZkClientMap) {
+        // Because of potential race condition: thread B to get ZkClient could be blocked by this
+        // synchronized, while thread A is executing closed() in its synchronized block. So thread B
+        // could still enter this synchronized block once A completes executing closed() and
+        // releases the synchronized lock.
+        // Check closed state again to avoid creating a new ZkClient after FederatedZkClient
+        // is already closed.
+        checkClosedState();
+
+        if (!_zkRealmToZkClientMap.containsKey(zkRealm)) {
+          zkClient = createZkClient(zkRealm);
+          _zkRealmToZkClientMap.put(zkRealm, zkClient);
+        } else {
+          zkClient = _zkRealmToZkClientMap.get(zkRealm);
+        }
+      }
+    }
+
+    return zkClient;
+  }
+
+  private String getZkRealm(String path) {
+    String zkRealm;
+    try {
+      zkRealm = _metadataStoreRoutingData.getMetadataStoreRealm(path);
+    } catch (NoSuchElementException ex) {
+      throw new NoSuchElementException("Cannot find ZK realm for the path: " + path);
+    }
+
+    if (zkRealm == null || zkRealm.isEmpty()) {
+      throw new NoSuchElementException("Cannot find ZK realm for the path: " + path);
+    }
+
+    return zkRealm;
+  }
+
+  private ZkClient createZkClient(String zkAddress) {
+    LOG.debug("Creating ZkClient for realm: {}.", zkAddress);
+    return new ZkClient(new ZkConnection(zkAddress), (int) _clientConfig.getConnectInitTimeout(),
+        _clientConfig.getOperationRetryTimeout(), _pathBasedZkSerializer,
+        _clientConfig.getMonitorType(), _clientConfig.getMonitorKey(),
+        _clientConfig.getMonitorInstanceName(), _clientConfig.isMonitorRootPathOnly());
+  }
+
+  private void checkClosedState() {
+    if (isClosed()) {
+      throw new IllegalStateException(FEDERATED_ZK_CLIENT + " is closed!");
+    }
+  }
+
+  private void throwUnsupportedOperationException() {
+    throw new UnsupportedOperationException(
+        "Session-aware operation is not supported by " + FEDERATED_ZK_CLIENT
+            + ". Instead, please use " + DEDICATED_ZK_CLIENT_FACTORY
+            + " to create a dedicated RealmAwareZkClient for this operation.");
   }
 }
diff --git a/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/ZkTestBase.java b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/ZkTestBase.java
index 10edaf4..7e59652 100644
--- a/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/ZkTestBase.java
+++ b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/ZkTestBase.java
@@ -57,12 +57,11 @@ public class ZkTestBase {
    * Multiple ZK references
    */
   // The following maps hold ZK connect string as keys
-  protected Map<String, ZkServer> _zkServerMap = new HashMap<>();
-  protected int _numZk = 1; // Initial value
+  protected final Map<String, ZkServer> _zkServerMap = new HashMap<>();
+  protected static int _numZk = 1; // Initial value
 
   @BeforeSuite
-  public void beforeSuite()
-      throws IOException {
+  public void beforeSuite() throws IOException {
     // Due to ZOOKEEPER-2693 fix, we need to specify whitelist for execute zk commends
     System.setProperty("zookeeper.4lw.commands.whitelist", "*");
 
@@ -80,8 +79,7 @@ public class ZkTestBase {
   }
 
   @AfterSuite
-  public void afterSuite()
-      throws IOException {
+  public void afterSuite() throws IOException {
     // Clean up all JMX objects
     for (ObjectName mbean : MBEAN_SERVER.queryNames(null, null)) {
       try {
@@ -124,7 +122,7 @@ public class ZkTestBase {
    * @param zkAddress
    * @return
    */
-  private ZkServer startZkServer(final String zkAddress) {
+  protected ZkServer startZkServer(final String zkAddress) {
     String zkDir = zkAddress.replace(':', '_');
     final String logDir = "/tmp/" + zkDir + "/logs";
     final String dataDir = "/tmp/" + zkDir + "/dataDir";
@@ -142,6 +140,7 @@ public class ZkTestBase {
 
     int port = Integer.parseInt(zkAddress.substring(zkAddress.lastIndexOf(':') + 1));
     ZkServer zkServer = new ZkServer(dataDir, logDir, defaultNameSpace, port);
+    System.out.println("Starting ZK server at " + zkAddress);
     zkServer.start();
     return zkServer;
   }
diff --git a/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/TestFederatedZkClient.java b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/TestFederatedZkClient.java
new file mode 100644
index 0000000..5801690
--- /dev/null
+++ b/zookeeper-api/src/test/java/org/apache/helix/zookeeper/impl/client/TestFederatedZkClient.java
@@ -0,0 +1,312 @@
+package org.apache.helix.zookeeper.impl.client;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.helix.msdcommon.datamodel.TrieRoutingData;
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
+import org.apache.helix.zookeeper.datamodel.ZNRecord;
+import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
+import org.apache.helix.zookeeper.impl.ZkTestBase;
+import org.apache.helix.zookeeper.zkclient.IZkStateListener;
+import org.apache.helix.zookeeper.zkclient.ZkServer;
+import org.apache.zookeeper.CreateMode;
+import org.apache.zookeeper.Op;
+import org.apache.zookeeper.Watcher;
+import org.apache.zookeeper.ZooDefs;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+
+public class TestFederatedZkClient extends ZkTestBase {
+  private static final String TEST_SHARDING_KEY_PREFIX = "/test_sharding_key_";
+  private static final String TEST_REALM_ONE_VALID_PATH = TEST_SHARDING_KEY_PREFIX + "1/a/b/c";
+  private static final String TEST_REALM_TWO_VALID_PATH = TEST_SHARDING_KEY_PREFIX + "2/x/y/z";
+  private static final String TEST_INVALID_PATH = TEST_SHARDING_KEY_PREFIX + "invalid/a/b/c";
+  private static final String UNSUPPORTED_OPERATION_MESSAGE =
+      "Session-aware operation is not supported by FederatedZkClient.";
+
+  private RealmAwareZkClient _realmAwareZkClient;
+  // Need to start an extra ZK server for multi-realm test, if only one ZK server is running.
+  private String _extraZkRealm;
+  private ZkServer _extraZkServer;
+
+  @BeforeClass
+  public void beforeClass() throws InvalidRoutingDataException {
+    System.out.println("Starting " + TestFederatedZkClient.class.getSimpleName());
+
+    // Populate rawRoutingData
+    // <Realm, List of sharding keys> Mapping
+    Map<String, List<String>> rawRoutingData = new HashMap<>();
+    for (int i = 0; i < _numZk; i++) {
+      List<String> shardingKeyList = Collections.singletonList(TEST_SHARDING_KEY_PREFIX + (i + 1));
+      String realmName = ZK_PREFIX + (ZK_START_PORT + i);
+      rawRoutingData.put(realmName, shardingKeyList);
+    }
+
+    if (rawRoutingData.size() < 2) {
+      System.out.println("There is only one ZK realm. Starting one more ZK to test multi-realm.");
+      _extraZkRealm = ZK_PREFIX + (ZK_START_PORT + 1);
+      _extraZkServer = startZkServer(_extraZkRealm);
+      // RealmTwo's sharding key: /test_sharding_key_2
+      List<String> shardingKeyList = Collections.singletonList(TEST_SHARDING_KEY_PREFIX + "2");
+      rawRoutingData.put(_extraZkRealm, shardingKeyList);
+    }
+
+    // Feed the raw routing data into TrieRoutingData to construct an in-memory representation
+    // of routing information.
+    _realmAwareZkClient = new FederatedZkClient(new RealmAwareZkClient.RealmAwareZkClientConfig(),
+        new TrieRoutingData(rawRoutingData));
+  }
+
+  @AfterClass
+  public void afterClass() {
+    // Close it as it is created in before class.
+    _realmAwareZkClient.close();
+
+    // Close the extra zk server.
+    if (_extraZkServer != null) {
+      _extraZkServer.shutdown();
+    }
+
+    System.out.println("Ending " + TestFederatedZkClient.class.getSimpleName());
+  }
+
+  /*
+   * Tests that an unsupported operation should throw an UnsupportedOperationException.
+   */
+  @Test
+  public void testUnsupportedOperations() {
+    // Test creating ephemeral.
+    try {
+      _realmAwareZkClient.create(TEST_REALM_ONE_VALID_PATH, "Hello", CreateMode.EPHEMERAL);
+      Assert.fail("Ephemeral node should not be created.");
+    } catch (UnsupportedOperationException ex) {
+      Assert.assertTrue(ex.getMessage().startsWith(UNSUPPORTED_OPERATION_MESSAGE));
+    }
+
+    // Test creating ephemeral sequential.
+    try {
+      _realmAwareZkClient
+          .create(TEST_REALM_ONE_VALID_PATH, "Hello", CreateMode.EPHEMERAL_SEQUENTIAL);
+      Assert.fail("Ephemeral node should not be created.");
+    } catch (UnsupportedOperationException ex) {
+      Assert.assertTrue(ex.getMessage().startsWith(UNSUPPORTED_OPERATION_MESSAGE));
+    }
+
+    List<Op> ops = Arrays.asList(
+        Op.create(TEST_REALM_ONE_VALID_PATH, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,
+            CreateMode.PERSISTENT), Op.delete(TEST_REALM_ONE_VALID_PATH, -1));
+    try {
+      _realmAwareZkClient.multi(ops);
+      Assert.fail("multi() should not be supported.");
+    } catch (UnsupportedOperationException ex) {
+      Assert.assertTrue(ex.getMessage().startsWith(UNSUPPORTED_OPERATION_MESSAGE));
+    }
+
+    try {
+      _realmAwareZkClient.getSessionId();
+      Assert.fail("getSessionId() should not be supported.");
+    } catch (UnsupportedOperationException ex) {
+      Assert.assertTrue(ex.getMessage().startsWith(UNSUPPORTED_OPERATION_MESSAGE));
+    }
+
+    try {
+      _realmAwareZkClient.getServers();
+      Assert.fail("getServers() should not be supported.");
+    } catch (UnsupportedOperationException ex) {
+      Assert.assertTrue(ex.getMessage().startsWith(UNSUPPORTED_OPERATION_MESSAGE));
+    }
+
+    try {
+      _realmAwareZkClient.waitUntilConnected(5L, TimeUnit.SECONDS);
+      Assert.fail("getServers() should not be supported.");
+    } catch (UnsupportedOperationException ex) {
+      Assert.assertTrue(ex.getMessage().startsWith(UNSUPPORTED_OPERATION_MESSAGE));
+    }
+
+    // Test state change subscription.
+    IZkStateListener listener = new IZkStateListener() {
+      @Override
+      public void handleStateChanged(Watcher.Event.KeeperState state) {
+        System.out.println("Handle new state: " + state);
+      }
+
+      @Override
+      public void handleNewSession(String sessionId) {
+        System.out.println("Handle new session: " + sessionId);
+      }
+
+      @Override
+      public void handleSessionEstablishmentError(Throwable error) {
+        System.out.println("Handle session establishment error: " + error);
+      }
+    };
+
+    try {
+      _realmAwareZkClient.subscribeStateChanges(listener);
+      Assert.fail("getServers() should not be supported.");
+    } catch (UnsupportedOperationException ex) {
+      Assert.assertTrue(ex.getMessage().startsWith(UNSUPPORTED_OPERATION_MESSAGE));
+    }
+
+    try {
+      _realmAwareZkClient.unsubscribeStateChanges(listener);
+      Assert.fail("getServers() should not be supported.");
+    } catch (UnsupportedOperationException ex) {
+      Assert.assertTrue(ex.getMessage().startsWith(UNSUPPORTED_OPERATION_MESSAGE));
+    }
+  }
+
+  /*
+   * Tests the persistent create() call against a valid path and an invalid path.
+   * Valid path is one that belongs to the realm designated by the sharding key.
+   * Invalid path is one that does not belong to the realm designated by the sharding key.
+   */
+  @Test(dependsOnMethods = "testUnsupportedOperations")
+  public void testCreatePersistent() {
+    _realmAwareZkClient.setZkSerializer(new ZNRecordSerializer());
+
+    // Create a dummy ZNRecord
+    ZNRecord znRecord = new ZNRecord("DummyRecord");
+    znRecord.setSimpleField("Dummy", "Value");
+
+    // Test writing and reading against the validPath
+    _realmAwareZkClient.createPersistent(TEST_REALM_ONE_VALID_PATH, true);
+    _realmAwareZkClient.writeData(TEST_REALM_ONE_VALID_PATH, znRecord);
+    Assert.assertEquals(_realmAwareZkClient.readData(TEST_REALM_ONE_VALID_PATH), znRecord);
+
+    // Test writing and reading against the invalid path
+    try {
+      _realmAwareZkClient.createPersistent(TEST_INVALID_PATH, true);
+      Assert.fail("Create() should not succeed on an invalid path!");
+    } catch (NoSuchElementException ex) {
+      Assert
+          .assertEquals(ex.getMessage(), "Cannot find ZK realm for the path: " + TEST_INVALID_PATH);
+    }
+  }
+
+  /*
+   * Tests that exists() works on valid path and fails on invalid path.
+   */
+  @Test(dependsOnMethods = "testCreatePersistent")
+  public void testExists() {
+    Assert.assertTrue(_realmAwareZkClient.exists(TEST_REALM_ONE_VALID_PATH));
+
+    try {
+      _realmAwareZkClient.exists(TEST_INVALID_PATH);
+      Assert.fail("Exists() should not succeed on an invalid path!");
+    } catch (NoSuchElementException ex) {
+      Assert
+          .assertEquals(ex.getMessage(), "Cannot find ZK realm for the path: " + TEST_INVALID_PATH);
+    }
+  }
+
+  /*
+   * Tests that delete() works on valid path and fails on invalid path.
+   */
+  @Test(dependsOnMethods = "testExists")
+  public void testDelete() {
+    try {
+      _realmAwareZkClient.delete(TEST_INVALID_PATH);
+      Assert.fail("Exists() should not succeed on an invalid path!");
+    } catch (NoSuchElementException ex) {
+      Assert
+          .assertEquals(ex.getMessage(), "Cannot find ZK realm for the path: " + TEST_INVALID_PATH);
+    }
+
+    Assert.assertTrue(_realmAwareZkClient.delete(TEST_REALM_ONE_VALID_PATH));
+    Assert.assertFalse(_realmAwareZkClient.exists(TEST_REALM_ONE_VALID_PATH));
+  }
+
+  /*
+   * Tests that multi-realm feature.
+   */
+  @Test(dependsOnMethods = "testDelete")
+  public void testMultiRealmCRUD() {
+    ZNRecord realmOneZnRecord = new ZNRecord("realmOne");
+    realmOneZnRecord.setSimpleField("realmOne", "Value");
+
+    ZNRecord realmTwoZnRecord = new ZNRecord("realmTwo");
+    realmTwoZnRecord.setSimpleField("realmTwo", "Value");
+
+    // Writing on realmOne.
+    _realmAwareZkClient.createPersistent(TEST_REALM_ONE_VALID_PATH, true);
+    _realmAwareZkClient.writeData(TEST_REALM_ONE_VALID_PATH, realmOneZnRecord);
+
+    // RealmOne path is created but realmTwo path is not.
+    Assert.assertTrue(_realmAwareZkClient.exists(TEST_REALM_ONE_VALID_PATH));
+    Assert.assertFalse(_realmAwareZkClient.exists(TEST_REALM_TWO_VALID_PATH));
+
+    // Writing on realmTwo.
+    _realmAwareZkClient.createPersistent(TEST_REALM_TWO_VALID_PATH, true);
+    _realmAwareZkClient.writeData(TEST_REALM_TWO_VALID_PATH, realmTwoZnRecord);
+
+    // RealmTwo path is created.
+    Assert.assertTrue(_realmAwareZkClient.exists(TEST_REALM_TWO_VALID_PATH));
+
+    // Reading on both realms.
+    Assert.assertEquals(_realmAwareZkClient.readData(TEST_REALM_ONE_VALID_PATH), realmOneZnRecord);
+    Assert.assertEquals(_realmAwareZkClient.readData(TEST_REALM_TWO_VALID_PATH), realmTwoZnRecord);
+
+    Assert.assertTrue(_realmAwareZkClient.delete(TEST_REALM_ONE_VALID_PATH));
+    Assert.assertFalse(_realmAwareZkClient.exists(TEST_REALM_ONE_VALID_PATH));
+
+    // Deleting on realmOne does not delete on realmTwo.
+    Assert.assertTrue(_realmAwareZkClient.exists(TEST_REALM_TWO_VALID_PATH));
+
+    // Deleting on realmTwo.
+    Assert.assertTrue(_realmAwareZkClient.delete(TEST_REALM_TWO_VALID_PATH));
+    Assert.assertFalse(_realmAwareZkClient.exists(TEST_REALM_TWO_VALID_PATH));
+  }
+
+  /*
+   * Tests that close() works.
+   * TODO: test that all raw zkClients are closed after FederatedZkClient close() is called. This
+   *  could help avoid ZkClient leakage.
+   */
+  @Test(dependsOnMethods = "testMultiRealmCRUD")
+  public void testClose() {
+    Assert.assertFalse(_realmAwareZkClient.isClosed());
+
+    _realmAwareZkClient.close();
+
+    Assert.assertTrue(_realmAwareZkClient.isClosed());
+
+    // Client is closed, so operation should not be executed.
+    try {
+      _realmAwareZkClient.createPersistent(TEST_REALM_ONE_VALID_PATH);
+      Assert
+          .fail("createPersistent() should not be executed because RealmAwareZkClient is closed.");
+    } catch (IllegalStateException ex) {
+      Assert.assertEquals(ex.getMessage(), "FederatedZkClient is closed!");
+    }
+  }
+}


[helix] 37/49: Make ZkHelixClusterVerifier and its child classes realm-aware (#867)

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

hulee pushed a commit to branch zooscalability
in repository https://gitbox.apache.org/repos/asf/helix.git

commit de2a100b5c61745e8a411d8992c9c71602e905aa
Author: Hunter Lee <hu...@linkedin.com>
AuthorDate: Wed Mar 11 20:06:18 2020 -0700

    Make ZkHelixClusterVerifier and its child classes realm-aware (#867)
    
    Changelist:
    Make sure constructors accept RealmAwareZkClient
    Add Builders in each child class of ZkHelixClusterVerifier so that ZkClient configs are configurable and uses realm-aware ZkClient APIs
---
 .../BestPossibleExternalViewVerifier.java          |  81 +++++++++----
 .../ClusterVerifiers/ClusterLiveNodesVerifier.java |  46 +++++++-
 .../StrictMatchExternalViewVerifier.java           |  83 ++++++++-----
 .../ClusterVerifiers/ZkHelixClusterVerifier.java   | 130 ++++++++++++++++++---
 .../impl/factory/HelixZkClientFactory.java         |   2 +
 5 files changed, 274 insertions(+), 68 deletions(-)

diff --git a/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/BestPossibleExternalViewVerifier.java b/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/BestPossibleExternalViewVerifier.java
index 0efd187..7f57fa9 100644
--- a/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/BestPossibleExternalViewVerifier.java
+++ b/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/BestPossibleExternalViewVerifier.java
@@ -50,8 +50,6 @@ import org.apache.helix.controller.stages.CurrentStateComputationStage;
 import org.apache.helix.controller.stages.CurrentStateOutput;
 import org.apache.helix.controller.stages.ResourceComputationStage;
 import org.apache.helix.manager.zk.ZkBucketDataAccessor;
-import org.apache.helix.zookeeper.impl.client.ZkClient;
-import org.apache.helix.zookeeper.api.client.HelixZkClient;
 import org.apache.helix.model.ClusterConfig;
 import org.apache.helix.model.ExternalView;
 import org.apache.helix.model.IdealState;
@@ -60,6 +58,7 @@ import org.apache.helix.model.Resource;
 import org.apache.helix.model.ResourceAssignment;
 import org.apache.helix.model.StateModelDefinition;
 import org.apache.helix.task.TaskConstants;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -75,6 +74,15 @@ public class BestPossibleExternalViewVerifier extends ZkHelixClusterVerifier {
   private final Set<String> _expectLiveInstances;
   private final ResourceControllerDataProvider _dataProvider;
 
+  /**
+   * Deprecated - please use the Builder to construct this class.
+   * @param zkAddr
+   * @param clusterName
+   * @param resources
+   * @param errStates
+   * @param expectLiveInstances
+   */
+  @Deprecated
   public BestPossibleExternalViewVerifier(String zkAddr, String clusterName, Set<String> resources,
       Map<String, Map<String, String>> errStates, Set<String> expectLiveInstances) {
     super(zkAddr, clusterName);
@@ -84,7 +92,16 @@ public class BestPossibleExternalViewVerifier extends ZkHelixClusterVerifier {
     _dataProvider = new ResourceControllerDataProvider();
   }
 
-  public BestPossibleExternalViewVerifier(HelixZkClient zkClient, String clusterName,
+  /**
+   * Deprecated - please use the Builder to construct this class.
+   * @param zkClient
+   * @param clusterName
+   * @param resources
+   * @param errStates
+   * @param expectLiveInstances
+   */
+  @Deprecated
+  public BestPossibleExternalViewVerifier(RealmAwareZkClient zkClient, String clusterName,
       Set<String> resources, Map<String, Map<String, String>> errStates,
       Set<String> expectLiveInstances) {
     super(zkClient, clusterName);
@@ -94,20 +111,31 @@ public class BestPossibleExternalViewVerifier extends ZkHelixClusterVerifier {
     _dataProvider = new ResourceControllerDataProvider();
   }
 
-  public static class Builder {
-    private String _clusterName;
+  private BestPossibleExternalViewVerifier(Builder builder) {
+    super(builder);
+    // Deep copy data from Builder
+    _errStates = new HashMap<>();
+    if (builder._errStates != null) {
+      builder._errStates.forEach((k, v) -> _errStates.put(k, new HashMap<>(v)));
+    }
+    _resources = new HashSet<>(builder._resources);
+    _expectLiveInstances = new HashSet<>(builder._expectLiveInstances);
+    _dataProvider = new ResourceControllerDataProvider();
+  }
+
+  public static class Builder extends ZkHelixClusterVerifier.Builder {
+    private final String _clusterName;
     private Map<String, Map<String, String>> _errStates;
     private Set<String> _resources;
     private Set<String> _expectLiveInstances;
-    private String _zkAddr;
-    private HelixZkClient _zkClient;
+    private RealmAwareZkClient _zkClient;
 
     public Builder(String clusterName) {
       _clusterName = clusterName;
     }
 
     public BestPossibleExternalViewVerifier build() {
-      if (_clusterName == null || (_zkAddr == null && _zkClient == null)) {
+      if (_clusterName == null || (_zkAddress == null && _zkClient == null)) {
         throw new IllegalArgumentException("Cluster name or zookeeper info is missing!");
       }
 
@@ -115,8 +143,15 @@ public class BestPossibleExternalViewVerifier extends ZkHelixClusterVerifier {
         return new BestPossibleExternalViewVerifier(_zkClient, _clusterName, _resources, _errStates,
             _expectLiveInstances);
       }
-      return new BestPossibleExternalViewVerifier(_zkAddr, _clusterName, _resources, _errStates,
-          _expectLiveInstances);
+
+      if (_realmAwareZkConnectionConfig == null || _realmAwareZkClientConfig == null) {
+        // For backward-compatibility
+        return new BestPossibleExternalViewVerifier(_zkAddress, _clusterName, _resources,
+            _errStates, _expectLiveInstances);
+      }
+
+      validate();
+      return new BestPossibleExternalViewVerifier(this);
     }
 
     public String getClusterName() {
@@ -151,25 +186,29 @@ public class BestPossibleExternalViewVerifier extends ZkHelixClusterVerifier {
     }
 
     public String getZkAddr() {
-      return _zkAddr;
+      return _zkAddress;
     }
 
-    public Builder setZkAddr(String zkAddr) {
-      _zkAddr = zkAddr;
+    public Builder setZkClient(RealmAwareZkClient zkClient) {
+      _zkClient = zkClient;
       return this;
     }
 
-    public HelixZkClient getHelixZkClient() {
-      return _zkClient;
+    @Override
+    public Builder setZkAddr(String zkAddress) {
+      return (Builder) super.setZkAddr(zkAddress);
     }
 
-    @Deprecated
-    public ZkClient getZkClient() {
-      return (ZkClient) getHelixZkClient();
+    @Override
+    public Builder setRealmAwareZkConnectionConfig(
+        RealmAwareZkClient.RealmAwareZkConnectionConfig realmAwareZkConnectionConfig) {
+      return (Builder) super.setRealmAwareZkConnectionConfig(realmAwareZkConnectionConfig);
     }
-    public Builder setZkClient(HelixZkClient zkClient) {
-      _zkClient = zkClient;
-      return this;
+
+    @Override
+    public Builder setRealmAwareZkClientConfig(
+        RealmAwareZkClient.RealmAwareZkClientConfig realmAwareZkClientConfig) {
+      return (Builder) super.setRealmAwareZkClientConfig(realmAwareZkClientConfig);
     }
   }
 
diff --git a/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/ClusterLiveNodesVerifier.java b/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/ClusterLiveNodesVerifier.java
index 0c284ff..8aeb64a 100644
--- a/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/ClusterLiveNodesVerifier.java
+++ b/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/ClusterLiveNodesVerifier.java
@@ -24,18 +24,25 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
-import org.apache.helix.zookeeper.api.client.HelixZkClient;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
+
 
 public class ClusterLiveNodesVerifier extends ZkHelixClusterVerifier {
 
   final Set<String> _expectLiveNodes;
 
-  public ClusterLiveNodesVerifier(HelixZkClient zkclient, String clusterName,
+  @Deprecated
+  public ClusterLiveNodesVerifier(RealmAwareZkClient zkclient, String clusterName,
       List<String> expectLiveNodes) {
     super(zkclient, clusterName);
     _expectLiveNodes = new HashSet<>(expectLiveNodes);
   }
 
+  private ClusterLiveNodesVerifier(Builder builder) {
+    super(builder);
+    _expectLiveNodes = new HashSet<>(builder._expectLiveNodes);
+  }
+
   @Override
   public boolean verifyByZkCallback(long timeout) {
     List<ClusterVerifyTrigger> triggers = new ArrayList<ClusterVerifyTrigger>();
@@ -51,4 +58,39 @@ public class ClusterLiveNodesVerifier extends ZkHelixClusterVerifier {
     return _expectLiveNodes.equals(actualLiveNodes);
   }
 
+  public static class Builder extends ZkHelixClusterVerifier.Builder {
+    private final String _clusterName; // This is the ZK path sharding key
+    private final Set<String> _expectLiveNodes;
+
+    public Builder(String clusterName, Set<String> expectLiveNodes) {
+      _clusterName = clusterName;
+      _expectLiveNodes = expectLiveNodes;
+    }
+
+    public ClusterLiveNodesVerifier build() {
+      if (_clusterName == null || _clusterName.isEmpty()) {
+        throw new IllegalArgumentException("Cluster name is missing!");
+      }
+
+      validate();
+      return new ClusterLiveNodesVerifier(this);
+    }
+
+    @Override
+    public Builder setZkAddr(String zkAddress) {
+      return (Builder) super.setZkAddr(zkAddress);
+    }
+
+    @Override
+    public Builder setRealmAwareZkConnectionConfig(
+        RealmAwareZkClient.RealmAwareZkConnectionConfig realmAwareZkConnectionConfig) {
+      return (Builder) super.setRealmAwareZkConnectionConfig(realmAwareZkConnectionConfig);
+    }
+
+    @Override
+    public Builder setRealmAwareZkClientConfig(
+        RealmAwareZkClient.RealmAwareZkClientConfig realmAwareZkClientConfig) {
+      return (Builder) super.setRealmAwareZkClientConfig(realmAwareZkClientConfig);
+    }
+  }
 }
diff --git a/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/StrictMatchExternalViewVerifier.java b/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/StrictMatchExternalViewVerifier.java
index fcc7261..9c4a587 100644
--- a/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/StrictMatchExternalViewVerifier.java
+++ b/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/StrictMatchExternalViewVerifier.java
@@ -23,6 +23,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -33,8 +34,6 @@ import org.apache.helix.HelixException;
 import org.apache.helix.PropertyKey;
 import org.apache.helix.controller.dataproviders.ResourceControllerDataProvider;
 import org.apache.helix.controller.rebalancer.AbstractRebalancer;
-import org.apache.helix.zookeeper.impl.client.ZkClient;
-import org.apache.helix.zookeeper.api.client.HelixZkClient;
 import org.apache.helix.model.ClusterConfig;
 import org.apache.helix.model.ExternalView;
 import org.apache.helix.model.IdealState;
@@ -42,6 +41,7 @@ import org.apache.helix.model.Partition;
 import org.apache.helix.model.StateModelDefinition;
 import org.apache.helix.task.TaskConstants;
 import org.apache.helix.util.HelixUtil;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -64,11 +64,12 @@ public class StrictMatchExternalViewVerifier extends ZkHelixClusterVerifier {
   }
 
   @Deprecated
-  public StrictMatchExternalViewVerifier(HelixZkClient zkClient, String clusterName,
+  public StrictMatchExternalViewVerifier(RealmAwareZkClient zkClient, String clusterName,
       Set<String> resources, Set<String> expectLiveInstances) {
     this(zkClient, clusterName, resources, expectLiveInstances, false);
   }
 
+  @Deprecated
   private StrictMatchExternalViewVerifier(String zkAddr, String clusterName, Set<String> resources,
       Set<String> expectLiveInstances, boolean isDeactivatedNodeAware) {
     super(zkAddr, clusterName);
@@ -77,7 +78,8 @@ public class StrictMatchExternalViewVerifier extends ZkHelixClusterVerifier {
     _isDeactivatedNodeAware = isDeactivatedNodeAware;
   }
 
-  private StrictMatchExternalViewVerifier(HelixZkClient zkClient, String clusterName,
+  @Deprecated
+  private StrictMatchExternalViewVerifier(RealmAwareZkClient zkClient, String clusterName,
       Set<String> resources, Set<String> expectLiveInstances, boolean isDeactivatedNodeAware) {
     super(zkClient, clusterName);
     _resources = resources;
@@ -85,17 +87,23 @@ public class StrictMatchExternalViewVerifier extends ZkHelixClusterVerifier {
     _isDeactivatedNodeAware = isDeactivatedNodeAware;
   }
 
-  public static class Builder {
-    private String _clusterName;
+  private StrictMatchExternalViewVerifier(Builder builder) {
+    super(builder);
+    _resources = new HashSet<>(builder._resources);
+    _expectLiveInstances = new HashSet<>(builder._expectLiveInstances);
+    _isDeactivatedNodeAware = builder._isDeactivatedNodeAware;
+  }
+
+  public static class Builder extends ZkHelixClusterVerifier.Builder {
+    private final String _clusterName; // This is the ZK path sharding key
     private Set<String> _resources;
     private Set<String> _expectLiveInstances;
-    private String _zkAddr;
-    private HelixZkClient _zkClient;
+    private RealmAwareZkClient _zkClient;
     // For backward compatibility, set the default isDeactivatedNodeAware to be false.
     private boolean _isDeactivatedNodeAware = false;
 
     public StrictMatchExternalViewVerifier build() {
-      if (_clusterName == null || (_zkAddr == null && _zkClient == null)) {
+      if (_clusterName == null || (_zkAddress == null && _zkClient == null)) {
         throw new IllegalArgumentException("Cluster name or zookeeper info is missing!");
       }
 
@@ -103,8 +111,15 @@ public class StrictMatchExternalViewVerifier extends ZkHelixClusterVerifier {
         return new StrictMatchExternalViewVerifier(_zkClient, _clusterName, _resources,
             _expectLiveInstances, _isDeactivatedNodeAware);
       }
-      return new StrictMatchExternalViewVerifier(_zkAddr, _clusterName, _resources,
-          _expectLiveInstances, _isDeactivatedNodeAware);
+
+      if (_realmAwareZkConnectionConfig == null || _realmAwareZkClientConfig == null) {
+        // For backward-compatibility
+        return new StrictMatchExternalViewVerifier(_zkAddress, _clusterName, _resources,
+            _expectLiveInstances, _isDeactivatedNodeAware);
+      }
+
+      validate();
+      return new StrictMatchExternalViewVerifier(this);
     }
 
     public Builder(String clusterName) {
@@ -133,25 +148,12 @@ public class StrictMatchExternalViewVerifier extends ZkHelixClusterVerifier {
       return this;
     }
 
-    public String getZkAddr() {
-      return _zkAddr;
-    }
-
-    public Builder setZkAddr(String zkAddr) {
-      _zkAddr = zkAddr;
-      return this;
-    }
-
-    public HelixZkClient getHelixZkClient() {
-      return _zkClient;
+    public String getZkAddress() {
+      return _zkAddress;
     }
 
     @Deprecated
-    public ZkClient getZkClient() {
-      return (ZkClient) getHelixZkClient();
-    }
-
-    public Builder setZkClient(HelixZkClient zkClient) {
+    public Builder setZkClient(RealmAwareZkClient zkClient) {
       _zkClient = zkClient;
       return this;
     }
@@ -164,6 +166,33 @@ public class StrictMatchExternalViewVerifier extends ZkHelixClusterVerifier {
       _isDeactivatedNodeAware = isDeactivatedNodeAware;
       return this;
     }
+
+    @Override
+    public Builder setZkAddr(String zkAddress) {
+      return (Builder) super.setZkAddr(zkAddress);
+    }
+
+    @Override
+    public Builder setRealmAwareZkConnectionConfig(
+        RealmAwareZkClient.RealmAwareZkConnectionConfig realmAwareZkConnectionConfig) {
+      return (Builder) super.setRealmAwareZkConnectionConfig(realmAwareZkConnectionConfig);
+    }
+
+    @Override
+    public Builder setRealmAwareZkClientConfig(
+        RealmAwareZkClient.RealmAwareZkClientConfig realmAwareZkClientConfig) {
+      return (Builder) super.setRealmAwareZkClientConfig(realmAwareZkClientConfig);
+    }
+
+    protected void validate() {
+      super.validate();
+      if (!_clusterName.equals(_realmAwareZkConnectionConfig.getZkRealmShardingKey())) {
+        throw new IllegalArgumentException(
+            "StrictMatchExternalViewVerifier: Cluster name: " + _clusterName
+                + " and ZK realm sharding key: " + _realmAwareZkConnectionConfig
+                .getZkRealmShardingKey() + " do not match!");
+      }
+    }
   }
 
   @Override
diff --git a/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/ZkHelixClusterVerifier.java b/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/ZkHelixClusterVerifier.java
index e82fccf..449b659 100644
--- a/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/ZkHelixClusterVerifier.java
+++ b/helix-core/src/main/java/org/apache/helix/tools/ClusterVerifiers/ZkHelixClusterVerifier.java
@@ -19,6 +19,7 @@ package org.apache.helix.tools.ClusterVerifiers;
  * under the License.
  */
 
+import java.io.IOException;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutorService;
@@ -26,14 +27,17 @@ import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 
 import org.apache.helix.HelixDataAccessor;
+import org.apache.helix.HelixException;
 import org.apache.helix.PropertyKey;
+import org.apache.helix.SystemPropertyKeys;
 import org.apache.helix.api.listeners.PreFetch;
 import org.apache.helix.manager.zk.ZKHelixDataAccessor;
-import org.apache.helix.manager.zk.ZNRecordSerializer;
 import org.apache.helix.manager.zk.ZkBaseDataAccessor;
-import org.apache.helix.zookeeper.impl.client.ZkClient;
-import org.apache.helix.zookeeper.impl.factory.DedicatedZkClientFactory;
+import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
+import org.apache.helix.zookeeper.api.client.RealmAwareZkClient;
+import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
+import org.apache.helix.zookeeper.impl.factory.DedicatedZkClientFactory;
 import org.apache.helix.zookeeper.zkclient.IZkChildListener;
 import org.apache.helix.zookeeper.zkclient.IZkDataListener;
 import org.slf4j.Logger;
@@ -45,8 +49,8 @@ public abstract class ZkHelixClusterVerifier
   protected static int DEFAULT_TIMEOUT = 300 * 1000;
   protected static int DEFAULT_PERIOD = 500;
 
-  protected final HelixZkClient _zkClient;
-  // true if ZkHelixClusterVerifier was instantiated with a HelixZkClient, false otherwise
+  protected final RealmAwareZkClient _zkClient;
+  // true if ZkHelixClusterVerifier was instantiated with a RealmAwareZkClient, false otherwise
   // This is used for close() to determine how ZkHelixClusterVerifier should close the underlying
   // ZkClient
   private final boolean _usesExternalZkClient;
@@ -89,7 +93,13 @@ public abstract class ZkHelixClusterVerifier
     }
   }
 
-  public ZkHelixClusterVerifier(HelixZkClient zkClient, String clusterName) {
+  /**
+   * Deprecated because we discourage passing in a ZkClient directly.
+   * @param zkClient
+   * @param clusterName
+   */
+  @Deprecated
+  public ZkHelixClusterVerifier(RealmAwareZkClient zkClient, String clusterName) {
     if (zkClient == null || clusterName == null) {
       throw new IllegalArgumentException("requires zkClient|clusterName");
     }
@@ -100,12 +110,33 @@ public abstract class ZkHelixClusterVerifier
     _keyBuilder = _accessor.keyBuilder();
   }
 
+  @Deprecated
   public ZkHelixClusterVerifier(String zkAddr, String clusterName) {
-    if (zkAddr == null || clusterName == null) {
-      throw new IllegalArgumentException("requires zkAddr|clusterName");
+    if (clusterName == null || clusterName.isEmpty()) {
+      throw new IllegalArgumentException("ZkHelixClusterVerifier: clusterName is null or empty!");
+    }
+    // If the multi ZK config is enabled, use DedicatedZkClient on multi-realm mode
+    if (Boolean.parseBoolean(System.getProperty(SystemPropertyKeys.MULTI_ZK_ENABLED))) {
+      try {
+        RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder connectionConfigBuilder =
+            new RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder();
+        connectionConfigBuilder.setZkRealmShardingKey("/" + clusterName);
+        RealmAwareZkClient.RealmAwareZkClientConfig clientConfig =
+            new RealmAwareZkClient.RealmAwareZkClientConfig();
+        _zkClient = DedicatedZkClientFactory.getInstance()
+            .buildZkClient(connectionConfigBuilder.build(), clientConfig);
+      } catch (IOException | InvalidRoutingDataException | IllegalStateException e) {
+        // Note: IllegalStateException is for HttpRoutingDataReader if MSDS endpoint cannot be
+        // found
+        throw new HelixException("ZkHelixClusterVerifier: failed to create ZkClient!", e);
+      }
+    } else {
+      if (zkAddr == null) {
+        throw new IllegalArgumentException("ZkHelixClusterVerifier: ZkAddress is null or empty!");
+      }
+      _zkClient = DedicatedZkClientFactory.getInstance()
+          .buildZkClient(new HelixZkClient.ZkConnectionConfig(zkAddr));
     }
-    _zkClient = DedicatedZkClientFactory.getInstance()
-        .buildZkClient(new HelixZkClient.ZkConnectionConfig(zkAddr));
     _usesExternalZkClient = false;
     _zkClient.setZkSerializer(new ZNRecordSerializer());
     _clusterName = clusterName;
@@ -113,6 +144,27 @@ public abstract class ZkHelixClusterVerifier
     _keyBuilder = _accessor.keyBuilder();
   }
 
+  protected ZkHelixClusterVerifier(Builder builder) {
+    if (Boolean.parseBoolean(System.getProperty(SystemPropertyKeys.MULTI_ZK_ENABLED))) {
+      try {
+        // First, try to create a RealmAwareZkClient that's a DedicatedZkClient
+        _zkClient = DedicatedZkClientFactory.getInstance()
+            .buildZkClient(builder._realmAwareZkConnectionConfig,
+                builder._realmAwareZkClientConfig);
+      } catch (IOException | InvalidRoutingDataException | IllegalStateException e) {
+        throw new HelixException("ZkHelixClusterVerifier: failed to create ZkClient!", e);
+      }
+    } else {
+      _zkClient = DedicatedZkClientFactory.getInstance()
+          .buildZkClient(new HelixZkClient.ZkConnectionConfig(builder._zkAddress));
+    }
+    _usesExternalZkClient = false;
+    _zkClient.setZkSerializer(new ZNRecordSerializer());
+    _clusterName = builder._realmAwareZkConnectionConfig.getZkRealmShardingKey();
+    _accessor = new ZKHelixDataAccessor(_clusterName, new ZkBaseDataAccessor<>(_zkClient));
+    _keyBuilder = _accessor.keyBuilder();
+  }
+
   /**
    * Verify the cluster.
    * The method will be blocked at most {@code timeout}.
@@ -289,16 +341,58 @@ public abstract class ZkHelixClusterVerifier
     }
   }
 
-  public HelixZkClient getHelixZkClient() {
-    return _zkClient;
+  public String getClusterName() {
+    return _clusterName;
   }
 
-  @Deprecated
-  public ZkClient getZkClient() {
-    return (ZkClient) getHelixZkClient();
-  }
+  // TODO: refactor Builders for Java APIs
+  protected abstract static class Builder {
+    // Note: ZkHelixClusterVerifier is a single-realm API, so RealmMode is assumed to be
+    // SINGLE-REALM
+    protected String _zkAddress;
+    protected RealmAwareZkClient.RealmAwareZkConnectionConfig _realmAwareZkConnectionConfig;
+    protected RealmAwareZkClient.RealmAwareZkClientConfig _realmAwareZkClientConfig;
 
-  public String getClusterName() {
-    return _clusterName;
+    public Builder() {
+    }
+
+    public Builder setZkAddr(String zkAddress) {
+      _zkAddress = zkAddress;
+      return this;
+    }
+
+    public Builder setRealmAwareZkConnectionConfig(
+        RealmAwareZkClient.RealmAwareZkConnectionConfig realmAwareZkConnectionConfig) {
+      _realmAwareZkConnectionConfig = realmAwareZkConnectionConfig;
+      return this;
+    }
+
+    public Builder setRealmAwareZkClientConfig(
+        RealmAwareZkClient.RealmAwareZkClientConfig realmAwareZkClientConfig) {
+      _realmAwareZkClientConfig = realmAwareZkClientConfig;
+      return this;
+    }
+
+    protected void validate() {
+      // Validate that either ZkAddress or ZkRealmShardingKey is set.
+      if (_zkAddress == null || _zkAddress.isEmpty()) {
+        if (_realmAwareZkConnectionConfig == null
+            || _realmAwareZkConnectionConfig.getZkRealmShardingKey() == null
+            || _realmAwareZkConnectionConfig.getZkRealmShardingKey().isEmpty()) {
+          throw new IllegalArgumentException(
+              "ZkHelixClusterVerifier: one of either ZkAddress or ZkRealmShardingKey must be set! ZkAddress: "
+                  + _zkAddress + " RealmAwareZkConnectionConfig: " + _realmAwareZkConnectionConfig);
+        }
+      }
+
+      // Resolve all default values
+      if (_realmAwareZkConnectionConfig == null) {
+        _realmAwareZkConnectionConfig =
+            new RealmAwareZkClient.RealmAwareZkConnectionConfig.Builder().build();
+      }
+      if (_realmAwareZkClientConfig == null) {
+        _realmAwareZkClientConfig = new RealmAwareZkClient.RealmAwareZkClientConfig();
+      }
+    }
   }
 }
diff --git a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/HelixZkClientFactory.java b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/HelixZkClientFactory.java
index 9584c57..ca13321 100644
--- a/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/HelixZkClientFactory.java
+++ b/zookeeper-api/src/main/java/org/apache/helix/zookeeper/impl/factory/HelixZkClientFactory.java
@@ -38,6 +38,7 @@ abstract class HelixZkClientFactory implements RealmAwareZkClientFactory {
    * @param clientConfig
    * @return HelixZkClient
    */
+  @Deprecated
   public abstract HelixZkClient buildZkClient(HelixZkClient.ZkConnectionConfig connectionConfig,
       HelixZkClient.ZkClientConfig clientConfig);
 
@@ -47,6 +48,7 @@ abstract class HelixZkClientFactory implements RealmAwareZkClientFactory {
    * @param connectionConfig
    * @return HelixZkClient
    */
+  @Deprecated
   public HelixZkClient buildZkClient(HelixZkClient.ZkConnectionConfig connectionConfig) {
     return buildZkClient(connectionConfig, new HelixZkClient.ZkClientConfig());
   }