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 0259ab2dda3 Migrate org.apache.solr.cli tools from V1 to V2 APIs
(#4154)
0259ab2dda3 is described below
commit 0259ab2dda3cd422246fb74801934c6bbe29ec88
Author: Eric Pugh <[email protected]>
AuthorDate: Tue Mar 17 06:59:11 2026 -0400
Migrate org.apache.solr.cli tools from V1 to V2 APIs (#4154)
Co-authored-by: copilot-swe-agent[bot]
<[email protected]>
Co-authored-by: epugh <[email protected]>
---
.../client/api/endpoint/ListClusterNodesApi.java | 33 ++++++++++++++++
.../client/api/model/ListClusterNodesResponse.java | 33 ++++++++++++++++
.../src/java/org/apache/solr/cli/CLIUtils.java | 13 +++---
.../src/java/org/apache/solr/cli/CreateTool.java | 44 ++++++++-------------
.../src/java/org/apache/solr/cli/DeleteTool.java | 46 ++++++----------------
.../src/java/org/apache/solr/cli/StatusTool.java | 25 ++++++------
.../src/java/org/apache/solr/cli/ToolBase.java | 11 ++++++
7 files changed, 125 insertions(+), 80 deletions(-)
diff --git
a/solr/api/src/java/org/apache/solr/client/api/endpoint/ListClusterNodesApi.java
b/solr/api/src/java/org/apache/solr/client/api/endpoint/ListClusterNodesApi.java
new file mode 100644
index 00000000000..4141e8ae825
--- /dev/null
+++
b/solr/api/src/java/org/apache/solr/client/api/endpoint/ListClusterNodesApi.java
@@ -0,0 +1,33 @@
+/*
+ * 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 jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import org.apache.solr.client.api.model.ListClusterNodesResponse;
+
+/** V2 API definition for listing the nodes in the SolrCloud cluster. */
+@Path("/cluster/nodes")
+public interface ListClusterNodesApi {
+
+ @GET
+ @Operation(
+ summary = "List the nodes in this Solr cluster.",
+ tags = {"cluster"})
+ ListClusterNodesResponse listClusterNodes();
+}
diff --git
a/solr/api/src/java/org/apache/solr/client/api/model/ListClusterNodesResponse.java
b/solr/api/src/java/org/apache/solr/client/api/model/ListClusterNodesResponse.java
new file mode 100644
index 00000000000..bd7a0195ae1
--- /dev/null
+++
b/solr/api/src/java/org/apache/solr/client/api/model/ListClusterNodesResponse.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+import java.util.Set;
+
+/**
+ * Response for the v2 "list cluster nodes" API. This is a bit unusual that
it's wrapping a non
+ * JAX-RS V2 API defined in org.apache.solr.handler.ClusterAPI.getNodes(). The
calls are made using
+ * just the defaults. TODO: Update this when we migrate ClusterAPI to JAX-RS.
+ */
+public class ListClusterNodesResponse extends SolrJerseyResponse {
+
+ @Schema(description = "The live nodes in the cluster.")
+ @JsonProperty("nodes")
+ public Set<String> nodes;
+}
diff --git a/solr/core/src/java/org/apache/solr/cli/CLIUtils.java
b/solr/core/src/java/org/apache/solr/cli/CLIUtils.java
index 1c05377e889..5653bffa897 100644
--- a/solr/core/src/java/org/apache/solr/cli/CLIUtils.java
+++ b/solr/core/src/java/org/apache/solr/cli/CLIUtils.java
@@ -27,7 +27,6 @@ import java.net.URISyntaxException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
-import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
@@ -41,7 +40,7 @@ import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.impl.SolrZkClientTimeout;
import org.apache.solr.client.solrj.jetty.HttpJettySolrClient;
-import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.client.solrj.request.CollectionsApi;
import org.apache.solr.client.solrj.request.CoresApi;
import org.apache.solr.client.solrj.request.SystemInfoRequest;
import org.apache.solr.client.solrj.response.SystemInfoResponse;
@@ -49,7 +48,6 @@ import org.apache.solr.common.SolrException;
import org.apache.solr.common.cloud.SolrZkClient;
import org.apache.solr.common.cloud.ZkStateReader;
import org.apache.solr.common.util.EnvUtils;
-import org.apache.solr.common.util.NamedList;
/** Utility class that holds various helper methods for the CLI. */
public final class CLIUtils {
@@ -311,10 +309,11 @@ public final class CLIUtils {
String solrUrl, String collection, String credentials) {
boolean exists = false;
try (var solrClient = getSolrClient(solrUrl, credentials)) {
- NamedList<Object> existsCheckResult = solrClient.request(new
CollectionAdminRequest.List());
- @SuppressWarnings("unchecked")
- List<String> collections = (List<String>)
existsCheckResult.get("collections");
- exists = collections != null && collections.contains(collection);
+ var response = new CollectionsApi.ListCollections().process(solrClient);
+ exists =
+ response != null
+ && response.collections != null
+ && response.collections.contains(collection);
} catch (Exception exc) {
// just ignore it since we're only interested in a positive result here
}
diff --git a/solr/core/src/java/org/apache/solr/cli/CreateTool.java
b/solr/core/src/java/org/apache/solr/cli/CreateTool.java
index 33ab28ef383..74778e7e490 100644
--- a/solr/core/src/java/org/apache/solr/cli/CreateTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/CreateTool.java
@@ -32,18 +32,13 @@ import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.jetty.HttpJettySolrClient;
-import org.apache.solr.client.solrj.request.CollectionAdminRequest;
-import org.apache.solr.client.solrj.request.CoreAdminRequest;
+import org.apache.solr.client.solrj.request.CollectionsApi;
+import org.apache.solr.client.solrj.request.CoresApi;
import org.apache.solr.client.solrj.request.SystemInfoRequest;
-import org.apache.solr.client.solrj.response.CoreAdminResponse;
import org.apache.solr.client.solrj.response.SystemInfoResponse;
-import org.apache.solr.client.solrj.response.json.JsonMapResponseParser;
import org.apache.solr.cloud.ZkConfigSetService;
import org.apache.solr.common.cloud.ZkStateReader;
-import org.apache.solr.common.util.NamedList;
import org.apache.solr.core.ConfigSetService;
-import org.noggit.CharArr;
-import org.noggit.JSONWriter;
/** Supports create command in the bin/solr script. */
public class CreateTool extends ToolBase {
@@ -182,14 +177,13 @@ public class CreateTool extends ToolBase {
+ coreInstanceDir.toAbsolutePath());
}
- echoIfVerbose("\nCreating new core '" + coreName + "' using
CoreAdminRequest");
+ echoIfVerbose("\nCreating new core '" + coreName + "' using V2 Cores API");
try {
- CoreAdminResponse res = CoreAdminRequest.createCore(coreName, coreName,
solrClient);
- if (isVerbose()) {
- echo(res.jsonStr());
- echo("\n");
- }
+ var req = new CoresApi.CreateCore();
+ req.setName(coreName);
+ req.setInstanceDir(coreName);
+ req.process(solrClient);
echo(String.format(Locale.ROOT, "\nCreated new core '%s'", coreName));
} catch (Exception e) {
@@ -277,31 +271,25 @@ public class CreateTool extends ToolBase {
throw new IllegalStateException(
"\nCollection '"
+ collectionName
- + "' already exists!\nChecked collection existence using
CollectionAdminRequest");
+ + "' already exists!\nChecked collection existence using V2
Collections API");
}
// doesn't seem to exist ... try to create
- echoIfVerbose(
- "\nCreating new collection '" + collectionName + "' using
CollectionAdminRequest");
+ echoIfVerbose("\nCreating new collection '" + collectionName + "' using V2
Collections API");
- NamedList<Object> response;
try {
- var req =
- CollectionAdminRequest.createCollection(
- collectionName, confName, numShards, replicationFactor);
- req.setResponseParser(new JsonMapResponseParser());
- response = cloudSolrClient.request(req);
+ var req = new CollectionsApi.CreateCollection();
+ req.setName(collectionName);
+ req.setConfig(confName);
+ req.setNumShards(numShards);
+ req.setReplicationFactor(replicationFactor);
+ var response = req.process(cloudSolrClient);
+ echoIfVerbose(response);
} catch (SolrServerException sse) {
throw new Exception(
"Failed to create collection '" + collectionName + "' due to: " +
sse.getMessage());
}
- if (isVerbose()) {
- // pretty-print the response to stdout
- CharArr arr = new CharArr();
- new JSONWriter(arr, 2).write(response.asMap(10));
- echo(arr.toString());
- }
String endMessage =
String.format(
Locale.ROOT,
diff --git a/solr/core/src/java/org/apache/solr/cli/DeleteTool.java
b/solr/core/src/java/org/apache/solr/cli/DeleteTool.java
index 2610e2af18d..77db2184a4c 100644
--- a/solr/core/src/java/org/apache/solr/cli/DeleteTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/DeleteTool.java
@@ -29,13 +29,9 @@ import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.jetty.HttpJettySolrClient;
-import org.apache.solr.client.solrj.request.CollectionAdminRequest;
-import org.apache.solr.client.solrj.request.CoreAdminRequest;
-import org.apache.solr.client.solrj.response.json.JsonMapResponseParser;
+import org.apache.solr.client.solrj.request.CollectionsApi;
+import org.apache.solr.client.solrj.request.CoresApi;
import org.apache.solr.common.cloud.ZkStateReader;
-import org.apache.solr.common.util.NamedList;
-import org.noggit.CharArr;
-import org.noggit.JSONWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -176,13 +172,12 @@ public class DeleteTool extends ToolBase {
}
}
- echoIfVerbose("\nDeleting collection '" + collectionName + "' using
CollectionAdminRequest");
+ echoIfVerbose("\nDeleting collection '" + collectionName + "' using V2
Collections API");
- NamedList<Object> response;
try {
- var req = CollectionAdminRequest.deleteCollection(collectionName);
- req.setResponseParser(new JsonMapResponseParser());
- response = cloudSolrClient.request(req);
+ var req = new CollectionsApi.DeleteCollection(collectionName);
+ var response = req.process(cloudSolrClient);
+ echoIfVerbose(response);
} catch (SolrServerException sse) {
throw new Exception(
"Failed to delete collection '" + collectionName + "' due to: " +
sse.getMessage());
@@ -202,38 +197,23 @@ public class DeleteTool extends ToolBase {
}
}
- if (isVerbose() && response != null) {
- // pretty-print the response to stdout
- CharArr arr = new CharArr();
- new JSONWriter(arr, 2).write(response.asMap(10));
- echo(arr.toString());
- echo("\n");
- }
-
echo(String.format(Locale.ROOT, "\nDeleted collection '%s'",
collectionName));
}
protected void deleteCore(CommandLine cli, SolrClient solrClient) throws
Exception {
String coreName = cli.getOptionValue(COLLECTION_NAME_OPTION);
- echo("\nDeleting core '" + coreName + "' using CoreAdminRequest\n");
+ echo("\nDeleting core '" + coreName + "' using V2 Cores API\n");
- NamedList<Object> response;
try {
- CoreAdminRequest.Unload unloadRequest = new
CoreAdminRequest.Unload(true);
- unloadRequest.setDeleteIndex(true);
- unloadRequest.setDeleteDataDir(true);
- unloadRequest.setDeleteInstanceDir(true);
- unloadRequest.setCoreName(coreName);
- unloadRequest.setResponseParser(new JsonMapResponseParser());
- response = solrClient.request(unloadRequest);
+ var req = new CoresApi.UnloadCore(coreName);
+ req.setDeleteIndex(true);
+ req.setDeleteDataDir(true);
+ req.setDeleteInstanceDir(true);
+ var response = req.process(solrClient);
+ echoIfVerbose(response);
} catch (SolrServerException sse) {
throw new Exception("Failed to delete core '" + coreName + "' due to: "
+ sse.getMessage());
}
-
- if (response != null) {
- echoIfVerbose((String) response.get("response"));
- echoIfVerbose("\n");
- }
}
}
diff --git a/solr/core/src/java/org/apache/solr/cli/StatusTool.java
b/solr/core/src/java/org/apache/solr/cli/StatusTool.java
index 1783e1bef1d..ca9391fca18 100644
--- a/solr/core/src/java/org/apache/solr/cli/StatusTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/StatusTool.java
@@ -31,10 +31,10 @@ import org.apache.commons.cli.OptionGroup;
import org.apache.commons.cli.Options;
import org.apache.solr.cli.SolrProcessManager.SolrProcess;
import org.apache.solr.client.solrj.SolrClient;
-import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.client.solrj.request.ClusterApi;
+import org.apache.solr.client.solrj.request.CollectionsApi;
import org.apache.solr.client.solrj.request.SystemInfoRequest;
import org.apache.solr.client.solrj.response.SystemInfoResponse;
-import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.URLUtil;
import org.noggit.CharArr;
import org.noggit.JSONWriter;
@@ -318,23 +318,24 @@ public class StatusTool extends ToolBase {
}
/**
- * Calls the CLUSTERSTATUS endpoint in Solr to get basic status information
about the SolrCloud
- * cluster.
+ * Calls V2 API endpoints to get basic status information about the
SolrCloud cluster.
+ *
+ * <p>Uses GET /cluster/nodes for live node count and GET /collections for
collection count.
*/
- @SuppressWarnings("unchecked")
private static Map<String, String> getCloudStatus(SolrClient solrClient,
String zkHost)
throws Exception {
Map<String, String> cloudStatus = new LinkedHashMap<>();
cloudStatus.put("ZooKeeper", (zkHost != null) ? zkHost : "?");
- // TODO add booleans to request just what we want; not everything
- NamedList<Object> json = solrClient.request(new
CollectionAdminRequest.ClusterStatus());
-
- List<String> liveNodes = (List<String>) json._get(List.of("cluster",
"live_nodes"), null);
- cloudStatus.put("liveNodes", String.valueOf(liveNodes.size()));
+ var nodesResponse = new ClusterApi.ListClusterNodes().process(solrClient);
+ var liveNodes = nodesResponse != null ? nodesResponse.nodes : null;
+ cloudStatus.put("liveNodes", String.valueOf(liveNodes != null ?
liveNodes.size() : 0));
- // TODO get this as a metric from the metrics API instead, or something
else.
- var collections = (Map<String, Object>) json._get(List.of("cluster",
"collections"), null);
+ var collectionsResponse = new
CollectionsApi.ListCollections().process(solrClient);
+ var collections =
+ collectionsResponse != null && collectionsResponse.collections != null
+ ? collectionsResponse.collections
+ : List.of();
cloudStatus.put("collections", String.valueOf(collections.size()));
return cloudStatus;
diff --git a/solr/core/src/java/org/apache/solr/cli/ToolBase.java
b/solr/core/src/java/org/apache/solr/cli/ToolBase.java
index 96ed3138ace..9d3734c3d93 100644
--- a/solr/core/src/java/org/apache/solr/cli/ToolBase.java
+++ b/solr/core/src/java/org/apache/solr/cli/ToolBase.java
@@ -17,9 +17,11 @@
package org.apache.solr.cli;
+import com.fasterxml.jackson.core.JsonProcessingException;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.OptionGroup;
import org.apache.commons.cli.Options;
+import org.apache.solr.client.solrj.request.json.JacksonContentWriter;
import org.apache.solr.util.StartupLoggingUtils;
public abstract class ToolBase implements Tool {
@@ -43,6 +45,15 @@ public abstract class ToolBase implements Tool {
}
}
+ protected void echoIfVerbose(Object response) throws JsonProcessingException
{
+ if (verbose && response != null) {
+ echo(
+ JacksonContentWriter.DEFAULT_MAPPER
+ .writerWithDefaultPrettyPrinter()
+ .writeValueAsString(response));
+ }
+ }
+
protected void echo(final String msg) {
runtime.println(msg);
}