Node should start up despite of a lingering `.es_temp_file` (#21210)
When ES starts up we verify we can write to all data folders and that they support atomic moves. We do so by creating and deleting temp files. If for some reason the files was successfully created but not successfully deleted, we still shut down correctly but subsequent start attempts will fail with a file already exists exception. This commit makes sure to first clean any existing temporary files. Superseeds #21007
This commit is contained in:
parent
764f7ef2ef
commit
9f1d116967
|
@ -901,11 +901,12 @@ public final class NodeEnvironment implements Closeable {
|
||||||
final NodePath[] nodePaths = nodePaths();
|
final NodePath[] nodePaths = nodePaths();
|
||||||
for (NodePath nodePath : nodePaths) {
|
for (NodePath nodePath : nodePaths) {
|
||||||
assert Files.isDirectory(nodePath.path) : nodePath.path + " is not a directory";
|
assert Files.isDirectory(nodePath.path) : nodePath.path + " is not a directory";
|
||||||
final Path src = nodePath.path.resolve("__es__.tmp");
|
final Path src = nodePath.path.resolve(TEMP_FILE_NAME + ".tmp");
|
||||||
final Path target = nodePath.path.resolve("__es__.final");
|
final Path target = nodePath.path.resolve(TEMP_FILE_NAME + ".final");
|
||||||
try {
|
try {
|
||||||
|
Files.deleteIfExists(src);
|
||||||
Files.createFile(src);
|
Files.createFile(src);
|
||||||
Files.move(src, target, StandardCopyOption.ATOMIC_MOVE);
|
Files.move(src, target, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
|
||||||
} catch (AtomicMoveNotSupportedException ex) {
|
} catch (AtomicMoveNotSupportedException ex) {
|
||||||
throw new IllegalStateException("atomic_move is not supported by the filesystem on path ["
|
throw new IllegalStateException("atomic_move is not supported by the filesystem on path ["
|
||||||
+ nodePath.path
|
+ nodePath.path
|
||||||
|
@ -1005,19 +1006,19 @@ public final class NodeEnvironment implements Closeable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// package private for testing
|
||||||
|
static final String TEMP_FILE_NAME = ".es_temp_file";
|
||||||
|
|
||||||
private static void tryWriteTempFile(Path path) throws IOException {
|
private static void tryWriteTempFile(Path path) throws IOException {
|
||||||
if (Files.exists(path)) {
|
if (Files.exists(path)) {
|
||||||
Path resolve = path.resolve(".es_temp_file");
|
Path resolve = path.resolve(TEMP_FILE_NAME);
|
||||||
boolean tempFileCreated = false;
|
|
||||||
try {
|
try {
|
||||||
|
// delete any lingering file from a previous failure
|
||||||
|
Files.deleteIfExists(resolve);
|
||||||
Files.createFile(resolve);
|
Files.createFile(resolve);
|
||||||
tempFileCreated = true;
|
Files.delete(resolve);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new IOException("failed to write in data directory [" + path + "] write permission is required", ex);
|
throw new IOException("failed to test writes in data directory [" + path + "] write permission is required", ex);
|
||||||
} finally {
|
|
||||||
if (tempFileCreated) {
|
|
||||||
Files.deleteIfExists(resolve);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ import org.elasticsearch.test.ESTestCase;
|
||||||
import org.elasticsearch.test.IndexSettingsModule;
|
import org.elasticsearch.test.IndexSettingsModule;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.file.AtomicMoveNotSupportedException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -416,6 +417,39 @@ public class NodeEnvironmentTests extends ESTestCase {
|
||||||
env.close();
|
env.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testExistingTempFiles() throws IOException {
|
||||||
|
String[] paths = tmpPaths();
|
||||||
|
// simulate some previous left over temp files
|
||||||
|
for (String path : randomSubsetOf(randomIntBetween(1, paths.length), paths)) {
|
||||||
|
final Path nodePath = NodeEnvironment.resolveNodePath(PathUtils.get(path), 0);
|
||||||
|
Files.createDirectories(nodePath);
|
||||||
|
Files.createFile(nodePath.resolve(NodeEnvironment.TEMP_FILE_NAME));
|
||||||
|
if (randomBoolean()) {
|
||||||
|
Files.createFile(nodePath.resolve(NodeEnvironment.TEMP_FILE_NAME + ".tmp"));
|
||||||
|
}
|
||||||
|
if (randomBoolean()) {
|
||||||
|
Files.createFile(nodePath.resolve(NodeEnvironment.TEMP_FILE_NAME + ".final"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NodeEnvironment env = newNodeEnvironment(paths, Settings.EMPTY);
|
||||||
|
try {
|
||||||
|
env.ensureAtomicMoveSupported();
|
||||||
|
} catch (AtomicMoveNotSupportedException e) {
|
||||||
|
// that's OK :)
|
||||||
|
}
|
||||||
|
env.close();
|
||||||
|
// check we clean up
|
||||||
|
for (String path: paths) {
|
||||||
|
final Path nodePath = NodeEnvironment.resolveNodePath(PathUtils.get(path), 0);
|
||||||
|
final Path tempFile = nodePath.resolve(NodeEnvironment.TEMP_FILE_NAME);
|
||||||
|
assertFalse(tempFile + " should have been cleaned", Files.exists(tempFile));
|
||||||
|
final Path srcTempFile = nodePath.resolve(NodeEnvironment.TEMP_FILE_NAME + ".src");
|
||||||
|
assertFalse(srcTempFile + " should have been cleaned", Files.exists(srcTempFile));
|
||||||
|
final Path targetTempFile = nodePath.resolve(NodeEnvironment.TEMP_FILE_NAME + ".target");
|
||||||
|
assertFalse(targetTempFile + " should have been cleaned", Files.exists(targetTempFile));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Converts an array of Strings to an array of Paths, adding an additional child if specified */
|
/** Converts an array of Strings to an array of Paths, adding an additional child if specified */
|
||||||
private Path[] stringsToPaths(String[] strings, String additional) {
|
private Path[] stringsToPaths(String[] strings, String additional) {
|
||||||
Path[] locations = new Path[strings.length];
|
Path[] locations = new Path[strings.length];
|
||||||
|
|
|
@ -30,7 +30,6 @@ import java.nio.file.Path;
|
||||||
import java.nio.file.attribute.PosixFileAttributeView;
|
import java.nio.file.attribute.PosixFileAttributeView;
|
||||||
import java.nio.file.attribute.PosixFilePermission;
|
import java.nio.file.attribute.PosixFilePermission;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
|
||||||
public class NodeEnvironmentEvilTests extends ESTestCase {
|
public class NodeEnvironmentEvilTests extends ESTestCase {
|
||||||
|
@ -75,7 +74,7 @@ public class NodeEnvironmentEvilTests extends ESTestCase {
|
||||||
IOException ioException = expectThrows(IOException.class, () -> {
|
IOException ioException = expectThrows(IOException.class, () -> {
|
||||||
new NodeEnvironment(build, new Environment(build));
|
new NodeEnvironment(build, new Environment(build));
|
||||||
});
|
});
|
||||||
assertTrue(ioException.getMessage(), ioException.getMessage().startsWith("failed to write in data directory"));
|
assertTrue(ioException.getMessage(), ioException.getMessage().startsWith("failed to test writes in data directory"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,7 +99,7 @@ public class NodeEnvironmentEvilTests extends ESTestCase {
|
||||||
IOException ioException = expectThrows(IOException.class, () -> {
|
IOException ioException = expectThrows(IOException.class, () -> {
|
||||||
new NodeEnvironment(build, new Environment(build));
|
new NodeEnvironment(build, new Environment(build));
|
||||||
});
|
});
|
||||||
assertTrue(ioException.getMessage(), ioException.getMessage().startsWith("failed to write in data directory"));
|
assertTrue(ioException.getMessage(), ioException.getMessage().startsWith("failed to test writes in data directory"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue