Skip to content

Commit 0259ab2

Browse files
epughCopilot
andauthored
Migrate org.apache.solr.cli tools from V1 to V2 APIs (#4154)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: epugh <22395+epugh@users.noreply.github.com>
1 parent 8c64932 commit 0259ab2

7 files changed

Lines changed: 125 additions & 80 deletions

File tree

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.solr.client.api.endpoint;
18+
19+
import io.swagger.v3.oas.annotations.Operation;
20+
import jakarta.ws.rs.GET;
21+
import jakarta.ws.rs.Path;
22+
import org.apache.solr.client.api.model.ListClusterNodesResponse;
23+
24+
/** V2 API definition for listing the nodes in the SolrCloud cluster. */
25+
@Path("/cluster/nodes")
26+
public interface ListClusterNodesApi {
27+
28+
@GET
29+
@Operation(
30+
summary = "List the nodes in this Solr cluster.",
31+
tags = {"cluster"})
32+
ListClusterNodesResponse listClusterNodes();
33+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.solr.client.api.model;
18+
19+
import com.fasterxml.jackson.annotation.JsonProperty;
20+
import io.swagger.v3.oas.annotations.media.Schema;
21+
import java.util.Set;
22+
23+
/**
24+
* Response for the v2 "list cluster nodes" API. This is a bit unusual that it's wrapping a non
25+
* JAX-RS V2 API defined in org.apache.solr.handler.ClusterAPI.getNodes(). The calls are made using
26+
* just the defaults. TODO: Update this when we migrate ClusterAPI to JAX-RS.
27+
*/
28+
public class ListClusterNodesResponse extends SolrJerseyResponse {
29+
30+
@Schema(description = "The live nodes in the cluster.")
31+
@JsonProperty("nodes")
32+
public Set<String> nodes;
33+
}

solr/core/src/java/org/apache/solr/cli/CLIUtils.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
import java.nio.file.Path;
2828
import java.util.Arrays;
2929
import java.util.Collections;
30-
import java.util.List;
3130
import java.util.Locale;
3231
import java.util.Map;
3332
import java.util.Optional;
@@ -41,15 +40,14 @@
4140
import org.apache.solr.client.solrj.impl.CloudSolrClient;
4241
import org.apache.solr.client.solrj.impl.SolrZkClientTimeout;
4342
import org.apache.solr.client.solrj.jetty.HttpJettySolrClient;
44-
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
43+
import org.apache.solr.client.solrj.request.CollectionsApi;
4544
import org.apache.solr.client.solrj.request.CoresApi;
4645
import org.apache.solr.client.solrj.request.SystemInfoRequest;
4746
import org.apache.solr.client.solrj.response.SystemInfoResponse;
4847
import org.apache.solr.common.SolrException;
4948
import org.apache.solr.common.cloud.SolrZkClient;
5049
import org.apache.solr.common.cloud.ZkStateReader;
5150
import org.apache.solr.common.util.EnvUtils;
52-
import org.apache.solr.common.util.NamedList;
5351

5452
/** Utility class that holds various helper methods for the CLI. */
5553
public final class CLIUtils {
@@ -311,10 +309,11 @@ public static boolean safeCheckCollectionExists(
311309
String solrUrl, String collection, String credentials) {
312310
boolean exists = false;
313311
try (var solrClient = getSolrClient(solrUrl, credentials)) {
314-
NamedList<Object> existsCheckResult = solrClient.request(new CollectionAdminRequest.List());
315-
@SuppressWarnings("unchecked")
316-
List<String> collections = (List<String>) existsCheckResult.get("collections");
317-
exists = collections != null && collections.contains(collection);
312+
var response = new CollectionsApi.ListCollections().process(solrClient);
313+
exists =
314+
response != null
315+
&& response.collections != null
316+
&& response.collections.contains(collection);
318317
} catch (Exception exc) {
319318
// just ignore it since we're only interested in a positive result here
320319
}

solr/core/src/java/org/apache/solr/cli/CreateTool.java

Lines changed: 16 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,13 @@
3232
import org.apache.solr.client.solrj.SolrServerException;
3333
import org.apache.solr.client.solrj.impl.CloudSolrClient;
3434
import org.apache.solr.client.solrj.jetty.HttpJettySolrClient;
35-
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
36-
import org.apache.solr.client.solrj.request.CoreAdminRequest;
35+
import org.apache.solr.client.solrj.request.CollectionsApi;
36+
import org.apache.solr.client.solrj.request.CoresApi;
3737
import org.apache.solr.client.solrj.request.SystemInfoRequest;
38-
import org.apache.solr.client.solrj.response.CoreAdminResponse;
3938
import org.apache.solr.client.solrj.response.SystemInfoResponse;
40-
import org.apache.solr.client.solrj.response.json.JsonMapResponseParser;
4139
import org.apache.solr.cloud.ZkConfigSetService;
4240
import org.apache.solr.common.cloud.ZkStateReader;
43-
import org.apache.solr.common.util.NamedList;
4441
import org.apache.solr.core.ConfigSetService;
45-
import org.noggit.CharArr;
46-
import org.noggit.JSONWriter;
4742

4843
/** Supports create command in the bin/solr script. */
4944
public class CreateTool extends ToolBase {
@@ -182,14 +177,13 @@ protected void createCore(CommandLine cli, SolrClient solrClient) throws Excepti
182177
+ coreInstanceDir.toAbsolutePath());
183178
}
184179

185-
echoIfVerbose("\nCreating new core '" + coreName + "' using CoreAdminRequest");
180+
echoIfVerbose("\nCreating new core '" + coreName + "' using V2 Cores API");
186181

187182
try {
188-
CoreAdminResponse res = CoreAdminRequest.createCore(coreName, coreName, solrClient);
189-
if (isVerbose()) {
190-
echo(res.jsonStr());
191-
echo("\n");
192-
}
183+
var req = new CoresApi.CreateCore();
184+
req.setName(coreName);
185+
req.setInstanceDir(coreName);
186+
req.process(solrClient);
193187
echo(String.format(Locale.ROOT, "\nCreated new core '%s'", coreName));
194188

195189
} catch (Exception e) {
@@ -277,31 +271,25 @@ protected void createCollection(CloudSolrClient cloudSolrClient, CommandLine cli
277271
throw new IllegalStateException(
278272
"\nCollection '"
279273
+ collectionName
280-
+ "' already exists!\nChecked collection existence using CollectionAdminRequest");
274+
+ "' already exists!\nChecked collection existence using V2 Collections API");
281275
}
282276

283277
// doesn't seem to exist ... try to create
284-
echoIfVerbose(
285-
"\nCreating new collection '" + collectionName + "' using CollectionAdminRequest");
278+
echoIfVerbose("\nCreating new collection '" + collectionName + "' using V2 Collections API");
286279

287-
NamedList<Object> response;
288280
try {
289-
var req =
290-
CollectionAdminRequest.createCollection(
291-
collectionName, confName, numShards, replicationFactor);
292-
req.setResponseParser(new JsonMapResponseParser());
293-
response = cloudSolrClient.request(req);
281+
var req = new CollectionsApi.CreateCollection();
282+
req.setName(collectionName);
283+
req.setConfig(confName);
284+
req.setNumShards(numShards);
285+
req.setReplicationFactor(replicationFactor);
286+
var response = req.process(cloudSolrClient);
287+
echoIfVerbose(response);
294288
} catch (SolrServerException sse) {
295289
throw new Exception(
296290
"Failed to create collection '" + collectionName + "' due to: " + sse.getMessage());
297291
}
298292

299-
if (isVerbose()) {
300-
// pretty-print the response to stdout
301-
CharArr arr = new CharArr();
302-
new JSONWriter(arr, 2).write(response.asMap(10));
303-
echo(arr.toString());
304-
}
305293
String endMessage =
306294
String.format(
307295
Locale.ROOT,

solr/core/src/java/org/apache/solr/cli/DeleteTool.java

Lines changed: 13 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,9 @@
2929
import org.apache.solr.client.solrj.SolrServerException;
3030
import org.apache.solr.client.solrj.impl.CloudSolrClient;
3131
import org.apache.solr.client.solrj.jetty.HttpJettySolrClient;
32-
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
33-
import org.apache.solr.client.solrj.request.CoreAdminRequest;
34-
import org.apache.solr.client.solrj.response.json.JsonMapResponseParser;
32+
import org.apache.solr.client.solrj.request.CollectionsApi;
33+
import org.apache.solr.client.solrj.request.CoresApi;
3534
import org.apache.solr.common.cloud.ZkStateReader;
36-
import org.apache.solr.common.util.NamedList;
37-
import org.noggit.CharArr;
38-
import org.noggit.JSONWriter;
3935
import org.slf4j.Logger;
4036
import org.slf4j.LoggerFactory;
4137

@@ -176,13 +172,12 @@ protected void deleteCollection(CloudSolrClient cloudSolrClient, CommandLine cli
176172
}
177173
}
178174

179-
echoIfVerbose("\nDeleting collection '" + collectionName + "' using CollectionAdminRequest");
175+
echoIfVerbose("\nDeleting collection '" + collectionName + "' using V2 Collections API");
180176

181-
NamedList<Object> response;
182177
try {
183-
var req = CollectionAdminRequest.deleteCollection(collectionName);
184-
req.setResponseParser(new JsonMapResponseParser());
185-
response = cloudSolrClient.request(req);
178+
var req = new CollectionsApi.DeleteCollection(collectionName);
179+
var response = req.process(cloudSolrClient);
180+
echoIfVerbose(response);
186181
} catch (SolrServerException sse) {
187182
throw new Exception(
188183
"Failed to delete collection '" + collectionName + "' due to: " + sse.getMessage());
@@ -202,38 +197,23 @@ protected void deleteCollection(CloudSolrClient cloudSolrClient, CommandLine cli
202197
}
203198
}
204199

205-
if (isVerbose() && response != null) {
206-
// pretty-print the response to stdout
207-
CharArr arr = new CharArr();
208-
new JSONWriter(arr, 2).write(response.asMap(10));
209-
echo(arr.toString());
210-
echo("\n");
211-
}
212-
213200
echo(String.format(Locale.ROOT, "\nDeleted collection '%s'", collectionName));
214201
}
215202

216203
protected void deleteCore(CommandLine cli, SolrClient solrClient) throws Exception {
217204
String coreName = cli.getOptionValue(COLLECTION_NAME_OPTION);
218205

219-
echo("\nDeleting core '" + coreName + "' using CoreAdminRequest\n");
206+
echo("\nDeleting core '" + coreName + "' using V2 Cores API\n");
220207

221-
NamedList<Object> response;
222208
try {
223-
CoreAdminRequest.Unload unloadRequest = new CoreAdminRequest.Unload(true);
224-
unloadRequest.setDeleteIndex(true);
225-
unloadRequest.setDeleteDataDir(true);
226-
unloadRequest.setDeleteInstanceDir(true);
227-
unloadRequest.setCoreName(coreName);
228-
unloadRequest.setResponseParser(new JsonMapResponseParser());
229-
response = solrClient.request(unloadRequest);
209+
var req = new CoresApi.UnloadCore(coreName);
210+
req.setDeleteIndex(true);
211+
req.setDeleteDataDir(true);
212+
req.setDeleteInstanceDir(true);
213+
var response = req.process(solrClient);
214+
echoIfVerbose(response);
230215
} catch (SolrServerException sse) {
231216
throw new Exception("Failed to delete core '" + coreName + "' due to: " + sse.getMessage());
232217
}
233-
234-
if (response != null) {
235-
echoIfVerbose((String) response.get("response"));
236-
echoIfVerbose("\n");
237-
}
238218
}
239219
}

solr/core/src/java/org/apache/solr/cli/StatusTool.java

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@
3131
import org.apache.commons.cli.Options;
3232
import org.apache.solr.cli.SolrProcessManager.SolrProcess;
3333
import org.apache.solr.client.solrj.SolrClient;
34-
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
34+
import org.apache.solr.client.solrj.request.ClusterApi;
35+
import org.apache.solr.client.solrj.request.CollectionsApi;
3536
import org.apache.solr.client.solrj.request.SystemInfoRequest;
3637
import org.apache.solr.client.solrj.response.SystemInfoResponse;
37-
import org.apache.solr.common.util.NamedList;
3838
import org.apache.solr.common.util.URLUtil;
3939
import org.noggit.CharArr;
4040
import org.noggit.JSONWriter;
@@ -318,23 +318,24 @@ public static Map<String, Object> reportStatus(SolrClient solrClient) throws Exc
318318
}
319319

