/* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch 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.elasticsearch.common.util; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.message.ParameterizedMessage; import org.apache.logging.log4j.util.Supplier; import org.apache.lucene.util.IOUtils; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.index.Index; import org.elasticsearch.index.IndexSettings; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.StandardCopyOption; /** * Renames index folders from {index.name} to {index.uuid} */ public class IndexFolderUpgrader { private final NodeEnvironment nodeEnv; private final Settings settings; private final Logger logger = Loggers.getLogger(IndexFolderUpgrader.class); /** * Creates a new upgrader instance * @param settings node settings * @param nodeEnv the node env to operate on */ IndexFolderUpgrader(Settings settings, NodeEnvironment nodeEnv) { this.settings = settings; this.nodeEnv = nodeEnv; } /** * Moves the index folder found in <code>source</code> to <code>target</code> */ void upgrade(final Index index, final Path source, final Path target) throws IOException { boolean success = false; try { Files.move(source, target, StandardCopyOption.ATOMIC_MOVE); success = true; } catch (NoSuchFileException | FileNotFoundException exception) { // thrown when the source is non-existent because the folder was renamed // by another node (shared FS) after we checked if the target exists logger.error((Supplier<?>) () -> new ParameterizedMessage("multiple nodes trying to upgrade [{}] in parallel, retry " + "upgrading with single node", target), exception); throw exception; } finally { if (success) { logger.info("{} moved from [{}] to [{}]", index, source, target); logger.trace("{} syncing directory [{}]", index, target); IOUtils.fsync(target, true); } } } /** * Renames <code>indexFolderName</code> index folders found in node paths and custom path * iff {@link #needsUpgrade(Index, String)} is true. * Index folder in custom paths are renamed first followed by index folders in each node path. */ void upgrade(final String indexFolderName) throws IOException { for (NodeEnvironment.NodePath nodePath : nodeEnv.nodePaths()) { final Path indexFolderPath = nodePath.indicesPath.resolve(indexFolderName); final IndexMetaData indexMetaData = IndexMetaData.FORMAT.loadLatestState(logger, NamedXContentRegistry.EMPTY, indexFolderPath); if (indexMetaData != null) { final Index index = indexMetaData.getIndex(); if (needsUpgrade(index, indexFolderName)) { logger.info("{} upgrading [{}] to new naming convention", index, indexFolderPath); final IndexSettings indexSettings = new IndexSettings(indexMetaData, settings); if (indexSettings.hasCustomDataPath()) { // we rename index folder in custom path before renaming them in any node path // to have the index state under a not-yet-upgraded index folder, which we use to // continue renaming after a incomplete upgrade. final Path customLocationSource = nodeEnv.resolveBaseCustomLocation(indexSettings) .resolve(indexFolderName); final Path customLocationTarget = customLocationSource.resolveSibling(index.getUUID()); // we rename the folder in custom path only the first time we encounter a state // in a node path, which needs upgrading, it is a no-op for subsequent node paths if (Files.exists(customLocationSource) // might not exist if no data was written for this index && Files.exists(customLocationTarget) == false) { upgrade(index, customLocationSource, customLocationTarget); } else { logger.info("[{}] no upgrade needed - already upgraded", customLocationTarget); } } upgrade(index, indexFolderPath, indexFolderPath.resolveSibling(index.getUUID())); } else { logger.debug("[{}] no upgrade needed - already upgraded", indexFolderPath); } } else { logger.warn("[{}] no index state found - ignoring", indexFolderPath); } } } /** * Upgrades all indices found under <code>nodeEnv</code>. Already upgraded indices are ignored. */ public static void upgradeIndicesIfNeeded(final Settings settings, final NodeEnvironment nodeEnv) throws IOException { final IndexFolderUpgrader upgrader = new IndexFolderUpgrader(settings, nodeEnv); for (String indexFolderName : nodeEnv.availableIndexFolders()) { upgrader.upgrade(indexFolderName); } } static boolean needsUpgrade(Index index, String indexFolderName) { return indexFolderName.equals(index.getUUID()) == false; } }