This is an automated email from the ASF dual-hosted git repository.
gerlowskija pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git
The following commit(s) were added to refs/heads/main by this push:
new 9b1f8dd SOLR-15792: Rewrite shardname APIs using annotation framework
(#415)
9b1f8dd is described below
commit 9b1f8ddab33ebee870780918554699980bd635ad
Author: Jason Gerlowski <[email protected]>
AuthorDate: Mon Nov 15 22:00:29 2021 -0500
SOLR-15792: Rewrite shardname APIs using annotation framework (#415)
V2 APIs implementations are in the process of migrating from a JSON file
'apispec' framework, to an annotation-based framework.
This commit makes this change for Solr's
/v2/collections/<collName>/shards/<shardName> APIs: delete-shard,
force-leader, and sync-shard.
---
.../solr/handler/admin/api/DeleteShardAPI.java | 63 ++++
.../solr/handler/admin/api/ForceLeaderAPI.java | 68 ++++
.../solr/handler/admin/api/SyncShardAPI.java | 68 ++++
.../org/apache/solr/handler/api/ApiRegistrar.java | 3 +
.../handler/admin/api/V2ShardsAPIMappingTest.java | 385 ++++++++++++---------
.../client/solrj/request/CollectionApiMapping.java | 24 +-
.../solrj/request/beans/ForceLeaderPayload.java | 25 ++
.../solrj/request/beans/SyncShardPayload.java | 25 ++
...llections.collection.shards.shard.Commands.json | 24 --
...collections.collection.shards.shard.delete.json | 27 --
.../apache/solr/common/util/JsonValidatorTest.java | 5 +-
11 files changed, 471 insertions(+), 246 deletions(-)
diff --git
a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteShardAPI.java
b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteShardAPI.java
new file mode 100644
index 0000000..4ab541c
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteShardAPI.java
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.handler.admin.api;
+
+import com.google.common.collect.Maps;
+import org.apache.solr.api.EndPoint;
+import org.apache.solr.common.params.CollectionParams;
+import org.apache.solr.handler.admin.CollectionsHandler;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+
+import java.util.Map;
+
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.DELETE;
+import static org.apache.solr.common.params.CollectionAdminParams.COLLECTION;
+import static org.apache.solr.common.params.CommonParams.ACTION;
+import static org.apache.solr.common.params.CoreAdminParams.SHARD;
+import static org.apache.solr.handler.ClusterAPI.wrapParams;
+import static
org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
+
+/**
+ * V2 API for deleting a particular shard from its collection.
+ *
+ * This API (DELETE /v2/collections/collectionName/shards/shardName) is
analogous to the v1
+ * /admin/collections?action=DELETESHARD command.
+ */
+public class DeleteShardAPI {
+ private final CollectionsHandler collectionsHandler;
+
+ public DeleteShardAPI(CollectionsHandler collectionsHandler) {
+ this.collectionsHandler = collectionsHandler;
+ }
+
+ @EndPoint(
+ path = {"/c/{collection}/shards/{shard}",
"/collections/{collection}/shards/{shard}"},
+ method = DELETE,
+ permission = COLL_EDIT_PERM)
+ public void deleteShard(SolrQueryRequest req, SolrQueryResponse rsp)
throws Exception {
+ final Map<String, String> pathParams = req.getPathTemplateValues();
+
+ final Map<String, Object> addedV1Params = Maps.newHashMap();
+ addedV1Params.put(ACTION,
CollectionParams.CollectionAction.DELETESHARD.toLower());
+ addedV1Params.put(COLLECTION, pathParams.get(COLLECTION));
+ addedV1Params.put(SHARD, pathParams.get(SHARD));
+
+ collectionsHandler.handleRequestBody(wrapParams(req, addedV1Params),
rsp);
+ }
+}
diff --git
a/solr/core/src/java/org/apache/solr/handler/admin/api/ForceLeaderAPI.java
b/solr/core/src/java/org/apache/solr/handler/admin/api/ForceLeaderAPI.java
new file mode 100644
index 0000000..09d1771
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/ForceLeaderAPI.java
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.handler.admin.api;
+
+import com.google.common.collect.Maps;
+import org.apache.solr.api.Command;
+import org.apache.solr.api.EndPoint;
+import org.apache.solr.api.PayloadObj;
+import org.apache.solr.client.solrj.request.beans.ForceLeaderPayload;
+import org.apache.solr.common.params.CollectionParams;
+import org.apache.solr.handler.admin.CollectionsHandler;
+
+import java.util.Map;
+
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST;
+import static org.apache.solr.common.params.CollectionAdminParams.COLLECTION;
+import static org.apache.solr.common.params.CollectionParams.ACTION;
+import static org.apache.solr.common.params.CoreAdminParams.SHARD;
+import static org.apache.solr.handler.ClusterAPI.wrapParams;
+import static
org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
+
+/**
+ * V2 API for triggering a leader election on a particular collection and
shard.
+ *
+ * This API (POST /v2/collections/collectionName/shards/shardName
{'force-leader': {}}) is analogous to the v1
+ * /admin/collections?action=FORCELEADER command.
+ *
+ * @see ForceLeaderPayload
+ */
+@EndPoint(
+ path = {"/c/{collection}/shards/{shard}",
"/collections/{collection}/shards/{shard}"},
+ method = POST,
+ permission = COLL_EDIT_PERM)
+public class ForceLeaderAPI {
+ private static final String V2_FORCE_LEADER_CMD = "force-leader";
+
+ private final CollectionsHandler collectionsHandler;
+
+ public ForceLeaderAPI(CollectionsHandler collectionsHandler) {
+ this.collectionsHandler = collectionsHandler;
+ }
+
+ @Command(name = V2_FORCE_LEADER_CMD)
+ public void forceLeader(PayloadObj<ForceLeaderPayload> obj) throws
Exception {
+ final Map<String, Object> addedV1Params = Maps.newHashMap();
+ final Map<String, String> pathParams =
obj.getRequest().getPathTemplateValues();
+ addedV1Params.put(ACTION,
CollectionParams.CollectionAction.FORCELEADER.toLower());
+ addedV1Params.put(COLLECTION, pathParams.get(COLLECTION));
+ addedV1Params.put(SHARD, pathParams.get(SHARD));
+
+ collectionsHandler.handleRequestBody(wrapParams(obj.getRequest(),
addedV1Params), obj.getResponse());
+ }
+}
diff --git
a/solr/core/src/java/org/apache/solr/handler/admin/api/SyncShardAPI.java
b/solr/core/src/java/org/apache/solr/handler/admin/api/SyncShardAPI.java
new file mode 100644
index 0000000..94a1953
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/SyncShardAPI.java
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.handler.admin.api;
+
+import com.google.common.collect.Maps;
+import org.apache.solr.api.Command;
+import org.apache.solr.api.EndPoint;
+import org.apache.solr.api.PayloadObj;
+import org.apache.solr.client.solrj.request.beans.SyncShardPayload;
+import org.apache.solr.common.params.CollectionParams;
+import org.apache.solr.handler.admin.CollectionsHandler;
+
+import java.util.Map;
+
+import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST;
+import static org.apache.solr.common.params.CollectionAdminParams.COLLECTION;
+import static org.apache.solr.common.params.CollectionParams.ACTION;
+import static org.apache.solr.common.params.CoreAdminParams.SHARD;
+import static org.apache.solr.handler.ClusterAPI.wrapParams;
+import static
org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
+
+/**
+ * V2 API for triggering a shard-sync operation within a particular collection
and shard.
+ *
+ * This API (POST /v2/collections/collectionName/shards/shardName
{'sync-shard': {}}) is analogous to the v1
+ * /admin/collections?action=SYNCSHARD command.
+ *
+ * @see SyncShardPayload
+ */
+@EndPoint(
+ path = {"/c/{collection}/shards/{shard}",
"/collections/{collection}/shards/{shard}"},
+ method = POST,
+ permission = COLL_EDIT_PERM)
+public class SyncShardAPI {
+ private static final String V2_SYNC_SHARD_CMD = "sync-shard";
+
+ private final CollectionsHandler collectionsHandler;
+
+ public SyncShardAPI(CollectionsHandler collectionsHandler) {
+ this.collectionsHandler = collectionsHandler;
+ }
+
+ @Command(name = V2_SYNC_SHARD_CMD)
+ public void syncShard(PayloadObj<SyncShardPayload> obj) throws Exception {
+ final Map<String, Object> addedV1Params = Maps.newHashMap();
+ final Map<String, String> pathParams =
obj.getRequest().getPathTemplateValues();
+ addedV1Params.put(ACTION,
CollectionParams.CollectionAction.SYNCSHARD.toLower());
+ addedV1Params.put(COLLECTION, pathParams.get(COLLECTION));
+ addedV1Params.put(SHARD, pathParams.get(SHARD));
+
+ collectionsHandler.handleRequestBody(wrapParams(obj.getRequest(),
addedV1Params), obj.getResponse());
+ }
+}
diff --git a/solr/core/src/java/org/apache/solr/handler/api/ApiRegistrar.java
b/solr/core/src/java/org/apache/solr/handler/api/ApiRegistrar.java
index e699bd8..0da03a6 100644
--- a/solr/core/src/java/org/apache/solr/handler/api/ApiRegistrar.java
+++ b/solr/core/src/java/org/apache/solr/handler/api/ApiRegistrar.java
@@ -48,5 +48,8 @@ public class ApiRegistrar {
apiBag.registerObject(new SplitShardAPI(collectionsHandler));
apiBag.registerObject(new CreateShardAPI(collectionsHandler));
apiBag.registerObject(new AddReplicaAPI(collectionsHandler));
+ apiBag.registerObject(new DeleteShardAPI(collectionsHandler));
+ apiBag.registerObject(new SyncShardAPI(collectionsHandler));
+ apiBag.registerObject(new ForceLeaderAPI(collectionsHandler));
}
}
diff --git
a/solr/core/src/test/org/apache/solr/handler/admin/api/V2ShardsAPIMappingTest.java
b/solr/core/src/test/org/apache/solr/handler/admin/api/V2ShardsAPIMappingTest.java
index 769fc9e..b7da82b 100644
---
a/solr/core/src/test/org/apache/solr/handler/admin/api/V2ShardsAPIMappingTest.java
+++
b/solr/core/src/test/org/apache/solr/handler/admin/api/V2ShardsAPIMappingTest.java
@@ -6,7 +6,11 @@
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
+<<<<<<< HEAD
+ * http://www.apache.org/licenses/LICENSE-2.0
+=======
* http://www.apache.org/licenses/LICENSE-2.0
+>>>>>>> main
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -17,18 +21,12 @@
package org.apache.solr.handler.admin.api;
-import com.google.common.collect.Maps;
-import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.api.Api;
import org.apache.solr.api.ApiBag;
-import org.apache.solr.common.params.CollectionAdminParams;
-import org.apache.solr.common.params.CollectionParams;
-import org.apache.solr.common.params.CoreAdminParams;
-import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.params.*;
import org.apache.solr.common.util.CommandOperation;
import org.apache.solr.common.util.ContentStreamBase;
import org.apache.solr.handler.admin.CollectionsHandler;
-import org.apache.solr.handler.admin.TestCollectionAPIs;
import org.apache.solr.handler.api.ApiRegistrar;
import org.apache.solr.request.LocalSolrQueryRequest;
import org.apache.solr.request.SolrQueryRequest;
@@ -43,182 +41,225 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import static org.apache.solr.SolrTestCaseJ4.assumeWorkingMockito;
import static org.apache.solr.common.cloud.ZkStateReader.*;
-import static
org.apache.solr.common.params.CollectionAdminParams.CREATE_NODE_SET_PARAM;
-import static
org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES;
+import static org.apache.solr.common.params.CollectionAdminParams.*;
import static org.apache.solr.common.params.CommonAdminParams.*;
import static org.apache.solr.common.params.CommonParams.*;
+import static org.apache.solr.common.params.CoreAdminParams.SHARD;
+import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
- * Unit tests for the V2 APIs found in {@link
org.apache.solr.handler.admin.api} that use the /c/{collection}/shards path.
- *
- * This test bears many similarities to {@link TestCollectionAPIs} which
appears to test the mappings indirectly by
- * checking message sent to the ZK overseer (which is similar, but not
identical to the v1 param list). If there's no
- * particular benefit to testing the mappings in this way (there very well may
be), then we should combine these two
- * test classes at some point in the future using the simpler approach here.
+ * Unit tests for the V2 APIs that use the /c/{collection}/shards or
/c/{collection}/shards/{shard} paths.
*
* Note that the V2 requests made by these tests are not necessarily
semantically valid. They shouldn't be taken as
* examples. In several instances, mutually exclusive JSON parameters are
provided. This is done to exercise conversion
* of all parameters, even if particular combinations are never expected in
the same request.
*/
-public class V2ShardsAPIMappingTest extends SolrTestCaseJ4 {
- private ApiBag apiBag;
-
- private ArgumentCaptor<SolrQueryRequest> queryRequestCaptor;
- private CollectionsHandler mockCollectionsHandler;
-
- @BeforeClass
- public static void ensureWorkingMockito() {
- assumeWorkingMockito();
- }
-
- @Before
- public void setupApiBag() throws Exception {
- mockCollectionsHandler = mock(CollectionsHandler.class);
- queryRequestCaptor = ArgumentCaptor.forClass(SolrQueryRequest.class);
-
- apiBag = new ApiBag(false);
- ApiRegistrar.registerShardApis(apiBag, mockCollectionsHandler);
- }
-
- @Test
- public void testSplitShardAllProperties() throws Exception {
- final SolrParams v1Params =
captureConvertedV1Params("/collections/collName/shards", "POST",
- "{ 'split': {" +
- "'shard': 'shard1', " +
- "'ranges': 'someRangeValues', " +
- "'splitKey': 'someSplitKey', " +
- "'numSubShards': 123, " +
- "'splitFuzz': 'some_fuzz_value', " +
- "'timing': true, " +
- "'splitByPrefix': true, " +
- "'followAliases': true, " +
- "'splitMethod': 'rewrite', " +
- "'async': 'some_async_id', " +
- "'waitForFinalState': true, " +
- "'coreProperties': {" +
- " 'foo': 'foo1', " +
- " 'bar': 'bar1', " +
- "}}}");
-
- assertEquals(CollectionParams.CollectionAction.SPLITSHARD.lowerName,
v1Params.get(ACTION));
- assertEquals("collName", v1Params.get(CollectionAdminParams.COLLECTION));
- assertEquals("shard1", v1Params.get(SHARD_ID_PROP));
- assertEquals("someRangeValues", v1Params.get(CoreAdminParams.RANGES));
- assertEquals("someSplitKey", v1Params.get(SPLIT_KEY));
- assertEquals(123, v1Params.getPrimitiveInt(NUM_SUB_SHARDS));
- assertEquals("some_fuzz_value", v1Params.get(SPLIT_FUZZ));
- assertEquals(true, v1Params.getPrimitiveBool(TIMING));
- assertEquals(true, v1Params.getPrimitiveBool(SPLIT_BY_PREFIX));
- assertEquals(true, v1Params.getPrimitiveBool(FOLLOW_ALIASES));
- assertEquals("rewrite", v1Params.get(SPLIT_METHOD));
- assertEquals("some_async_id", v1Params.get(ASYNC));
- assertEquals(true, v1Params.getPrimitiveBool(WAIT_FOR_FINAL_STATE));
- assertEquals("foo1", v1Params.get("property.foo"));
- assertEquals("bar1", v1Params.get("property.bar"));
- }
-
- @Test
- public void testCreateShardAllProperties() throws Exception {
- final SolrParams v1Params =
captureConvertedV1Params("/collections/collName/shards", "POST",
- "{ 'create': {" +
- "'shard': 'shard1', " +
- "'nodeSet': ['foo', 'bar', 'baz'], " +
- "'followAliases': true, " +
- "'async': 'some_async_id', " +
- "'waitForFinalState': true, " +
- "'replicationFactor': 123, " +
- "'nrtReplicas': 456, " +
- "'tlogReplicas': 789, " +
- "'pullReplicas': 101, " +
- "'coreProperties': {" +
- " 'foo': 'foo1', " +
- " 'bar': 'bar1', " +
- "}}}");
-
- assertEquals(CollectionParams.CollectionAction.CREATESHARD.lowerName,
v1Params.get(ACTION));
- assertEquals("collName", v1Params.get(CollectionAdminParams.COLLECTION));
- assertEquals("shard1", v1Params.get(SHARD_ID_PROP));
- assertEquals("foo,bar,baz", v1Params.get(CREATE_NODE_SET_PARAM));
- assertEquals(true, v1Params.getPrimitiveBool(FOLLOW_ALIASES));
- assertEquals("some_async_id", v1Params.get(ASYNC));
- assertEquals(true, v1Params.getPrimitiveBool(WAIT_FOR_FINAL_STATE));
- assertEquals(123, v1Params.getPrimitiveInt(REPLICATION_FACTOR));
- assertEquals(456, v1Params.getPrimitiveInt(NRT_REPLICAS));
- assertEquals(789, v1Params.getPrimitiveInt(TLOG_REPLICAS));
- assertEquals(101, v1Params.getPrimitiveInt(PULL_REPLICAS));
- assertEquals("foo1", v1Params.get("property.foo"));
- assertEquals("bar1", v1Params.get("property.bar"));
- }
-
- @Test
- public void testAddReplicaAllProperties() throws Exception {
- final SolrParams v1Params =
captureConvertedV1Params("/collections/collName/shards", "POST",
- "{ 'add-replica': {" +
- "'shard': 'shard1', " +
- "'_route_': 'someRouteValue', " +
- "'node': 'someNodeValue', " +
- "'name': 'someName', " +
- "'instanceDir': 'dir1', " +
- "'dataDir': 'dir2', " +
- "'ulogDir': 'dir3', " +
- "'createNodeSet': ['foo', 'bar', 'baz'], " +
- "'followAliases': true, " +
- "'async': 'some_async_id', " +
- "'waitForFinalState': true, " +
- "'skipNodeAssignment': true, " +
- "'type': 'tlog', " +
- "'coreProperties': {" +
- " 'foo': 'foo1', " +
- " 'bar': 'bar1', " +
- "}}}");
-
- assertEquals(CollectionParams.CollectionAction.ADDREPLICA.lowerName,
v1Params.get(ACTION));
- assertEquals("collName", v1Params.get(CollectionAdminParams.COLLECTION));
- assertEquals("shard1", v1Params.get(SHARD_ID_PROP));
- assertEquals("someRouteValue", v1Params.get("_route_"));
- assertEquals("someNodeValue", v1Params.get("node"));
- assertEquals("foo,bar,baz", v1Params.get(CREATE_NODE_SET_PARAM));
- assertEquals("someName", v1Params.get(NAME));
- assertEquals("dir1", v1Params.get("instanceDir"));
- assertEquals("dir2", v1Params.get("dataDir"));
- assertEquals("dir3", v1Params.get("ulogDir"));
- assertEquals(true, v1Params.getPrimitiveBool(FOLLOW_ALIASES));
- assertEquals("some_async_id", v1Params.get(ASYNC));
- assertEquals(true, v1Params.getPrimitiveBool(WAIT_FOR_FINAL_STATE));
- assertEquals(true, v1Params.getPrimitiveBool("skipNodeAssignment"));
- assertEquals("tlog", v1Params.get("type"));
- assertEquals("foo1", v1Params.get("property.foo"));
- assertEquals("bar1", v1Params.get("property.bar"));
- }
-
- private SolrParams captureConvertedV1Params(String path, String method,
String v2RequestBody) throws Exception {
- final HashMap<String, String> parts = new HashMap<>();
- final Api api = apiBag.lookup(path, method, parts);
- final SolrQueryResponse rsp = new SolrQueryResponse();
- final LocalSolrQueryRequest req = new LocalSolrQueryRequest(null,
Maps.newHashMap()) {
- @Override
- public List<CommandOperation> getCommands(boolean validateInput) {
- if (v2RequestBody == null) return Collections.emptyList();
- return ApiBag.getCommandOperations(new
ContentStreamBase.StringStream(v2RequestBody), api.getCommandSchema(), true);
- }
-
- @Override
- public Map<String, String> getPathTemplateValues() {
- return parts;
- }
-
- @Override
- public String getHttpMethod() {
- return method;
- }
- };
-
- api.call(req, rsp);
-
verify(mockCollectionsHandler).handleRequestBody(queryRequestCaptor.capture(),
any());
- return queryRequestCaptor.getValue().getParams();
- }
+public class V2ShardsAPIMappingTest {
+ private ApiBag apiBag;
+
+ private ArgumentCaptor<SolrQueryRequest> queryRequestCaptor;
+ private CollectionsHandler mockCollectionsHandler;
+
+ @BeforeClass
+ public static void ensureWorkingMockito() {
+ assumeWorkingMockito();
+ }
+
+ @Before
+ public void setupApiBag() throws Exception {
+ mockCollectionsHandler = mock(CollectionsHandler.class);
+ queryRequestCaptor = ArgumentCaptor.forClass(SolrQueryRequest.class);
+
+ apiBag = new ApiBag(false);
+ ApiRegistrar.registerShardApis(apiBag, mockCollectionsHandler);
+ }
+
+ @Test
+ public void testForceLeaderAllProperties() throws Exception {
+ final SolrParams v1Params =
captureConvertedV1Params("/collections/collName/shards/shardName", "POST",
+ "{ 'force-leader': {}}");
+
+ assertEquals(CollectionParams.CollectionAction.FORCELEADER.lowerName,
v1Params.get(ACTION));
+ assertEquals("collName", v1Params.get(COLLECTION));
+ assertEquals("shardName", v1Params.get(SHARD));
+ }
+
+ @Test
+ public void testSyncShardAllProperties() throws Exception {
+ final SolrParams v1Params =
captureConvertedV1Params("/collections/collName/shards/shardName", "POST",
+ "{ 'sync-shard': {}}");
+
+ assertEquals(CollectionParams.CollectionAction.SYNCSHARD.lowerName,
v1Params.get(ACTION));
+ assertEquals("collName", v1Params.get(COLLECTION));
+ assertEquals("shardName", v1Params.get(SHARD));
+ }
+
+ @Test
+ public void testDeleteShardAllProperties() throws Exception {
+ final ModifiableSolrParams v2QueryParams = new ModifiableSolrParams();
+ v2QueryParams.add("deleteIndex", "true");
+ v2QueryParams.add("deleteDataDir", "true");
+ v2QueryParams.add("deleteInstanceDir", "true");
+ v2QueryParams.add("followAliases", "true");
+ final SolrParams v1Params =
captureConvertedV1Params("/collections/collName/shards/shardName", "DELETE",
v2QueryParams);
+
+ assertEquals(CollectionParams.CollectionAction.DELETESHARD.lowerName,
v1Params.get(ACTION));
+ assertEquals("collName", v1Params.get(COLLECTION));
+ assertEquals("shardName", v1Params.get(SHARD));
+ assertEquals("true", v1Params.get("deleteIndex"));
+ assertEquals("true", v1Params.get("deleteDataDir"));
+ assertEquals("true", v1Params.get("deleteInstanceDir"));
+ assertEquals("true", v1Params.get("followAliases"));
+ }
+
+ @Test
+ public void testSplitShardAllProperties() throws Exception {
+ final SolrParams v1Params =
captureConvertedV1Params("/collections/collName/shards", "POST",
+ "{ 'split': {" +
+ "'shard': 'shard1', " +
+ "'ranges': 'someRangeValues', " +
+ "'splitKey': 'someSplitKey', " +
+ "'numSubShards': 123, " +
+ "'splitFuzz': 'some_fuzz_value', " +
+ "'timing': true, " +
+ "'splitByPrefix': true, " +
+ "'followAliases': true, " +
+ "'splitMethod': 'rewrite', " +
+ "'async': 'some_async_id', " +
+ "'waitForFinalState': true, " +
+ "'coreProperties': {" +
+ " 'foo': 'foo1', " +
+ " 'bar': 'bar1', " +
+ "}}}");
+
+ assertEquals(CollectionParams.CollectionAction.SPLITSHARD.lowerName,
v1Params.get(ACTION));
+ assertEquals("collName",
v1Params.get(CollectionAdminParams.COLLECTION));
+ assertEquals("shard1", v1Params.get(SHARD_ID_PROP));
+ assertEquals("someRangeValues", v1Params.get(CoreAdminParams.RANGES));
+ assertEquals("someSplitKey", v1Params.get(SPLIT_KEY));
+ assertEquals(123, v1Params.getPrimitiveInt(NUM_SUB_SHARDS));
+ assertEquals("some_fuzz_value", v1Params.get(SPLIT_FUZZ));
+ assertEquals(true, v1Params.getPrimitiveBool(TIMING));
+ assertEquals(true, v1Params.getPrimitiveBool(SPLIT_BY_PREFIX));
+ assertEquals(true, v1Params.getPrimitiveBool(FOLLOW_ALIASES));
+ assertEquals("rewrite", v1Params.get(SPLIT_METHOD));
+ assertEquals("some_async_id", v1Params.get(ASYNC));
+ assertEquals(true, v1Params.getPrimitiveBool(WAIT_FOR_FINAL_STATE));
+ assertEquals("foo1", v1Params.get("property.foo"));
+ assertEquals("bar1", v1Params.get("property.bar"));
+ }
+
+ @Test
+ public void testCreateShardAllProperties() throws Exception {
+ final SolrParams v1Params =
captureConvertedV1Params("/collections/collName/shards", "POST",
+ "{ 'create': {" +
+ "'shard': 'shard1', " +
+ "'nodeSet': ['foo', 'bar', 'baz'], " +
+ "'followAliases': true, " +
+ "'async': 'some_async_id', " +
+ "'waitForFinalState': true, " +
+ "'replicationFactor': 123, " +
+ "'nrtReplicas': 456, " +
+ "'tlogReplicas': 789, " +
+ "'pullReplicas': 101, " +
+ "'coreProperties': {" +
+ " 'foo': 'foo1', " +
+ " 'bar': 'bar1', " +
+ "}}}");
+
+ assertEquals(CollectionParams.CollectionAction.CREATESHARD.lowerName,
v1Params.get(ACTION));
+ assertEquals("collName",
v1Params.get(CollectionAdminParams.COLLECTION));
+ assertEquals("shard1", v1Params.get(SHARD_ID_PROP));
+ assertEquals("foo,bar,baz", v1Params.get(CREATE_NODE_SET_PARAM));
+ assertEquals(true, v1Params.getPrimitiveBool(FOLLOW_ALIASES));
+ assertEquals("some_async_id", v1Params.get(ASYNC));
+ assertEquals(true, v1Params.getPrimitiveBool(WAIT_FOR_FINAL_STATE));
+ assertEquals(123, v1Params.getPrimitiveInt(REPLICATION_FACTOR));
+ assertEquals(456, v1Params.getPrimitiveInt(NRT_REPLICAS));
+ assertEquals(789, v1Params.getPrimitiveInt(TLOG_REPLICAS));
+ assertEquals(101, v1Params.getPrimitiveInt(PULL_REPLICAS));
+ assertEquals("foo1", v1Params.get("property.foo"));
+ assertEquals("bar1", v1Params.get("property.bar"));
+ }
+
+ @Test
+ public void testAddReplicaAllProperties() throws Exception {
+ final SolrParams v1Params =
captureConvertedV1Params("/collections/collName/shards", "POST",
+ "{ 'add-replica': {" +
+ "'shard': 'shard1', " +
+ "'_route_': 'someRouteValue', " +
+ "'node': 'someNodeValue', " +
+ "'name': 'someName', " +
+ "'instanceDir': 'dir1', " +
+ "'dataDir': 'dir2', " +
+ "'ulogDir': 'dir3', " +
+ "'createNodeSet': ['foo', 'bar', 'baz'], " +
+ "'followAliases': true, " +
+ "'async': 'some_async_id', " +
+ "'waitForFinalState': true, " +
+ "'skipNodeAssignment': true, " +
+ "'type': 'tlog', " +
+ "'coreProperties': {" +
+ " 'foo': 'foo1', " +
+ " 'bar': 'bar1', " +
+ "}}}");
+
+ assertEquals(CollectionParams.CollectionAction.ADDREPLICA.lowerName,
v1Params.get(ACTION));
+ assertEquals("collName",
v1Params.get(CollectionAdminParams.COLLECTION));
+ assertEquals("shard1", v1Params.get(SHARD_ID_PROP));
+ assertEquals("someRouteValue", v1Params.get("_route_"));
+ assertEquals("someNodeValue", v1Params.get("node"));
+ assertEquals("foo,bar,baz", v1Params.get(CREATE_NODE_SET_PARAM));
+ assertEquals("someName", v1Params.get(NAME));
+ assertEquals("dir1", v1Params.get("instanceDir"));
+ assertEquals("dir2", v1Params.get("dataDir"));
+ assertEquals("dir3", v1Params.get("ulogDir"));
+ assertEquals(true, v1Params.getPrimitiveBool(FOLLOW_ALIASES));
+ assertEquals("some_async_id", v1Params.get(ASYNC));
+ assertEquals(true, v1Params.getPrimitiveBool(WAIT_FOR_FINAL_STATE));
+ assertEquals(true, v1Params.getPrimitiveBool("skipNodeAssignment"));
+ assertEquals("tlog", v1Params.get("type"));
+ assertEquals("foo1", v1Params.get("property.foo"));
+ assertEquals("bar1", v1Params.get("property.bar"));
+ }
+
+ private SolrParams captureConvertedV1Params(String path, String method,
SolrParams queryParams) throws Exception {
+ return captureConvertedV1Params(path, method, queryParams, null);
+ }
+
+ private SolrParams captureConvertedV1Params(String path, String method,
String v2RequestBody) throws Exception {
+ return captureConvertedV1Params(path, method, new
ModifiableSolrParams(), v2RequestBody);
+ }
+
+ private SolrParams captureConvertedV1Params(String path, String method,
SolrParams queryParams, String v2RequestBody) throws Exception {
+ final HashMap<String, String> parts = new HashMap<>();
+ final Api api = apiBag.lookup(path, method, parts);
+ final SolrQueryResponse rsp = new SolrQueryResponse();
+ final LocalSolrQueryRequest req = new LocalSolrQueryRequest(null,
queryParams) {
+ @Override
+ public List<CommandOperation> getCommands(boolean validateInput) {
+ if (v2RequestBody == null) return Collections.emptyList();
+ return ApiBag.getCommandOperations(new
ContentStreamBase.StringStream(v2RequestBody), api.getCommandSchema(), true);
+ }
+
+ @Override
+ public Map<String, String> getPathTemplateValues() {
+ return parts;
+ }
+
+ @Override
+ public String getHttpMethod() {
+ return method;
+ }
+ };
+
+ api.call(req, rsp);
+
verify(mockCollectionsHandler).handleRequestBody(queryRequestCaptor.capture(),
any());
+ return queryRequestCaptor.getValue().getParams();
+ }
}
diff --git
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/CollectionApiMapping.java
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/CollectionApiMapping.java
index bd9767a..3981c08 100644
---
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/CollectionApiMapping.java
+++
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/CollectionApiMapping.java
@@ -23,19 +23,12 @@ import
org.apache.solr.common.params.CollectionParams.CollectionAction;
import org.apache.solr.common.util.CommandOperation;
import org.apache.solr.common.util.Pair;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
import java.util.stream.Stream;
import static org.apache.solr.client.solrj.SolrRequest.METHOD.DELETE;
-import static org.apache.solr.client.solrj.SolrRequest.METHOD.*;
-import static
org.apache.solr.client.solrj.request.CollectionApiMapping.EndPoint.*;
-import static
org.apache.solr.common.params.CollectionParams.CollectionAction.*;
+import static
org.apache.solr.client.solrj.request.CollectionApiMapping.EndPoint.PER_COLLECTION_PER_SHARD_PER_REPLICA_DELETE;
+import static
org.apache.solr.common.params.CollectionParams.CollectionAction.DELETEREPLICA;
/**
* Stores the mapping of v1 API parameters to v2 API parameters
@@ -44,14 +37,7 @@ import static
org.apache.solr.common.params.CollectionParams.CollectionAction.*;
public class CollectionApiMapping {
public enum Meta implements CommandMeta {
- DELETE_SHARD(PER_COLLECTION_PER_SHARD_DELETE, DELETE, DELETESHARD),
- DELETE_REPLICA(PER_COLLECTION_PER_SHARD_PER_REPLICA_DELETE, DELETE,
DELETEREPLICA),
- SYNC_SHARD(PER_COLLECTION_PER_SHARD_COMMANDS,
- POST,
- CollectionAction.SYNCSHARD,
- "synch-shard",
- null),
- FORCE_LEADER(PER_COLLECTION_PER_SHARD_COMMANDS, POST,
CollectionAction.FORCELEADER, "force-leader", null);
+ DELETE_REPLICA(PER_COLLECTION_PER_SHARD_PER_REPLICA_DELETE, DELETE,
DELETEREPLICA);
public final String commandName;
public final EndPoint endPoint;
@@ -170,8 +156,6 @@ public class CollectionApiMapping {
}
public enum EndPoint implements V2EndPoint {
-
PER_COLLECTION_PER_SHARD_COMMANDS("collections.collection.shards.shard.Commands"),
-
PER_COLLECTION_PER_SHARD_DELETE("collections.collection.shards.shard.delete"),
PER_COLLECTION_PER_SHARD_PER_REPLICA_DELETE("collections.collection.shards.shard.replica.delete");
final String specName;
diff --git
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/ForceLeaderPayload.java
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/ForceLeaderPayload.java
new file mode 100644
index 0000000..6c89e14
--- /dev/null
+++
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/ForceLeaderPayload.java
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.client.solrj.request.beans;
+
+import org.apache.solr.common.util.ReflectMapWriter;
+
+/**
+ * Empty payload for the POST /v2/collections/collName/shards/shardName
{"force-leader": {}} API.
+ */
+public class ForceLeaderPayload implements ReflectMapWriter { }
diff --git
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/SyncShardPayload.java
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/SyncShardPayload.java
new file mode 100644
index 0000000..3e947da
--- /dev/null
+++
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/SyncShardPayload.java
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.client.solrj.request.beans;
+
+import org.apache.solr.common.util.ReflectMapWriter;
+
+/**
+ * Empty payload for the /v2/collections/collName/shards/shardName
{"sync-shard": {}} API.
+ */
+public class SyncShardPayload implements ReflectMapWriter { }
diff --git
a/solr/solrj/src/resources/apispec/collections.collection.shards.shard.Commands.json
b/solr/solrj/src/resources/apispec/collections.collection.shards.shard.Commands.json
deleted file mode 100644
index 2f8485f..0000000
---
a/solr/solrj/src/resources/apispec/collections.collection.shards.shard.Commands.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "documentation":
"https://lucene.apache.org/solr/guide/shard-management.html",
- "description": "Commands to force leader election and synchronize shards.",
- "methods": [
- "POST",
- "DELETE"
- ],
- "url": {
- "paths": [
- "/collections/{collection}/shards/{shard}",
- "/c/{collection}/shards/{shard}"
- ]
- },
- "commands": {
- "force-leader": {
- "documentation":
"https://lucene.apache.org/solr/guide/shard-management.html#forceleader",
- "description": "In the unlikely event of a shard losing its leader, this
command can be invoked to force the election of a new leader",
- "type": "object"
- },
- "sync-shard": {
- "type": "object"
- }
- }
-}
diff --git
a/solr/solrj/src/resources/apispec/collections.collection.shards.shard.delete.json
b/solr/solrj/src/resources/apispec/collections.collection.shards.shard.delete.json
deleted file mode 100644
index 50c1e3b..0000000
---
a/solr/solrj/src/resources/apispec/collections.collection.shards.shard.delete.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "documentation":
"https://lucene.apache.org/solr/guide/shard-management.html#deleteshard",
- "description": "Deletes a shard by unloading all replicas of the shard,
removing it from the collection's state.json, and by default deleting the
instanceDir and dataDir. Only inactive shards or those which have no range for
custom sharding will be deleted.",
- "methods": [
- "DELETE"
- ],
- "url": {
- "paths": [
- "/collections/{collection}/shards/{shard}",
- "/c/{collection}/shards/{shard}"
- ],
- "params":{
- "deleteInstanceDir":{
- "type": "boolean",
- "description":"By default Solr will delete the entire instanceDir of
each replica that is deleted. Set this to false to prevent the instance
directory from being deleted."
- },
- "deleteDataDir":{
- "type":"boolean",
- "description":"y default Solr will delete the dataDir of each replica
that is deleted. Set this to false to prevent the data directory from being
deleted."
- },
- "async": {
- "type": "string",
- "description": "Defines a request ID that can be used to track this
action after it's submitted. The action will be processed asynchronously when
this is defined. This command can be long-running, so running it asynchronously
is recommended."
- }
- }
- }
-}
diff --git
a/solr/solrj/src/test/org/apache/solr/common/util/JsonValidatorTest.java
b/solr/solrj/src/test/org/apache/solr/common/util/JsonValidatorTest.java
index 184ea47..73f1ef1 100644
--- a/solr/solrj/src/test/org/apache/solr/common/util/JsonValidatorTest.java
+++ b/solr/solrj/src/test/org/apache/solr/common/util/JsonValidatorTest.java
@@ -17,18 +17,17 @@
package org.apache.solr.common.util;
+import org.apache.solr.SolrTestCaseJ4;
+
import java.util.List;
import java.util.Map;
-import org.apache.solr.SolrTestCaseJ4;
-
import static org.apache.solr.common.util.Utils.toJSONString;
import static org.apache.solr.common.util.ValidatingJsonMap.NOT_NULL;
public class JsonValidatorTest extends SolrTestCaseJ4 {
public void testSchema() {
- checkSchema("collections.collection.shards.shard.Commands");
checkSchema("cores.Commands");
checkSchema("cores.core.Commands");
checkSchema("node.Commands");