Java tutorial
/* * 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.core; import java.io.Closeable; import java.io.IOException; import java.lang.invoke.MethodHandles; import java.nio.file.Path; import java.nio.file.Paths; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import org.apache.commons.lang3.StringUtils; import org.apache.http.auth.AuthSchemeProvider; import org.apache.http.client.CredentialsProvider; import org.apache.http.config.Lookup; import org.apache.lucene.index.CorruptIndexException; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.store.Directory; import org.apache.solr.api.AnnotatedApi; import org.apache.solr.client.solrj.SolrClient; import org.apache.solr.client.solrj.cloud.SolrCloudManager; import org.apache.solr.client.solrj.embedded.EmbeddedSolrServer; import org.apache.solr.client.solrj.impl.CloudSolrClient; import org.apache.solr.client.solrj.impl.HttpClientUtil; import org.apache.solr.client.solrj.impl.SolrHttpClientBuilder; import org.apache.solr.client.solrj.impl.SolrHttpClientContextBuilder; import org.apache.solr.client.solrj.impl.SolrHttpClientContextBuilder.AuthSchemeRegistryProvider; import org.apache.solr.client.solrj.impl.SolrHttpClientContextBuilder.CredentialsProviderProvider; import org.apache.solr.client.solrj.util.SolrIdentifierValidator; import org.apache.solr.cloud.CloudDescriptor; import org.apache.solr.cloud.Overseer; import org.apache.solr.cloud.OverseerTaskQueue; import org.apache.solr.cloud.ZkController; import org.apache.solr.cloud.autoscaling.AutoScalingHandler; import org.apache.solr.common.AlreadyClosedException; import org.apache.solr.common.SolrException; import org.apache.solr.common.SolrException.ErrorCode; import org.apache.solr.common.cloud.DocCollection; import org.apache.solr.common.cloud.Replica; import org.apache.solr.common.cloud.Replica.State; import org.apache.solr.common.cloud.ZkStateReader; import org.apache.solr.common.util.ExecutorUtil; import org.apache.solr.common.util.IOUtils; import org.apache.solr.common.util.SolrjNamedThreadFactory; import org.apache.solr.common.util.Utils; import org.apache.solr.core.DirectoryFactory.DirContext; import org.apache.solr.core.backup.repository.BackupRepository; import org.apache.solr.core.backup.repository.BackupRepositoryFactory; import org.apache.solr.filestore.PackageStoreAPI; import org.apache.solr.handler.RequestHandlerBase; import org.apache.solr.handler.SnapShooter; import org.apache.solr.handler.admin.AutoscalingHistoryHandler; import org.apache.solr.handler.admin.CollectionsHandler; import org.apache.solr.handler.admin.ConfigSetsHandler; import org.apache.solr.handler.admin.CoreAdminHandler; import org.apache.solr.handler.admin.HealthCheckHandler; import org.apache.solr.handler.admin.InfoHandler; import org.apache.solr.handler.admin.MetricsCollectorHandler; import org.apache.solr.handler.admin.MetricsHandler; import org.apache.solr.handler.admin.MetricsHistoryHandler; import org.apache.solr.handler.admin.SecurityConfHandler; import org.apache.solr.handler.admin.SecurityConfHandlerLocal; import org.apache.solr.handler.admin.SecurityConfHandlerZk; import org.apache.solr.handler.admin.ZookeeperInfoHandler; import org.apache.solr.handler.admin.ZookeeperStatusHandler; import org.apache.solr.handler.component.ShardHandlerFactory; import org.apache.solr.logging.LogWatcher; import org.apache.solr.logging.MDCLoggingContext; import org.apache.solr.metrics.SolrCoreMetricManager; import org.apache.solr.metrics.SolrMetricManager; import org.apache.solr.metrics.SolrMetricProducer; import org.apache.solr.metrics.SolrMetricsContext; import org.apache.solr.pkg.PackageLoader; import org.apache.solr.request.SolrRequestHandler; import org.apache.solr.request.SolrRequestInfo; import org.apache.solr.search.SolrFieldCacheBean; import org.apache.solr.security.AuditLoggerPlugin; import org.apache.solr.security.AuthenticationPlugin; import org.apache.solr.security.AuthorizationPlugin; import org.apache.solr.security.HttpClientBuilderPlugin; import org.apache.solr.security.PKIAuthenticationPlugin; import org.apache.solr.security.PublicKeyHandler; import org.apache.solr.security.SecurityPluginHolder; import org.apache.solr.update.SolrCoreState; import org.apache.solr.update.UpdateShardHandler; import org.apache.solr.util.DefaultSolrThreadFactory; import org.apache.solr.util.OrderedExecutor; import org.apache.solr.util.RefCounted; import org.apache.solr.util.stats.MetricUtils; import org.apache.zookeeper.KeeperException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static java.util.Objects.requireNonNull; import static org.apache.solr.common.params.CommonParams.AUTHC_PATH; import static org.apache.solr.common.params.CommonParams.AUTHZ_PATH; import static org.apache.solr.common.params.CommonParams.AUTOSCALING_HISTORY_PATH; import static org.apache.solr.common.params.CommonParams.COLLECTIONS_HANDLER_PATH; import static org.apache.solr.common.params.CommonParams.CONFIGSETS_HANDLER_PATH; import static org.apache.solr.common.params.CommonParams.CORES_HANDLER_PATH; import static org.apache.solr.common.params.CommonParams.INFO_HANDLER_PATH; import static org.apache.solr.common.params.CommonParams.METRICS_HISTORY_PATH; import static org.apache.solr.common.params.CommonParams.METRICS_PATH; import static org.apache.solr.common.params.CommonParams.ZK_PATH; import static org.apache.solr.common.params.CommonParams.ZK_STATUS_PATH; import static org.apache.solr.core.CorePropertiesLocator.PROPERTIES_FILENAME; import static org.apache.solr.security.AuthenticationPlugin.AUTHENTICATION_PLUGIN_PROP; /** * @since solr 1.3 */ public class CoreContainer { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); final SolrCores solrCores = new SolrCores(this); public static class CoreLoadFailure { public final CoreDescriptor cd; public final Exception exception; public CoreLoadFailure(CoreDescriptor cd, Exception loadFailure) { this.cd = new CoreDescriptor(cd.getName(), cd); this.exception = loadFailure; } } protected final Map<String, CoreLoadFailure> coreInitFailures = new ConcurrentHashMap<>(); protected volatile CoreAdminHandler coreAdminHandler = null; protected volatile CollectionsHandler collectionsHandler = null; protected volatile HealthCheckHandler healthCheckHandler = null; private volatile InfoHandler infoHandler; protected volatile ConfigSetsHandler configSetsHandler = null; private volatile PKIAuthenticationPlugin pkiAuthenticationPlugin; protected volatile Properties containerProperties; private volatile ConfigSetService coreConfigService; protected final ZkContainer zkSys = new ZkContainer(); protected volatile ShardHandlerFactory shardHandlerFactory; private volatile UpdateShardHandler updateShardHandler; private volatile ExecutorService coreContainerWorkExecutor = ExecutorUtil .newMDCAwareCachedThreadPool(new DefaultSolrThreadFactory("coreContainerWorkExecutor")); private final OrderedExecutor replayUpdatesExecutor; protected volatile LogWatcher logging = null; private volatile CloserThread backgroundCloser = null; protected final NodeConfig cfg; protected final SolrResourceLoader loader; protected final String solrHome; protected final CoresLocator coresLocator; private volatile String hostName; private final BlobRepository blobRepository = new BlobRepository(this); private volatile PluginBag<SolrRequestHandler> containerHandlers = new PluginBag<>(SolrRequestHandler.class, null); private volatile boolean asyncSolrCoreLoad; protected volatile SecurityConfHandler securityConfHandler; private volatile SecurityPluginHolder<AuthorizationPlugin> authorizationPlugin; private volatile SecurityPluginHolder<AuthenticationPlugin> authenticationPlugin; private volatile SecurityPluginHolder<AuditLoggerPlugin> auditloggerPlugin; private volatile BackupRepositoryFactory backupRepoFactory; protected volatile SolrMetricManager metricManager; protected volatile String metricTag = SolrMetricProducer.getUniqueMetricTag(this, null); protected volatile SolrMetricsContext solrMetricsContext; protected MetricsHandler metricsHandler; protected volatile MetricsHistoryHandler metricsHistoryHandler; protected volatile MetricsCollectorHandler metricsCollectorHandler; protected volatile AutoscalingHistoryHandler autoscalingHistoryHandler; private PackageStoreAPI packageStoreAPI; private PackageLoader packageLoader; // Bits for the state variable. public final static long LOAD_COMPLETE = 0x1L; public final static long CORE_DISCOVERY_COMPLETE = 0x2L; public final static long INITIAL_CORE_LOAD_COMPLETE = 0x4L; private volatile long status = 0L; protected volatile AutoScalingHandler autoScalingHandler; private ExecutorService coreContainerAsyncTaskExecutor = ExecutorUtil .newMDCAwareCachedThreadPool("Core Container Async Task"); private enum CoreInitFailedAction { fromleader, none } /** * This method instantiates a new instance of {@linkplain BackupRepository}. * * @param repositoryName The name of the backup repository (Optional). * If not specified, a default implementation is used. * @return a new instance of {@linkplain BackupRepository}. */ public BackupRepository newBackupRepository(Optional<String> repositoryName) { BackupRepository repository; if (repositoryName.isPresent()) { repository = backupRepoFactory.newInstance(getResourceLoader(), repositoryName.get()); } else { repository = backupRepoFactory.newInstance(getResourceLoader()); } return repository; } public ExecutorService getCoreZkRegisterExecutorService() { return zkSys.getCoreZkRegisterExecutorService(); } public SolrRequestHandler getRequestHandler(String path) { return RequestHandlerBase.getRequestHandler(path, containerHandlers); } public PluginBag<SolrRequestHandler> getRequestHandlers() { return this.containerHandlers; } { log.debug("New CoreContainer " + System.identityHashCode(this)); } /** * Create a new CoreContainer using system properties to detect the solr home * directory. The container's cores are not loaded. * * @see #load() */ public CoreContainer() { this(new SolrResourceLoader(SolrResourceLoader.locateSolrHome())); } /** * Create a new CoreContainer using the given SolrResourceLoader. The container's * cores are not loaded. * * @param loader the SolrResourceLoader * @see #load() */ public CoreContainer(SolrResourceLoader loader) { this(SolrXmlConfig.fromSolrHome(loader, loader.getInstancePath())); } /** * Create a new CoreContainer using the given solr home directory. The container's * cores are not loaded. * * @param solrHome a String containing the path to the solr home directory * @see #load() */ public CoreContainer(String solrHome) { this(new SolrResourceLoader(Paths.get(solrHome))); } /** * Create a new CoreContainer using the given SolrResourceLoader, * configuration and CoresLocator. The container's cores are * not loaded. * * @param config a ConfigSolr representation of this container's configuration * @see #load() */ public CoreContainer(NodeConfig config) { this(config, new Properties()); } public CoreContainer(NodeConfig config, Properties properties) { this(config, properties, new CorePropertiesLocator(config.getCoreRootDirectory())); } public CoreContainer(NodeConfig config, Properties properties, boolean asyncSolrCoreLoad) { this(config, properties, new CorePropertiesLocator(config.getCoreRootDirectory()), asyncSolrCoreLoad); } public CoreContainer(NodeConfig config, Properties properties, CoresLocator locator) { this(config, properties, locator, false); } public CoreContainer(NodeConfig config, Properties properties, CoresLocator locator, boolean asyncSolrCoreLoad) { this.loader = config.getSolrResourceLoader(); this.solrHome = loader.getInstancePath().toString(); containerHandlers.put(PublicKeyHandler.PATH, new PublicKeyHandler()); this.cfg = requireNonNull(config); if (null != this.cfg.getBooleanQueryMaxClauseCount()) { IndexSearcher.setMaxClauseCount(this.cfg.getBooleanQueryMaxClauseCount()); } this.coresLocator = locator; this.containerProperties = new Properties(properties); this.asyncSolrCoreLoad = asyncSolrCoreLoad; this.replayUpdatesExecutor = new OrderedExecutor(cfg.getReplayUpdatesThreads(), ExecutorUtil.newMDCAwareCachedThreadPool(cfg.getReplayUpdatesThreads(), new DefaultSolrThreadFactory("replayUpdatesExecutor"))); } private synchronized void initializeAuthorizationPlugin(Map<String, Object> authorizationConf) { authorizationConf = Utils.getDeepCopy(authorizationConf, 4); int newVersion = readVersion(authorizationConf); //Initialize the Authorization module SecurityPluginHolder<AuthorizationPlugin> old = authorizationPlugin; SecurityPluginHolder<AuthorizationPlugin> authorizationPlugin = null; if (authorizationConf != null) { String klas = (String) authorizationConf.get("class"); if (klas == null) { throw new SolrException(ErrorCode.SERVER_ERROR, "class is required for authorization plugin"); } if (old != null && old.getZnodeVersion() == newVersion && newVersion > 0) { log.debug("Authorization config not modified"); return; } log.info("Initializing authorization plugin: " + klas); authorizationPlugin = new SecurityPluginHolder<>(newVersion, getResourceLoader().newInstance(klas, AuthorizationPlugin.class)); // Read and pass the authorization context to the plugin authorizationPlugin.plugin.init(authorizationConf); } else { log.debug("Security conf doesn't exist. Skipping setup for authorization module."); } this.authorizationPlugin = authorizationPlugin; if (old != null) { try { old.plugin.close(); } catch (Exception e) { log.error("Exception while attempting to close old authorization plugin", e); } } } private void initializeAuditloggerPlugin(Map<String, Object> auditConf) { auditConf = Utils.getDeepCopy(auditConf, 4); int newVersion = readVersion(auditConf); //Initialize the Auditlog module SecurityPluginHolder<AuditLoggerPlugin> old = auditloggerPlugin; SecurityPluginHolder<AuditLoggerPlugin> newAuditloggerPlugin = null; if (auditConf != null) { String klas = (String) auditConf.get("class"); if (klas == null) { throw new SolrException(ErrorCode.SERVER_ERROR, "class is required for auditlogger plugin"); } if (old != null && old.getZnodeVersion() == newVersion && newVersion > 0) { log.debug("Auditlogger config not modified"); return; } log.info("Initializing auditlogger plugin: " + klas); newAuditloggerPlugin = new SecurityPluginHolder<>(newVersion, getResourceLoader().newInstance(klas, AuditLoggerPlugin.class)); newAuditloggerPlugin.plugin.init(auditConf); newAuditloggerPlugin.plugin.initializeMetrics(solrMetricsContext, "/auditlogging"); } else { log.debug("Security conf doesn't exist. Skipping setup for audit logging module."); } this.auditloggerPlugin = newAuditloggerPlugin; if (old != null) { try { old.plugin.close(); } catch (Exception e) { log.error("Exception while attempting to close old auditlogger plugin", e); } } } private synchronized void initializeAuthenticationPlugin(Map<String, Object> authenticationConfig) { authenticationConfig = Utils.getDeepCopy(authenticationConfig, 4); int newVersion = readVersion(authenticationConfig); String pluginClassName = null; if (authenticationConfig != null) { if (authenticationConfig.containsKey("class")) { pluginClassName = String.valueOf(authenticationConfig.get("class")); } else { throw new SolrException(ErrorCode.SERVER_ERROR, "No 'class' specified for authentication in ZK."); } } if (pluginClassName != null) { log.debug("Authentication plugin class obtained from security.json: " + pluginClassName); } else if (System.getProperty(AUTHENTICATION_PLUGIN_PROP) != null) { pluginClassName = System.getProperty(AUTHENTICATION_PLUGIN_PROP); log.debug("Authentication plugin class obtained from system property '" + AUTHENTICATION_PLUGIN_PROP + "': " + pluginClassName); } else { log.debug("No authentication plugin used."); } SecurityPluginHolder<AuthenticationPlugin> old = authenticationPlugin; SecurityPluginHolder<AuthenticationPlugin> authenticationPlugin = null; if (old != null && old.getZnodeVersion() == newVersion && newVersion > 0) { log.debug("Authentication config not modified"); return; } // Initialize the plugin if (pluginClassName != null) { log.info("Initializing authentication plugin: " + pluginClassName); authenticationPlugin = new SecurityPluginHolder<>(newVersion, getResourceLoader().newInstance(pluginClassName, AuthenticationPlugin.class, null, new Class[] { CoreContainer.class }, new Object[] { this })); } if (authenticationPlugin != null) { authenticationPlugin.plugin.init(authenticationConfig); setupHttpClientForAuthPlugin(authenticationPlugin.plugin); authenticationPlugin.plugin.initializeMetrics(solrMetricsContext, "/authentication"); } this.authenticationPlugin = authenticationPlugin; try { if (old != null) old.plugin.close(); } catch (Exception e) { log.error("Exception while attempting to close old authentication plugin", e); } } private void setupHttpClientForAuthPlugin(Object authcPlugin) { if (authcPlugin instanceof HttpClientBuilderPlugin) { // Setup HttpClient for internode communication HttpClientBuilderPlugin builderPlugin = ((HttpClientBuilderPlugin) authcPlugin); SolrHttpClientBuilder builder = builderPlugin .getHttpClientBuilder(HttpClientUtil.getHttpClientBuilder()); shardHandlerFactory.setSecurityBuilder(builderPlugin); updateShardHandler.setSecurityBuilder(builderPlugin); // The default http client of the core container's shardHandlerFactory has already been created and // configured using the default httpclient configurer. We need to reconfigure it using the plugin's // http client configurer to set it up for internode communication. log.debug("Reconfiguring HttpClient settings."); SolrHttpClientContextBuilder httpClientBuilder = new SolrHttpClientContextBuilder(); if (builder.getCredentialsProviderProvider() != null) { httpClientBuilder.setDefaultCredentialsProvider(new CredentialsProviderProvider() { @Override public CredentialsProvider getCredentialsProvider() { return builder.getCredentialsProviderProvider().getCredentialsProvider(); } }); } if (builder.getAuthSchemeRegistryProvider() != null) { httpClientBuilder.setAuthSchemeRegistryProvider(new AuthSchemeRegistryProvider() { @Override public Lookup<AuthSchemeProvider> getAuthSchemeRegistry() { return builder.getAuthSchemeRegistryProvider().getAuthSchemeRegistry(); } }); } HttpClientUtil.setHttpClientRequestContextBuilder(httpClientBuilder); } // Always register PKI auth interceptor, which will then delegate the decision of who should secure // each request to the configured authentication plugin. if (pkiAuthenticationPlugin != null && !pkiAuthenticationPlugin.isInterceptorRegistered()) { pkiAuthenticationPlugin.getHttpClientBuilder(HttpClientUtil.getHttpClientBuilder()); shardHandlerFactory.setSecurityBuilder(pkiAuthenticationPlugin); updateShardHandler.setSecurityBuilder(pkiAuthenticationPlugin); } } private static int readVersion(Map<String, Object> conf) { if (conf == null) return -1; Map meta = (Map) conf.get(""); if (meta == null) return -1; Number v = (Number) meta.get("v"); return v == null ? -1 : v.intValue(); } /** * This method allows subclasses to construct a CoreContainer * without any default init behavior. * * @param testConstructor pass (Object)null. * @lucene.experimental */ protected CoreContainer(Object testConstructor) { solrHome = null; loader = null; coresLocator = null; cfg = null; containerProperties = null; replayUpdatesExecutor = null; } public static CoreContainer createAndLoad(Path solrHome) { return createAndLoad(solrHome, solrHome.resolve(SolrXmlConfig.SOLR_XML_FILE)); } /** * Create a new CoreContainer and load its cores * * @param solrHome the solr home directory * @param configFile the file containing this container's configuration * @return a loaded CoreContainer */ public static CoreContainer createAndLoad(Path solrHome, Path configFile) { SolrResourceLoader loader = new SolrResourceLoader(solrHome); CoreContainer cc = new CoreContainer(SolrXmlConfig.fromFile(loader, configFile)); try { cc.load(); } catch (Exception e) { cc.shutdown(); throw e; } return cc; } public Properties getContainerProperties() { return containerProperties; } public PKIAuthenticationPlugin getPkiAuthenticationPlugin() { return pkiAuthenticationPlugin; } public SolrMetricManager getMetricManager() { return metricManager; } public MetricsHandler getMetricsHandler() { return metricsHandler; } public MetricsHistoryHandler getMetricsHistoryHandler() { return metricsHistoryHandler; } public OrderedExecutor getReplayUpdatesExecutor() { return replayUpdatesExecutor; } public PackageLoader getPackageLoader() { return packageLoader; } public PackageStoreAPI getPackageStoreAPI() { return packageStoreAPI; } //------------------------------------------------------------------- // Initialization / Cleanup //------------------------------------------------------------------- /** * Load the cores defined for this CoreContainer */ public void load() { log.debug("Loading cores into CoreContainer [instanceDir={}]", loader.getInstancePath()); // add the sharedLib to the shared resource loader before initializing cfg based plugins String libDir = cfg.getSharedLibDirectory(); if (libDir != null) { Path libPath = loader.getInstancePath().resolve(libDir); try { loader.addToClassLoader(SolrResourceLoader.getURLs(libPath)); loader.reloadLuceneSPI(); } catch (IOException e) { if (!libDir.equals("lib")) { // Don't complain if default "lib" dir does not exist log.warn("Couldn't add files from {} to classpath: {}", libPath, e.getMessage()); } } } packageStoreAPI = new PackageStoreAPI(this); containerHandlers.getApiBag().register(new AnnotatedApi(packageStoreAPI.readAPI), Collections.EMPTY_MAP); containerHandlers.getApiBag().register(new AnnotatedApi(packageStoreAPI.writeAPI), Collections.EMPTY_MAP); metricManager = new SolrMetricManager(loader, cfg.getMetricsConfig()); String registryName = SolrMetricManager.getRegistryName(SolrInfoBean.Group.node); solrMetricsContext = new SolrMetricsContext(metricManager, registryName, metricTag); coreContainerWorkExecutor = MetricUtils.instrumentedExecutorService(coreContainerWorkExecutor, null, metricManager.registry(SolrMetricManager.getRegistryName(SolrInfoBean.Group.node)), SolrMetricManager.mkName("coreContainerWorkExecutor", SolrInfoBean.Category.CONTAINER.toString(), "threadPool")); shardHandlerFactory = ShardHandlerFactory.newInstance(cfg.getShardHandlerFactoryPluginInfo(), loader); if (shardHandlerFactory instanceof SolrMetricProducer) { SolrMetricProducer metricProducer = (SolrMetricProducer) shardHandlerFactory; metricProducer.initializeMetrics(solrMetricsContext, "httpShardHandler"); } updateShardHandler = new UpdateShardHandler(cfg.getUpdateShardHandlerConfig()); updateShardHandler.initializeMetrics(solrMetricsContext, "updateShardHandler"); solrCores.load(loader); logging = LogWatcher.newRegisteredLogWatcher(cfg.getLogWatcherConfig(), loader); hostName = cfg.getNodeName(); zkSys.initZooKeeper(this, solrHome, cfg.getCloudConfig()); if (isZooKeeperAware()) { pkiAuthenticationPlugin = new PKIAuthenticationPlugin(this, zkSys.getZkController().getNodeName(), (PublicKeyHandler) containerHandlers.get(PublicKeyHandler.PATH)); // use deprecated API for back-compat, remove in 9.0 pkiAuthenticationPlugin.initializeMetrics(solrMetricsContext, "/authentication/pki"); TracerConfigurator.loadTracer(loader, cfg.getTracerConfiguratorPluginInfo(), getZkController().getZkStateReader()); } MDCLoggingContext.setNode(this); securityConfHandler = isZooKeeperAware() ? new SecurityConfHandlerZk(this) : new SecurityConfHandlerLocal(this); reloadSecurityProperties(); warnUsersOfInsecureSettings(); this.backupRepoFactory = new BackupRepositoryFactory(cfg.getBackupRepositoryPlugins()); createHandler(ZK_PATH, ZookeeperInfoHandler.class.getName(), ZookeeperInfoHandler.class); createHandler(ZK_STATUS_PATH, ZookeeperStatusHandler.class.getName(), ZookeeperStatusHandler.class); collectionsHandler = createHandler(COLLECTIONS_HANDLER_PATH, cfg.getCollectionsHandlerClass(), CollectionsHandler.class); infoHandler = createHandler(INFO_HANDLER_PATH, cfg.getInfoHandlerClass(), InfoHandler.class); coreAdminHandler = createHandler(CORES_HANDLER_PATH, cfg.getCoreAdminHandlerClass(), CoreAdminHandler.class); configSetsHandler = createHandler(CONFIGSETS_HANDLER_PATH, cfg.getConfigSetsHandlerClass(), ConfigSetsHandler.class); // metricsHistoryHandler uses metricsHandler, so create it first metricsHandler = new MetricsHandler(this); containerHandlers.put(METRICS_PATH, metricsHandler); metricsHandler.initializeMetrics(solrMetricsContext, METRICS_PATH); createMetricsHistoryHandler(); autoscalingHistoryHandler = createHandler(AUTOSCALING_HISTORY_PATH, AutoscalingHistoryHandler.class.getName(), AutoscalingHistoryHandler.class); metricsCollectorHandler = createHandler(MetricsCollectorHandler.HANDLER_PATH, MetricsCollectorHandler.class.getName(), MetricsCollectorHandler.class); // may want to add some configuration here in the future metricsCollectorHandler.init(null); containerHandlers.put(AUTHZ_PATH, securityConfHandler); securityConfHandler.initializeMetrics(solrMetricsContext, AUTHZ_PATH); containerHandlers.put(AUTHC_PATH, securityConfHandler); PluginInfo[] metricReporters = cfg.getMetricsConfig().getMetricReporters(); metricManager.loadReporters(metricReporters, loader, this, null, null, SolrInfoBean.Group.node); metricManager.loadReporters(metricReporters, loader, this, null, null, SolrInfoBean.Group.jvm); metricManager.loadReporters(metricReporters, loader, this, null, null, SolrInfoBean.Group.jetty); coreConfigService = ConfigSetService.createConfigSetService(cfg, loader, zkSys.zkController); containerProperties.putAll(cfg.getSolrProperties()); // initialize gauges for reporting the number of cores and disk total/free solrMetricsContext.gauge(() -> solrCores.getCores().size(), true, "loaded", SolrInfoBean.Category.CONTAINER.toString(), "cores"); solrMetricsContext.gauge(() -> solrCores.getLoadedCoreNames().size() - solrCores.getCores().size(), true, "lazy", SolrInfoBean.Category.CONTAINER.toString(), "cores"); solrMetricsContext.gauge(() -> solrCores.getAllCoreNames().size() - solrCores.getLoadedCoreNames().size(), true, "unloaded", SolrInfoBean.Category.CONTAINER.toString(), "cores"); Path dataHome = cfg.getSolrDataHome() != null ? cfg.getSolrDataHome() : cfg.getCoreRootDirectory(); solrMetricsContext.gauge(() -> dataHome.toFile().getTotalSpace(), true, "totalSpace", SolrInfoBean.Category.CONTAINER.toString(), "fs"); solrMetricsContext.gauge(() -> dataHome.toFile().getUsableSpace(), true, "usableSpace", SolrInfoBean.Category.CONTAINER.toString(), "fs"); solrMetricsContext.gauge(() -> dataHome.toAbsolutePath().toString(), true, "path", SolrInfoBean.Category.CONTAINER.toString(), "fs"); solrMetricsContext.gauge(() -> { try { return org.apache.lucene.util.IOUtils.spins(dataHome.toAbsolutePath()); } catch (IOException e) { // default to spinning return true; } }, true, "spins", SolrInfoBean.Category.CONTAINER.toString(), "fs"); solrMetricsContext.gauge(() -> cfg.getCoreRootDirectory().toFile().getTotalSpace(), true, "totalSpace", SolrInfoBean.Category.CONTAINER.toString(), "fs", "coreRoot"); solrMetricsContext.gauge(() -> cfg.getCoreRootDirectory().toFile().getUsableSpace(), true, "usableSpace", SolrInfoBean.Category.CONTAINER.toString(), "fs", "coreRoot"); solrMetricsContext.gauge(() -> cfg.getCoreRootDirectory().toAbsolutePath().toString(), true, "path", SolrInfoBean.Category.CONTAINER.toString(), "fs", "coreRoot"); solrMetricsContext.gauge(() -> { try { return org.apache.lucene.util.IOUtils.spins(cfg.getCoreRootDirectory().toAbsolutePath()); } catch (IOException e) { // default to spinning return true; } }, true, "spins", SolrInfoBean.Category.CONTAINER.toString(), "fs", "coreRoot"); // add version information solrMetricsContext.gauge(() -> this.getClass().getPackage().getSpecificationVersion(), true, "specification", SolrInfoBean.Category.CONTAINER.toString(), "version"); solrMetricsContext.gauge(() -> this.getClass().getPackage().getImplementationVersion(), true, "implementation", SolrInfoBean.Category.CONTAINER.toString(), "version"); SolrFieldCacheBean fieldCacheBean = new SolrFieldCacheBean(); fieldCacheBean.initializeMetrics(solrMetricsContext, null); if (isZooKeeperAware()) { metricManager.loadClusterReporters(metricReporters, this); packageLoader = new PackageLoader(this); containerHandlers.getApiBag().register(new AnnotatedApi(packageLoader.getPackageAPI().editAPI), Collections.EMPTY_MAP); containerHandlers.getApiBag().register(new AnnotatedApi(packageLoader.getPackageAPI().readAPI), Collections.EMPTY_MAP); } // setup executor to load cores in parallel ExecutorService coreLoadExecutor = MetricUtils.instrumentedExecutorService( ExecutorUtil.newMDCAwareFixedThreadPool(cfg.getCoreLoadThreadCount(isZooKeeperAware()), new DefaultSolrThreadFactory("coreLoadExecutor")), null, metricManager.registry(SolrMetricManager.getRegistryName(SolrInfoBean.Group.node)), SolrMetricManager.mkName("coreLoadExecutor", SolrInfoBean.Category.CONTAINER.toString(), "threadPool")); final List<Future<SolrCore>> futures = new ArrayList<>(); try { List<CoreDescriptor> cds = coresLocator.discover(this); if (isZooKeeperAware()) { //sort the cores if it is in SolrCloud. In standalone node the order does not matter CoreSorter coreComparator = new CoreSorter().init(this); cds = new ArrayList<>(cds);//make a copy Collections.sort(cds, coreComparator::compare); } checkForDuplicateCoreNames(cds); status |= CORE_DISCOVERY_COMPLETE; for (final CoreDescriptor cd : cds) { if (cd.isTransient() || !cd.isLoadOnStartup()) { solrCores.addCoreDescriptor(cd); } else if (asyncSolrCoreLoad) { solrCores.markCoreAsLoading(cd); } if (cd.isLoadOnStartup()) { futures.add(coreLoadExecutor.submit(() -> { SolrCore core; try { if (zkSys.getZkController() != null) { zkSys.getZkController().throwErrorIfReplicaReplaced(cd); } solrCores.waitAddPendingCoreOps(cd.getName()); core = createFromDescriptor(cd, false, false); } finally { solrCores.removeFromPendingOps(cd.getName()); if (asyncSolrCoreLoad) { solrCores.markCoreAsNotLoading(cd); } } try { zkSys.registerInZk(core, true, false); } catch (RuntimeException e) { SolrException.log(log, "Error registering SolrCore", e); } return core; })); } } // Start the background thread backgroundCloser = new CloserThread(this, solrCores, cfg); backgroundCloser.start(); } finally { if (asyncSolrCoreLoad && futures != null) { coreContainerWorkExecutor.submit(() -> { try { for (Future<SolrCore> future : futures) { try { future.get(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } catch (ExecutionException e) { log.error("Error waiting for SolrCore to be loaded on startup", e.getCause()); } } } finally { ExecutorUtil.shutdownAndAwaitTermination(coreLoadExecutor); } }); } else { ExecutorUtil.shutdownAndAwaitTermination(coreLoadExecutor); } } if (isZooKeeperAware()) { zkSys.getZkController().checkOverseerDesignate(); // initialize this handler here when SolrCloudManager is ready autoScalingHandler = new AutoScalingHandler(getZkController().getSolrCloudManager(), loader); containerHandlers.put(AutoScalingHandler.HANDLER_PATH, autoScalingHandler); autoScalingHandler.initializeMetrics(solrMetricsContext, AutoScalingHandler.HANDLER_PATH); } // This is a bit redundant but these are two distinct concepts for all they're accomplished at the same time. status |= LOAD_COMPLETE | INITIAL_CORE_LOAD_COMPLETE; } // MetricsHistoryHandler supports both cloud and standalone configs private void createMetricsHistoryHandler() { PluginInfo plugin = cfg.getMetricsConfig().getHistoryHandler(); Map<String, Object> initArgs; if (plugin != null && plugin.initArgs != null) { initArgs = plugin.initArgs.asMap(5); initArgs.put(MetricsHistoryHandler.ENABLE_PROP, plugin.isEnabled()); } else { initArgs = new HashMap<>(); } String name; SolrCloudManager cloudManager; SolrClient client; if (isZooKeeperAware()) { name = getZkController().getNodeName(); cloudManager = getZkController().getSolrCloudManager(); client = new CloudSolrClient.Builder(Collections.singletonList(getZkController().getZkServerAddress()), Optional.empty()).withSocketTimeout(30000).withConnectionTimeout(15000) .withHttpClient(updateShardHandler.getDefaultHttpClient()).build(); } else { name = getNodeConfig().getNodeName(); if (name == null || name.isEmpty()) { name = "localhost"; } cloudManager = null; client = new EmbeddedSolrServer(this, null) { @Override public void close() throws IOException { // do nothing - we close the container ourselves } }; // enable local metrics unless specifically set otherwise if (!initArgs.containsKey(MetricsHistoryHandler.ENABLE_NODES_PROP)) { initArgs.put(MetricsHistoryHandler.ENABLE_NODES_PROP, true); } if (!initArgs.containsKey(MetricsHistoryHandler.ENABLE_REPLICAS_PROP)) { initArgs.put(MetricsHistoryHandler.ENABLE_REPLICAS_PROP, true); } } metricsHistoryHandler = new MetricsHistoryHandler(name, metricsHandler, client, cloudManager, initArgs); containerHandlers.put(METRICS_HISTORY_PATH, metricsHistoryHandler); metricsHistoryHandler.initializeMetrics(solrMetricsContext, METRICS_HISTORY_PATH); } public void securityNodeChanged() { log.info("Security node changed, reloading security.json"); reloadSecurityProperties(); } /** * Make sure securityConfHandler is initialized */ private void reloadSecurityProperties() { SecurityConfHandler.SecurityConfig securityConfig = securityConfHandler.getSecurityConfig(false); initializeAuthorizationPlugin((Map<String, Object>) securityConfig.getData().get("authorization")); initializeAuthenticationPlugin((Map<String, Object>) securityConfig.getData().get("authentication")); initializeAuditloggerPlugin((Map<String, Object>) securityConfig.getData().get("auditlogging")); } private void warnUsersOfInsecureSettings() { if (authenticationPlugin == null || authorizationPlugin == null) { log.warn( "Not all security plugins configured! authentication={} authorization={}. Solr is only as secure as " + "you make it. Consider configuring authentication/authorization before exposing Solr to users internal or " + "external. See https://s.apache.org/solrsecurity for more info", (authenticationPlugin != null) ? "enabled" : "disabled", (authorizationPlugin != null) ? "enabled" : "disabled"); } if (authenticationPlugin != null && StringUtils.isNotEmpty(System.getProperty("solr.jetty.https.port"))) { log.warn( "Solr authentication is enabled, but SSL is off. Consider enabling SSL to protect user credentials and " + "data with encryption."); } } private static void checkForDuplicateCoreNames(List<CoreDescriptor> cds) { Map<String, Path> addedCores = Maps.newHashMap(); for (CoreDescriptor cd : cds) { final String name = cd.getName(); if (addedCores.containsKey(name)) throw new SolrException(ErrorCode.SERVER_ERROR, String.format(Locale.ROOT, "Found multiple cores with the name [%s], with instancedirs [%s] and [%s]", name, addedCores.get(name), cd.getInstanceDir())); addedCores.put(name, cd.getInstanceDir()); } } private volatile boolean isShutDown = false; public boolean isShutDown() { return isShutDown; } public void shutdown() { ZkController zkController = getZkController(); if (zkController != null) { OverseerTaskQueue overseerCollectionQueue = zkController.getOverseerCollectionQueue(); overseerCollectionQueue.allowOverseerPendingTasksToComplete(); } log.info("Shutting down CoreContainer instance=" + System.identityHashCode(this)); ExecutorUtil.shutdownAndAwaitTermination(coreContainerAsyncTaskExecutor); ExecutorService customThreadPool = ExecutorUtil .newMDCAwareCachedThreadPool(new SolrjNamedThreadFactory("closeThreadPool")); isShutDown = true; try { if (isZooKeeperAware()) { cancelCoreRecoveries(); zkSys.zkController.preClose(); } ExecutorUtil.shutdownAndAwaitTermination(coreContainerWorkExecutor); // First wake up the closer thread, it'll terminate almost immediately since it checks isShutDown. synchronized (solrCores.getModifyLock()) { solrCores.getModifyLock().notifyAll(); // wake up anyone waiting } if (backgroundCloser != null) { // Doesn't seem right, but tests get in here without initializing the core. try { while (true) { backgroundCloser.join(15000); if (backgroundCloser.isAlive()) { synchronized (solrCores.getModifyLock()) { solrCores.getModifyLock().notifyAll(); // there is a race we have to protect against } } else { break; } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); if (log.isDebugEnabled()) { log.debug("backgroundCloser thread was interrupted before finishing"); } } } // Now clear all the cores that are being operated upon. solrCores.close(); // It's still possible that one of the pending dynamic load operation is waiting, so wake it up if so. // Since all the pending operations queues have been drained, there should be nothing to do. synchronized (solrCores.getModifyLock()) { solrCores.getModifyLock().notifyAll(); // wake up the thread } customThreadPool.submit(() -> { replayUpdatesExecutor.shutdownAndAwaitTermination(); }); if (metricsHistoryHandler != null) { metricsHistoryHandler.close(); IOUtils.closeQuietly(metricsHistoryHandler.getSolrClient()); } if (metricManager != null) { metricManager.closeReporters(SolrMetricManager.getRegistryName(SolrInfoBean.Group.node)); metricManager.closeReporters(SolrMetricManager.getRegistryName(SolrInfoBean.Group.jvm)); metricManager.closeReporters(SolrMetricManager.getRegistryName(SolrInfoBean.Group.jetty)); metricManager.unregisterGauges(SolrMetricManager.getRegistryName(SolrInfoBean.Group.node), metricTag); metricManager.unregisterGauges(SolrMetricManager.getRegistryName(SolrInfoBean.Group.jvm), metricTag); metricManager.unregisterGauges(SolrMetricManager.getRegistryName(SolrInfoBean.Group.jetty), metricTag); } if (isZooKeeperAware()) { cancelCoreRecoveries(); if (metricManager != null) { metricManager.closeReporters(SolrMetricManager.getRegistryName(SolrInfoBean.Group.cluster)); } } try { if (coreAdminHandler != null) { customThreadPool.submit(() -> { coreAdminHandler.shutdown(); }); } } catch (Exception e) { log.warn("Error shutting down CoreAdminHandler. Continuing to close CoreContainer.", e); } } finally { try { if (shardHandlerFactory != null) { customThreadPool.submit(() -> { shardHandlerFactory.close(); }); } } finally { try { if (updateShardHandler != null) { customThreadPool.submit( () -> Collections.singleton(shardHandlerFactory).parallelStream().forEach(c -> { updateShardHandler.close(); })); } } finally { try { // we want to close zk stuff last zkSys.close(); } finally { ExecutorUtil.shutdownAndAwaitTermination(customThreadPool); } } } } // It should be safe to close the authorization plugin at this point. try { if (authorizationPlugin != null) { authorizationPlugin.plugin.close(); } } catch (IOException e) { log.warn("Exception while closing authorization plugin.", e); } // It should be safe to close the authentication plugin at this point. try { if (authenticationPlugin != null) { authenticationPlugin.plugin.close(); authenticationPlugin = null; } } catch (Exception e) { log.warn("Exception while closing authentication plugin.", e); } // It should be safe to close the auditlogger plugin at this point. try { if (auditloggerPlugin != null) { auditloggerPlugin.plugin.close(); auditloggerPlugin = null; } } catch (Exception e) { log.warn("Exception while closing auditlogger plugin.", e); } if (packageLoader != null) { org.apache.lucene.util.IOUtils.closeWhileHandlingException(packageLoader); } org.apache.lucene.util.IOUtils.closeWhileHandlingException(loader); // best effort } public void cancelCoreRecoveries() { List<SolrCore> cores = solrCores.getCores(); // we must cancel without holding the cores sync // make sure we wait for any recoveries to stop for (SolrCore core : cores) { try { core.getSolrCoreState().cancelRecovery(); } catch (Exception e) { SolrException.log(log, "Error canceling recovery for core", e); } } } public CoresLocator getCoresLocator() { return coresLocator; } protected SolrCore registerCore(CoreDescriptor cd, SolrCore core, boolean registerInZk, boolean skipRecovery) { if (core == null) { throw new RuntimeException("Can not register a null core."); } if (isShutDown) { core.close(); throw new IllegalStateException("This CoreContainer has been closed"); } SolrCore old = solrCores.putCore(cd, core); /* * set both the name of the descriptor and the name of the * core, since the descriptors name is used for persisting. */ core.setName(cd.getName()); coreInitFailures.remove(cd.getName()); if (old == null || old == core) { log.debug("registering core: " + cd.getName()); if (registerInZk) { zkSys.registerInZk(core, false, skipRecovery); } return null; } else { log.debug("replacing core: " + cd.getName()); old.close(); if (registerInZk) { zkSys.registerInZk(core, false, skipRecovery); } return old; } } /** * Creates a new core, publishing the core state to the cluster * * @param coreName the core name * @param parameters the core parameters * @return the newly created core */ public SolrCore create(String coreName, Map<String, String> parameters) { return create(coreName, cfg.getCoreRootDirectory().resolve(coreName), parameters, false); } /** * Creates a new core in a specified instance directory, publishing the core state to the cluster * * @param coreName the core name * @param instancePath the instance directory * @param parameters the core parameters * @return the newly created core */ public SolrCore create(String coreName, Path instancePath, Map<String, String> parameters, boolean newCollection) { CoreDescriptor cd = new CoreDescriptor(coreName, instancePath, parameters, getContainerProperties(), isZooKeeperAware()); // TODO: There's a race here, isn't there? // Since the core descriptor is removed when a core is unloaded, it should never be anywhere when a core is created. if (getAllCoreNames().contains(coreName)) { log.warn("Creating a core with existing name is not allowed"); // TODO: Shouldn't this be a BAD_REQUEST? throw new SolrException(ErrorCode.SERVER_ERROR, "Core with name '" + coreName + "' already exists."); } boolean preExisitingZkEntry = false; try { if (getZkController() != null) { if (!Overseer.isLegacy(getZkController().getZkStateReader())) { if (cd.getCloudDescriptor().getCoreNodeName() == null) { throw new SolrException(ErrorCode.SERVER_ERROR, "non legacy mode coreNodeName missing " + parameters.toString()); } } preExisitingZkEntry = getZkController().checkIfCoreNodeNameAlreadyExists(cd); } // Much of the logic in core handling pre-supposes that the core.properties file already exists, so create it // first and clean it up if there's an error. coresLocator.create(this, cd); SolrCore core = null; try { solrCores.waitAddPendingCoreOps(cd.getName()); core = createFromDescriptor(cd, true, newCollection); coresLocator.persist(this, cd); // Write out the current core properties in case anything changed when the core was created } finally { solrCores.removeFromPendingOps(cd.getName()); } return core; } catch (Exception ex) { // First clean up any core descriptor, there should never be an existing core.properties file for any core that // failed to be created on-the-fly. coresLocator.delete(this, cd); if (isZooKeeperAware() && !preExisitingZkEntry) { try { getZkController().unregister(coreName, cd); } catch (InterruptedException e) { Thread.currentThread().interrupt(); SolrException.log(log, null, e); } catch (KeeperException e) { SolrException.log(log, null, e); } catch (Exception e) { SolrException.log(log, null, e); } } Throwable tc = ex; Throwable c = null; do { tc = tc.getCause(); if (tc != null) { c = tc; } } while (tc != null); String rootMsg = ""; if (c != null) { rootMsg = " Caused by: " + c.getMessage(); } throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Error CREATEing SolrCore '" + coreName + "': " + ex.getMessage() + rootMsg, ex); } } /** * Creates a new core based on a CoreDescriptor. * * @param dcore a core descriptor * @param publishState publish core state to the cluster if true * <p> * WARNING: Any call to this method should be surrounded by a try/finally block * that calls solrCores.waitAddPendingCoreOps(...) and solrCores.removeFromPendingOps(...) * * <pre> * <code> * try { * solrCores.waitAddPendingCoreOps(dcore.getName()); * createFromDescriptor(...); * } finally { * solrCores.removeFromPendingOps(dcore.getName()); * } * </code> * </pre> * <p> * Trying to put the waitAddPending... in this method results in Bad Things Happening due to race conditions. * getCore() depends on getting the core returned _if_ it's in the pending list due to some other thread opening it. * If the core is not in the pending list and not loaded, then getCore() calls this method. Anything that called * to check if the core was loaded _or_ in pending ops and, based on the return called createFromDescriptor would * introduce a race condition, see getCore() for the place it would be a problem * @return the newly created core */ @SuppressWarnings("resource") private SolrCore createFromDescriptor(CoreDescriptor dcore, boolean publishState, boolean newCollection) { if (isShutDown) { throw new SolrException(ErrorCode.SERVICE_UNAVAILABLE, "Solr has been shutdown."); } SolrCore core = null; try { MDCLoggingContext.setCoreDescriptor(this, dcore); SolrIdentifierValidator.validateCoreName(dcore.getName()); if (zkSys.getZkController() != null) { zkSys.getZkController().preRegister(dcore, publishState); } ConfigSet coreConfig = getConfigSet(dcore); dcore.setConfigSetTrusted(coreConfig.isTrusted()); log.info("Creating SolrCore '{}' using configuration from {}, trusted={}", dcore.getName(), coreConfig.getName(), dcore.isConfigSetTrusted()); try { core = new SolrCore(this, dcore, coreConfig); } catch (SolrException e) { core = processCoreCreateException(e, dcore, coreConfig); } // always kick off recovery if we are in non-Cloud mode if (!isZooKeeperAware() && core.getUpdateHandler().getUpdateLog() != null) { core.getUpdateHandler().getUpdateLog().recoverFromLog(); } registerCore(dcore, core, publishState, newCollection); return core; } catch (Exception e) { coreInitFailures.put(dcore.getName(), new CoreLoadFailure(dcore, e)); if (e instanceof ZkController.NotInClusterStateException && !newCollection) { // this mostly happen when the core is deleted when this node is down unload(dcore.getName(), true, true, true); throw e; } solrCores.removeCoreDescriptor(dcore); final SolrException solrException = new SolrException(ErrorCode.SERVER_ERROR, "Unable to create core [" + dcore.getName() + "]", e); if (core != null && !core.isClosed()) IOUtils.closeQuietly(core); throw solrException; } catch (Throwable t) { SolrException e = new SolrException(ErrorCode.SERVER_ERROR, "JVM Error creating core [" + dcore.getName() + "]: " + t.getMessage(), t); coreInitFailures.put(dcore.getName(), new CoreLoadFailure(dcore, e)); solrCores.removeCoreDescriptor(dcore); if (core != null && !core.isClosed()) IOUtils.closeQuietly(core); throw t; } finally { MDCLoggingContext.clear(); } } public boolean isSharedFs(CoreDescriptor cd) { try (SolrCore core = this.getCore(cd.getName())) { if (core != null) { return core.getDirectoryFactory().isSharedStorage(); } else { ConfigSet configSet = getConfigSet(cd); return DirectoryFactory.loadDirectoryFactory(configSet.getSolrConfig(), this, null) .isSharedStorage(); } } } private ConfigSet getConfigSet(CoreDescriptor cd) { return coreConfigService.getConfig(cd); } /** * Take action when we failed to create a SolrCore. If error is due to corrupt index, try to recover. Various recovery * strategies can be specified via system properties "-DCoreInitFailedAction={fromleader, none}" * * @param original the problem seen when loading the core the first time. * @param dcore core descriptor for the core to create * @param coreConfig core config for the core to create * @return if possible * @throws SolrException rethrows the original exception if we will not attempt to recover, throws a new SolrException with the * original exception as a suppressed exception if there is a second problem creating the solr core. * @see CoreInitFailedAction */ private SolrCore processCoreCreateException(SolrException original, CoreDescriptor dcore, ConfigSet coreConfig) { // Traverse full chain since CIE may not be root exception Throwable cause = original; while ((cause = cause.getCause()) != null) { if (cause instanceof CorruptIndexException) { break; } } // If no CorruptIndexException, nothing we can try here if (cause == null) throw original; CoreInitFailedAction action = CoreInitFailedAction .valueOf(System.getProperty(CoreInitFailedAction.class.getSimpleName(), "none")); log.debug("CorruptIndexException while creating core, will attempt to repair via {}", action); switch (action) { case fromleader: // Recovery from leader on a CorruptedIndexException if (isZooKeeperAware()) { CloudDescriptor desc = dcore.getCloudDescriptor(); try { Replica leader = getZkController().getClusterState().getCollection(desc.getCollectionName()) .getSlice(desc.getShardId()).getLeader(); if (leader != null && leader.getState() == State.ACTIVE) { log.info("Found active leader, will attempt to create fresh core and recover."); resetIndexDirectory(dcore, coreConfig); // the index of this core is emptied, its term should be set to 0 getZkController().getShardTerms(desc.getCollectionName(), desc.getShardId()) .setTermToZero(desc.getCoreNodeName()); return new SolrCore(this, dcore, coreConfig); } } catch (SolrException se) { se.addSuppressed(original); throw se; } } throw original; case none: throw original; default: log.warn( "Failed to create core, and did not recognize specified 'CoreInitFailedAction': [{}]. Valid options are {}.", action, Arrays.asList(CoreInitFailedAction.values())); throw original; } } /** * Write a new index directory for the a SolrCore, but do so without loading it. */ private void resetIndexDirectory(CoreDescriptor dcore, ConfigSet coreConfig) { SolrConfig config = coreConfig.getSolrConfig(); String registryName = SolrMetricManager.getRegistryName(SolrInfoBean.Group.core, dcore.getName()); DirectoryFactory df = DirectoryFactory.loadDirectoryFactory(config, this, registryName); String dataDir = SolrCore.findDataDir(df, null, config, dcore); String tmpIdxDirName = "index." + new SimpleDateFormat(SnapShooter.DATE_FMT, Locale.ROOT).format(new Date()); SolrCore.modifyIndexProps(df, dataDir, config, tmpIdxDirName); // Free the directory object that we had to create for this Directory dir = null; try { dir = df.get(dataDir, DirContext.META_DATA, config.indexConfig.lockType); } catch (IOException e) { throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e); } finally { try { df.release(dir); df.doneWithDirectory(dir); } catch (IOException e) { SolrException.log(log, e); } } } /** * @return a Collection of registered SolrCores */ public Collection<SolrCore> getCores() { return solrCores.getCores(); } /** * Gets the cores that are currently loaded, i.e. cores that have * 1: loadOnStartup=true and are either not-transient or, if transient, have been loaded and have not been aged out * 2: loadOnStartup=false and have been loaded but are either non-transient or have not been aged out. * <p> * Put another way, this will not return any names of cores that are lazily loaded but have not been called for yet * or are transient and either not loaded or have been swapped out. */ public Collection<String> getLoadedCoreNames() { return solrCores.getLoadedCoreNames(); } /** * This method is currently experimental. * * @return a Collection of the names that a specific core object is mapped to, there are more than one. */ public Collection<String> getNamesForCore(SolrCore core) { return solrCores.getNamesForCore(core); } /** * get a list of all the cores that are currently known, whether currently loaded or not * * @return a list of all the available core names in either permanent or transient cores */ public Collection<String> getAllCoreNames() { return solrCores.getAllCoreNames(); } /** * Returns an immutable Map of Exceptions that occurred when initializing * SolrCores (either at startup, or do to runtime requests to create cores) * keyed off of the name (String) of the SolrCore that had the Exception * during initialization. * <p> * While the Map returned by this method is immutable and will not change * once returned to the client, the source data used to generate this Map * can be changed as various SolrCore operations are performed: * </p> * <ul> * <li>Failed attempts to create new SolrCores will add new Exceptions.</li> * <li>Failed attempts to re-create a SolrCore using a name already contained in this Map will replace the Exception.</li> * <li>Failed attempts to reload a SolrCore will cause an Exception to be added to this list -- even though the existing SolrCore with that name will continue to be available.</li> * <li>Successful attempts to re-created a SolrCore using a name already contained in this Map will remove the Exception.</li> * <li>Registering an existing SolrCore with a name already contained in this Map (ie: ALIAS or SWAP) will remove the Exception.</li> * </ul> */ public Map<String, CoreLoadFailure> getCoreInitFailures() { return ImmutableMap.copyOf(coreInitFailures); } // ---------------- Core name related methods --------------- private CoreDescriptor reloadCoreDescriptor(CoreDescriptor oldDesc) { if (oldDesc == null) { return null; } CorePropertiesLocator cpl = new CorePropertiesLocator(null); CoreDescriptor ret = cpl.buildCoreDescriptor(oldDesc.getInstanceDir().resolve(PROPERTIES_FILENAME), this); // Ok, this little jewel is all because we still create core descriptors on the fly from lists of properties // in tests particularly. Theoretically, there should be _no_ way to create a CoreDescriptor in the new world // of core discovery without writing the core.properties file out first. // // TODO: remove core.properties from the conf directory in test files, it's in a bad place there anyway. if (ret == null) { oldDesc.loadExtraProperties(); // there may be changes to extra properties that we need to pick up. return oldDesc; } // The CloudDescriptor bit here is created in a very convoluted way, requiring access to private methods // in ZkController. When reloading, this behavior is identical to what used to happen where a copy of the old // CoreDescriptor was just re-used. if (ret.getCloudDescriptor() != null) { ret.getCloudDescriptor().reload(oldDesc.getCloudDescriptor()); } return ret; } /** * Recreates a SolrCore. * While the new core is loading, requests will continue to be dispatched to * and processed by the old core * * @param name the name of the SolrCore to reload */ public void reload(String name) { if (isShutDown) { throw new AlreadyClosedException(); } SolrCore newCore = null; SolrCore core = solrCores.getCoreFromAnyList(name, false); if (core != null) { // The underlying core properties files may have changed, we don't really know. So we have a (perhaps) stale // CoreDescriptor and we need to reload it from the disk files CoreDescriptor cd = reloadCoreDescriptor(core.getCoreDescriptor()); solrCores.addCoreDescriptor(cd); Closeable oldCore = null; boolean success = false; try { solrCores.waitAddPendingCoreOps(cd.getName()); ConfigSet coreConfig = coreConfigService.getConfig(cd); log.info("Reloading SolrCore '{}' using configuration from {}", cd.getName(), coreConfig.getName()); newCore = core.reload(coreConfig); DocCollection docCollection = null; if (getZkController() != null) { docCollection = getZkController().getClusterState().getCollection(cd.getCollectionName()); // turn off indexing now, before the new core is registered if (docCollection.getBool(ZkStateReader.READ_ONLY, false)) { newCore.readOnly = true; } } registerCore(cd, newCore, false, false); // force commit on old core if the new one is readOnly and prevent any new updates if (newCore.readOnly) { RefCounted<IndexWriter> iwRef = core.getSolrCoreState().getIndexWriter(null); if (iwRef != null) { IndexWriter iw = iwRef.get(); // switch old core to readOnly core.readOnly = true; try { if (iw != null) { iw.commit(); } } finally { iwRef.decref(); } } } if (docCollection != null) { Replica replica = docCollection.getReplica(cd.getCloudDescriptor().getCoreNodeName()); assert replica != null; if (replica.getType() == Replica.Type.TLOG) { // TODO: needed here? getZkController().stopReplicationFromLeader(core.getName()); if (!cd.getCloudDescriptor().isLeader()) { getZkController().startReplicationFromLeader(newCore.getName(), true); } } else if (replica.getType() == Replica.Type.PULL) { getZkController().stopReplicationFromLeader(core.getName()); getZkController().startReplicationFromLeader(newCore.getName(), false); } } success = true; } catch (SolrCoreState.CoreIsClosedException e) { throw e; } catch (Exception e) { coreInitFailures.put(cd.getName(), new CoreLoadFailure(cd, (Exception) e)); throw new SolrException(ErrorCode.SERVER_ERROR, "Unable to reload core [" + cd.getName() + "]", e); } finally { if (!success && newCore != null && newCore.getOpenCount() > 0) { IOUtils.closeQuietly(newCore); } solrCores.removeFromPendingOps(cd.getName()); } } else { CoreLoadFailure clf = coreInitFailures.get(name); if (clf != null) { try { solrCores.waitAddPendingCoreOps(clf.cd.getName()); createFromDescriptor(clf.cd, true, false); } finally { solrCores.removeFromPendingOps(clf.cd.getName()); } } else { throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "No such core: " + name); } } } /** * Swaps two SolrCore descriptors. */ public void swap(String n0, String n1) { if (n0 == null || n1 == null) { throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Can not swap unnamed cores."); } solrCores.swap(n0, n1); coresLocator.swap(this, solrCores.getCoreDescriptor(n0), solrCores.getCoreDescriptor(n1)); log.info("swapped: " + n0 + " with " + n1); } /** * Unload a core from this container, leaving all files on disk * * @param name the name of the core to unload */ public void unload(String name) { unload(name, false, false, false); } /** * Unload a core from this container, optionally removing the core's data and configuration * * @param name the name of the core to unload * @param deleteIndexDir if true, delete the core's index on close * @param deleteDataDir if true, delete the core's data directory on close * @param deleteInstanceDir if true, delete the core's instance directory on close */ public void unload(String name, boolean deleteIndexDir, boolean deleteDataDir, boolean deleteInstanceDir) { CoreDescriptor cd = solrCores.getCoreDescriptor(name); if (name != null) { // check for core-init errors first CoreLoadFailure loadFailure = coreInitFailures.remove(name); if (loadFailure != null) { // getting the index directory requires opening a DirectoryFactory with a SolrConfig, etc, // which we may not be able to do because of the init error. So we just go with what we // can glean from the CoreDescriptor - datadir and instancedir SolrCore.deleteUnloadedCore(loadFailure.cd, deleteDataDir, deleteInstanceDir); // If last time around we didn't successfully load, make sure that all traces of the coreDescriptor are gone. if (cd != null) { solrCores.removeCoreDescriptor(cd); coresLocator.delete(this, cd); } return; } } if (cd == null) { throw new SolrException(ErrorCode.BAD_REQUEST, "Cannot unload non-existent core [" + name + "]"); } boolean close = solrCores.isLoadedNotPendingClose(name); SolrCore core = solrCores.remove(name); solrCores.removeCoreDescriptor(cd); coresLocator.delete(this, cd); if (core == null) { // transient core SolrCore.deleteUnloadedCore(cd, deleteDataDir, deleteInstanceDir); return; } // delete metrics specific to this core metricManager.removeRegistry(core.getCoreMetricManager().getRegistryName()); if (zkSys.getZkController() != null) { // cancel recovery in cloud mode core.getSolrCoreState().cancelRecovery(); if (cd.getCloudDescriptor().getReplicaType() == Replica.Type.PULL || cd.getCloudDescriptor().getReplicaType() == Replica.Type.TLOG) { // Stop replication if this is part of a pull/tlog replica before closing the core zkSys.getZkController().stopReplicationFromLeader(name); } } core.unloadOnClose(cd, deleteIndexDir, deleteDataDir, deleteInstanceDir); if (close) core.closeAndWait(); if (zkSys.getZkController() != null) { try { zkSys.getZkController().unregister(name, cd); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new SolrException(ErrorCode.SERVER_ERROR, "Interrupted while unregistering core [" + name + "] from cloud state"); } catch (KeeperException e) { throw new SolrException(ErrorCode.SERVER_ERROR, "Error unregistering core [" + name + "] from cloud state", e); } catch (Exception e) { throw new SolrException(ErrorCode.SERVER_ERROR, "Error unregistering core [" + name + "] from cloud state", e); } } } public void rename(String name, String toName) { SolrIdentifierValidator.validateCoreName(toName); try (SolrCore core = getCore(name)) { if (core != null) { String oldRegistryName = core.getCoreMetricManager().getRegistryName(); String newRegistryName = SolrCoreMetricManager.createRegistryName(core, toName); metricManager.swapRegistries(oldRegistryName, newRegistryName); // The old coreDescriptor is obsolete, so remove it. registerCore will put it back. CoreDescriptor cd = core.getCoreDescriptor(); solrCores.removeCoreDescriptor(cd); cd.setProperty("name", toName); solrCores.addCoreDescriptor(cd); core.setName(toName); registerCore(cd, core, true, false); SolrCore old = solrCores.remove(name); coresLocator.rename(this, old.getCoreDescriptor(), core.getCoreDescriptor()); } } } /** * Get the CoreDescriptors for all cores managed by this container * * @return a List of CoreDescriptors */ public List<CoreDescriptor> getCoreDescriptors() { return solrCores.getCoreDescriptors(); } public CoreDescriptor getCoreDescriptor(String coreName) { return solrCores.getCoreDescriptor(coreName); } public Path getCoreRootDirectory() { return cfg.getCoreRootDirectory(); } /** * Gets a core by name and increase its refcount. * * @param name the core name * @return the core if found, null if a SolrCore by this name does not exist * @throws SolrCoreInitializationException if a SolrCore with this name failed to be initialized * @see SolrCore#close() */ public SolrCore getCore(String name) { // Do this in two phases since we don't want to lock access to the cores over a load. SolrCore core = solrCores.getCoreFromAnyList(name, true); // If a core is loaded, we're done just return it. if (core != null) { return core; } // If it's not yet loaded, we can check if it's had a core init failure and "do the right thing" CoreDescriptor desc = solrCores.getCoreDescriptor(name); // if there was an error initializing this core, throw a 500 // error with the details for clients attempting to access it. CoreLoadFailure loadFailure = getCoreInitFailures().get(name); if (null != loadFailure) { throw new SolrCoreInitializationException(name, loadFailure.exception); } // This is a bit of awkwardness where SolrCloud and transient cores don't play nice together. For transient cores, // we have to allow them to be created at any time there hasn't been a core load failure (use reload to cure that). // But for TestConfigSetsAPI.testUploadWithScriptUpdateProcessor, this needs to _not_ try to load the core if // the core is null and there was an error. If you change this, be sure to run both TestConfiSetsAPI and // TestLazyCores if (desc == null || zkSys.getZkController() != null) return null; // This will put an entry in pending core ops if the core isn't loaded. Here's where moving the // waitAddPendingCoreOps to createFromDescriptor would introduce a race condition. core = solrCores.waitAddPendingCoreOps(name); if (isShutDown) return null; // We're quitting, so stop. This needs to be after the wait above since we may come off // the wait as a consequence of shutting down. try { if (core == null) { if (zkSys.getZkController() != null) { zkSys.getZkController().throwErrorIfReplicaReplaced(desc); } core = createFromDescriptor(desc, true, false); // This should throw an error if it fails. } core.open(); } finally { solrCores.removeFromPendingOps(name); } return core; } public BlobRepository getBlobRepository() { return blobRepository; } /** * If using asyncSolrCoreLoad=true, calling this after {@link #load()} will * not return until all cores have finished loading. * * @param timeoutMs timeout, upon which method simply returns */ public void waitForLoadingCoresToFinish(long timeoutMs) { solrCores.waitForLoadingCoresToFinish(timeoutMs); } public void waitForLoadingCore(String name, long timeoutMs) { solrCores.waitForLoadingCoreToFinish(name, timeoutMs); } // ---------------- CoreContainer request handlers -------------- protected <T> T createHandler(String path, String handlerClass, Class<T> clazz) { T handler = loader.newInstance(handlerClass, clazz, null, new Class[] { CoreContainer.class }, new Object[] { this }); if (handler instanceof SolrRequestHandler) { containerHandlers.put(path, (SolrRequestHandler) handler); } if (handler instanceof SolrMetricProducer) { ((SolrMetricProducer) handler).initializeMetrics(solrMetricsContext, path); } return handler; } public CoreAdminHandler getMultiCoreHandler() { return coreAdminHandler; } public CollectionsHandler getCollectionsHandler() { return collectionsHandler; } public HealthCheckHandler getHealthCheckHandler() { return healthCheckHandler; } public InfoHandler getInfoHandler() { return infoHandler; } public ConfigSetsHandler getConfigSetsHandler() { return configSetsHandler; } public String getHostName() { return this.hostName; } /** * Gets the alternate path for multicore handling: * This is used in case there is a registered unnamed core (aka name is "") to * declare an alternate way of accessing named cores. * This can also be used in a pseudo single-core environment so admins can prepare * a new version before swapping. */ public String getManagementPath() { return cfg.getManagementPath(); } public LogWatcher getLogging() { return logging; } /** * Determines whether the core is already loaded or not but does NOT load the core */ public boolean isLoaded(String name) { return solrCores.isLoaded(name); } public boolean isLoadedNotPendingClose(String name) { return solrCores.isLoadedNotPendingClose(name); } // Primarily for transient cores when a core is aged out. public void queueCoreToClose(SolrCore coreToClose) { solrCores.queueCoreToClose(coreToClose); } /** * Gets a solr core descriptor for a core that is not loaded. Note that if the caller calls this on a * loaded core, the unloaded descriptor will be returned. * * @param cname - name of the unloaded core descriptor to load. NOTE: * @return a coreDescriptor. May return null */ public CoreDescriptor getUnloadedCoreDescriptor(String cname) { return solrCores.getUnloadedCoreDescriptor(cname); } public String getSolrHome() { return solrHome; } public boolean isZooKeeperAware() { return zkSys.getZkController() != null; } public ZkController getZkController() { return zkSys.getZkController(); } public NodeConfig getConfig() { return cfg; } /** * The default ShardHandlerFactory used to communicate with other solr instances */ public ShardHandlerFactory getShardHandlerFactory() { return shardHandlerFactory; } public UpdateShardHandler getUpdateShardHandler() { return updateShardHandler; } public SolrResourceLoader getResourceLoader() { return loader; } public boolean isCoreLoading(String name) { return solrCores.isCoreLoading(name); } public AuthorizationPlugin getAuthorizationPlugin() { return authorizationPlugin == null ? null : authorizationPlugin.plugin; } public AuthenticationPlugin getAuthenticationPlugin() { return authenticationPlugin == null ? null : authenticationPlugin.plugin; } public AuditLoggerPlugin getAuditLoggerPlugin() { return auditloggerPlugin == null ? null : auditloggerPlugin.plugin; } public NodeConfig getNodeConfig() { return cfg; } public long getStatus() { return status; } // Occasionally we need to access the transient cache handler in places other than coreContainer. public TransientSolrCoreCache getTransientCache() { return solrCores.getTransientCacheHandler(); } /** * @param cd CoreDescriptor, presumably a deficient one * @param prop The property that needs to be repaired. * @return true if we were able to successfuly perisist the repaired coreDescriptor, false otherwise. * <p> * See SOLR-11503, This can be removed when there's no chance we'll need to upgrade a * Solr installation created with legacyCloud=true from 6.6.1 through 7.1 */ public boolean repairCoreProperty(CoreDescriptor cd, String prop) { // So far, coreNodeName is the only property that we need to repair, this may get more complex as other properties // are added. if (CoreDescriptor.CORE_NODE_NAME.equals(prop) == false) { throw new SolrException(ErrorCode.SERVER_ERROR, String.format(Locale.ROOT, "The only supported property for repair is currently [%s]", CoreDescriptor.CORE_NODE_NAME)); } // Try to read the coreNodeName from the cluster state. String coreName = cd.getName(); DocCollection coll = getZkController().getZkStateReader().getClusterState() .getCollection(cd.getCollectionName()); for (Replica rep : coll.getReplicas()) { if (coreName.equals(rep.getCoreName())) { log.warn( "Core properties file for node {} found with no coreNodeName, attempting to repair with value {}. See SOLR-11503. " + "This message should only appear if upgrading from collections created Solr 6.6.1 through 7.1.", rep.getCoreName(), rep.getName()); cd.getCloudDescriptor().setCoreNodeName(rep.getName()); coresLocator.persist(this, cd); return true; } } log.error("Could not repair coreNodeName in core.properties file for core {}", coreName); return false; } /** * @param solrCore the core against which we check if there has been a tragic exception * @return whether this Solr core has tragic exception * @see org.apache.lucene.index.IndexWriter#getTragicException() */ public boolean checkTragicException(SolrCore solrCore) { Throwable tragicException; try { tragicException = solrCore.getSolrCoreState().getTragicException(); } catch (IOException e) { // failed to open an indexWriter tragicException = e; } if (tragicException != null && isZooKeeperAware()) { getZkController().giveupLeadership(solrCore.getCoreDescriptor(), tragicException); } return tragicException != null; } static { ExecutorUtil.addThreadLocalProvider(SolrRequestInfo.getInheritableThreadLocalProvider()); } /** * Run an arbitrary task in it's own thread. This is an expert option and is * a method you should use with great care. It would be bad to run something that never stopped * or run something that took a very long time. Typically this is intended for actions that take * a few seconds, and therefore would be bad to wait for within a request, or actions that need to happen * when a core has zero references, but but would not pose a significant hindrance to server shut down times. * It is not intended for long running tasks and if you are using a Runnable with a loop in it, you are * almost certainly doing it wrong. * <p><br> * WARNING: Solr wil not be able to shut down gracefully until this task completes! * <p><br> * A significant upside of using this method vs creating your own ExecutorService is that your code * does not have to properly shutdown executors which typically is risky from a unit testing * perspective since the test framework will complain if you don't carefully ensure the executor * shuts down before the end of the test. Also the threads running this task are sure to have * a proper MDC for logging. * <p><br> * Normally, one uses {@link SolrCore#runAsync(Runnable)} if possible, but in some cases * you might need to execute a task asynchronously when you could be running on a node with no * cores, and then use of this method is indicated. * * @param r the task to run */ public void runAsync(Runnable r) { coreContainerAsyncTaskExecutor.submit(r); } } class CloserThread extends Thread { CoreContainer container; SolrCores solrCores; NodeConfig cfg; CloserThread(CoreContainer container, SolrCores solrCores, NodeConfig cfg) { this.container = container; this.solrCores = solrCores; this.cfg = cfg; } // It's important that this be the _only_ thread removing things from pendingDynamicCloses! // This is single-threaded, but I tried a multi-threaded approach and didn't see any performance gains, so // there's no good justification for the complexity. I suspect that the locking on things like DefaultSolrCoreState // essentially create a single-threaded process anyway. @Override public void run() { while (!container.isShutDown()) { synchronized (solrCores.getModifyLock()) { // need this so we can wait and be awoken. try { solrCores.getModifyLock().wait(); } catch (InterruptedException e) { // Well, if we've been told to stop, we will. Otherwise, continue on and check to see if there are // any cores to close. } } for (SolrCore removeMe = solrCores.getCoreToClose(); removeMe != null && !container.isShutDown(); removeMe = solrCores.getCoreToClose()) { try { removeMe.close(); } finally { solrCores.removeFromPendingOps(removeMe.getName()); } } } } }