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:47:22 UTC

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

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