diff --git a/solr/api/src/java/org/apache/solr/client/api/util/Constants.java b/solr/api/src/java/org/apache/solr/client/api/util/Constants.java index 3e3c7443c18f..13f8009fbb41 100644 --- a/solr/api/src/java/org/apache/solr/client/api/util/Constants.java +++ b/solr/api/src/java/org/apache/solr/client/api/util/Constants.java @@ -24,8 +24,18 @@ private Constants() { public static final String INDEX_TYPE_PATH_PARAMETER = "indexType"; public static final String INDEX_NAME_PATH_PARAMETER = "indexName"; + + // "c" is accepted alongside "collections" because Solr internally forwards a v2 request to the + // node actually hosting a collection using the short "/c/{collection}/..." alias (see + // HttpSolrCall#sendRemoteProxy and V2HttpCall's "c"/"collections" handling); without it, any + // JAX-RS resource built on this prefix 404s with "Cannot find API for the path" whenever the + // request has to be proxied to another node. public static final String INDEX_PATH_PREFIX = - "/{" + INDEX_TYPE_PATH_PARAMETER + ":cores|collections}/{" + INDEX_NAME_PATH_PARAMETER + "}"; + "/{" + + INDEX_TYPE_PATH_PARAMETER + + ":cores|collections|c}/{" + + INDEX_NAME_PATH_PARAMETER + + "}"; public static final String CORE_NAME_PATH_PARAMETER = "coreName"; diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/GetConfigSolrCloudTest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/GetConfigSolrCloudTest.java index 19b29380afd8..dd51d21ddeaf 100644 --- a/solr/core/src/test/org/apache/solr/handler/admin/api/GetConfigSolrCloudTest.java +++ b/solr/core/src/test/org/apache/solr/handler/admin/api/GetConfigSolrCloudTest.java @@ -19,9 +19,12 @@ import static org.apache.solr.core.CoreContainer.ALLOW_PATHS_SYSPROP; import org.apache.solr.client.api.model.IndexType; +import org.apache.solr.client.solrj.jetty.HttpJettySolrClient; import org.apache.solr.client.solrj.request.CollectionAdminRequest; import org.apache.solr.client.solrj.request.ConfigApi; import org.apache.solr.cloud.SolrCloudTestCase; +import org.apache.solr.common.cloud.Replica; +import org.apache.solr.embedded.JettySolrRunner; import org.apache.solr.util.ExternalPaths; import org.junit.BeforeClass; import org.junit.Test; @@ -34,7 +37,9 @@ public class GetConfigSolrCloudTest extends SolrCloudTestCase { @BeforeClass public static void setupCluster() throws Exception { System.setProperty(ALLOW_PATHS_SYSPROP, ExternalPaths.SERVER_HOME.toAbsolutePath().toString()); - configureCluster(1).addConfig("conf", configset("cloud-minimal")).configure(); + // 2 nodes so that a request can land on a node that does *not* host the collection's only + // replica, forcing Solr to internally proxy the request to the node that does. + configureCluster(2).addConfig("conf", configset("cloud-minimal")).configure(); CollectionAdminRequest.createCollection(COLLECTION_NAME, "conf", 1, 1) .process(cluster.getSolrClient()); } @@ -77,6 +82,27 @@ public void testGetZnodeVersionFromCollection() throws Exception { assertTrue(response.znodeVersion.params >= -1); } + @Test + public void testGetOverlayFromCollectionViaNonHostingNode() throws Exception { + Replica replica = getCollectionState(COLLECTION_NAME).replicaStream().findFirst().orElseThrow(); + JettySolrRunner hostingJetty = cluster.getReplicaJetty(replica); + JettySolrRunner otherJetty = + cluster.getJettySolrRunners().stream() + .filter(jetty -> !jetty.equals(hostingJetty)) + .findFirst() + .orElseThrow(); + + try (var client = new HttpJettySolrClient.Builder(otherJetty.getBaseUrl().toString()).build()) { + var request = new ConfigApi.GetOverlay(IndexType.COLLECTION, COLLECTION_NAME); + var response = request.process(client); + + assertNotNull(response); + assertNull(response.error); + assertNotNull(response.overlay); + assertTrue(response.overlay.containsKey("znodeVersion")); + } + } + @Test public void testGetConfigComponentFromCollection() throws Exception { for (String component : GetConfig.MIGRATED_CONFIG_COMPONENTS) {