Skip to content

Commit c23049a

Browse files
Ignore obsolete dangling indices (#37824)
Now warn about both left-behind data and metadata for non-data or non-data and non-master nodes. Disable dangling indices check completely for coordinating only nodes (non-data and non-master). Issue #27073 6.x backport of #37347 and #37748 (without failing start up).
1 parent 7df7c7f commit c23049a

File tree

5 files changed

+331
-3
lines changed

5 files changed

+331
-3
lines changed

server/src/main/java/org/elasticsearch/env/NodeEnvironment.java

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.apache.lucene.store.SimpleFSDirectory;
3737
import org.elasticsearch.common.CheckedFunction;
3838
import org.elasticsearch.common.lease.Releasable;
39+
import org.elasticsearch.common.logging.DeprecationLogger;
3940
import org.elasticsearch.core.internal.io.IOUtils;
4041
import org.elasticsearch.ElasticsearchException;
4142
import org.elasticsearch.cluster.metadata.IndexMetaData;
@@ -59,6 +60,7 @@
5960
import org.elasticsearch.monitor.fs.FsInfo;
6061
import org.elasticsearch.monitor.fs.FsProbe;
6162
import org.elasticsearch.monitor.jvm.JvmInfo;
63+
import org.elasticsearch.node.Node;
6264

6365
import java.io.Closeable;
6466
import java.io.IOException;
@@ -145,6 +147,7 @@ public String toString() {
145147
}
146148

147149
private final Logger logger = LogManager.getLogger(NodeEnvironment.class);
150+
private final DeprecationLogger deprecationLogger = new DeprecationLogger(logger);
148151
private final NodePath[] nodePaths;
149152
private final Path sharedDataPath;
150153
private final Lock[] locks;
@@ -311,6 +314,26 @@ public NodeEnvironment(Settings settings, Environment environment, Consumer<Stri
311314

312315
applySegmentInfosTrace(settings);
313316
assertCanWrite();
317+
318+
// backported from 7.0, but turned into warnings.
319+
if (DiscoveryNode.isDataNode(settings) == false) {
320+
if (DiscoveryNode.isMasterNode(settings) == false) {
321+
try {
322+
ensureNoIndexMetaData(nodePaths);
323+
} catch (IllegalStateException e) {
324+
deprecationLogger.deprecated(e.getMessage()
325+
+ ", this should be cleaned up (will refuse to start in 7.0). Create a backup copy before removing.");
326+
}
327+
}
328+
329+
try {
330+
ensureNoShardData(nodePaths);
331+
} catch (IllegalStateException e) {
332+
deprecationLogger.deprecated(e.getMessage()
333+
+ ", this should be cleaned up (will refuse to start in 7.0). Create a backup copy before removing.");
334+
}
335+
}
336+
314337
success = true;
315338
} finally {
316339
if (success == false) {
@@ -1035,6 +1058,61 @@ public void ensureAtomicMoveSupported() throws IOException {
10351058
}
10361059
}
10371060

1061+
// identical to 7.0 checks
1062+
private void ensureNoShardData(final NodePath[] nodePaths) throws IOException {
1063+
List<Path> shardDataPaths = collectIndexSubPaths(nodePaths, this::isShardPath);
1064+
if (shardDataPaths.isEmpty() == false) {
1065+
throw new IllegalStateException("Node is started with "
1066+
+ Node.NODE_DATA_SETTING.getKey()
1067+
+ "=false, but has shard data: "
1068+
+ shardDataPaths);
1069+
}
1070+
}
1071+
1072+
private void ensureNoIndexMetaData(final NodePath[] nodePaths) throws IOException {
1073+
List<Path> indexMetaDataPaths = collectIndexSubPaths(nodePaths, this::isIndexMetaDataPath);
1074+
if (indexMetaDataPaths.isEmpty() == false) {
1075+
throw new IllegalStateException("Node is started with "
1076+
+ Node.NODE_DATA_SETTING.getKey()
1077+
+ "=false and "
1078+
+ Node.NODE_MASTER_SETTING.getKey()
1079+
+ "=false, but has index metadata: "
1080+
+ indexMetaDataPaths);
1081+
}
1082+
}
1083+
1084+
private List<Path> collectIndexSubPaths(NodePath[] nodePaths, Predicate<Path> subPathPredicate) throws IOException {
1085+
List<Path> indexSubPaths = new ArrayList<>();
1086+
for (NodePath nodePath : nodePaths) {
1087+
Path indicesPath = nodePath.indicesPath;
1088+
if (Files.isDirectory(indicesPath)) {
1089+
try (DirectoryStream<Path> indexStream = Files.newDirectoryStream(indicesPath)) {
1090+
for (Path indexPath : indexStream) {
1091+
if (Files.isDirectory(indexPath)) {
1092+
try (Stream<Path> shardStream = Files.list(indexPath)) {
1093+
shardStream.filter(subPathPredicate)
1094+
.map(Path::toAbsolutePath)
1095+
.forEach(indexSubPaths::add);
1096+
}
1097+
}
1098+
}
1099+
}
1100+
}
1101+
}
1102+
1103+
return indexSubPaths;
1104+
}
1105+
1106+
private boolean isShardPath(Path path) {
1107+
return Files.isDirectory(path)
1108+
&& path.getFileName().toString().chars().allMatch(Character::isDigit);
1109+
}
1110+
1111+
private boolean isIndexMetaDataPath(Path path) {
1112+
return Files.isDirectory(path)
1113+
&& path.getFileName().toString().equals(MetaDataStateFormat.STATE_DIR_NAME);
1114+
}
1115+
10381116
/**
10391117
* Resolve the custom path for a index's shard.
10401118
* Uses the {@code IndexMetaData.SETTING_DATA_PATH} setting to determine

server/src/main/java/org/elasticsearch/gateway/DanglingIndicesState.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@
2727
import org.elasticsearch.cluster.metadata.IndexGraveyard;
2828
import org.elasticsearch.cluster.metadata.IndexMetaData;
2929
import org.elasticsearch.cluster.metadata.MetaData;
30+
import org.elasticsearch.cluster.node.DiscoveryNode;
3031
import org.elasticsearch.cluster.service.ClusterService;
3132
import org.elasticsearch.common.inject.Inject;
33+
import org.elasticsearch.common.settings.Settings;
3234
import org.elasticsearch.common.util.concurrent.ConcurrentCollections;
3335
import org.elasticsearch.env.NodeEnvironment;
3436
import org.elasticsearch.index.Index;
@@ -62,12 +64,15 @@ public class DanglingIndicesState implements ClusterStateListener {
6264
private final Map<Index, IndexMetaData> danglingIndices = ConcurrentCollections.newConcurrentMap();
6365

6466
@Inject
65-
public DanglingIndicesState(NodeEnvironment nodeEnv, MetaStateService metaStateService,
67+
public DanglingIndicesState(Settings settings, NodeEnvironment nodeEnv, MetaStateService metaStateService,
6668
LocalAllocateDangledIndices allocateDangledIndices, ClusterService clusterService) {
6769
this.nodeEnv = nodeEnv;
6870
this.metaStateService = metaStateService;
6971
this.allocateDangledIndices = allocateDangledIndices;
70-
clusterService.addListener(this);
72+
if (DiscoveryNode.isDataNode(settings)
73+
|| DiscoveryNode.isMasterNode(settings)) {
74+
clusterService.addListener(this);
75+
}
7176
}
7277

7378
/**

server/src/test/java/org/elasticsearch/env/NodeEnvironmentTests.java

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,17 @@
1818
*/
1919
package org.elasticsearch.env;
2020

21+
import junit.framework.AssertionFailedError;
22+
import org.apache.logging.log4j.Level;
23+
import org.apache.logging.log4j.LogManager;
24+
import org.apache.logging.log4j.Logger;
25+
import org.apache.logging.log4j.LoggingException;
26+
import org.apache.logging.log4j.core.Appender;
27+
import org.apache.logging.log4j.core.ErrorHandler;
28+
import org.apache.logging.log4j.core.LogEvent;
29+
import org.apache.logging.log4j.core.appender.AbstractAppender;
2130
import org.apache.lucene.index.SegmentInfos;
31+
import org.elasticsearch.common.logging.Loggers;
2232
import org.elasticsearch.common.util.set.Sets;
2333
import org.elasticsearch.core.internal.io.IOUtils;
2434
import org.apache.lucene.util.LuceneTestCase;
@@ -31,6 +41,7 @@
3141
import org.elasticsearch.index.Index;
3242
import org.elasticsearch.index.IndexSettings;
3343
import org.elasticsearch.index.shard.ShardId;
44+
import org.elasticsearch.node.Node;
3445
import org.elasticsearch.test.ESTestCase;
3546
import org.elasticsearch.test.IndexSettingsModule;
3647

@@ -53,6 +64,7 @@
5364
import static org.hamcrest.Matchers.containsString;
5465
import static org.hamcrest.Matchers.empty;
5566
import static org.hamcrest.Matchers.not;
67+
import static org.hamcrest.Matchers.startsWith;
5668

5769
@LuceneTestCase.SuppressFileSystems("ExtrasFS") // TODO: fix test to allow extras
5870
public class NodeEnvironmentTests extends ESTestCase {
@@ -475,6 +487,159 @@ public void testExistingTempFiles() throws IOException {
475487
}
476488
}
477489

490+
// backported from 7.0, but in 6.x this only prints warnings. We keep the original test as is to ease further backports and ensure
491+
// that log messages convert into exceptions.
492+
public void testEnsureNoShardDataOrIndexMetaData6x() throws IOException, IllegalAccessException {
493+
// Convert warn log messages into exceptions and call original test case.
494+
Appender appender = new AbstractAppender("convertToException", null, null, false) {
495+
@Override
496+
public void append(LogEvent event) {
497+
if (event.getLevel() == Level.WARN
498+
&& event.getMessage().getFormattedMessage()
499+
.endsWith(", this should be cleaned up (will refuse to start in 7.0). Create a backup copy before removing.")) {
500+
assertWarnings(event.getMessage().getFormattedMessage());
501+
throw new LoggingException(new IllegalStateException(event.getMessage().getFormattedMessage()));
502+
}
503+
}
504+
};
505+
appender.setHandler(new ErrorHandler() {
506+
@Override
507+
public void error(String msg) {
508+
}
509+
510+
@Override
511+
public void error(String msg, Throwable t) {
512+
}
513+
514+
@Override
515+
public void error(String msg, LogEvent event, Throwable t) {
516+
}
517+
});
518+
appender.start();
519+
Logger nodeEnvironmentLogger = LogManager.getLogger(NodeEnvironment.class.getName().replace("org.elasticsearch.",
520+
"org.elasticsearch.deprecation."));
521+
Loggers.addAppender(nodeEnvironmentLogger, appender);
522+
try {
523+
testEnsureNoShardDataOrIndexMetaData();
524+
} finally {
525+
Loggers.removeAppender(nodeEnvironmentLogger, appender);
526+
appender.stop();
527+
}
528+
}
529+
530+
private static <T extends Throwable> T expectLoggingThrows(Class<T> expectedType,
531+
String noExceptionMessage,
532+
ThrowingRunnable runnable) {
533+
try {
534+
runnable.run();
535+
} catch (Throwable e) {
536+
if (e instanceof LoggingException) {
537+
e = e.getCause();
538+
}
539+
if (expectedType.isInstance(e)) {
540+
return expectedType.cast(e);
541+
}
542+
AssertionFailedError assertion =
543+
new AssertionFailedError("Unexpected exception type, expected " + expectedType.getSimpleName() + " but got " + e);
544+
assertion.initCause(e);
545+
throw assertion;
546+
}
547+
throw new AssertionFailedError(noExceptionMessage);
548+
}
549+
550+
// exact 7.0 copy (except private on purpose to disable test and expectLoggingThrows used)
551+
private void testEnsureNoShardDataOrIndexMetaData() throws IOException {
552+
Settings settings = buildEnvSettings(Settings.EMPTY);
553+
Index index = new Index("test", "testUUID");
554+
555+
// build settings using same path.data as original but with node.data=false and node.master=false
556+
Settings noDataNoMasterSettings = Settings.builder()
557+
.put(settings)
558+
.put(Node.NODE_DATA_SETTING.getKey(), false)
559+
.put(Node.NODE_MASTER_SETTING.getKey(), false)
560+
.build();
561+
562+
// test that we can create data=false and master=false with no meta information
563+
newNodeEnvironment(noDataNoMasterSettings).close();
564+
565+
Path indexPath;
566+
try (NodeEnvironment env = newNodeEnvironment(settings)) {
567+
for (Path path : env.indexPaths(index)) {
568+
Files.createDirectories(path.resolve(MetaDataStateFormat.STATE_DIR_NAME));
569+
}
570+
indexPath = env.indexPaths(index)[0];
571+
}
572+
573+
verifyFailsOnMetaData(noDataNoMasterSettings, indexPath);
574+
575+
// build settings using same path.data as original but with node.data=false
576+
Settings noDataSettings = Settings.builder()
577+
.put(settings)
578+
.put(Node.NODE_DATA_SETTING.getKey(), false).build();
579+
580+
String shardDataDirName = Integer.toString(randomInt(10));
581+
582+
// test that we can create data=false env with only meta information. Also create shard data for following asserts
583+
try (NodeEnvironment env = newNodeEnvironment(noDataSettings)) {
584+
for (Path path : env.indexPaths(index)) {
585+
Files.createDirectories(path.resolve(shardDataDirName));
586+
}
587+
}
588+
589+
verifyFailsOnShardData(noDataSettings, indexPath, shardDataDirName);
590+
591+
// assert that we get the stricter message on meta-data when both conditions fail
592+
verifyFailsOnMetaData(noDataNoMasterSettings, indexPath);
593+
594+
// build settings using same path.data as original but with node.master=false
595+
Settings noMasterSettings = Settings.builder()
596+
.put(settings)
597+
.put(Node.NODE_MASTER_SETTING.getKey(), false)
598+
.build();
599+
600+
// test that we can create master=false env regardless of data.
601+
newNodeEnvironment(noMasterSettings).close();
602+
603+
// test that we can create data=true, master=true env. Also remove state dir to leave only shard data for following asserts
604+
try (NodeEnvironment env = newNodeEnvironment(settings)) {
605+
for (Path path : env.indexPaths(index)) {
606+
Files.delete(path.resolve(MetaDataStateFormat.STATE_DIR_NAME));
607+
}
608+
}
609+
610+
// assert that we fail on shard data even without the metadata dir.
611+
verifyFailsOnShardData(noDataSettings, indexPath, shardDataDirName);
612+
verifyFailsOnShardData(noDataNoMasterSettings, indexPath, shardDataDirName);
613+
}
614+
615+
private void verifyFailsOnShardData(Settings settings, Path indexPath, String shardDataDirName) {
616+
IllegalStateException ex = expectLoggingThrows(IllegalStateException.class,
617+
"Must fail creating NodeEnvironment on a data path that has shard data if node.data=false",
618+
() -> newNodeEnvironment(settings).close());
619+
620+
assertThat(ex.getMessage(),
621+
containsString(indexPath.resolve(shardDataDirName).toAbsolutePath().toString()));
622+
assertThat(ex.getMessage(),
623+
startsWith("Node is started with "
624+
+ Node.NODE_DATA_SETTING.getKey()
625+
+ "=false, but has shard data"));
626+
}
627+
628+
private void verifyFailsOnMetaData(Settings settings, Path indexPath) {
629+
IllegalStateException ex = expectLoggingThrows(IllegalStateException.class,
630+
"Must fail creating NodeEnvironment on a data path that has index meta-data if node.data=false and node.master=false",
631+
() -> newNodeEnvironment(settings).close());
632+
633+
assertThat(ex.getMessage(),
634+
containsString(indexPath.resolve(MetaDataStateFormat.STATE_DIR_NAME).toAbsolutePath().toString()));
635+
assertThat(ex.getMessage(),
636+
startsWith("Node is started with "
637+
+ Node.NODE_DATA_SETTING.getKey()
638+
+ "=false and "
639+
+ Node.NODE_MASTER_SETTING.getKey()
640+
+ "=false, but has index metadata"));
641+
}
642+
478643
/** Converts an array of Strings to an array of Paths, adding an additional child if specified */
479644
private Path[] stringsToPaths(String[] strings, String additional) {
480645
Path[] locations = new Path[strings.length];

0 commit comments

Comments
 (0)