diff --git a/solr/core/src/java/org/apache/solr/cloud/ZkConfigSetService.java b/solr/core/src/java/org/apache/solr/cloud/ZkConfigSetService.java index 5f83f88c8eb..07f310c696e 100644 --- a/solr/core/src/java/org/apache/solr/cloud/ZkConfigSetService.java +++ b/solr/core/src/java/org/apache/solr/cloud/ZkConfigSetService.java @@ -66,6 +66,20 @@ public ZkConfigSetService(SolrZkClient zkClient) { this.zkClient = zkClient; } + /** + * Do not perform Zk operations if configSetName is provided. + * + *

This is only used by {@link org.apache.solr.core.SyntheticSolrCore}, which is not registered + * with Zookeeper + */ + @Override + protected SolrResourceLoader createCoreResourceLoader(CoreDescriptor cd, String configSetName) { + return configSetName != null + ? new ZkSolrResourceLoader( + cd.getInstanceDir(), configSetName, parentLoader.getClassLoader(), zkController) + : createCoreResourceLoader(cd); + } + @Override public SolrResourceLoader createCoreResourceLoader(CoreDescriptor cd) { final String colName = cd.getCollectionName(); diff --git a/solr/core/src/java/org/apache/solr/core/ConfigSetService.java b/solr/core/src/java/org/apache/solr/core/ConfigSetService.java index 32432d9f18b..c6d333f7d21 100644 --- a/solr/core/src/java/org/apache/solr/core/ConfigSetService.java +++ b/solr/core/src/java/org/apache/solr/core/ConfigSetService.java @@ -251,8 +251,19 @@ public boolean isConfigSetTrusted(SolrResourceLoader coreLoader) throws IOExcept * @return a ConfigSet */ public final ConfigSet loadConfigSet(CoreDescriptor dcore) { + return loadConfigSet(dcore, null); + } + + /** + * Load the ConfigSet for a core with an explicit config set name + * + * @param dcore the core's CoreDescriptor + * @param configSetName an optional and explicit config set name + * @return a ConfigSet + */ + final ConfigSet loadConfigSet(CoreDescriptor dcore, String configSetName) { - SolrResourceLoader coreLoader = createCoreResourceLoader(dcore); + SolrResourceLoader coreLoader = createCoreResourceLoader(dcore, configSetName); try { // ConfigSet properties are loaded from ConfigSetProperties.DEFAULT_FILENAME file. @@ -391,6 +402,20 @@ protected NamedList loadConfigSetFlags(SolrResourceLoader loader) throws */ protected abstract SolrResourceLoader createCoreResourceLoader(CoreDescriptor cd); + /** + * Create a SolrResourceLoader for a core with the provided configSetName. + * + *

By default, this will just call {@link + * ConfigSetService#createConfigSetService(CoreContainer)}. Child implementation might override + * this to make use of the configSetName directly + * + * @param cd the core's CoreDescriptor + * @param configSetName an optional config set name + * @return a SolrResourceLoader + */ + protected SolrResourceLoader createCoreResourceLoader(CoreDescriptor cd, String configSetName) { + return createCoreResourceLoader(cd); + } /** * Return a name for the ConfigSet for a core to be used for printing/diagnostic purposes. * diff --git a/solr/core/src/java/org/apache/solr/core/SolrCore.java b/solr/core/src/java/org/apache/solr/core/SolrCore.java index cccc0cba005..351aafbf729 100644 --- a/solr/core/src/java/org/apache/solr/core/SolrCore.java +++ b/solr/core/src/java/org/apache/solr/core/SolrCore.java @@ -1255,7 +1255,7 @@ public void seedVersionBuckets() { } /** Set UpdateLog to buffer updates if the slice is in construction. */ - private void bufferUpdatesIfConstructing(CoreDescriptor coreDescriptor) { + protected void bufferUpdatesIfConstructing(CoreDescriptor coreDescriptor) { if (coreContainer != null && coreContainer.isZooKeeperAware()) { if (reqHandlers.get("/get") == null) { diff --git a/solr/core/src/java/org/apache/solr/core/SyntheticSolrCore.java b/solr/core/src/java/org/apache/solr/core/SyntheticSolrCore.java new file mode 100644 index 00000000000..b93ddb45455 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/core/SyntheticSolrCore.java @@ -0,0 +1,66 @@ +package org.apache.solr.core; + +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; +import org.apache.solr.common.SolrException; +import org.apache.solr.common.params.CoreAdminParams; +import org.apache.solr.rest.RestManager; + +/** + * A synthetic core that is created only in memory and not registered against Zookeeper. + * + *

This is only used in Coordinator node to support a subset of SolrCore functionalities required + * by Coordinator operations such as aggregating and writing out response and providing configset + * info. + * + *

There should only be one instance of SyntheticSolrCore per configset + */ +public class SyntheticSolrCore extends SolrCore { + public SyntheticSolrCore(CoreContainer coreContainer, CoreDescriptor cd, ConfigSet configSet) { + super(coreContainer, cd, configSet); + } + + public static SyntheticSolrCore createAndRegisterCore( + CoreContainer coreContainer, String syntheticCoreName, String configSetName) { + Map coreProps = new HashMap<>(); + coreProps.put(CoreAdminParams.CORE_NODE_NAME, coreContainer.getHostName()); + coreProps.put(CoreAdminParams.COLLECTION, syntheticCoreName); + + CoreDescriptor syntheticCoreDescriptor = + new CoreDescriptor( + syntheticCoreName, + Paths.get(coreContainer.getSolrHome() + "/" + syntheticCoreName), + coreProps, + coreContainer.getContainerProperties(), + coreContainer.getZkController()); + + ConfigSet coreConfig = + coreContainer.getConfigSetService().loadConfigSet(syntheticCoreDescriptor, configSetName); + syntheticCoreDescriptor.setConfigSetTrusted(coreConfig.isTrusted()); + SyntheticSolrCore syntheticCore = + new SyntheticSolrCore(coreContainer, syntheticCoreDescriptor, coreConfig); + coreContainer.registerCore(syntheticCoreDescriptor, syntheticCore, false, false); + + return syntheticCore; + } + + @Override + protected void bufferUpdatesIfConstructing(CoreDescriptor coreDescriptor) { + // no updates to SyntheticSolrCore + } + + @Override + protected RestManager initRestManager() throws SolrException { + // returns an initialized RestManager. As init routines requires reading configname of the + // core's collection from ZK + // which synthetic core is not registered in ZK. + // We do not expect RestManager ops on Coordinator Nodes + return new RestManager(); + } + + @Override + public void close() { + super.close(); + } +} diff --git a/solr/core/src/java/org/apache/solr/servlet/CoordinatorHttpSolrCall.java b/solr/core/src/java/org/apache/solr/servlet/CoordinatorHttpSolrCall.java index 82664b23623..0ec74206c68 100644 --- a/solr/core/src/java/org/apache/solr/servlet/CoordinatorHttpSolrCall.java +++ b/solr/core/src/java/org/apache/solr/servlet/CoordinatorHttpSolrCall.java @@ -18,32 +18,25 @@ package org.apache.solr.servlet; import java.lang.invoke.MethodHandles; -import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.solr.api.CoordinatorV2HttpSolrCall; -import org.apache.solr.client.solrj.request.CollectionAdminRequest; import org.apache.solr.cloud.CloudDescriptor; import org.apache.solr.cloud.api.collections.Assign; -import org.apache.solr.common.SolrException; import org.apache.solr.common.cloud.ClusterState; import org.apache.solr.common.cloud.DocCollection; import org.apache.solr.common.cloud.Replica; import org.apache.solr.common.cloud.ZkStateReader; -import org.apache.solr.common.params.SolrParams; -import org.apache.solr.common.util.Utils; import org.apache.solr.core.CoreContainer; import org.apache.solr.core.CoreDescriptor; import org.apache.solr.core.SolrCore; +import org.apache.solr.core.SyntheticSolrCore; import org.apache.solr.logging.MDCLoggingContext; import org.apache.solr.request.DelegatingSolrQueryRequest; -import org.apache.solr.request.LocalSolrQueryRequest; import org.apache.solr.request.SolrQueryRequest; -import org.apache.solr.response.SolrQueryResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -82,123 +75,59 @@ public static SolrCore getCore( setMDCLoggingContext(collectionName); return syntheticCore; } else { + // first time loading this collection ZkStateReader zkStateReader = solrCall.cores.getZkController().getZkStateReader(); ClusterState clusterState = zkStateReader.getClusterState(); DocCollection coll = clusterState.getCollectionOrNull(collectionName, true); - SolrCore core = null; - if (coll != null) { - String confName = coll.getConfigName(); - String syntheticCollectionName = getSyntheticCollectionName(confName); - DocCollection syntheticColl = clusterState.getCollectionOrNull(syntheticCollectionName); - synchronized (CoordinatorHttpSolrCall.class) { - if (syntheticColl == null) { - // no synthetic collection for this config, let's create one - if (log.isInfoEnabled()) { - log.info( - "synthetic collection: {} does not exist, creating.. ", syntheticCollectionName); - } - - SolrException createException = null; - try { - createColl(syntheticCollectionName, solrCall.cores, confName); - } catch (SolrException exception) { - // concurrent requests could have created the collection hence causing collection - // exists - // exception - createException = exception; - } finally { - syntheticColl = - zkStateReader.getClusterState().getCollectionOrNull(syntheticCollectionName); - } - - // then indeed the collection was not created properly, either by this or other - // concurrent - // requests - if (syntheticColl == null) { - if (createException != null) { - throw createException; // rethrow the exception since such collection was not - // created - } else { - throw new SolrException( - SolrException.ErrorCode.SERVER_ERROR, - "Could not locate synthetic collection [" - + syntheticCollectionName - + "] after creation!"); - } - } - } - - // get docCollection again to ensure we get the fresh state - syntheticColl = - zkStateReader.getClusterState().getCollectionOrNull(syntheticCollectionName); - List nodeNameSyntheticReplicas = - syntheticColl.getReplicas(solrCall.cores.getZkController().getNodeName()); - if (nodeNameSyntheticReplicas == null || nodeNameSyntheticReplicas.isEmpty()) { - // this node does not have a replica. add one - if (log.isInfoEnabled()) { - log.info( - "this node does not have a replica of the synthetic collection: {} , adding replica ", - syntheticCollectionName); - } + if (coll == null) { // querying on a non-existent collection, it could have been removed + log.info( + "Cannot find collection {} to proxy call to, it could have been deleted", + collectionName); + return null; + } - addReplica(syntheticCollectionName, solrCall.cores); - } + String confName = coll.getConfigName(); + String syntheticCollectionName = getSyntheticCollectionName(confName); + syntheticCoreName = syntheticCollectionName + "_core"; + + SolrCore syntheticCore; + synchronized (CoordinatorHttpSolrCall.class) { + CoreContainer coreContainer = solrCall.cores; + syntheticCore = coreContainer.getCore(syntheticCoreName); + if (syntheticCore == null) { + // first time loading this config set + log.info("Loading synthetic core for config set {}", confName); + syntheticCore = + SyntheticSolrCore.createAndRegisterCore( + coreContainer, syntheticCoreName, coll.getConfigName()); + // getting the core should open it + syntheticCore.open(); + } - // still have to ensure that it's active, otherwise super.getCoreByCollection - // will return null and then CoordinatorHttpSolrCall will call getCore again - // hence creating a calling loop - try { - zkStateReader.waitForState( - syntheticCollectionName, - 10, - TimeUnit.SECONDS, - docCollection -> { - for (Replica nodeNameSyntheticReplica : - docCollection.getReplicas(solrCall.cores.getZkController().getNodeName())) { - if (nodeNameSyntheticReplica.getState() == Replica.State.ACTIVE) { - return true; - } + factory.collectionVsCoreNameMapping.put(collectionName, syntheticCore.getName()); + + // for the watcher, only remove on collection deletion (ie collection == null), since + // watch from coordinator is collection specific + coreContainer + .getZkController() + .getZkStateReader() + .registerDocCollectionWatcher( + collectionName, + collection -> { + if (collection == null) { + factory.collectionVsCoreNameMapping.remove(collectionName); + return true; + } else { + return false; } - return false; }); - } catch (Exception e) { - throw new SolrException( - SolrException.ErrorCode.SERVER_ERROR, - "Failed to wait for active replica for synthetic collection [" - + syntheticCollectionName - + "]", - e); - } - } - - core = solrCall.getCoreByCollection(syntheticCollectionName, isPreferLeader); - if (core != null) { - factory.collectionVsCoreNameMapping.put(collectionName, core.getName()); - // for the watcher, only remove on collection deletion (ie collection == null), since - // watch from coordinator is collection specific - solrCall - .cores - .getZkController() - .getZkStateReader() - .registerDocCollectionWatcher( - collectionName, - collection -> { - if (collection == null) { - factory.collectionVsCoreNameMapping.remove(collectionName); - return true; - } else { - return false; - } - }); - if (log.isDebugEnabled()) { - log.debug("coordinator node, returns synthetic core: {}", core.getName()); - } - } - setMDCLoggingContext(collectionName); - return core; } - return null; + setMDCLoggingContext(collectionName); + if (log.isDebugEnabled()) { + log.debug("coordinator node, returns synthetic core: {}", syntheticCore.getName()); + } + return syntheticCore; } } @@ -206,69 +135,6 @@ public static String getSyntheticCollectionName(String configName) { return SYNTHETIC_COLL_PREFIX + configName; } - /** - * Overrides the MDC context as the core set was synthetic core, which does not reflect the - * collection being operated on - */ - private static void setMDCLoggingContext(String collectionName) { - MDCLoggingContext.setCollection(collectionName); - - // below is irrelevant for call to coordinator - MDCLoggingContext.setCoreName(null); - MDCLoggingContext.setShard(null); - MDCLoggingContext.setCoreName(null); - } - - private static void addReplica(String syntheticCollectionName, CoreContainer cores) { - SolrQueryResponse rsp = new SolrQueryResponse(); - try { - CollectionAdminRequest.AddReplica addReplicaRequest = - CollectionAdminRequest.addReplicaToShard(syntheticCollectionName, "shard1") - // we are fixing the name, so that no two replicas are created in the same node - .setNode(cores.getZkController().getNodeName()); - addReplicaRequest.setWaitForFinalState(true); - cores - .getCollectionsHandler() - .handleRequestBody(new LocalSolrQueryRequest(null, addReplicaRequest.getParams()), rsp); - if (rsp.getValues().get("success") == null) { - throw new SolrException( - SolrException.ErrorCode.SERVER_ERROR, - "Could not auto-create collection: " + Utils.toJSONString(rsp.getValues())); - } - } catch (Exception e) { - throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e); - } - } - - private static void createColl( - String syntheticCollectionName, CoreContainer cores, String confName) { - SolrQueryResponse rsp = new SolrQueryResponse(); - try { - CollectionAdminRequest.Create collCreationRequest = - CollectionAdminRequest.createCollection(syntheticCollectionName, confName, 1, 1) - .setCreateNodeSet(cores.getZkController().getNodeName()); - collCreationRequest.setWaitForFinalState(true); - SolrParams params = collCreationRequest.getParams(); - if (log.isInfoEnabled()) { - log.info("sending collection admin command : {}", Utils.toJSONString(params)); - } - cores.getCollectionsHandler().handleRequestBody(new LocalSolrQueryRequest(null, params), rsp); - if (rsp.getValues().get("success") == null) { - throw new SolrException( - SolrException.ErrorCode.SERVER_ERROR, - "Could not create :" - + syntheticCollectionName - + " collection: " - + Utils.toJSONString(rsp.getValues())); - } - } catch (SolrException e) { - throw e; - - } catch (Exception e) { - throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e); - } - } - @Override protected void init() throws Exception { super.init(); @@ -280,7 +146,9 @@ protected void init() throws Exception { public static SolrQueryRequest wrappedReq( SolrQueryRequest delegate, String collectionName, HttpSolrCall httpSolrCall) { Properties p = new Properties(); - p.put(CoreDescriptor.CORE_COLLECTION, collectionName); + if (collectionName != null) { + p.put(CoreDescriptor.CORE_COLLECTION, collectionName); + } p.put(CloudDescriptor.REPLICA_TYPE, Replica.Type.PULL.toString()); p.put(CoreDescriptor.CORE_SHARD, "_"); @@ -300,6 +168,19 @@ public CloudDescriptor getCloudDescriptor() { }; } + /** + * Overrides the MDC context as the core set was synthetic core, which does not reflect the + * collection being operated on + */ + private static void setMDCLoggingContext(String collectionName) { + MDCLoggingContext.setCollection(collectionName); + + // below is irrelevant for call to coordinator + MDCLoggingContext.setCoreName(null); + MDCLoggingContext.setShard(null); + MDCLoggingContext.setCoreName(null); + } + // The factory that creates an instance of HttpSolrCall public static class Factory implements SolrDispatchFilter.HttpSolrCallFactory { private final Map collectionVsCoreNameMapping = new ConcurrentHashMap<>(); diff --git a/solr/core/src/test/org/apache/solr/search/TestCoordinatorRole.java b/solr/core/src/test/org/apache/solr/search/TestCoordinatorRole.java index a0b1d1bef99..6107ccec44a 100644 --- a/solr/core/src/test/org/apache/solr/search/TestCoordinatorRole.java +++ b/solr/core/src/test/org/apache/solr/search/TestCoordinatorRole.java @@ -23,11 +23,9 @@ import java.lang.invoke.MethodHandles; import java.util.Date; import java.util.EnumSet; -import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Random; -import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; @@ -56,7 +54,6 @@ import org.apache.solr.common.util.SolrNamedThreadFactory; import org.apache.solr.core.NodeRoles; import org.apache.solr.embedded.JettySolrRunner; -import org.apache.solr.servlet.CoordinatorHttpSolrCall; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -69,7 +66,6 @@ public void testSimple() throws Exception { try { CloudSolrClient client = cluster.getSolrClient(); String COLLECTION_NAME = "test_coll"; - String SYNTHETIC_COLLECTION = CoordinatorHttpSolrCall.SYNTHETIC_COLL_PREFIX + "conf"; CollectionAdminRequest.createCollection(COLLECTION_NAME, "conf", 2, 2) .process(cluster.getSolrClient()); cluster.waitForActiveCollection(COLLECTION_NAME, 2, 4); @@ -97,15 +93,6 @@ public void testSimple() throws Exception { .process(client, COLLECTION_NAME); assertEquals(10, rslt.getResults().size()); - - DocCollection collection = - cluster.getSolrClient().getClusterStateProvider().getCollection(SYNTHETIC_COLLECTION); - assertNotNull(collection); - - Set expectedNodes = new HashSet<>(); - expectedNodes.add(coordinatorJetty.getNodeName()); - collection.forEachReplica((s, replica) -> expectedNodes.remove(replica.getNodeName())); - assertTrue(expectedNodes.isEmpty()); } finally { cluster.shutdown(); } @@ -117,7 +104,6 @@ public void testMultiCollectionMultiNode() throws Exception { try { CloudSolrClient client = cluster.getSolrClient(); String COLLECTION_NAME = "test_coll"; - String SYNTHETIC_COLLECTION = CoordinatorHttpSolrCall.SYNTHETIC_COLL_PREFIX + "conf"; for (int j = 1; j <= 10; j++) { String collname = COLLECTION_NAME + "_" + j; CollectionAdminRequest.createCollection(collname, "conf", 2, 2) @@ -163,15 +149,6 @@ public void testMultiCollectionMultiNode() throws Exception { assertEquals(10, rslt.getResults().size()); } - - DocCollection collection = - cluster.getSolrClient().getClusterStateProvider().getCollection(SYNTHETIC_COLLECTION); - assertNotNull(collection); - - int coordNode1NumCores = coordinatorJetty1.getCoreContainer().getNumAllCores(); - assertEquals("Unexpected number of cores found for coordinator node", 1, coordNode1NumCores); - int coordNode2NumCores = coordinatorJetty2.getCoreContainer().getNumAllCores(); - assertEquals("Unexpected number of cores found for coordinator node", 1, coordNode2NumCores); } finally { cluster.shutdown(); }