|
@@ -35,6 +35,7 @@ import org.elasticsearch.Version;
|
|
|
import org.elasticsearch.cluster.metadata.IndexMetaData;
|
|
|
import org.elasticsearch.cluster.node.DiscoveryNode;
|
|
|
import org.elasticsearch.common.CheckedFunction;
|
|
|
+import org.elasticsearch.common.CheckedRunnable;
|
|
|
import org.elasticsearch.common.Randomness;
|
|
|
import org.elasticsearch.common.SuppressForbidden;
|
|
|
import org.elasticsearch.common.UUIDs;
|
|
@@ -45,6 +46,7 @@ import org.elasticsearch.common.settings.Setting.Property;
|
|
|
import org.elasticsearch.common.settings.Settings;
|
|
|
import org.elasticsearch.common.unit.ByteSizeValue;
|
|
|
import org.elasticsearch.common.unit.TimeValue;
|
|
|
+import org.elasticsearch.common.util.set.Sets;
|
|
|
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
|
|
import org.elasticsearch.core.internal.io.IOUtils;
|
|
|
import org.elasticsearch.gateway.MetaDataStateFormat;
|
|
@@ -81,6 +83,7 @@ import java.util.Set;
|
|
|
import java.util.concurrent.Semaphore;
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
+import java.util.function.Function;
|
|
|
import java.util.function.Predicate;
|
|
|
import java.util.stream.Collectors;
|
|
|
import java.util.stream.Stream;
|
|
@@ -90,9 +93,9 @@ import java.util.stream.Stream;
|
|
|
*/
|
|
|
public final class NodeEnvironment implements Closeable {
|
|
|
public static class NodePath {
|
|
|
- /* ${data.paths}/nodes/0 */
|
|
|
+ /* ${data.paths} */
|
|
|
public final Path path;
|
|
|
- /* ${data.paths}/nodes/0/indices */
|
|
|
+ /* ${data.paths}/indices */
|
|
|
public final Path indicesPath;
|
|
|
/** Cached FileStore from path */
|
|
|
public final FileStore fileStore;
|
|
@@ -115,7 +118,7 @@ public final class NodeEnvironment implements Closeable {
|
|
|
|
|
|
/**
|
|
|
* Resolves the given shards directory against this NodePath
|
|
|
- * ${data.paths}/nodes/{node.id}/indices/{index.uuid}/{shard.id}
|
|
|
+ * ${data.paths}/indices/{index.uuid}/{shard.id}
|
|
|
*/
|
|
|
public Path resolve(ShardId shardId) {
|
|
|
return resolve(shardId.getIndex()).resolve(Integer.toString(shardId.id()));
|
|
@@ -123,7 +126,7 @@ public final class NodeEnvironment implements Closeable {
|
|
|
|
|
|
/**
|
|
|
* Resolves index directory against this NodePath
|
|
|
- * ${data.paths}/nodes/{node.id}/indices/{index.uuid}
|
|
|
+ * ${data.paths}/indices/{index.uuid}
|
|
|
*/
|
|
|
public Path resolve(Index index) {
|
|
|
return resolve(index.getUUID());
|
|
@@ -170,7 +173,6 @@ public final class NodeEnvironment implements Closeable {
|
|
|
public static final Setting<Boolean> ENABLE_LUCENE_SEGMENT_INFOS_TRACE_SETTING =
|
|
|
Setting.boolSetting("node.enable_lucene_segment_infos_trace", false, Property.NodeScope);
|
|
|
|
|
|
- public static final String NODES_FOLDER = "nodes";
|
|
|
public static final String INDICES_FOLDER = "indices";
|
|
|
public static final String NODE_LOCK_FILENAME = "node.lock";
|
|
|
|
|
@@ -179,20 +181,28 @@ public final class NodeEnvironment implements Closeable {
|
|
|
private final Lock[] locks;
|
|
|
private final NodePath[] nodePaths;
|
|
|
|
|
|
+
|
|
|
+ public NodeLock(final Logger logger,
|
|
|
+ final Environment environment,
|
|
|
+ final CheckedFunction<Path, Boolean, IOException> pathFunction) throws IOException {
|
|
|
+ this(logger, environment, pathFunction, Function.identity());
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Tries to acquire a node lock for a node id, throws {@code IOException} if it is unable to acquire it
|
|
|
* @param pathFunction function to check node path before attempt of acquiring a node lock
|
|
|
*/
|
|
|
public NodeLock(final Logger logger,
|
|
|
final Environment environment,
|
|
|
- final CheckedFunction<Path, Boolean, IOException> pathFunction) throws IOException {
|
|
|
+ final CheckedFunction<Path, Boolean, IOException> pathFunction,
|
|
|
+ final Function<Path, Path> subPathMapping) throws IOException {
|
|
|
nodePaths = new NodePath[environment.dataFiles().length];
|
|
|
locks = new Lock[nodePaths.length];
|
|
|
try {
|
|
|
final Path[] dataPaths = environment.dataFiles();
|
|
|
for (int dirIndex = 0; dirIndex < dataPaths.length; dirIndex++) {
|
|
|
Path dataDir = dataPaths[dirIndex];
|
|
|
- Path dir = resolveNodePath(dataDir);
|
|
|
+ Path dir = subPathMapping.apply(dataDir);
|
|
|
if (pathFunction.apply(dir) == false) {
|
|
|
continue;
|
|
|
}
|
|
@@ -247,7 +257,7 @@ public final class NodeEnvironment implements Closeable {
|
|
|
sharedDataPath = environment.sharedDataFile();
|
|
|
|
|
|
for (Path path : environment.dataFiles()) {
|
|
|
- Files.createDirectories(resolveNodePath(path));
|
|
|
+ Files.createDirectories(path);
|
|
|
}
|
|
|
|
|
|
final NodeLock nodeLock;
|
|
@@ -264,7 +274,6 @@ public final class NodeEnvironment implements Closeable {
|
|
|
|
|
|
this.locks = nodeLock.locks;
|
|
|
this.nodePaths = nodeLock.nodePaths;
|
|
|
- this.nodeMetaData = loadOrCreateNodeMetaData(settings, logger, nodePaths);
|
|
|
|
|
|
logger.debug("using node location {}", Arrays.toString(nodePaths));
|
|
|
|
|
@@ -278,6 +287,10 @@ public final class NodeEnvironment implements Closeable {
|
|
|
ensureAtomicMoveSupported(nodePaths);
|
|
|
}
|
|
|
|
|
|
+ if (upgradeLegacyNodeFolders(logger, settings, environment, nodeLock)) {
|
|
|
+ assertCanWrite();
|
|
|
+ }
|
|
|
+
|
|
|
if (DiscoveryNode.isDataNode(settings) == false) {
|
|
|
if (DiscoveryNode.isMasterNode(settings) == false) {
|
|
|
ensureNoIndexMetaData(nodePaths);
|
|
@@ -286,6 +299,8 @@ public final class NodeEnvironment implements Closeable {
|
|
|
ensureNoShardData(nodePaths);
|
|
|
}
|
|
|
|
|
|
+ this.nodeMetaData = loadOrCreateNodeMetaData(settings, logger, nodePaths);
|
|
|
+
|
|
|
success = true;
|
|
|
} finally {
|
|
|
if (success == false) {
|
|
@@ -295,13 +310,128 @@ public final class NodeEnvironment implements Closeable {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Resolve a specific nodes/{node.id} path for the specified path and node lock id.
|
|
|
- *
|
|
|
- * @param path the path
|
|
|
- * @return the resolved path
|
|
|
+ * Upgrades all data paths that have been written to by an older ES version to the 8.0+ compatible folder layout,
|
|
|
+ * removing the "nodes/${lockId}" folder prefix
|
|
|
*/
|
|
|
- public static Path resolveNodePath(final Path path) {
|
|
|
- return path.resolve(NODES_FOLDER).resolve("0");
|
|
|
+ private static boolean upgradeLegacyNodeFolders(Logger logger, Settings settings, Environment environment,
|
|
|
+ NodeLock nodeLock) throws IOException {
|
|
|
+ boolean upgradeNeeded = false;
|
|
|
+
|
|
|
+ // check if we can do an auto-upgrade
|
|
|
+ for (Path path : environment.dataFiles()) {
|
|
|
+ final Path nodesFolderPath = path.resolve("nodes");
|
|
|
+ if (Files.isDirectory(nodesFolderPath)) {
|
|
|
+ final List<Integer> nodeLockIds = new ArrayList<>();
|
|
|
+
|
|
|
+ try (DirectoryStream<Path> stream = Files.newDirectoryStream(nodesFolderPath)) {
|
|
|
+ for (Path nodeLockIdPath : stream) {
|
|
|
+ String fileName = nodeLockIdPath.getFileName().toString();
|
|
|
+ if (Files.isDirectory(nodeLockIdPath) && fileName.chars().allMatch(Character::isDigit)) {
|
|
|
+ int nodeLockId = Integer.parseInt(fileName);
|
|
|
+ nodeLockIds.add(nodeLockId);
|
|
|
+ } else if (FileSystemUtils.isDesktopServicesStore(nodeLockIdPath) == false) {
|
|
|
+ throw new IllegalStateException("unexpected file/folder encountered during data folder upgrade: " +
|
|
|
+ nodeLockIdPath);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (nodeLockIds.isEmpty() == false) {
|
|
|
+ upgradeNeeded = true;
|
|
|
+
|
|
|
+ if (nodeLockIds.equals(Arrays.asList(0)) == false) {
|
|
|
+ throw new IllegalStateException("data path " + nodesFolderPath + " cannot be upgraded automatically because it " +
|
|
|
+ "contains data from nodes with ordinals " + nodeLockIds + ", due to previous use of the now obsolete " +
|
|
|
+ "[node.max_local_storage_nodes] setting. Please check the breaking changes docs for the current version of " +
|
|
|
+ "Elasticsearch to find an upgrade path");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (upgradeNeeded == false) {
|
|
|
+ logger.trace("data folder upgrade not required");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ logger.info("upgrading legacy data folders: {}", Arrays.toString(environment.dataFiles()));
|
|
|
+
|
|
|
+ // acquire locks on legacy path for duration of upgrade (to ensure there is no older ES version running on this path)
|
|
|
+ final NodeLock legacyNodeLock;
|
|
|
+ try {
|
|
|
+ legacyNodeLock = new NodeLock(logger, environment, dir -> true, path -> path.resolve("nodes").resolve("0"));
|
|
|
+ } catch (IOException e) {
|
|
|
+ final String message = String.format(
|
|
|
+ Locale.ROOT,
|
|
|
+ "failed to obtain legacy node locks, tried %s;" +
|
|
|
+ " maybe these locations are not writable or multiple nodes were started on the same data path?",
|
|
|
+ Arrays.toString(environment.dataFiles()));
|
|
|
+ throw new IllegalStateException(message, e);
|
|
|
+ }
|
|
|
+
|
|
|
+ // move contents from legacy path to new path
|
|
|
+ assert nodeLock.getNodePaths().length == legacyNodeLock.getNodePaths().length;
|
|
|
+ try {
|
|
|
+ final List<CheckedRunnable<IOException>> upgradeActions = new ArrayList<>();
|
|
|
+ for (int i = 0; i < legacyNodeLock.getNodePaths().length; i++) {
|
|
|
+ final NodePath legacyNodePath = legacyNodeLock.getNodePaths()[i];
|
|
|
+ final NodePath nodePath = nodeLock.getNodePaths()[i];
|
|
|
+
|
|
|
+ // determine folders to move and check that there are no extra files/folders
|
|
|
+ final Set<String> folderNames = new HashSet<>();
|
|
|
+
|
|
|
+ try (DirectoryStream<Path> stream = Files.newDirectoryStream(legacyNodePath.path)) {
|
|
|
+ for (Path subFolderPath : stream) {
|
|
|
+ final String fileName = subFolderPath.getFileName().toString();
|
|
|
+ if (FileSystemUtils.isDesktopServicesStore(subFolderPath)) {
|
|
|
+ // ignore
|
|
|
+ } else if (FileSystemUtils.isAccessibleDirectory(subFolderPath, logger)) {
|
|
|
+ if (fileName.equals(INDICES_FOLDER) == false && // indices folder
|
|
|
+ fileName.equals(MetaDataStateFormat.STATE_DIR_NAME) == false) { // global metadata & node state folder
|
|
|
+ throw new IllegalStateException("unexpected folder encountered during data folder upgrade: " +
|
|
|
+ subFolderPath);
|
|
|
+ }
|
|
|
+ final Path targetSubFolderPath = nodePath.path.resolve(fileName);
|
|
|
+ if (Files.exists(targetSubFolderPath)) {
|
|
|
+ throw new IllegalStateException("target folder already exists during data folder upgrade: " +
|
|
|
+ targetSubFolderPath);
|
|
|
+ }
|
|
|
+ folderNames.add(fileName);
|
|
|
+ } else if (fileName.equals(NODE_LOCK_FILENAME) == false &&
|
|
|
+ fileName.equals(TEMP_FILE_NAME) == false) {
|
|
|
+ throw new IllegalStateException("unexpected file/folder encountered during data folder upgrade: " +
|
|
|
+ subFolderPath);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ assert Sets.difference(folderNames, Sets.newHashSet(INDICES_FOLDER, MetaDataStateFormat.STATE_DIR_NAME)).isEmpty() :
|
|
|
+ "expected indices and/or state dir folder but was " + folderNames;
|
|
|
+
|
|
|
+ upgradeActions.add(() -> {
|
|
|
+ for (String folderName : folderNames) {
|
|
|
+ final Path sourceSubFolderPath = legacyNodePath.path.resolve(folderName);
|
|
|
+ final Path targetSubFolderPath = nodePath.path.resolve(folderName);
|
|
|
+ Files.move(sourceSubFolderPath, targetSubFolderPath, StandardCopyOption.ATOMIC_MOVE);
|
|
|
+ logger.info("data folder upgrade: moved from [{}] to [{}]", sourceSubFolderPath, targetSubFolderPath);
|
|
|
+ }
|
|
|
+ IOUtils.fsync(nodePath.path, true);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ // now do the actual upgrade. start by upgrading the node metadata file before moving anything, since a downgrade in an
|
|
|
+ // intermediate state would be pretty disastrous
|
|
|
+ loadOrCreateNodeMetaData(settings, logger, legacyNodeLock.getNodePaths());
|
|
|
+ for (CheckedRunnable<IOException> upgradeAction : upgradeActions) {
|
|
|
+ upgradeAction.run();
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ legacyNodeLock.close();
|
|
|
+ }
|
|
|
+
|
|
|
+ // upgrade successfully completed, remove legacy nodes folders
|
|
|
+ IOUtils.rm(Stream.of(environment.dataFiles()).map(path -> path.resolve("nodes")).toArray(Path[]::new));
|
|
|
+
|
|
|
+ return true;
|
|
|
}
|
|
|
|
|
|
private void maybeLogPathDetails() throws IOException {
|
|
@@ -801,14 +931,14 @@ public final class NodeEnvironment implements Closeable {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Returns all folder names in ${data.paths}/nodes/{node.id}/indices folder
|
|
|
+ * Returns all folder names in ${data.paths}/indices folder
|
|
|
*/
|
|
|
public Set<String> availableIndexFolders() throws IOException {
|
|
|
return availableIndexFolders(p -> false);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Returns folder names in ${data.paths}/nodes/{node.id}/indices folder that don't match the given predicate.
|
|
|
+ * Returns folder names in ${data.paths}/indices folder that don't match the given predicate.
|
|
|
* @param excludeIndexPathIdsPredicate folder names to exclude
|
|
|
*/
|
|
|
public Set<String> availableIndexFolders(Predicate<String> excludeIndexPathIdsPredicate) throws IOException {
|
|
@@ -825,7 +955,7 @@ public final class NodeEnvironment implements Closeable {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Return all directory names in the nodes/{node.id}/indices directory for the given node path.
|
|
|
+ * Return all directory names in the indices directory for the given node path.
|
|
|
*
|
|
|
* @param nodePath the path
|
|
|
* @return all directories that could be indices for the given node path.
|
|
@@ -836,7 +966,7 @@ public final class NodeEnvironment implements Closeable {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Return directory names in the nodes/{node.id}/indices directory for the given node path that don't match the given predicate.
|
|
|
+ * Return directory names in the indices directory for the given node path that don't match the given predicate.
|
|
|
*
|
|
|
* @param nodePath the path
|
|
|
* @param excludeIndexPathIdsPredicate folder names to exclude
|
|
@@ -865,7 +995,7 @@ public final class NodeEnvironment implements Closeable {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Resolves all existing paths to <code>indexFolderName</code> in ${data.paths}/nodes/{node.id}/indices
|
|
|
+ * Resolves all existing paths to <code>indexFolderName</code> in ${data.paths}/indices
|
|
|
*/
|
|
|
public Path[] resolveIndexFolder(String indexFolderName) {
|
|
|
if (nodePaths == null || locks == null) {
|