This is an automated email from the ASF dual-hosted git repository. dsmiley pushed a commit to branch branch_10x in repository https://gitbox.apache.org/repos/asf/solr.git
commit b4fa4bd8d7c798d9c95abf98e9ad0acde7e6b9a7 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]> (cherry picked from commit 0259ab2dda3cd422246fb74801934c6bbe29ec88) --- .../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 e0b3db25d29..06ed95eae07 100644 --- a/solr/core/src/java/org/apache/solr/cli/CLIUtils.java +++ b/solr/core/src/java/org/apache/solr/cli/CLIUtils.java @@ -26,7 +26,6 @@ import java.net.URI; import java.net.URISyntaxException; import java.nio.file.Path; import java.util.Arrays; -import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; @@ -39,14 +38,13 @@ 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; import org.apache.solr.common.SolrException; import org.apache.solr.common.cloud.SolrZkClient; import org.apache.solr.common.util.EnvUtils; -import org.apache.solr.common.util.NamedList; import org.apache.solr.common.util.URLUtil; /** Utility class that holds various helper methods for the CLI. */ @@ -393,10 +391,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 9878c6b9890..6b7c6549503 100644 --- a/solr/core/src/java/org/apache/solr/cli/CreateTool.java +++ b/solr/core/src/java/org/apache/solr/cli/CreateTool.java @@ -32,19 +32,14 @@ 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.EnvUtils; -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) { @@ -287,31 +281,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 441e29cfcf3..59450cf00c1 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.CollectionsApi; import org.apache.solr.client.solrj.request.ConfigsetsApi; -import org.apache.solr.client.solrj.request.CoreAdminRequest; -import org.apache.solr.client.solrj.response.json.JsonMapResponseParser; -import org.apache.solr.common.util.NamedList; -import org.noggit.CharArr; -import org.noggit.JSONWriter; +import org.apache.solr.client.solrj.request.CoresApi; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -175,13 +171,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()); @@ -201,38 +196,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 4f3e895e7a3..27a4840a3a4 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; @@ -320,23 +320,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 5bf27b8b587..f06a6cfd088 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); }
