mirror of https://github.com/apache/lucene.git
SOLR-9836: Add ability to recover from leader when index corruption is detected on SolrCore creation.
This commit is contained in:
parent
a37bfa75a0
commit
a89560bb72
|
@ -90,6 +90,12 @@ Jetty 9.3.14.v20161028
|
||||||
Detailed Change List
|
Detailed Change List
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
New Features
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
* SOLR-9836: Add ability to recover from leader when index corruption is detected on SolrCore creation.
|
||||||
|
(Mike Drob via Mark Miller)
|
||||||
|
|
||||||
Bug Fixes
|
Bug Fixes
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
|
|
@ -20,9 +20,12 @@ import java.io.IOException;
|
||||||
import java.lang.invoke.MethodHandles;
|
import java.lang.invoke.MethodHandles;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -39,22 +42,29 @@ import com.google.common.collect.Maps;
|
||||||
import org.apache.http.auth.AuthSchemeProvider;
|
import org.apache.http.auth.AuthSchemeProvider;
|
||||||
import org.apache.http.client.CredentialsProvider;
|
import org.apache.http.client.CredentialsProvider;
|
||||||
import org.apache.http.config.Lookup;
|
import org.apache.http.config.Lookup;
|
||||||
|
import org.apache.lucene.index.CorruptIndexException;
|
||||||
|
import org.apache.lucene.store.Directory;
|
||||||
import org.apache.solr.client.solrj.impl.HttpClientUtil;
|
import org.apache.solr.client.solrj.impl.HttpClientUtil;
|
||||||
import org.apache.solr.client.solrj.impl.SolrHttpClientBuilder;
|
import org.apache.solr.client.solrj.impl.SolrHttpClientBuilder;
|
||||||
import org.apache.solr.client.solrj.impl.SolrHttpClientContextBuilder;
|
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.AuthSchemeRegistryProvider;
|
||||||
import org.apache.solr.client.solrj.impl.SolrHttpClientContextBuilder.CredentialsProviderProvider;
|
import org.apache.solr.client.solrj.impl.SolrHttpClientContextBuilder.CredentialsProviderProvider;
|
||||||
import org.apache.solr.client.solrj.util.SolrIdentifierValidator;
|
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.Overseer;
|
||||||
import org.apache.solr.cloud.ZkController;
|
import org.apache.solr.cloud.ZkController;
|
||||||
import org.apache.solr.common.SolrException;
|
import org.apache.solr.common.SolrException;
|
||||||
import org.apache.solr.common.SolrException.ErrorCode;
|
import org.apache.solr.common.SolrException.ErrorCode;
|
||||||
|
import org.apache.solr.common.cloud.Replica;
|
||||||
|
import org.apache.solr.common.cloud.Replica.State;
|
||||||
import org.apache.solr.common.util.ExecutorUtil;
|
import org.apache.solr.common.util.ExecutorUtil;
|
||||||
import org.apache.solr.common.util.IOUtils;
|
import org.apache.solr.common.util.IOUtils;
|
||||||
import org.apache.solr.common.util.Utils;
|
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.BackupRepository;
|
||||||
import org.apache.solr.core.backup.repository.BackupRepositoryFactory;
|
import org.apache.solr.core.backup.repository.BackupRepositoryFactory;
|
||||||
import org.apache.solr.handler.RequestHandlerBase;
|
import org.apache.solr.handler.RequestHandlerBase;
|
||||||
|
import org.apache.solr.handler.SnapShooter;
|
||||||
import org.apache.solr.handler.admin.CollectionsHandler;
|
import org.apache.solr.handler.admin.CollectionsHandler;
|
||||||
import org.apache.solr.handler.admin.ConfigSetsHandler;
|
import org.apache.solr.handler.admin.ConfigSetsHandler;
|
||||||
import org.apache.solr.handler.admin.CoreAdminHandler;
|
import org.apache.solr.handler.admin.CoreAdminHandler;
|
||||||
|
@ -166,6 +176,8 @@ public class CoreContainer {
|
||||||
|
|
||||||
protected MetricsHandler metricsHandler;
|
protected MetricsHandler metricsHandler;
|
||||||
|
|
||||||
|
private enum CoreInitFailedAction { fromleader, none }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method instantiates a new instance of {@linkplain BackupRepository}.
|
* This method instantiates a new instance of {@linkplain BackupRepository}.
|
||||||
*
|
*
|
||||||
|
@ -911,7 +923,11 @@ public class CoreContainer {
|
||||||
|
|
||||||
ConfigSet coreConfig = coreConfigService.getConfig(dcore);
|
ConfigSet coreConfig = coreConfigService.getConfig(dcore);
|
||||||
log.info("Creating SolrCore '{}' using configuration from {}", dcore.getName(), coreConfig.getName());
|
log.info("Creating SolrCore '{}' using configuration from {}", dcore.getName(), coreConfig.getName());
|
||||||
core = new SolrCore(dcore, coreConfig);
|
try {
|
||||||
|
core = new SolrCore(dcore, coreConfig);
|
||||||
|
} catch (SolrException e) {
|
||||||
|
core = processCoreCreateException(e, dcore, coreConfig);
|
||||||
|
}
|
||||||
|
|
||||||
// always kick off recovery if we are in non-Cloud mode
|
// always kick off recovery if we are in non-Cloud mode
|
||||||
if (!isZooKeeperAware() && core.getUpdateHandler().getUpdateLog() != null) {
|
if (!isZooKeeperAware() && core.getUpdateHandler().getUpdateLog() != null) {
|
||||||
|
@ -923,14 +939,12 @@ public class CoreContainer {
|
||||||
return core;
|
return core;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
coreInitFailures.put(dcore.getName(), new CoreLoadFailure(dcore, e));
|
coreInitFailures.put(dcore.getName(), new CoreLoadFailure(dcore, e));
|
||||||
log.error("Error creating core [{}]: {}", dcore.getName(), e.getMessage(), e);
|
|
||||||
final SolrException solrException = new SolrException(ErrorCode.SERVER_ERROR, "Unable to create core [" + dcore.getName() + "]", e);
|
final SolrException solrException = new SolrException(ErrorCode.SERVER_ERROR, "Unable to create core [" + dcore.getName() + "]", e);
|
||||||
if(core != null && !core.isClosed())
|
if(core != null && !core.isClosed())
|
||||||
IOUtils.closeQuietly(core);
|
IOUtils.closeQuietly(core);
|
||||||
throw solrException;
|
throw solrException;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
SolrException e = new SolrException(ErrorCode.SERVER_ERROR, "JVM Error creating core [" + dcore.getName() + "]: " + t.getMessage(), t);
|
SolrException e = new SolrException(ErrorCode.SERVER_ERROR, "JVM Error creating core [" + dcore.getName() + "]: " + t.getMessage(), t);
|
||||||
log.error("Error creating core [{}]: {}", dcore.getName(), t.getMessage(), t);
|
|
||||||
coreInitFailures.put(dcore.getName(), new CoreLoadFailure(dcore, e));
|
coreInitFailures.put(dcore.getName(), new CoreLoadFailure(dcore, e));
|
||||||
if(core != null && !core.isClosed())
|
if(core != null && !core.isClosed())
|
||||||
IOUtils.closeQuietly(core);
|
IOUtils.closeQuietly(core);
|
||||||
|
@ -938,7 +952,96 @@ public class CoreContainer {
|
||||||
} finally {
|
} finally {
|
||||||
MDCLoggingContext.clear();
|
MDCLoggingContext.clear();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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}"
|
||||||
|
*
|
||||||
|
* @see CoreInitFailedAction
|
||||||
|
*
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
|
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 CorruptIndexExeption, 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);
|
||||||
|
return new SolrCore(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(SolrInfoMBean.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -383,4 +383,31 @@ public abstract class DirectoryFactory implements NamedListInitializedPlugin,
|
||||||
|
|
||||||
return baseDir;
|
return baseDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new DirectoryFactory instance from the given SolrConfig and tied to the specified core container.
|
||||||
|
*/
|
||||||
|
static DirectoryFactory loadDirectoryFactory(SolrConfig config, CoreContainer cc, String registryName) {
|
||||||
|
final PluginInfo info = config.getPluginInfo(DirectoryFactory.class.getName());
|
||||||
|
final DirectoryFactory dirFactory;
|
||||||
|
if (info != null) {
|
||||||
|
log.debug(info.className);
|
||||||
|
dirFactory = config.getResourceLoader().newInstance(info.className, DirectoryFactory.class);
|
||||||
|
// allow DirectoryFactory instances to access the CoreContainer
|
||||||
|
dirFactory.initCoreContainer(cc);
|
||||||
|
dirFactory.init(info.initArgs);
|
||||||
|
} else {
|
||||||
|
log.debug("solr.NRTCachingDirectoryFactory");
|
||||||
|
dirFactory = new NRTCachingDirectoryFactory();
|
||||||
|
dirFactory.initCoreContainer(cc);
|
||||||
|
}
|
||||||
|
if (config.indexConfig.metricsInfo != null && config.indexConfig.metricsInfo.isEnabled()) {
|
||||||
|
final DirectoryFactory factory = new MetricsDirectoryFactory(cc.getMetricManager(),
|
||||||
|
registryName, dirFactory);
|
||||||
|
factory.init(config.indexConfig.metricsInfo.initArgs);
|
||||||
|
return factory;
|
||||||
|
} else {
|
||||||
|
return dirFactory;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,8 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.io.Writer;
|
||||||
import java.lang.invoke.MethodHandles;
|
import java.lang.invoke.MethodHandles;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
@ -67,6 +69,7 @@ import org.apache.lucene.search.BooleanQuery;
|
||||||
import org.apache.lucene.store.Directory;
|
import org.apache.lucene.store.Directory;
|
||||||
import org.apache.lucene.store.IOContext;
|
import org.apache.lucene.store.IOContext;
|
||||||
import org.apache.lucene.store.IndexInput;
|
import org.apache.lucene.store.IndexInput;
|
||||||
|
import org.apache.lucene.store.IndexOutput;
|
||||||
import org.apache.lucene.store.LockObtainFailedException;
|
import org.apache.lucene.store.LockObtainFailedException;
|
||||||
import org.apache.solr.client.solrj.impl.BinaryResponseParser;
|
import org.apache.solr.client.solrj.impl.BinaryResponseParser;
|
||||||
import org.apache.solr.cloud.CloudDescriptor;
|
import org.apache.solr.cloud.CloudDescriptor;
|
||||||
|
@ -148,6 +151,7 @@ import org.apache.solr.update.processor.UpdateRequestProcessorFactory;
|
||||||
import org.apache.solr.util.DefaultSolrThreadFactory;
|
import org.apache.solr.util.DefaultSolrThreadFactory;
|
||||||
import org.apache.solr.util.NumberUtils;
|
import org.apache.solr.util.NumberUtils;
|
||||||
import org.apache.solr.util.PropertiesInputStream;
|
import org.apache.solr.util.PropertiesInputStream;
|
||||||
|
import org.apache.solr.util.PropertiesOutputStream;
|
||||||
import org.apache.solr.util.RefCounted;
|
import org.apache.solr.util.RefCounted;
|
||||||
import org.apache.solr.util.plugin.NamedListInitializedPlugin;
|
import org.apache.solr.util.plugin.NamedListInitializedPlugin;
|
||||||
import org.apache.solr.util.plugin.PluginInfoInitialized;
|
import org.apache.solr.util.plugin.PluginInfoInitialized;
|
||||||
|
@ -646,27 +650,7 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private DirectoryFactory initDirectoryFactory() {
|
private DirectoryFactory initDirectoryFactory() {
|
||||||
final PluginInfo info = solrConfig.getPluginInfo(DirectoryFactory.class.getName());
|
return DirectoryFactory.loadDirectoryFactory(solrConfig, getCoreDescriptor().getCoreContainer(), coreMetricManager.getRegistryName());
|
||||||
final DirectoryFactory dirFactory;
|
|
||||||
if (info != null) {
|
|
||||||
log.debug(info.className);
|
|
||||||
dirFactory = getResourceLoader().newInstance(info.className, DirectoryFactory.class);
|
|
||||||
// allow DirectoryFactory instances to access the CoreContainer
|
|
||||||
dirFactory.initCoreContainer(getCoreDescriptor().getCoreContainer());
|
|
||||||
dirFactory.init(info.initArgs);
|
|
||||||
} else {
|
|
||||||
log.debug("solr.NRTCachingDirectoryFactory");
|
|
||||||
dirFactory = new NRTCachingDirectoryFactory();
|
|
||||||
dirFactory.initCoreContainer(getCoreDescriptor().getCoreContainer());
|
|
||||||
}
|
|
||||||
if (solrConfig.indexConfig.metricsInfo != null && solrConfig.indexConfig.metricsInfo.isEnabled()) {
|
|
||||||
final DirectoryFactory factory = new MetricsDirectoryFactory(coreDescriptor.getCoreContainer().getMetricManager(),
|
|
||||||
coreMetricManager.getRegistryName(), dirFactory);
|
|
||||||
factory.init(solrConfig.indexConfig.metricsInfo.initArgs);
|
|
||||||
return factory;
|
|
||||||
} else {
|
|
||||||
return dirFactory;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initIndexReaderFactory() {
|
private void initIndexReaderFactory() {
|
||||||
|
@ -1145,6 +1129,26 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private String initDataDir(String dataDir, SolrConfig config, CoreDescriptor coreDescriptor) {
|
private String initDataDir(String dataDir, SolrConfig config, CoreDescriptor coreDescriptor) {
|
||||||
|
return findDataDir(getDirectoryFactory(), dataDir, config, coreDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Locate the data directory for a given config and core descriptor.
|
||||||
|
*
|
||||||
|
* @param directoryFactory
|
||||||
|
* The directory factory to use if necessary to calculate an absolute path. Should be the same as what will
|
||||||
|
* be used to open the data directory later.
|
||||||
|
* @param dataDir
|
||||||
|
* An optional hint to the data directory location. Will be normalized and used if not null.
|
||||||
|
* @param config
|
||||||
|
* A solr config to retrieve the default data directory location, if used.
|
||||||
|
* @param coreDescriptor
|
||||||
|
* descriptor to load the actual data dir from, if not using the defualt.
|
||||||
|
* @return a normalized data directory name
|
||||||
|
* @throws SolrException
|
||||||
|
* if the data directory cannot be loaded from the core descriptor
|
||||||
|
*/
|
||||||
|
static String findDataDir(DirectoryFactory directoryFactory, String dataDir, SolrConfig config, CoreDescriptor coreDescriptor) {
|
||||||
if (dataDir == null) {
|
if (dataDir == null) {
|
||||||
if (coreDescriptor.usingDefaultDataDir()) {
|
if (coreDescriptor.usingDefaultDataDir()) {
|
||||||
dataDir = config.getDataDir();
|
dataDir = config.getDataDir();
|
||||||
|
@ -1163,6 +1167,80 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
|
||||||
return SolrResourceLoader.normalizeDir(dataDir);
|
return SolrResourceLoader.normalizeDir(dataDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean modifyIndexProps(String tmpIdxDirName) {
|
||||||
|
return SolrCore.modifyIndexProps(getDirectoryFactory(), getDataDir(), getSolrConfig(), tmpIdxDirName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the index.properties file with the new index sub directory name
|
||||||
|
*/
|
||||||
|
// package private
|
||||||
|
static boolean modifyIndexProps(DirectoryFactory directoryFactory, String dataDir, SolrConfig solrConfig, String tmpIdxDirName) {
|
||||||
|
log.info("Updating index properties... index="+tmpIdxDirName);
|
||||||
|
Directory dir = null;
|
||||||
|
try {
|
||||||
|
dir = directoryFactory.get(dataDir, DirContext.META_DATA, solrConfig.indexConfig.lockType);
|
||||||
|
String tmpIdxPropName = IndexFetcher.INDEX_PROPERTIES + "." + System.nanoTime();
|
||||||
|
writeNewIndexProps(dir, tmpIdxPropName, tmpIdxDirName);
|
||||||
|
directoryFactory.renameWithOverwrite(dir, tmpIdxPropName, IndexFetcher.INDEX_PROPERTIES);
|
||||||
|
return true;
|
||||||
|
} catch (IOException e1) {
|
||||||
|
throw new RuntimeException(e1);
|
||||||
|
} finally {
|
||||||
|
if (dir != null) {
|
||||||
|
try {
|
||||||
|
directoryFactory.release(dir);
|
||||||
|
} catch (IOException e) {
|
||||||
|
SolrException.log(log, "", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the index.properties file with the new index sub directory name
|
||||||
|
* @param dir a data directory (containing an index.properties file)
|
||||||
|
* @param tmpFileName the file name to write the new index.properties to
|
||||||
|
* @param tmpIdxDirName new index directory name
|
||||||
|
*/
|
||||||
|
private static void writeNewIndexProps(Directory dir, String tmpFileName, String tmpIdxDirName) {
|
||||||
|
if (tmpFileName == null) {
|
||||||
|
tmpFileName = IndexFetcher.INDEX_PROPERTIES;
|
||||||
|
}
|
||||||
|
final Properties p = new Properties();
|
||||||
|
|
||||||
|
// Read existing properties
|
||||||
|
try {
|
||||||
|
final IndexInput input = dir.openInput(IndexFetcher.INDEX_PROPERTIES, DirectoryFactory.IOCONTEXT_NO_CACHE);
|
||||||
|
final InputStream is = new PropertiesInputStream(input);
|
||||||
|
try {
|
||||||
|
p.load(new InputStreamReader(is, StandardCharsets.UTF_8));
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Unable to load " + IndexFetcher.INDEX_PROPERTIES, e);
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(is);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// ignore; file does not exist
|
||||||
|
}
|
||||||
|
|
||||||
|
p.put("index", tmpIdxDirName);
|
||||||
|
|
||||||
|
// Write new properties
|
||||||
|
Writer os = null;
|
||||||
|
try {
|
||||||
|
IndexOutput out = dir.createOutput(tmpFileName, DirectoryFactory.IOCONTEXT_NO_CACHE);
|
||||||
|
os = new OutputStreamWriter(new PropertiesOutputStream(out), StandardCharsets.UTF_8);
|
||||||
|
p.store(os, IndexFetcher.INDEX_PROPERTIES);
|
||||||
|
dir.sync(Collections.singleton(tmpFileName));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unable to write " + IndexFetcher.INDEX_PROPERTIES, e);
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(os);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private String initUpdateLogDir(CoreDescriptor coreDescriptor) {
|
private String initUpdateLogDir(CoreDescriptor coreDescriptor) {
|
||||||
String updateLogDir = coreDescriptor.getUlogDir();
|
String updateLogDir = coreDescriptor.getUlogDir();
|
||||||
if (updateLogDir == null) {
|
if (updateLogDir == null) {
|
||||||
|
|
|
@ -21,7 +21,6 @@ import java.io.FileNotFoundException;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.OutputStreamWriter;
|
import java.io.OutputStreamWriter;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
import java.lang.invoke.MethodHandles;
|
import java.lang.invoke.MethodHandles;
|
||||||
|
@ -92,7 +91,6 @@ import org.apache.solr.update.UpdateLog;
|
||||||
import org.apache.solr.update.VersionInfo;
|
import org.apache.solr.update.VersionInfo;
|
||||||
import org.apache.solr.util.DefaultSolrThreadFactory;
|
import org.apache.solr.util.DefaultSolrThreadFactory;
|
||||||
import org.apache.solr.util.FileUtils;
|
import org.apache.solr.util.FileUtils;
|
||||||
import org.apache.solr.util.PropertiesInputStream;
|
|
||||||
import org.apache.solr.util.PropertiesOutputStream;
|
import org.apache.solr.util.PropertiesOutputStream;
|
||||||
import org.apache.solr.util.RTimer;
|
import org.apache.solr.util.RTimer;
|
||||||
import org.apache.solr.util.RefCounted;
|
import org.apache.solr.util.RefCounted;
|
||||||
|
@ -460,7 +458,7 @@ public class IndexFetcher {
|
||||||
reloadCore = true;
|
reloadCore = true;
|
||||||
downloadConfFiles(confFilesToDownload, latestGeneration);
|
downloadConfFiles(confFilesToDownload, latestGeneration);
|
||||||
if (isFullCopyNeeded) {
|
if (isFullCopyNeeded) {
|
||||||
successfulInstall = IndexFetcher.modifyIndexProps(solrCore, tmpIdxDirName);
|
successfulInstall = solrCore.modifyIndexProps(tmpIdxDirName);
|
||||||
deleteTmpIdxDir = false;
|
deleteTmpIdxDir = false;
|
||||||
} else {
|
} else {
|
||||||
successfulInstall = moveIndexFiles(tmpIndexDir, indexDir);
|
successfulInstall = moveIndexFiles(tmpIndexDir, indexDir);
|
||||||
|
@ -488,7 +486,7 @@ public class IndexFetcher {
|
||||||
} else {
|
} else {
|
||||||
terminateAndWaitFsyncService();
|
terminateAndWaitFsyncService();
|
||||||
if (isFullCopyNeeded) {
|
if (isFullCopyNeeded) {
|
||||||
successfulInstall = IndexFetcher.modifyIndexProps(solrCore, tmpIdxDirName);
|
successfulInstall = solrCore.modifyIndexProps(tmpIdxDirName);
|
||||||
deleteTmpIdxDir = false;
|
deleteTmpIdxDir = false;
|
||||||
} else {
|
} else {
|
||||||
successfulInstall = moveIndexFiles(tmpIndexDir, indexDir);
|
successfulInstall = moveIndexFiles(tmpIndexDir, indexDir);
|
||||||
|
@ -1189,60 +1187,6 @@ public class IndexFetcher {
|
||||||
return new SimpleDateFormat(SnapShooter.DATE_FMT, Locale.ROOT).format(d);
|
return new SimpleDateFormat(SnapShooter.DATE_FMT, Locale.ROOT).format(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* If the index is stale by any chance, load index from a different dir in the data dir.
|
|
||||||
*/
|
|
||||||
protected static boolean modifyIndexProps(SolrCore solrCore, String tmpIdxDirName) {
|
|
||||||
LOG.info("New index installed. Updating index properties... index="+tmpIdxDirName);
|
|
||||||
Properties p = new Properties();
|
|
||||||
Directory dir = null;
|
|
||||||
try {
|
|
||||||
dir = solrCore.getDirectoryFactory().get(solrCore.getDataDir(), DirContext.META_DATA, solrCore.getSolrConfig().indexConfig.lockType);
|
|
||||||
if (slowFileExists(dir, IndexFetcher.INDEX_PROPERTIES)){
|
|
||||||
final IndexInput input = dir.openInput(IndexFetcher.INDEX_PROPERTIES, DirectoryFactory.IOCONTEXT_NO_CACHE);
|
|
||||||
|
|
||||||
final InputStream is = new PropertiesInputStream(input);
|
|
||||||
try {
|
|
||||||
p.load(new InputStreamReader(is, StandardCharsets.UTF_8));
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.error("Unable to load " + IndexFetcher.INDEX_PROPERTIES, e);
|
|
||||||
} finally {
|
|
||||||
IOUtils.closeQuietly(is);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String tmpFileName = IndexFetcher.INDEX_PROPERTIES + "." + System.nanoTime();
|
|
||||||
final IndexOutput out = dir.createOutput(tmpFileName, DirectoryFactory.IOCONTEXT_NO_CACHE);
|
|
||||||
p.put("index", tmpIdxDirName);
|
|
||||||
Writer os = null;
|
|
||||||
try {
|
|
||||||
os = new OutputStreamWriter(new PropertiesOutputStream(out), StandardCharsets.UTF_8);
|
|
||||||
p.store(os, tmpFileName);
|
|
||||||
dir.sync(Collections.singleton(tmpFileName));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
|
|
||||||
"Unable to write " + IndexFetcher.INDEX_PROPERTIES, e);
|
|
||||||
} finally {
|
|
||||||
IOUtils.closeQuietly(os);
|
|
||||||
}
|
|
||||||
|
|
||||||
solrCore.getDirectoryFactory().renameWithOverwrite(dir, tmpFileName, IndexFetcher.INDEX_PROPERTIES);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
} catch (IOException e1) {
|
|
||||||
throw new RuntimeException(e1);
|
|
||||||
} finally {
|
|
||||||
if (dir != null) {
|
|
||||||
try {
|
|
||||||
solrCore.getDirectoryFactory().release(dir);
|
|
||||||
} catch (IOException e) {
|
|
||||||
SolrException.log(LOG, "", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Map<String, FileInfo> confFileInfoCache = new HashMap<>();
|
private final Map<String, FileInfo> confFileInfoCache = new HashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -101,7 +101,7 @@ public class RestoreCore implements Callable<Boolean> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.debug("Switching directories");
|
log.debug("Switching directories");
|
||||||
IndexFetcher.modifyIndexProps(core, restoreIndexName);
|
core.modifyIndexProps(restoreIndexName);
|
||||||
|
|
||||||
boolean success;
|
boolean success;
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
/*
|
||||||
|
* 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.cloud;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.lucene.util.LuceneTestCase.Slow;
|
||||||
|
import org.apache.solr.client.solrj.SolrQuery;
|
||||||
|
import org.apache.solr.client.solrj.SolrServerException;
|
||||||
|
import org.apache.solr.client.solrj.embedded.JettySolrRunner;
|
||||||
|
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
|
||||||
|
import org.apache.solr.client.solrj.response.QueryResponse;
|
||||||
|
import org.apache.solr.common.SolrInputDocument;
|
||||||
|
import org.apache.solr.common.cloud.DocCollection;
|
||||||
|
import org.apache.solr.common.cloud.Replica;
|
||||||
|
import org.apache.solr.core.SolrCore;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
@Slow
|
||||||
|
public class MissingSegmentRecoveryTest extends SolrCloudTestCase {
|
||||||
|
final String collection = getClass().getSimpleName();
|
||||||
|
|
||||||
|
Replica leader;
|
||||||
|
Replica replica;
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void setupCluster() throws Exception {
|
||||||
|
configureCluster(2)
|
||||||
|
.addConfig("conf", configset("cloud-minimal"))
|
||||||
|
.configure();
|
||||||
|
useFactory("solr.StandardDirectoryFactory");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() throws SolrServerException, IOException {
|
||||||
|
CollectionAdminRequest.createCollection(collection, "conf", 1, 2)
|
||||||
|
.setMaxShardsPerNode(1)
|
||||||
|
.process(cluster.getSolrClient());
|
||||||
|
waitForState("Expected a collection with one shard and two replicas", collection, clusterShape(1, 2));
|
||||||
|
cluster.getSolrClient().setDefaultCollection(collection);
|
||||||
|
|
||||||
|
List<SolrInputDocument> docs = new ArrayList<>();
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
SolrInputDocument doc = new SolrInputDocument();
|
||||||
|
doc.addField("id", i);
|
||||||
|
docs.add(doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
cluster.getSolrClient().add(docs);
|
||||||
|
cluster.getSolrClient().commit();
|
||||||
|
|
||||||
|
DocCollection state = getCollectionState(collection);
|
||||||
|
leader = state.getLeader("shard1");
|
||||||
|
replica = getRandomReplica(state.getSlice("shard1"), (r) -> leader != r);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void teardown() throws Exception {
|
||||||
|
System.clearProperty("CoreInitFailedAction");
|
||||||
|
CollectionAdminRequest.deleteCollection(collection).process(cluster.getSolrClient());
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void teardownCluster() throws Exception {
|
||||||
|
resetFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLeaderRecovery() throws Exception {
|
||||||
|
System.setProperty("CoreInitFailedAction", "fromleader");
|
||||||
|
|
||||||
|
// Simulate failure by truncating the segment_* files
|
||||||
|
for (File segment : getSegmentFiles(replica)) {
|
||||||
|
truncate(segment);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Might not need a sledge-hammer to reload the core
|
||||||
|
JettySolrRunner jetty = cluster.getReplicaJetty(replica);
|
||||||
|
jetty.stop();
|
||||||
|
jetty.start();
|
||||||
|
|
||||||
|
waitForState("Expected a collection with one shard and two replicas", collection, clusterShape(1, 2));
|
||||||
|
|
||||||
|
QueryResponse resp = cluster.getSolrClient().query(collection, new SolrQuery("*:*"));
|
||||||
|
assertEquals(10, resp.getResults().getNumFound());
|
||||||
|
}
|
||||||
|
|
||||||
|
private File[] getSegmentFiles(Replica replica) {
|
||||||
|
try (SolrCore core = cluster.getReplicaJetty(replica).getCoreContainer().getCore(replica.getCoreName())) {
|
||||||
|
File indexDir = new File(core.getDataDir(), "index");
|
||||||
|
return indexDir.listFiles((File dir, String name) -> {
|
||||||
|
return name.startsWith("segments_");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void truncate(File file) throws IOException {
|
||||||
|
Files.write(file.toPath(), new byte[0], StandardOpenOption.TRUNCATE_EXISTING);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue