| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF 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.apache.iotdb.db.engine.snapshot; |
| |
| import org.apache.iotdb.commons.conf.IoTDBConstant; |
| import org.apache.iotdb.db.conf.IoTDBDescriptor; |
| import org.apache.iotdb.db.conf.directories.DirectoryManager; |
| import org.apache.iotdb.db.engine.StorageEngineV2; |
| import org.apache.iotdb.db.engine.storagegroup.DataRegion; |
| import org.apache.iotdb.db.exception.DiskSpaceInsufficientException; |
| import org.apache.iotdb.tsfile.utils.Pair; |
| |
| import org.apache.commons.io.FileUtils; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.nio.file.FileSystemException; |
| import java.nio.file.Files; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Comparator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Objects; |
| |
| public class SnapshotLoader { |
| private Logger LOGGER = LoggerFactory.getLogger(SnapshotLoader.class); |
| private String storageGroupName; |
| private String snapshotPath; |
| private String dataRegionId; |
| private SnapshotLogAnalyzer logAnalyzer; |
| |
| public SnapshotLoader(String snapshotPath, String storageGroupName, String dataRegionId) { |
| this.snapshotPath = snapshotPath; |
| this.storageGroupName = storageGroupName; |
| this.dataRegionId = dataRegionId; |
| } |
| |
| private DataRegion loadSnapshot() { |
| try { |
| return new DataRegion( |
| IoTDBDescriptor.getInstance().getConfig().getSystemDir() |
| + File.separator |
| + "storage_groups" |
| + File.separator |
| + storageGroupName, |
| dataRegionId, |
| StorageEngineV2.getInstance().getFileFlushPolicy(), |
| storageGroupName); |
| } catch (Exception e) { |
| LOGGER.error("Exception occurs while load snapshot from {}", snapshotPath, e); |
| return null; |
| } |
| } |
| |
| private File getSnapshotLogFile() { |
| File sourceDataDir = new File(snapshotPath); |
| |
| if (sourceDataDir.exists()) { |
| File[] files = |
| sourceDataDir.listFiles((dir, name) -> name.equals(SnapshotLogger.SNAPSHOT_LOG_NAME)); |
| if (files == null || files.length == 0) { |
| LOGGER.warn("Failed to find snapshot log file, cannot recover it"); |
| } else if (files.length > 1) { |
| LOGGER.warn( |
| "Found more than one snapshot log file, cannot recover it. {}", Arrays.toString(files)); |
| } else { |
| LOGGER.info("Reading snapshot log file {}", files[0]); |
| return files[0]; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * 1. Clear origin data 2. Move snapshot data to data dir 3. Load data region |
| * |
| * @return |
| */ |
| public DataRegion loadSnapshotForStateMachine() { |
| LOGGER.info( |
| "Loading snapshot for {}-{}, source directory is {}", |
| storageGroupName, |
| dataRegionId, |
| snapshotPath); |
| |
| File snapshotLogFile = getSnapshotLogFile(); |
| |
| if (snapshotLogFile == null) { |
| return loadSnapshotWithoutLog(); |
| } else { |
| return loadSnapshotWithLog(snapshotLogFile); |
| } |
| } |
| |
| private DataRegion loadSnapshotWithoutLog() { |
| try { |
| try { |
| deleteAllFilesInDataDirs(); |
| LOGGER.info("Remove all data files in original data dir"); |
| } catch (IOException e) { |
| LOGGER.error("Failed to remove origin data files", e); |
| return null; |
| } |
| LOGGER.info("Moving snapshot file to data dirs"); |
| try { |
| deleteAllFilesInDataDirs(); |
| LOGGER.info("Remove all data files in original data dir"); |
| } catch (IOException e) { |
| return null; |
| } |
| createLinksFromSnapshotDirToDataDirWithoutLog(new File(snapshotPath)); |
| return loadSnapshot(); |
| } catch (IOException | DiskSpaceInsufficientException e) { |
| LOGGER.error( |
| "Exception occurs when loading snapshot for {}-{}", storageGroupName, dataRegionId, e); |
| return null; |
| } |
| } |
| |
| private DataRegion loadSnapshotWithLog(File logFile) { |
| try { |
| logAnalyzer = new SnapshotLogAnalyzer(logFile); |
| } catch (Exception e) { |
| LOGGER.error("Exception occurs when reading snapshot file", e); |
| return null; |
| } |
| |
| try { |
| try { |
| deleteAllFilesInDataDirs(); |
| LOGGER.info("Remove all data files in original data dir"); |
| } catch (IOException e) { |
| LOGGER.error("Failed to remove origin data files", e); |
| return null; |
| } |
| |
| createLinksFromSnapshotDirToDataDirWithLog(); |
| return loadSnapshot(); |
| } finally { |
| logAnalyzer.close(); |
| } |
| } |
| |
| private void deleteAllFilesInDataDirs() throws IOException { |
| String[] dataDirPaths = IoTDBDescriptor.getInstance().getConfig().getDataDirs(); |
| |
| // delete |
| List<File> timePartitions = new ArrayList<>(); |
| for (String dataDirPath : dataDirPaths) { |
| File seqDataDirForThisRegion = |
| new File( |
| dataDirPath |
| + File.separator |
| + IoTDBConstant.SEQUENCE_FLODER_NAME |
| + File.separator |
| + storageGroupName |
| + File.separator |
| + dataRegionId); |
| if (seqDataDirForThisRegion.exists()) { |
| File[] files = seqDataDirForThisRegion.listFiles(); |
| if (files != null) { |
| timePartitions.addAll(Arrays.asList(files)); |
| } |
| } |
| |
| File unseqDataDirForThisRegion = |
| new File( |
| dataDirPath |
| + File.separator |
| + IoTDBConstant.UNSEQUENCE_FLODER_NAME |
| + File.separator |
| + storageGroupName |
| + File.separator |
| + dataRegionId); |
| |
| if (unseqDataDirForThisRegion.exists()) { |
| File[] files = unseqDataDirForThisRegion.listFiles(); |
| if (files != null) { |
| timePartitions.addAll(Arrays.asList(files)); |
| } |
| } |
| } |
| |
| try { |
| for (File timePartition : timePartitions) { |
| FileUtils.forceDelete(timePartition); |
| } |
| } catch (IOException e) { |
| LOGGER.error( |
| "Exception occurs when deleting time partition directory for {}-{}", |
| storageGroupName, |
| dataRegionId, |
| e); |
| throw e; |
| } |
| } |
| |
| private void createLinksFromSnapshotDirToDataDirWithoutLog(File sourceDir) |
| throws IOException, DiskSpaceInsufficientException { |
| File seqFileDir = new File(sourceDir, "sequence" + File.separator + storageGroupName); |
| File unseqFileDir = new File(sourceDir, "unsequence" + File.separator + storageGroupName); |
| if (!seqFileDir.exists() && !unseqFileDir.exists()) { |
| throw new IOException( |
| String.format( |
| "Cannot find %s or %s", |
| seqFileDir.getAbsolutePath(), unseqFileDir.getAbsolutePath())); |
| } |
| |
| File[] seqRegionDirs = seqFileDir.listFiles(); |
| if (seqRegionDirs != null && seqRegionDirs.length > 0) { |
| for (File seqRegionDir : seqRegionDirs) { |
| if (!seqRegionDir.isDirectory()) { |
| LOGGER.info("Skip {}, because it is not a directory", seqRegionDir); |
| continue; |
| } |
| File[] seqPartitionDirs = seqRegionDir.listFiles(); |
| if (seqPartitionDirs != null && seqPartitionDirs.length > 0) { |
| for (File seqPartitionDir : seqPartitionDirs) { |
| String[] splitPath = |
| seqPartitionDir |
| .getAbsolutePath() |
| .split(File.separator.equals("\\") ? "\\\\" : File.separator); |
| long timePartition = Long.parseLong(splitPath[splitPath.length - 1]); |
| File[] files = seqPartitionDir.listFiles(); |
| if (files != null && files.length > 0) { |
| Arrays.sort(files, Comparator.comparing(File::getName)); |
| String currDir = DirectoryManager.getInstance().getNextFolderForSequenceFile(); |
| for (File file : files) { |
| if (file.getName().endsWith(".tsfile")) { |
| currDir = DirectoryManager.getInstance().getNextFolderForSequenceFile(); |
| } |
| File targetFile = |
| new File( |
| currDir, |
| storageGroupName |
| + File.separator |
| + dataRegionId |
| + File.separator |
| + timePartition |
| + File.separator |
| + file.getName()); |
| if (!targetFile.getParentFile().exists() && !targetFile.getParentFile().mkdirs()) { |
| throw new IOException( |
| String.format("Failed to create dir %s", targetFile.getParent())); |
| } |
| try { |
| Files.createLink(targetFile.toPath(), file.toPath()); |
| } catch (FileSystemException e) { |
| // cannot create link between two directories in different fs |
| // copy it |
| Files.copy(file.toPath(), targetFile.toPath()); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| File[] unseqRegionDirs = unseqFileDir.listFiles(); |
| if (unseqRegionDirs != null && unseqRegionDirs.length > 0) { |
| for (File unseqRegionDir : unseqRegionDirs) { |
| if (!unseqRegionDir.isDirectory()) { |
| LOGGER.info("Skip {}, because it is not a directory", unseqRegionDir); |
| continue; |
| } |
| File[] unseqPartitionDirs = unseqRegionDir.listFiles(); |
| if (unseqPartitionDirs != null && unseqPartitionDirs.length > 0) { |
| for (File unseqPartitionDir : unseqPartitionDirs) { |
| String[] splitPath = |
| unseqPartitionDir |
| .getAbsolutePath() |
| .split(File.separator.equals("\\") ? "\\\\" : File.separator); |
| long timePartition = Long.parseLong(splitPath[splitPath.length - 1]); |
| File[] files = unseqPartitionDir.listFiles(); |
| if (files != null && files.length > 0) { |
| Arrays.sort(files, Comparator.comparing(File::getName)); |
| String currDir = DirectoryManager.getInstance().getNextFolderForUnSequenceFile(); |
| for (File file : files) { |
| if (file.getName().endsWith(".tsfile")) { |
| currDir = DirectoryManager.getInstance().getNextFolderForUnSequenceFile(); |
| } |
| File targetFile = |
| new File( |
| currDir, |
| storageGroupName |
| + File.separator |
| + dataRegionId |
| + File.separator |
| + timePartition |
| + File.separator |
| + file.getName()); |
| if (!targetFile.getParentFile().exists() && !targetFile.getParentFile().mkdirs()) { |
| throw new IOException( |
| String.format("Failed to create dir %s", targetFile.getParent())); |
| } |
| try { |
| Files.createLink(targetFile.toPath(), file.toPath()); |
| } catch (FileSystemException e) { |
| Files.copy(file.toPath(), targetFile.toPath()); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private void createLinksFromSnapshotDirToDataDirWithLog() { |
| while (logAnalyzer.hasNext()) { |
| Pair<String, String> filesPath = logAnalyzer.getNextPairs(); |
| File sourceFile = new File(filesPath.left); |
| File linkedFile = new File(filesPath.right); |
| if (!linkedFile.exists()) { |
| LOGGER.warn("Snapshot file {} does not exist, skip it", linkedFile); |
| continue; |
| } |
| if (!sourceFile.getParentFile().exists() && !sourceFile.getParentFile().mkdirs()) { |
| LOGGER.error("Failed to create folder {}", sourceFile.getParentFile()); |
| continue; |
| } |
| try { |
| Files.createLink(sourceFile.toPath(), linkedFile.toPath()); |
| } catch (IOException e) { |
| LOGGER.error("Failed to create link from {} to {}", linkedFile, sourceFile, e); |
| } |
| } |
| } |
| |
| public List<File> getSnapshotFileInfo() throws IOException { |
| File snapshotLogFile = getSnapshotLogFile(); |
| |
| if (snapshotLogFile == null) { |
| return getSnapshotFileWithoutLog(); |
| } else { |
| return getSnapshotFileWithLog(snapshotLogFile); |
| } |
| } |
| |
| private List<File> getSnapshotFileWithLog(File logFile) throws IOException { |
| SnapshotLogAnalyzer analyzer = new SnapshotLogAnalyzer(logFile); |
| try { |
| |
| List<File> fileList = new LinkedList<>(); |
| |
| while (analyzer.hasNext()) { |
| fileList.add(new File(analyzer.getNextPairs().right)); |
| } |
| |
| return fileList; |
| } finally { |
| analyzer.close(); |
| } |
| } |
| |
| private List<File> getSnapshotFileWithoutLog() { |
| return new LinkedList<>( |
| Arrays.asList( |
| Objects.requireNonNull( |
| new File(snapshotPath) |
| .listFiles((dir, name) -> SnapshotFileSet.isDataFile(new File(dir, name)))))); |
| } |
| } |