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/08 22:54:02 UTC

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

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

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

commit d5257235d6e9ce4414aecb6d33583d1f9cfc2150
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 ad77b4e..031fbf1 100644
--- a/helix-core/src/main/java/org/apache/helix/ConfigAccessor.java
+++ b/helix-core/src/main/java/org/apache/helix/ConfigAccessor.java
@@ -90,10 +90,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();