Skip to content

Commit c203b3f

Browse files
refactor: remove standalone Solr support, require SolrCloud (#71)
--------- Signed-off-by: adityamparikh <aditya.m.parikh@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 71e0031 commit c203b3f

3 files changed

Lines changed: 41 additions & 140 deletions

File tree

src/main/java/org/apache/solr/mcp/server/collection/CollectionService.java

Lines changed: 16 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,13 @@
3131
import org.apache.solr.client.solrj.SolrQuery;
3232
import org.apache.solr.client.solrj.SolrRequest;
3333
import org.apache.solr.client.solrj.SolrServerException;
34-
import org.apache.solr.client.solrj.impl.CloudSolrClient;
3534
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
36-
import org.apache.solr.client.solrj.request.CoreAdminRequest;
3735
import org.apache.solr.client.solrj.request.GenericSolrRequest;
3836
import org.apache.solr.client.solrj.request.LukeRequest;
3937
import org.apache.solr.client.solrj.response.CollectionAdminResponse;
40-
import org.apache.solr.client.solrj.response.CoreAdminResponse;
4138
import org.apache.solr.client.solrj.response.LukeResponse;
4239
import org.apache.solr.client.solrj.response.QueryResponse;
4340
import org.apache.solr.client.solrj.response.SolrPingResponse;
44-
import org.apache.solr.common.params.CoreAdminParams;
4541
import org.apache.solr.common.params.ModifiableSolrParams;
4642
import org.apache.solr.common.util.NamedList;
4743
import org.apache.solr.mcp.server.config.SolrConfigurationProperties;
@@ -67,8 +63,8 @@
6763
* <strong>Core Capabilities:</strong>
6864
*
6965
* <ul>
70-
* <li><strong>Collection Discovery</strong>: Lists available collections with
71-
* automatic SolrCloud vs standalone detection
66+
* <li><strong>Collection Discovery</strong>: Lists available collections using
67+
* the SolrCloud Collections API
7268
* <li><strong>Performance Monitoring</strong>: Comprehensive metrics collection
7369
* including index, query, cache, and handler statistics
7470
* <li><strong>Health Monitoring</strong>: Real-time health checks with
@@ -100,7 +96,6 @@
10096
*
10197
* <ul>
10298
* <li><strong>SolrCloud</strong>: Distributed mode using Collections API
103-
* <li><strong>Standalone</strong>: Single-node mode using Core Admin API
10499
* </ul>
105100
*
106101
* <p>
@@ -319,23 +314,16 @@ public List<String> completeCollectionForSchema() {
319314
}
320315

321316
/**
322-
* Lists all available Solr collections in the cluster.
317+
* Lists all available Solr collections in the SolrCloud cluster.
323318
*
324319
* <p>
325-
* This method automatically detects the Solr deployment type and uses the
326-
* appropriate API:
327-
*
328-
* <ul>
329-
* <li><strong>SolrCloud</strong>: Uses Collections API to list distributed
330-
* collections
331-
* <li><strong>Standalone</strong>: Uses Core Admin API to list individual
332-
* collections
333-
* </ul>
320+
* This method uses the SolrCloud Collections API to retrieve the list of
321+
* available collections. Standalone Solr instances are not supported.
334322
*
335323
* <p>
336-
* In SolrCloud environments, the returned names may include shard identifiers
337-
* (e.g., "films_shard1_replica_n1"). Use {@link #extractCollectionName(String)}
338-
* to get the base collection name if needed.
324+
* The returned names may include shard identifiers (e.g.,
325+
* "films_shard1_replica_n1"). Use {@link #extractCollectionName(String)} to get
326+
* the base collection name if needed.
339327
*
340328
* <p>
341329
* <strong>Error Handling:</strong>
@@ -356,33 +344,17 @@ public List<String> completeCollectionForSchema() {
356344
* @return a list of collection names, or an empty list if unable to retrieve
357345
* them
358346
* @see CollectionAdminRequest.List
359-
* @see CoreAdminRequest
360347
*/
361348
@McpTool(name = "list-collections", description = "List solr collections")
362349
public List<String> listCollections() {
363350
try {
364-
if (solrClient instanceof CloudSolrClient) {
365-
// For SolrCloud - use Collections API
366-
CollectionAdminRequest.List request = new CollectionAdminRequest.List();
367-
CollectionAdminResponse response = request.process(solrClient);
351+
CollectionAdminRequest.List request = new CollectionAdminRequest.List();
352+
CollectionAdminResponse response = request.process(solrClient);
368353

369-
@SuppressWarnings("unchecked")
370-
List<String> collections = (List<String>) response.getResponse().get(COLLECTIONS_KEY);
371-
return collections != null ? collections : new ArrayList<>();
372-
} else {
373-
// For standalone Solr - use Core Admin API
374-
CoreAdminRequest coreAdminRequest = new CoreAdminRequest();
375-
coreAdminRequest.setAction(CoreAdminParams.CoreAdminAction.STATUS);
376-
CoreAdminResponse coreResponse = coreAdminRequest.process(solrClient);
377-
378-
List<String> cores = new ArrayList<>();
379-
NamedList<NamedList<Object>> coreStatus = coreResponse.getCoreStatus();
380-
for (int i = 0; i < coreStatus.size(); i++) {
381-
cores.add(coreStatus.getName(i));
382-
}
383-
return cores;
384-
}
385-
} catch (SolrServerException | IOException _) {
354+
@SuppressWarnings("unchecked")
355+
List<String> collections = (List<String>) response.getResponse().get(COLLECTIONS_KEY);
356+
return collections != null ? collections : new ArrayList<>();
357+
} catch (SolrServerException | IOException e) {
386358
return new ArrayList<>();
387359
}
388360
}
@@ -952,9 +924,8 @@ String extractCollectionName(String collectionOrShard) {
952924
* </ol>
953925
*
954926
* <p>
955-
* This dual approach ensures compatibility with both standalone Solr (which
956-
* returns collection names directly) and SolrCloud (which may return shard
957-
* names).
927+
* This dual approach ensures compatibility with SolrCloud environments where
928+
* shard names may be returned alongside collection names.
958929
*
959930
* <p>
960931
* <strong>Error Handling:</strong>

src/main/java/org/apache/solr/mcp/server/config/SolrConfig.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,8 @@ public class SolrConfig {
139139
*
140140
* <p>
141141
* Creates an {@code HttpSolrClient} configured for standard HTTP-based
142-
* communication with Solr servers. This client type is suitable for both
143-
* standalone Solr instances and SolrCloud deployments when used with load
144-
* balancers.
142+
* communication with SolrCloud servers. This client type is suitable for
143+
* SolrCloud deployments when used with load balancers.
145144
*
146145
* <p>
147146
* <strong>Error Handling:</strong>

src/test/java/org/apache/solr/mcp/server/collection/CollectionServiceTest.java

Lines changed: 23 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
import org.apache.solr.client.solrj.SolrClient;
3131
import org.apache.solr.client.solrj.SolrRequest;
3232
import org.apache.solr.client.solrj.SolrServerException;
33-
import org.apache.solr.client.solrj.impl.CloudSolrClient;
3433
import org.apache.solr.client.solrj.response.LukeResponse;
3534
import org.apache.solr.client.solrj.response.QueryResponse;
3635
import org.apache.solr.client.solrj.response.SolrPingResponse;
@@ -48,9 +47,6 @@ class CollectionServiceTest {
4847
@Mock
4948
private SolrClient solrClient;
5049

51-
@Mock
52-
private CloudSolrClient cloudSolrClient;
53-
5450
@Mock
5551
private QueryResponse queryResponse;
5652

@@ -75,32 +71,17 @@ void constructor_ShouldInitializeWithSolrClient() {
7571
assertNotNull(collectionService);
7672
}
7773

78-
@Test
79-
void listCollections_WithCloudSolrClient_ShouldReturnCollections() throws Exception {
80-
// Given - This test verifies the service can be constructed with
81-
// CloudSolrClient
82-
CollectionService cloudService = new CollectionService(cloudSolrClient, objectMapper);
83-
84-
// Note: This test cannot fully exercise listCollections() because it requires
85-
// mocking static methods in CollectionAdminRequest which requires PowerMock or
86-
// Mockito inline. The actual behavior is tested in integration tests.
87-
88-
// When/Then - Verify service construction
89-
assertNotNull(cloudService, "Should be able to construct service with CloudSolrClient");
90-
}
91-
9274
@Test
9375
void listCollections_WhenExceptionOccurs_ShouldReturnEmptyList() throws Exception {
94-
// Note: This test cannot fully exercise listCollections() with mock SolrClient
95-
// because it requires mocking CoreAdminRequest processing. The actual error
96-
// handling behavior is tested in integration tests.
76+
// Given - mock throws exception
77+
when(solrClient.request(any(), any())).thenThrow(new SolrServerException("Connection error"));
9778

98-
// Given/When - Using regular SolrClient (non-cloud) which will attempt
99-
// CoreAdmin
100-
// The mock doesn't have a real CoreAdmin implementation
79+
// When
80+
List<String> result = collectionService.listCollections();
10181

102-
// Then - Verify service is constructed
103-
assertNotNull(collectionService, "Service should be constructed successfully");
82+
// Then
83+
assertNotNull(result);
84+
assertTrue(result.isEmpty());
10485
}
10586

10687
// Collection name extraction tests
@@ -697,16 +678,13 @@ void isHandlerStatsEmpty() throws Exception {
697678

698679
// List collections tests
699680
@Test
700-
void listCollections_CloudClient_Success() throws Exception {
701-
CloudSolrClient cloudClient = mock(CloudSolrClient.class);
702-
681+
void listCollections_Success() throws Exception {
703682
NamedList<Object> response = new NamedList<>();
704683
response.add("collections", Arrays.asList("collection1", "collection2"));
705684

706-
when(cloudClient.request(any(), any())).thenReturn(response);
685+
when(solrClient.request(any(), any())).thenReturn(response);
707686

708-
CollectionService service = new CollectionService(cloudClient, objectMapper);
709-
List<String> result = service.listCollections();
687+
List<String> result = collectionService.listCollections();
710688

711689
assertNotNull(result);
712690
assertEquals(2, result.size());
@@ -715,64 +693,33 @@ void listCollections_CloudClient_Success() throws Exception {
715693
}
716694

717695
@Test
718-
void listCollections_CloudClient_NullCollections() throws Exception {
719-
CloudSolrClient cloudClient = mock(CloudSolrClient.class);
720-
696+
void listCollections_NullCollections() throws Exception {
721697
NamedList<Object> response = new NamedList<>();
722698
response.add("collections", null);
723699

724-
when(cloudClient.request(any(), any())).thenReturn(response);
700+
when(solrClient.request(any(), any())).thenReturn(response);
725701

726-
CollectionService service = new CollectionService(cloudClient, objectMapper);
727-
List<String> result = service.listCollections();
702+
List<String> result = collectionService.listCollections();
728703

729704
assertNotNull(result);
730705
assertTrue(result.isEmpty());
731706
}
732707

733708
@Test
734-
void listCollections_CloudClient_Error() throws Exception {
735-
CloudSolrClient cloudClient = mock(CloudSolrClient.class);
736-
when(cloudClient.request(any(), any())).thenThrow(new SolrServerException("Connection error"));
709+
void listCollections_Error() throws Exception {
710+
when(solrClient.request(any(), any())).thenThrow(new SolrServerException("Connection error"));
737711

738-
CollectionService service = new CollectionService(cloudClient, objectMapper);
739-
List<String> result = service.listCollections();
712+
List<String> result = collectionService.listCollections();
740713

741714
assertNotNull(result);
742715
assertTrue(result.isEmpty());
743716
}
744717

745718
@Test
746-
void listCollections_NonCloudClient_Success() throws Exception {
747-
// Create a NamedList to represent the core status response
748-
NamedList<Object> response = new NamedList<>();
749-
NamedList<Object> status = new NamedList<>();
750-
751-
NamedList<Object> core1Status = new NamedList<>();
752-
NamedList<Object> core2Status = new NamedList<>();
753-
754-
status.add("core1", core1Status);
755-
status.add("core2", core2Status);
756-
response.add("status", status);
757-
758-
// Mock the solrClient request to return the response
759-
when(solrClient.request(any(), any())).thenReturn(response);
760-
761-
CollectionService service = new CollectionService(solrClient, objectMapper);
762-
List<String> result = service.listCollections();
763-
764-
assertNotNull(result);
765-
assertEquals(2, result.size());
766-
assertTrue(result.contains("core1"));
767-
assertTrue(result.contains("core2"));
768-
}
769-
770-
@Test
771-
void listCollections_NonCloudClient_Error() throws Exception {
719+
void listCollections_IOError() throws Exception {
772720
when(solrClient.request(any(), any())).thenThrow(new IOException("IO error"));
773721

774-
CollectionService service = new CollectionService(solrClient, objectMapper);
775-
List<String> result = service.listCollections();
722+
List<String> result = collectionService.listCollections();
776723

777724
assertNotNull(result);
778725
assertTrue(result.isEmpty());
@@ -897,38 +844,22 @@ private NamedList<Object> createCompleteHandlerData() {
897844

898845
// createCollection tests
899846
@Test
900-
void createCollection_success_cloudClient() throws Exception {
901-
CloudSolrClient cloudClient = mock(CloudSolrClient.class);
902-
when(cloudClient.request(any(), any())).thenReturn(new NamedList<>());
903-
904-
CollectionService service = new CollectionService(cloudClient, objectMapper);
905-
CollectionCreationResult result = service.createCollection("new_collection", "_default", 1, 1);
906-
907-
assertNotNull(result);
908-
assertTrue(result.success());
909-
assertEquals("new_collection", result.name());
910-
assertNotNull(result.createdAt());
911-
}
912-
913-
@Test
914-
void createCollection_success_standaloneClient() throws Exception {
847+
void createCollection_success() throws Exception {
915848
when(solrClient.request(any(), isNull())).thenReturn(new NamedList<>());
916849

917-
CollectionCreationResult result = collectionService.createCollection("new_core", null, null, null);
850+
CollectionCreationResult result = collectionService.createCollection("new_collection", "_default", 1, 1);
918851

919852
assertNotNull(result);
920853
assertTrue(result.success());
921-
assertEquals("new_core", result.name());
854+
assertEquals("new_collection", result.name());
922855
assertNotNull(result.createdAt());
923856
}
924857

925858
@Test
926859
void createCollection_defaultsApplied() throws Exception {
927-
CloudSolrClient cloudClient = mock(CloudSolrClient.class);
928-
when(cloudClient.request(any(), any())).thenReturn(new NamedList<>());
860+
when(solrClient.request(any(), isNull())).thenReturn(new NamedList<>());
929861

930-
CollectionService service = new CollectionService(cloudClient, objectMapper);
931-
CollectionCreationResult result = service.createCollection("defaults_collection", null, null, null);
862+
CollectionCreationResult result = collectionService.createCollection("defaults_collection", null, null, null);
932863

933864
assertTrue(result.success());
934865
assertEquals("defaults_collection", result.name());

0 commit comments

Comments
 (0)