320320
/**
321-
* Calls the CLUSTERSTATUS endpoint in Solr to get basic status information about the SolrCloud
322-
* cluster.
321+
* Calls V2 API endpoints to get basic status information about the SolrCloud cluster.
322+
*
323+
* <p>Uses GET /cluster/nodes for live node count and GET /collections for collection count.
323324
*/
324-
@SuppressWarnings("unchecked")
325325
private static Map<String, String> getCloudStatus(SolrClient solrClient, String zkHost)
326326
throws Exception {
327327
Map<String, String> cloudStatus = new LinkedHashMap<>();
328328
cloudStatus.put("ZooKeeper", (zkHost != null) ? zkHost : "?");
329329

330-
// TODO add booleans to request just what we want; not everything
331-
NamedList<Object> json = solrClient.request(new CollectionAdminRequest.ClusterStatus());
332-
333-
List<String> liveNodes = (List<String>) json._get(List.of("cluster", "live_nodes"), null);
334-
cloudStatus.put("liveNodes", String.valueOf(liveNodes.size()));
330+
var nodesResponse = new ClusterApi.ListClusterNodes().process(solrClient);
331+
var liveNodes = nodesResponse != null ? nodesResponse.nodes : null;
332+
cloudStatus.put("liveNodes", String.valueOf(liveNodes != null ? liveNodes.size() : 0));
335333

336-
// TODO get this as a metric from the metrics API instead, or something else.
337-
var collections = (Map<String, Object>) json._get(List.of("cluster", "collections"), null);
334+
var collectionsResponse = new CollectionsApi.ListCollections().process(solrClient);
335+
var collections =
336+
collectionsResponse != null && collectionsResponse.collections != null
337+
? collectionsResponse.collections
338+
: List.of();
338339
cloudStatus.put("collections", String.valueOf(collections.size()));
339340

340341
return cloudStatus;

solr/core/src/java/org/apache/solr/cli/ToolBase.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@
1717

1818
package org.apache.solr.cli;
1919

20+
import com.fasterxml.jackson.core.JsonProcessingException;
2021
import org.apache.commons.cli.CommandLine;
2122
import org.apache.commons.cli.OptionGroup;
2223
import org.apache.commons.cli.Options;
24+
import org.apache.solr.client.solrj.request.json.JacksonContentWriter;
2325
import org.apache.solr.util.StartupLoggingUtils;
2426

2527
public abstract class ToolBase implements Tool {
@@ -43,6 +45,15 @@ protected void echoIfVerbose(final String msg) {
4345
}
4446
}
4547

48+
protected void echoIfVerbose(Object response) throws JsonProcessingException {
49+
if (verbose && response != null) {
50+
echo(
51+
JacksonContentWriter.DEFAULT_MAPPER
52+
.writerWithDefaultPrettyPrinter()
53+
.writeValueAsString(response));
54+
}
55+
}
56+
4657
protected void echo(final String msg) {
4758
runtime.println(msg);
4859
}

0 commit comments

Comments
 (0)