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