Skip to content
This repository was archived by the owner on Sep 24, 2019. It is now read-only.

Commit 4e3e7a0

Browse files
author
Vladimir Dolzhenko
committed
Prevent non-data nodes to start with dangling indices
elastic#27073
1 parent 09cac32 commit 4e3e7a0

File tree

3 files changed

+152
-0
lines changed

3 files changed

+152
-0
lines changed

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ public NodeEnvironment(Settings settings, Environment environment) throws IOExce
294294
this.locks = nodeLock.locks;
295295
this.nodePaths = nodeLock.nodePaths;
296296
this.nodeLockId = nodeLock.nodeId;
297+
checkDanglingIndices(settings);
297298
this.nodeMetaData = loadOrCreateNodeMetaData(settings, logger, nodePaths);
298299

299300
if (logger.isDebugEnabled()) {
@@ -313,6 +314,33 @@ public NodeEnvironment(Settings settings, Environment environment) throws IOExce
313314
}
314315
}
315316

317+
private void checkDanglingIndices(Settings settings) throws IOException {
318+
if (DiscoveryNode.isDataNode(settings) == false) {
319+
final List<Path> danglingShards = new ArrayList<>();
320+
for (final NodePath nodePath : nodePaths) {
321+
// path to shard is: <node-id>/indices/<index-uuid>/<shard-id>
322+
if (Files.exists(nodePath.indicesPath) && Files.isDirectory(nodePath.indicesPath)) {
323+
try (DirectoryStream<Path> stream = Files.newDirectoryStream(nodePath.indicesPath)) {
324+
for (final Path index : stream) {
325+
try (DirectoryStream<Path> indexStream = Files.newDirectoryStream(index)) {
326+
for (Path path: indexStream) {
327+
// it's ok for non data node to have _state folder
328+
if (MetaDataStateFormat.STATE_DIR_NAME.equals(path.getFileName().toString()) == false) {
329+
danglingShards.add(path.toAbsolutePath());
330+
}
331+
}
332+
}
333+
}
334+
}
335+
}
336+
}
337+
if (danglingShards.isEmpty() == false) {
338+
throw new IllegalStateException("Non data node cannot have dangling indices,"
339+
+ " data shards found: " + danglingShards);
340+
}
341+
}
342+
}
343+
316344
/**
317345
* Resolve a specific nodes/{node.id} path for the specified path and node lock id.
318346
*

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package org.elasticsearch.env;
2020

2121
import org.apache.lucene.index.SegmentInfos;
22+
import org.elasticsearch.common.UUIDs;
2223
import org.elasticsearch.common.util.set.Sets;
2324
import org.elasticsearch.core.internal.io.IOUtils;
2425
import org.apache.lucene.util.LuceneTestCase;
@@ -31,6 +32,7 @@
3132
import org.elasticsearch.index.Index;
3233
import org.elasticsearch.index.IndexSettings;
3334
import org.elasticsearch.index.shard.ShardId;
35+
import org.elasticsearch.node.Node;
3436
import org.elasticsearch.test.ESTestCase;
3537
import org.elasticsearch.test.IndexSettingsModule;
3638

@@ -53,6 +55,7 @@
5355
import static org.hamcrest.Matchers.containsString;
5456
import static org.hamcrest.Matchers.empty;
5557
import static org.hamcrest.Matchers.not;
58+
import static org.hamcrest.Matchers.notNullValue;
5659

5760
@LuceneTestCase.SuppressFileSystems("ExtrasFS") // TODO: fix test to allow extras
5861
public class NodeEnvironmentTests extends ESTestCase {
@@ -472,6 +475,41 @@ public void testExistingTempFiles() throws IOException {
472475
}
473476
}
474477

478+
public void testNonDataNodeFailsInPresenceOfDanglingIndices() throws IOException {
479+
final Settings settings = buildEnvSettings(Settings.builder().put(Node.NODE_DATA_SETTING.getKey(), false).build());
480+
final List<String> dataPaths = Environment.PATH_DATA_SETTING.get(settings);
481+
final Path nodePath = NodeEnvironment.resolveNodePath(PathUtils.get(dataPaths.get(0)), 0);
482+
final Path indicesPath = nodePath.resolve(NodeEnvironment.INDICES_FOLDER);
483+
final Path indexPath = indicesPath.resolve(UUIDs.randomBase64UUID());
484+
Files.createDirectories(indexPath);
485+
486+
if (randomBoolean()) {
487+
// _state is ok to keep on non-data node
488+
Files.createDirectories(indexPath.resolve(MetaDataStateFormat.STATE_DIR_NAME));
489+
}
490+
491+
final boolean allocateShard = randomBoolean();
492+
Path shardPath = null;
493+
if (allocateShard) {
494+
shardPath = indexPath.resolve("0");
495+
// allocate shard 0
496+
Files.createDirectories(shardPath);
497+
}
498+
499+
try {
500+
try (NodeEnvironment env = new NodeEnvironment(settings, TestEnvironment.newEnvironment(settings))) {
501+
logger.info("indicesPath: {}", env.nodePaths()[0].indicesPath);
502+
}
503+
assertThat(allocateShard, equalTo(false));
504+
} catch (IllegalStateException e) {
505+
assertThat(allocateShard, equalTo(true));
506+
assertThat(shardPath, notNullValue());
507+
assertThat(e.getMessage(),
508+
equalTo("Non data node cannot have dangling indices, data shards found: ["
509+
+ shardPath.toAbsolutePath() + "]"));
510+
}
511+
}
512+
475513
/** Converts an array of Strings to an array of Paths, adding an additional child if specified */
476514
private Path[] stringsToPaths(String[] strings, String additional) {
477515
Path[] locations = new Path[strings.length];
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.elasticsearch.gateway;
20+
21+
import org.elasticsearch.client.Requests;
22+
import org.elasticsearch.cluster.health.ClusterHealthStatus;
23+
import org.elasticsearch.cluster.metadata.IndexMetaData;
24+
import org.elasticsearch.common.settings.Settings;
25+
import org.elasticsearch.core.internal.io.IOUtils;
26+
import org.elasticsearch.env.NodeEnvironment;
27+
import org.elasticsearch.test.ESIntegTestCase;
28+
import org.elasticsearch.test.InternalTestCluster;
29+
30+
import java.nio.file.Path;
31+
32+
import static org.hamcrest.Matchers.equalTo;
33+
import static org.hamcrest.Matchers.startsWith;
34+
35+
36+
@ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST, numDataNodes = 0)
37+
public class DanglingIndicesIT extends ESIntegTestCase {
38+
39+
public void testDanglingIndices() throws Exception {
40+
// replicating the case described in https://github.com/elastic/elasticsearch/issues/27073
41+
final String indexName = "simple1";
42+
43+
final InternalTestCluster cluster = internalCluster();
44+
String node1 = cluster.startNode();
45+
String node2 = cluster.startNode();
46+
47+
final boolean allocateOnNode2 = randomBoolean();
48+
49+
createIndex(indexName,
50+
Settings.builder()
51+
.put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1)
52+
.put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0)
53+
// explicitly specify shard location
54+
.put("index.routing.allocation.include._name", allocateOnNode2 ? node2 : node1)
55+
.build());
56+
ensureGreen(indexName);
57+
58+
if (randomBoolean()) {
59+
index(indexName, "_doc", "1", "f", "f");
60+
}
61+
62+
final NodeEnvironment env = internalCluster().getInstance(NodeEnvironment.class, node1);
63+
final Path[] node1DataPaths = env.nodeDataPaths();
64+
65+
cluster.stopRandomNode(InternalTestCluster.nameFilter(node1));
66+
cluster.stopRandomNode(InternalTestCluster.nameFilter(node2));
67+
68+
// clean up data path on node1 to trigger dangling index being picked up
69+
IOUtils.rm(node1DataPaths);
70+
71+
// start node that could have dangling index
72+
node1 = cluster.startNode();
73+
try {
74+
node2 = randomBoolean() ? cluster.startMasterOnlyNode() : cluster.startCoordinatingOnlyNode(Settings.EMPTY);
75+
assertThat("Node 2 has to fail as it contains shard data",
76+
allocateOnNode2, equalTo(false));
77+
assertThat("index exists but has to be red",
78+
client().admin().cluster().health(Requests.clusterHealthRequest(indexName)).get().getStatus(),
79+
equalTo(ClusterHealthStatus.RED));
80+
} catch (IllegalStateException e) {
81+
assertThat(allocateOnNode2, equalTo(true));
82+
assertThat(e.getMessage(), startsWith("Non data node cannot have dangling indices"));
83+
}
84+
}
85+
86+
}

0 commit comments

Comments
 (0)