This is an automated email from the ASF dual-hosted git repository.
epugh 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 6699861b7f5 SOLR-16390: Move cluster command-status APIs to
/cluster/commands (#4866)
6699861b7f5 is described below
commit 6699861b7f53f744d08cadb39af4ef841158d05f
Author: Prithvi S <[email protected]>
AuthorDate: Fri Sep 4 22:52:56 2026 +0530
SOLR-16390: Move cluster command-status APIs to /cluster/commands (#4866)
Signed-off-by: prithvi <[email protected]>
Co-authored-by: Eric Pugh <[email protected]>
---
.../SOLR-16390-cluster-command-status.yml | 8 +
dev-docs/v2-api-conventions.adoc | 2 +
.../client/api/endpoint/ClusterCommandsApi.java | 71 ++++++
.../api/endpoint/GetNodeCommandStatusApi.java | 2 +-
.../model/DeleteClusterCommandStatusResponse.java | 31 +++
.../api/model/GetClusterCommandStatusResponse.java | 97 +++++++
.../java/org/apache/solr/handler/ClusterAPI.java | 24 --
.../solr/handler/admin/CollectionsHandler.java | 164 +-----------
.../solr/handler/admin/api/ClusterCommands.java | 278 +++++++++++++++++++++
.../solr/handler/V2ClusterAPIMappingTest.java | 19 --
.../handler/admin/api/ClusterCommandsTest.java | 128 ++++++++++
.../configuration-guide/pages/collections-api.adoc | 9 +-
12 files changed, 628 insertions(+), 205 deletions(-)
diff --git a/changelog/unreleased/SOLR-16390-cluster-command-status.yml
b/changelog/unreleased/SOLR-16390-cluster-command-status.yml
new file mode 100644
index 00000000000..775b6364937
--- /dev/null
+++ b/changelog/unreleased/SOLR-16390-cluster-command-status.yml
@@ -0,0 +1,8 @@
+title: "v2 cluster command-status APIs are now more REST-ful at GET/DELETE
/api/cluster/commands/{id} and DELETE /api/cluster/commands (the previous
/api/cluster/command-status paths are gone). SolrJ provides
ClusterApi.GetClusterCommandStatus, ClusterApi.DeleteClusterCommandStatus, and
ClusterApi.DeleteAllClusterCommandStatuses."
+type: changed
+authors:
+ - name: Prithvi S
+ nick: iprithv
+links:
+ - name: SOLR-16390
+ url: https://issues.apache.org/jira/browse/SOLR-16390
diff --git a/dev-docs/v2-api-conventions.adoc b/dev-docs/v2-api-conventions.adoc
index d40d7dd4a48..08a9d01fdba 100644
--- a/dev-docs/v2-api-conventions.adoc
+++ b/dev-docs/v2-api-conventions.adoc
@@ -23,6 +23,8 @@ Following these guidelines has given us the following
(non-exhaustive) list of v
* `/api/backups/specificBackupName/versions/specificVersion`
* `/api/cluster/nodes/specificNodeName/roles`
* `/api/cluster/nodes/specificNodeName/roles/specificRoleName`
+* `/api/cluster/commands`
+* `/api/cluster/commands/specificCommandId`
* `/api/cluster/properties`
* `/api/cluster/properties/specificPropertyName`
* `/api/collections`
diff --git
a/solr/api/src/java/org/apache/solr/client/api/endpoint/ClusterCommandsApi.java
b/solr/api/src/java/org/apache/solr/client/api/endpoint/ClusterCommandsApi.java
new file mode 100644
index 00000000000..bfd3c19eda8
--- /dev/null
+++
b/solr/api/src/java/org/apache/solr/client/api/endpoint/ClusterCommandsApi.java
@@ -0,0 +1,71 @@
+/*
+ * 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.api.endpoint;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import org.apache.solr.client.api.model.DeleteClusterCommandStatusResponse;
+import org.apache.solr.client.api.model.GetClusterCommandStatusResponse;
+
+/**
+ * V2 API definitions for cluster-level asynchronous Collection API command
status.
+ *
+ * <p>These APIs are analogous to the v1 {@code
/admin/collections?action=REQUESTSTATUS} and {@code
+ * /admin/collections?action=DELETESTATUS} commands. They are not to be
confused with the node-local
+ * {@link GetNodeCommandStatusApi} under {@code /api/node/commands}.
+ */
+@Path("/cluster/commands")
+public interface ClusterCommandsApi {
+
+ @GET
+ @Path("/{requestId}")
+ @Operation(
+ summary = "Request the status of an already submitted asynchronous
Collection API call.",
+ tags = {"cluster"})
+ GetClusterCommandStatusResponse getClusterCommandStatus(
+ @Parameter(
+ description = "The user defined request-id for the asynchronous
request.",
+ required = true)
+ @PathParam("requestId")
+ String requestId)
+ throws Exception;
+
+ @DELETE
+ @Path("/{requestId}")
+ @Operation(
+ summary =
+ "Delete the stored status of a completed or failed asynchronous
Collection API call.",
+ tags = {"cluster"})
+ DeleteClusterCommandStatusResponse deleteClusterCommandStatus(
+ @Parameter(
+ description = "The user defined request-id whose stored response
should be cleared.",
+ required = true)
+ @PathParam("requestId")
+ String requestId)
+ throws Exception;
+
+ @DELETE
+ @Operation(
+ summary =
+ "Delete the stored status of all completed and failed asynchronous
Collection API calls.",
+ tags = {"cluster"})
+ DeleteClusterCommandStatusResponse deleteAllClusterCommandStatuses() throws
Exception;
+}
diff --git
a/solr/api/src/java/org/apache/solr/client/api/endpoint/GetNodeCommandStatusApi.java
b/solr/api/src/java/org/apache/solr/client/api/endpoint/GetNodeCommandStatusApi.java
index a2b5bcc5986..cc807074d59 100644
---
a/solr/api/src/java/org/apache/solr/client/api/endpoint/GetNodeCommandStatusApi.java
+++
b/solr/api/src/java/org/apache/solr/client/api/endpoint/GetNodeCommandStatusApi.java
@@ -28,7 +28,7 @@ import
org.apache.solr.client.api.model.GetNodeCommandStatusResponse;
*
* <p>This API is analogous to the v1 /admin/cores?action=REQUESTSTATUS
command. It is not to be
* confused with the more robust asynchronous command support offered under
the v2
- * `/cluster/command-status` path (or the corresponding v1 path
+ * `/cluster/commands` path (or the corresponding v1 path
* `/solr/admin/collections?action=REQUESTSTATUS`). Async support at the core
level differs in that
* command IDs are local to individual Solr nodes and are not persisted across
restarts.
*
diff --git
a/solr/api/src/java/org/apache/solr/client/api/model/DeleteClusterCommandStatusResponse.java
b/solr/api/src/java/org/apache/solr/client/api/model/DeleteClusterCommandStatusResponse.java
new file mode 100644
index 00000000000..4571ec19f26
--- /dev/null
+++
b/solr/api/src/java/org/apache/solr/client/api/model/DeleteClusterCommandStatusResponse.java
@@ -0,0 +1,31 @@
+/*
+ * 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.api.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+/**
+ * Response body for {@code DELETE /api/cluster/commands} and {@code DELETE
+ * /api/cluster/commands/{requestId}}.
+ */
+public class DeleteClusterCommandStatusResponse extends SolrJerseyResponse {
+
+ @JsonProperty("status")
+ @Schema(description = "A message describing the result of the delete.")
+ public String status;
+}
diff --git
a/solr/api/src/java/org/apache/solr/client/api/model/GetClusterCommandStatusResponse.java
b/solr/api/src/java/org/apache/solr/client/api/model/GetClusterCommandStatusResponse.java
new file mode 100644
index 00000000000..6b96427cacd
--- /dev/null
+++
b/solr/api/src/java/org/apache/solr/client/api/model/GetClusterCommandStatusResponse.java
@@ -0,0 +1,97 @@
+/*
+ * 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.api.model;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonValue;
+import io.swagger.v3.oas.annotations.media.Schema;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.solr.client.api.util.ReflectWritable;
+
+/**
+ * Response body for {@code GET /api/cluster/commands/{requestId}}.
+ *
+ * <p>Completed and failed commands also flatten the original command response
(for example {@code
+ * success} / {@code failure} sub-responses) into this object as additional
properties.
+ */
+public class GetClusterCommandStatusResponse extends SolrJerseyResponse {
+
+ @JsonProperty("status")
+ @Schema(description = "The current state of the asynchronous request and a
descriptive message.")
+ public CommandStatus status;
+
+ private Map<String, Object> unknownFields = new HashMap<>();
+
+ @JsonAnyGetter
+ public Map<String, Object> unknownProperties() {
+ return unknownFields;
+ }
+
+ @JsonAnySetter
+ public void setUnknownProperty(String field, Object value) {
+ unknownFields.put(field, value);
+ }
+
+ /** Nested {@code status} object returned by REQUESTSTATUS. */
+ public static class CommandStatus implements ReflectWritable {
+ @JsonProperty("state")
+ @Schema(description = "Request state: submitted, running, completed,
failed, or notfound.")
+ public State state;
+
+ @JsonProperty("msg")
+ @Schema(description = "A message describing where the request was found,
if at all.")
+ public String msg;
+
+ /**
+ * The state of an asynchronous request. Mirrors {@code
+ * org.apache.solr.client.solrj.response.RequestStatusState}'s constants
and wire keys; kept as
+ * a separate type here since this module (solr:api) cannot depend on
solrj.
+ */
+ public enum State {
+ SUBMITTED("submitted"),
+ RUNNING("running"),
+ COMPLETED("completed"),
+ FAILED("failed"),
+ NOT_FOUND("notfound");
+
+ private final String key;
+
+ State(String key) {
+ this.key = key;
+ }
+
+ @JsonValue
+ public String getKey() {
+ return key;
+ }
+
+ @JsonCreator
+ public static State fromKey(String key) {
+ for (State state : values()) {
+ if (state.key.equalsIgnoreCase(key)) {
+ return state;
+ }
+ }
+ throw new IllegalArgumentException("Unknown request status state: " +
key);
+ }
+ }
+ }
+}
diff --git a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
index 36ee024f7f2..4e672348292 100644
--- a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
@@ -17,16 +17,11 @@
package org.apache.solr.handler;
-import static org.apache.solr.client.solrj.SolrRequest.METHOD.DELETE;
import static org.apache.solr.client.solrj.SolrRequest.METHOD.GET;
import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST;
-import static
org.apache.solr.cloud.api.collections.CollectionHandlingUtils.REQUESTID;
-import static org.apache.solr.common.params.CollectionParams.ACTION;
import static
org.apache.solr.common.params.CollectionParams.CollectionAction.ADDROLE;
-import static
org.apache.solr.common.params.CollectionParams.CollectionAction.DELETESTATUS;
import static
org.apache.solr.common.params.CollectionParams.CollectionAction.OVERSEERSTATUS;
import static
org.apache.solr.common.params.CollectionParams.CollectionAction.REMOVEROLE;
-import static
org.apache.solr.common.params.CollectionParams.CollectionAction.REQUESTSTATUS;
import static org.apache.solr.core.RateLimiterConfig.RL_CONFIG_KEY;
import static
org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
import static
org.apache.solr.security.PermissionNameProvider.Name.COLL_READ_PERM;
@@ -199,18 +194,6 @@ public class ClusterAPI {
collectionsHandler.handleRequestBody(wrapParams(req, "action",
OVERSEERSTATUS.lowerName), rsp);
}
- @EndPoint(method = DELETE, path = "/cluster/command-status/{id}", permission
= COLL_EDIT_PERM)
- public void deleteCommandStatus(SolrQueryRequest req, SolrQueryResponse rsp)
throws Exception {
- final Map<String, Object> v1Params =
- Map.of(ACTION, DELETESTATUS.lowerName, REQUESTID,
req.getPathTemplateValues().get("id"));
- collectionsHandler.handleRequestBody(wrapParams(req, v1Params), rsp);
- }
-
- @EndPoint(method = DELETE, path = "/cluster/command-status", permission =
COLL_EDIT_PERM)
- public void flushCommandStatus(SolrQueryRequest req, SolrQueryResponse rsp)
throws Exception {
- CollectionsHandler.CollectionOperation.DELETESTATUS_OP.execute(req, rsp,
collectionsHandler);
- }
-
public static SolrQueryRequest wrapParams(SolrQueryRequest req, Object...
def) {
Map<String, Object> m = Utils.makeMap(def);
return wrapParams(req, m);
@@ -232,13 +215,6 @@ public class ClusterAPI {
return req;
}
- @EndPoint(method = GET, path = "/cluster/command-status/{id}", permission =
COLL_READ_PERM)
- public void getCommandStatus(SolrQueryRequest req, SolrQueryResponse rsp)
throws Exception {
- final Map<String, Object> v1Params =
- Map.of(ACTION, REQUESTSTATUS.lowerName, REQUESTID,
req.getPathTemplateValues().get("id"));
- collectionsHandler.handleRequestBody(wrapParams(req, v1Params), rsp);
- }
-
@EndPoint(method = GET, path = "/cluster/nodes", permission = COLL_READ_PERM)
public void getNodes(SolrQueryRequest req, SolrQueryResponse rsp) {
rsp.add("nodes",
getCoreContainer().getZkController().getClusterState().getLiveNodes());
diff --git
a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
index 270afc24906..77ec443e38f 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
@@ -16,16 +16,10 @@
*/
package org.apache.solr.handler.admin;
-import static
org.apache.solr.client.solrj.response.RequestStatusState.COMPLETED;
-import static org.apache.solr.client.solrj.response.RequestStatusState.FAILED;
-import static
org.apache.solr.client.solrj.response.RequestStatusState.NOT_FOUND;
-import static org.apache.solr.client.solrj.response.RequestStatusState.RUNNING;
-import static
org.apache.solr.client.solrj.response.RequestStatusState.SUBMITTED;
import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION;
import static
org.apache.solr.cloud.api.collections.CollectionHandlingUtils.CREATE_NODE_SET;
import static
org.apache.solr.cloud.api.collections.CollectionHandlingUtils.CREATE_NODE_SET_SHUFFLE;
import static
org.apache.solr.cloud.api.collections.CollectionHandlingUtils.NUM_SLICES;
-import static
org.apache.solr.cloud.api.collections.CollectionHandlingUtils.REQUESTID;
import static
org.apache.solr.cloud.api.collections.CollectionHandlingUtils.SHARD_UNIQUE;
import static org.apache.solr.common.SolrException.ErrorCode.BAD_REQUEST;
import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP;
@@ -133,10 +127,8 @@ import
org.apache.solr.client.api.model.UpdateAliasPropertiesRequestBody;
import org.apache.solr.client.api.model.UpdateCollectionPropertyRequestBody;
import org.apache.solr.client.solrj.SolrResponse;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
-import org.apache.solr.client.solrj.response.RequestStatusState;
import org.apache.solr.cloud.OverseerSolrResponse;
import org.apache.solr.cloud.OverseerSolrResponseSerializer;
-import org.apache.solr.cloud.OverseerTaskQueue;
import org.apache.solr.cloud.OverseerTaskQueue.QueueEvent;
import org.apache.solr.cloud.ZkController;
import org.apache.solr.cloud.ZkController.NotInClusterStateException;
@@ -162,8 +154,6 @@ import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.RequiredSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.NamedList;
-import org.apache.solr.common.util.Pair;
-import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.common.util.Utils;
import org.apache.solr.core.CloudConfig;
@@ -176,6 +166,7 @@ import org.apache.solr.handler.admin.api.AdminAPIBase;
import org.apache.solr.handler.admin.api.AliasProperty;
import org.apache.solr.handler.admin.api.BalanceReplicas;
import org.apache.solr.handler.admin.api.BalanceShardUnique;
+import org.apache.solr.handler.admin.api.ClusterCommands;
import org.apache.solr.handler.admin.api.ClusterProperty;
import org.apache.solr.handler.admin.api.CollectionProperty;
import org.apache.solr.handler.admin.api.CollectionStatus;
@@ -437,13 +428,6 @@ public class CollectionsHandler extends RequestHandlerBase
implements Permission
return submitCollectionApiCommand(coreContainer.getZkController(),
adminCmdContext, m, timeout);
}
- private boolean overseerCollectionQueueContains(String asyncId)
- throws KeeperException, InterruptedException {
- OverseerTaskQueue collectionQueue =
- coreContainer.getZkController().getOverseerCollectionQueue();
- return collectionQueue.containsTaskWithRequestId(ASYNC, asyncId);
- }
-
/**
* Copy prefixed params into a map. There must only be one value for these
parameters.
*
@@ -490,14 +474,6 @@ public class CollectionsHandler extends RequestHandlerBase
implements Permission
return Category.ADMIN;
}
- private static void addStatusToResponse(
- NamedList<Object> results, RequestStatusState state, String msg) {
- SimpleOrderedMap<String> status = new SimpleOrderedMap<>();
- status.add("state", state.getKey());
- status.add("msg", msg);
- results.add("status", status);
- }
-
@SuppressWarnings("ImmutableEnumChecker")
public enum CollectionOperation implements CollectionOp {
CREATE_OP(
@@ -789,141 +765,14 @@ public class CollectionsHandler extends
RequestHandlerBase implements Permission
REQUESTSTATUS_OP(
REQUESTSTATUS,
(req, rsp, h) -> {
- req.getParams().required().check(REQUESTID);
-
- final CoreContainer coreContainer = h.coreContainer;
- final String requestId = req.getParams().get(REQUESTID);
- final ZkController zkController = coreContainer.getZkController();
-
- final NamedList<Object> status = new NamedList<>();
- if (zkController.getDistributedCommandRunner().isEmpty()) {
- if (zkController.getOverseerRunningMap().contains(requestId)) {
- addStatusToResponse(status, RUNNING, "found [" + requestId + "]
in running tasks");
- } else if
(zkController.getOverseerCompletedMap().contains(requestId)) {
- final byte[] mapEntry =
zkController.getOverseerCompletedMap().get(requestId);
- rsp.getValues()
-
.addAll(OverseerSolrResponseSerializer.deserialize(mapEntry).getResponse());
- addStatusToResponse(
- status, COMPLETED, "found [" + requestId + "] in completed
tasks");
- } else if
(zkController.getOverseerFailureMap().contains(requestId)) {
- final byte[] mapEntry =
zkController.getOverseerFailureMap().get(requestId);
- rsp.getValues()
-
.addAll(OverseerSolrResponseSerializer.deserialize(mapEntry).getResponse());
- addStatusToResponse(status, FAILED, "found [" + requestId + "]
in failed tasks");
- } else if (h.overseerCollectionQueueContains(requestId)) {
- addStatusToResponse(
- status, SUBMITTED, "found [" + requestId + "] in submitted
tasks");
- } else {
- addStatusToResponse(
- status, NOT_FOUND, "Did not find [" + requestId + "] in any
tasks queue");
- }
- } else {
- Pair<RequestStatusState, OverseerSolrResponse> sr =
- zkController
- .getDistributedCommandRunner()
- .get()
- .getAsyncTaskRequestStatus(requestId);
- final String message;
- switch (sr.first()) {
- case COMPLETED:
- message = "found [" + requestId + "] in completed tasks";
- rsp.getValues().addAll(sr.second().getResponse());
- break;
- case FAILED:
- message = "found [" + requestId + "] in failed tasks";
- rsp.getValues().addAll(sr.second().getResponse());
- break;
- case RUNNING:
- message = "found [" + requestId + "] in running tasks";
- break;
- case SUBMITTED:
- message = "found [" + requestId + "] in submitted tasks";
- break;
- default:
- message = "Did not find [" + requestId + "] in any tasks
queue";
- }
- addStatusToResponse(status, sr.first(), message);
- }
-
- rsp.getValues().addAll(status);
+ ClusterCommands.invokeGetFromV1Params(h.coreContainer, req, rsp);
return null;
}),
DELETESTATUS_OP(
DELETESTATUS,
- new CollectionOp() {
- @Override
- public Map<String, Object> execute(
- SolrQueryRequest req, SolrQueryResponse rsp, CollectionsHandler
h) throws Exception {
- final CoreContainer coreContainer = h.coreContainer;
- final String requestId = req.getParams().get(REQUESTID);
- final ZkController zkController = coreContainer.getZkController();
- boolean flush =
req.getParams().getBool(CollectionAdminParams.FLUSH, false);
-
- if (requestId == null && !flush) {
- throw new SolrException(
- ErrorCode.BAD_REQUEST, "Either requestid or flush parameter
must be specified.");
- }
-
- if (requestId != null && flush) {
- throw new SolrException(
- ErrorCode.BAD_REQUEST,
- "Both requestid and flush parameters can not be specified
together.");
- }
-
- if (zkController.getDistributedCommandRunner().isEmpty()) {
- if (flush) {
- Collection<String> completed =
zkController.getOverseerCompletedMap().keys();
- Collection<String> failed =
zkController.getOverseerFailureMap().keys();
- for (String asyncId : completed) {
- zkController.getOverseerCompletedMap().remove(asyncId);
- zkController.clearAsyncId(asyncId);
- }
- for (String asyncId : failed) {
- zkController.getOverseerFailureMap().remove(asyncId);
- zkController.clearAsyncId(asyncId);
- }
- rsp.getValues()
- .add("status", "successfully cleared stored collection api
responses");
- } else {
- // Request to cleanup
- if (zkController.getOverseerCompletedMap().remove(requestId)) {
- zkController.clearAsyncId(requestId);
- rsp.getValues()
- .add(
- "status", "successfully removed stored response for
[" + requestId + "]");
- } else if
(zkController.getOverseerFailureMap().remove(requestId)) {
- zkController.clearAsyncId(requestId);
- rsp.getValues()
- .add(
- "status", "successfully removed stored response for
[" + requestId + "]");
- } else {
- rsp.getValues()
- .add("status", "[" + requestId + "] not found in stored
responses");
- // Don't call zkController.clearAsyncId for this, since it
could be a
- // running/pending task
- }
- }
- } else {
- if (flush) {
-
zkController.getDistributedCommandRunner().get().deleteAllAsyncIds();
- rsp.getValues()
- .add("status", "successfully cleared stored collection api
responses");
- } else {
- if (zkController
- .getDistributedCommandRunner()
- .get()
- .deleteSingleAsyncId(requestId)) {
- rsp.getValues()
- .add(
- "status", "successfully removed stored response for
[" + requestId + "]");
- } else {
- rsp.getValues()
- .add("status", "[" + requestId + "] not found in stored
responses");
- }
- }
- }
- return null;
- }
+ (req, rsp, h) -> {
+ ClusterCommands.invokeDeleteFromV1Params(h.coreContainer, req, rsp);
+ return null;
}),
ADDREPLICA_OP(
ADDREPLICA,
@@ -1379,7 +1228,8 @@ public class CollectionsHandler extends
RequestHandlerBase implements Permission
ListCollectionSnapshots.class,
CreateCollectionSnapshot.class,
DeleteCollectionSnapshot.class,
- ClusterProperty.class);
+ ClusterProperty.class,
+ ClusterCommands.class);
}
@Override
diff --git
a/solr/core/src/java/org/apache/solr/handler/admin/api/ClusterCommands.java
b/solr/core/src/java/org/apache/solr/handler/admin/api/ClusterCommands.java
new file mode 100644
index 00000000000..792642dbe6c
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/ClusterCommands.java
@@ -0,0 +1,278 @@
+/*
+ * 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 static
org.apache.solr.client.solrj.response.RequestStatusState.COMPLETED;
+import static org.apache.solr.client.solrj.response.RequestStatusState.FAILED;
+import static
org.apache.solr.client.solrj.response.RequestStatusState.NOT_FOUND;
+import static org.apache.solr.client.solrj.response.RequestStatusState.RUNNING;
+import static
org.apache.solr.client.solrj.response.RequestStatusState.SUBMITTED;
+import static
org.apache.solr.cloud.api.collections.CollectionHandlingUtils.REQUESTID;
+import static org.apache.solr.common.params.CommonAdminParams.ASYNC;
+import static
org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
+import static
org.apache.solr.security.PermissionNameProvider.Name.COLL_READ_PERM;
+
+import jakarta.inject.Inject;
+import java.util.Collection;
+import java.util.Map;
+import org.apache.solr.client.api.endpoint.ClusterCommandsApi;
+import org.apache.solr.client.api.model.DeleteClusterCommandStatusResponse;
+import org.apache.solr.client.api.model.GetClusterCommandStatusResponse;
+import
org.apache.solr.client.api.model.GetClusterCommandStatusResponse.CommandStatus;
+import org.apache.solr.client.solrj.response.RequestStatusState;
+import org.apache.solr.cloud.OverseerSolrResponse;
+import org.apache.solr.cloud.OverseerSolrResponseSerializer;
+import org.apache.solr.cloud.ZkController;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.SolrException.ErrorCode;
+import org.apache.solr.common.params.CollectionAdminParams;
+import org.apache.solr.common.params.CoreAdminParams;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.common.util.Pair;
+import org.apache.solr.common.util.SimpleOrderedMap;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.handler.api.V2ApiUtils;
+import org.apache.solr.jersey.PermissionName;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+
+/**
+ * V2 APIs for checking and deleting cluster-level asynchronous Collection API
command status.
+ *
+ * <p>{@code GET /api/cluster/commands/{requestId}} is analogous to v1 {@code
+ * /admin/collections?action=REQUESTSTATUS}. {@code DELETE
/api/cluster/commands/{requestId}} and
+ * {@code DELETE /api/cluster/commands} are analogous to v1 {@code
+ * /admin/collections?action=DELETESTATUS}.
+ *
+ * <p>The v1 CollectionsHandler operations delegate to this class.
+ */
+public class ClusterCommands extends AdminAPIBase implements
ClusterCommandsApi {
+
+ @Inject
+ public ClusterCommands(
+ CoreContainer coreContainer,
+ SolrQueryRequest solrQueryRequest,
+ SolrQueryResponse solrQueryResponse) {
+ super(coreContainer, solrQueryRequest, solrQueryResponse);
+ }
+
+ @Override
+ @PermissionName(COLL_READ_PERM)
+ public GetClusterCommandStatusResponse getClusterCommandStatus(String
requestId)
+ throws Exception {
+ ensureRequiredParameterProvided(CoreAdminParams.REQUESTID, requestId);
+ fetchAndValidateZooKeeperAwareCoreContainer();
+
+ final GetClusterCommandStatusResponse response =
+ instantiateJerseyResponse(GetClusterCommandStatusResponse.class);
+ final ZkController zkController = coreContainer.getZkController();
+
+ if (zkController.getDistributedCommandRunner().isEmpty()) {
+ if (zkController.getOverseerRunningMap().contains(requestId)) {
+ setStatus(response, RUNNING, requestId);
+ } else if (zkController.getOverseerCompletedMap().contains(requestId)) {
+ copyStoredCommandResponse(
+ response,
+ OverseerSolrResponseSerializer.deserialize(
+ zkController.getOverseerCompletedMap().get(requestId))
+ .getResponse());
+ setStatus(response, COMPLETED, requestId);
+ } else if (zkController.getOverseerFailureMap().contains(requestId)) {
+ copyStoredCommandResponse(
+ response,
+ OverseerSolrResponseSerializer.deserialize(
+ zkController.getOverseerFailureMap().get(requestId))
+ .getResponse());
+ setStatus(response, FAILED, requestId);
+ } else if (overseerCollectionQueueContains(requestId)) {
+ setStatus(response, SUBMITTED, requestId);
+ } else {
+ setStatus(response, NOT_FOUND, requestId);
+ }
+ } else {
+ Pair<RequestStatusState, OverseerSolrResponse> sr =
+
zkController.getDistributedCommandRunner().get().getAsyncTaskRequestStatus(requestId);
+ switch (sr.first()) {
+ case COMPLETED:
+ case FAILED:
+ copyStoredCommandResponse(response, sr.second().getResponse());
+ break;
+ default:
+ break;
+ }
+ setStatus(response, sr.first(), requestId);
+ }
+
+ return response;
+ }
+
+ @Override
+ @PermissionName(COLL_EDIT_PERM)
+ public DeleteClusterCommandStatusResponse deleteClusterCommandStatus(String
requestId)
+ throws Exception {
+ ensureRequiredParameterProvided(CoreAdminParams.REQUESTID, requestId);
+ fetchAndValidateZooKeeperAwareCoreContainer();
+
+ final DeleteClusterCommandStatusResponse response =
+ instantiateJerseyResponse(DeleteClusterCommandStatusResponse.class);
+ final ZkController zkController = coreContainer.getZkController();
+
+ if (zkController.getDistributedCommandRunner().isEmpty()) {
+ if (zkController.getOverseerCompletedMap().remove(requestId)) {
+ zkController.clearAsyncId(requestId);
+ response.status = "successfully removed stored response for [" +
requestId + "]";
+ } else if (zkController.getOverseerFailureMap().remove(requestId)) {
+ zkController.clearAsyncId(requestId);
+ response.status = "successfully removed stored response for [" +
requestId + "]";
+ } else {
+ // Don't call zkController.clearAsyncId for this, since it could be a
running/pending task
+ response.status = "[" + requestId + "] not found in stored responses";
+ }
+ } else if
(zkController.getDistributedCommandRunner().get().deleteSingleAsyncId(requestId))
{
+ response.status = "successfully removed stored response for [" +
requestId + "]";
+ } else {
+ response.status = "[" + requestId + "] not found in stored responses";
+ }
+
+ return response;
+ }
+
+ @Override
+ @PermissionName(COLL_EDIT_PERM)
+ public DeleteClusterCommandStatusResponse deleteAllClusterCommandStatuses()
throws Exception {
+ fetchAndValidateZooKeeperAwareCoreContainer();
+
+ final DeleteClusterCommandStatusResponse response =
+ instantiateJerseyResponse(DeleteClusterCommandStatusResponse.class);
+ final ZkController zkController = coreContainer.getZkController();
+
+ if (zkController.getDistributedCommandRunner().isEmpty()) {
+ Collection<String> completed =
zkController.getOverseerCompletedMap().keys();
+ Collection<String> failed = zkController.getOverseerFailureMap().keys();
+ for (String asyncId : completed) {
+ zkController.getOverseerCompletedMap().remove(asyncId);
+ zkController.clearAsyncId(asyncId);
+ }
+ for (String asyncId : failed) {
+ zkController.getOverseerFailureMap().remove(asyncId);
+ zkController.clearAsyncId(asyncId);
+ }
+ } else {
+ zkController.getDistributedCommandRunner().get().deleteAllAsyncIds();
+ }
+ response.status = "successfully cleared stored collection api responses";
+ return response;
+ }
+
+ /**
+ * v1 {@code REQUESTSTATUS} entrypoint. Squashes the JAX-RS response into
{@code rsp}, converting
+ * the nested status object to a {@link SimpleOrderedMap} so SolrJ's v1
{@code RequestStatus}
+ * parser continues to work.
+ */
+ public static void invokeGetFromV1Params(
+ CoreContainer coreContainer, SolrQueryRequest req, SolrQueryResponse
rsp) throws Exception {
+ req.getParams().required().check(REQUESTID);
+ final ClusterCommands api = new ClusterCommands(coreContainer, req, rsp);
+ final GetClusterCommandStatusResponse jerseyResponse =
+ api.getClusterCommandStatus(req.getParams().get(REQUESTID));
+ V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, jerseyResponse);
+ convertStatusToNamedList(rsp);
+ }
+
+ /**
+ * v1 {@code DELETESTATUS} entrypoint. {@code requestid} and {@code flush}
remain mutually
+ * exclusive query parameters on v1; the v2 API uses distinct paths instead.
+ */
+ public static void invokeDeleteFromV1Params(
+ CoreContainer coreContainer, SolrQueryRequest req, SolrQueryResponse
rsp) throws Exception {
+ final String requestId = req.getParams().get(REQUESTID);
+ final boolean flush = req.getParams().getBool(CollectionAdminParams.FLUSH,
false);
+
+ if (requestId == null && !flush) {
+ throw new SolrException(
+ ErrorCode.BAD_REQUEST, "Either requestid or flush parameter must be
specified.");
+ }
+ if (requestId != null && flush) {
+ throw new SolrException(
+ ErrorCode.BAD_REQUEST,
+ "Both requestid and flush parameters can not be specified
together.");
+ }
+
+ final ClusterCommands api = new ClusterCommands(coreContainer, req, rsp);
+ final DeleteClusterCommandStatusResponse jerseyResponse =
+ flush ? api.deleteAllClusterCommandStatuses() :
api.deleteClusterCommandStatus(requestId);
+ V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, jerseyResponse);
+ }
+
+ private boolean overseerCollectionQueueContains(String asyncId) throws
Exception {
+ return coreContainer
+ .getZkController()
+ .getOverseerCollectionQueue()
+ .containsTaskWithRequestId(ASYNC, asyncId);
+ }
+
+ private static void setStatus(
+ GetClusterCommandStatusResponse response, RequestStatusState state,
String requestId) {
+ final CommandStatus status = new CommandStatus();
+ status.state = CommandStatus.State.valueOf(state.name());
+ status.msg = statusMessage(state, requestId);
+ response.status = status;
+ }
+
+ private static String statusMessage(RequestStatusState state, String
requestId) {
+ return switch (state) {
+ case RUNNING -> "found [" + requestId + "] in running tasks";
+ case COMPLETED -> "found [" + requestId + "] in completed tasks";
+ case FAILED -> "found [" + requestId + "] in failed tasks";
+ case SUBMITTED -> "found [" + requestId + "] in submitted tasks";
+ default -> "Did not find [" + requestId + "] in any tasks queue";
+ };
+ }
+
+ private static void copyStoredCommandResponse(
+ GetClusterCommandStatusResponse response, NamedList<Object> stored) {
+ if (stored == null) {
+ return;
+ }
+ for (Map.Entry<String, Object> entry : stored) {
+ final String key = entry.getKey();
+ if ("responseHeader".equals(key) || "error".equals(key) ||
"status".equals(key)) {
+ continue;
+ }
+ response.setUnknownProperty(key, entry.getValue());
+ }
+ }
+
+ /**
+ * v1 SolrJ reads {@code status} as a {@link NamedList}. Squash leaves the
JAX-RS {@link
+ * CommandStatus} POJO in place; replace it so existing clients keep working.
+ */
+ private static void convertStatusToNamedList(SolrQueryResponse rsp) {
+ final NamedList<Object> values = rsp.getValues();
+ final int idx = values.indexOf("status", 0);
+ if (idx < 0) {
+ return;
+ }
+ final Object statusVal = values.getVal(idx);
+ if (statusVal instanceof CommandStatus commandStatus) {
+ final SimpleOrderedMap<String> status = new SimpleOrderedMap<>();
+ status.add("state", commandStatus.state.getKey());
+ status.add("msg", commandStatus.msg);
+ values.setVal(idx, status);
+ }
+ }
+}
diff --git
a/solr/core/src/test/org/apache/solr/handler/V2ClusterAPIMappingTest.java
b/solr/core/src/test/org/apache/solr/handler/V2ClusterAPIMappingTest.java
index b41640f1534..4fc458bc589 100644
--- a/solr/core/src/test/org/apache/solr/handler/V2ClusterAPIMappingTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/V2ClusterAPIMappingTest.java
@@ -17,7 +17,6 @@
package org.apache.solr.handler;
-import static
org.apache.solr.cloud.api.collections.CollectionHandlingUtils.REQUESTID;
import static org.apache.solr.common.params.CommonParams.ACTION;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
@@ -69,15 +68,6 @@ public class V2ClusterAPIMappingTest extends SolrTestCaseJ4 {
apiBag.registerObject(clusterAPI.commands);
}
- @Test
- public void testAsyncCommandStatusAllParams() throws Exception {
- final SolrParams v1Params =
- captureConvertedV1Params("/cluster/command-status/someId", "GET",
null);
-
- assertEquals(CollectionParams.CollectionAction.REQUESTSTATUS.lowerName,
v1Params.get(ACTION));
- assertEquals("someId", v1Params.get(REQUESTID));
- }
-
@Test
public void testClusterOverseerAllParams() throws Exception {
final SolrParams v1Params = captureConvertedV1Params("/cluster/overseer",
"GET", null);
@@ -92,15 +82,6 @@ public class V2ClusterAPIMappingTest extends SolrTestCaseJ4 {
assertEquals(CollectionAction.CLUSTERSTATUS.lowerName,
v1Params.get(ACTION));
}
- @Test
- public void testDeleteCommandStatusAllParams() throws Exception {
- final SolrParams v1Params =
- captureConvertedV1Params("/cluster/command-status/someId", "DELETE",
null);
-
- assertEquals(CollectionParams.CollectionAction.DELETESTATUS.lowerName,
v1Params.get(ACTION));
- assertEquals("someId", v1Params.get(REQUESTID));
- }
-
@Test
public void testAddRoleAllParams() throws Exception {
final SolrParams v1Params =
diff --git
a/solr/core/src/test/org/apache/solr/handler/admin/api/ClusterCommandsTest.java
b/solr/core/src/test/org/apache/solr/handler/admin/api/ClusterCommandsTest.java
new file mode 100644
index 00000000000..dc6857cfa28
--- /dev/null
+++
b/solr/core/src/test/org/apache/solr/handler/admin/api/ClusterCommandsTest.java
@@ -0,0 +1,128 @@
+/*
+ * 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 java.util.concurrent.TimeUnit;
+import org.apache.solr.client.api.model.DeleteClusterCommandStatusResponse;
+import org.apache.solr.client.api.model.GetClusterCommandStatusResponse;
+import
org.apache.solr.client.api.model.GetClusterCommandStatusResponse.CommandStatus.State;
+import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.client.solrj.request.ClusterApi;
+import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.cloud.SolrCloudTestCase;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * HTTP tests for {@code GET/DELETE /api/cluster/commands} via the generated
SolrJ client classes.
+ */
+public class ClusterCommandsTest extends SolrCloudTestCase {
+
+ public static final int MAX_WAIT_TIMEOUT = 30;
+
+ @BeforeClass
+ public static void setupCluster() throws Exception {
+ configureCluster(1)
+ .addConfig(
+ "conf1",
TEST_PATH().resolve("configsets").resolve("cloud-minimal").resolve("conf"))
+ .configure();
+ }
+
+ @Test
+ public void testGetCommandStatusNotFound() throws Exception {
+ GetClusterCommandStatusResponse rsp =
+ new
ClusterApi.GetClusterCommandStatus("does-not-exist").process(cluster.getSolrClient());
+
+ assertNotNull(rsp);
+ assertNull(rsp.error);
+ assertEquals(State.NOT_FOUND, rsp.status.state);
+ assertEquals("Did not find [does-not-exist] in any tasks queue",
rsp.status.msg);
+ }
+
+ @Test
+ public void testGetAndDeleteSingleCommandStatus() throws Exception {
+ final SolrClient client = cluster.getSolrClient();
+ final String collection = "cluster-commands-single";
+ final String asyncId =
+ CollectionAdminRequest.createCollection(collection, "conf1", 1,
1).processAsync(client);
+
+ GetClusterCommandStatusResponse getRsp = waitForCompleted(asyncId, client);
+ assertEquals(State.COMPLETED, getRsp.status.state);
+ assertEquals("found [" + asyncId + "] in completed tasks",
getRsp.status.msg);
+ assertTrue(
+ "completed create should include sub-responses from the original
command",
+ getRsp.unknownProperties().containsKey("success")
+ || getRsp.unknownProperties().containsKey("failure"));
+
+ DeleteClusterCommandStatusResponse deleteRsp =
+ new ClusterApi.DeleteClusterCommandStatus(asyncId).process(client);
+ assertEquals("successfully removed stored response for [" + asyncId + "]",
deleteRsp.status);
+
+ GetClusterCommandStatusResponse afterDelete =
+ new ClusterApi.GetClusterCommandStatus(asyncId).process(client);
+ assertEquals(State.NOT_FOUND, afterDelete.status.state);
+ }
+
+ @Test
+ public void testDeleteUnknownCommandStatus() throws Exception {
+ DeleteClusterCommandStatusResponse rsp =
+ new
ClusterApi.DeleteClusterCommandStatus("foo").process(cluster.getSolrClient());
+ assertEquals("[foo] not found in stored responses", rsp.status);
+ }
+
+ @Test
+ public void testDeleteAllCommandStatuses() throws Exception {
+ final SolrClient client = cluster.getSolrClient();
+ final String id1 =
+ CollectionAdminRequest.createCollection("cluster-commands-flush-1",
"conf1", 1, 1)
+ .processAsync(client);
+ final String id2 =
+ CollectionAdminRequest.createCollection("cluster-commands-flush-2",
"conf1", 1, 1)
+ .processAsync(client);
+
+ waitForCompleted(id1, client);
+ waitForCompleted(id2, client);
+
+ DeleteClusterCommandStatusResponse flushRsp =
+ new ClusterApi.DeleteAllClusterCommandStatuses().process(client);
+ assertEquals("successfully cleared stored collection api responses",
flushRsp.status);
+
+ assertEquals(
+ State.NOT_FOUND, new
ClusterApi.GetClusterCommandStatus(id1).process(client).status.state);
+ assertEquals(
+ State.NOT_FOUND, new
ClusterApi.GetClusterCommandStatus(id2).process(client).status.state);
+ }
+
+ private static GetClusterCommandStatusResponse waitForCompleted(String id,
SolrClient client)
+ throws Exception {
+ GetClusterCommandStatusResponse rsp = null;
+ long endTime = System.nanoTime() +
TimeUnit.SECONDS.toNanos(MAX_WAIT_TIMEOUT);
+ while (System.nanoTime() < endTime) {
+ rsp = new ClusterApi.GetClusterCommandStatus(id).process(client);
+ State state = rsp.status.state;
+ assumeTrue("Error creating collection - skipping test", state !=
State.FAILED);
+ if (state == State.COMPLETED) {
+ return rsp;
+ }
+ TimeUnit.SECONDS.sleep(1);
+ }
+ assumeTrue(
+ "Timed out waiting for async request " + id,
+ rsp != null && State.COMPLETED.equals(rsp.status.state));
+ return rsp;
+ }
+}
diff --git
a/solr/solr-ref-guide/modules/configuration-guide/pages/collections-api.adoc
b/solr/solr-ref-guide/modules/configuration-guide/pages/collections-api.adoc
index 0ed88262b49..71a20e63acf 100644
--- a/solr/solr-ref-guide/modules/configuration-guide/pages/collections-api.adoc
+++ b/solr/solr-ref-guide/modules/configuration-guide/pages/collections-api.adoc
@@ -117,7 +117,7 @@ V2 API::
====
[source,bash]
----
-curl -X GET http://localhost:8983/api/cluster/command-status/1000
+curl -X GET http://localhost:8983/api/cluster/commands/1000
----
====
======
@@ -205,13 +205,13 @@ V2 API::
Delete a single request response:
[source,bash]
----
-curl -X DELETE http://localhost:8983/api/cluster/command-status/1000
+curl -X DELETE http://localhost:8983/api/cluster/commands/1000
----
Flush out all stored completed and failed async request responses:
[source,bash]
----
-curl -X DELETE http://localhost:8983/api/cluster/command-status?flush=true
+curl -X DELETE http://localhost:8983/api/cluster/commands
----
====
======
@@ -234,7 +234,8 @@ The request ID of the asynchronous call whose stored
response should be cleared.
|Optional |Default: none
|===
+
-Set to `true` to clear all stored completed and failed async request responses.
+v1 only. Set to `true` to clear all stored completed and failed async request
responses.
+The v2 API uses `DELETE /api/cluster/commands` for the same operation and does
not take this parameter.
=== Examples using DELETESTATUS