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 000000000000..4141e8ae8258 --- /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 000000000000..bd7a0195ae1d --- /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 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 1c05377e889e..5653bffa8970 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.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.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.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 static boolean safeCheckCollectionExists( String solrUrl, String collection, String credentials) { boolean exists = false; try (var solrClient = getSolrClient(solrUrl, credentials)) { - NamedList existsCheckResult = solrClient.request(new CollectionAdminRequest.List()); - @SuppressWarnings("unchecked") - List collections = (List) 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 33ab28ef383c..74778e7e4904 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.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 @@ protected void createCore(CommandLine cli, SolrClient solrClient) throws Excepti + 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 @@ protected void createCollection(CloudSolrClient cloudSolrClient, CommandLine cli 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 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 2610e2af18d2..77db2184a4cb 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.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 @@ protected void deleteCollection(CloudSolrClient cloudSolrClient, CommandLine cli } } - echoIfVerbose("\nDeleting collection '" + collectionName + "' using CollectionAdminRequest"); + echoIfVerbose("\nDeleting collection '" + collectionName + "' using V2 Collections API"); - NamedList 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 @@ protected void deleteCollection(CloudSolrClient cloudSolrClient, CommandLine cli } } - 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 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 1783e1bef1d3..ca9391fca18b 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.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 static Map reportStatus(SolrClient solrClient) throws Exc } /** - * 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. + * + *

Uses GET /cluster/nodes for live node count and GET /collections for collection count. */ - @SuppressWarnings("unchecked") private static Map getCloudStatus(SolrClient solrClient, String zkHost) throws Exception { Map cloudStatus = new LinkedHashMap<>(); cloudStatus.put("ZooKeeper", (zkHost != null) ? zkHost : "?"); - // TODO add booleans to request just what we want; not everything - NamedList json = solrClient.request(new CollectionAdminRequest.ClusterStatus()); - - List liveNodes = (List) 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) 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 96ed3138ace6..9d3734c3d931 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 @@ protected void echoIfVerbose(final String msg) { } } + 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); }