/** * 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.hadoop.hdfs.server.datanode.fsdataset.impl; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.apache.hadoop.fs.FileUtil; import org.apache.hadoop.hdfs.DFSUtil; import org.apache.hadoop.hdfs.protocol.Block; import org.apache.hadoop.hdfs.server.datanode.DataStorage; import org.apache.hadoop.util.DiskChecker; import org.apache.hadoop.util.DiskChecker.DiskErrorException; /** * A node type that can be built into a tree reflecting the * hierarchy of replicas on the local disk. */ class LDir { final File dir; final int maxBlocksPerDir; private int numBlocks = 0; private LDir[] children = null; private int lastChildIdx = 0; LDir(File dir, int maxBlocksPerDir) throws IOException { this.dir = dir; this.maxBlocksPerDir = maxBlocksPerDir; if (!dir.exists()) { if (!dir.mkdirs()) { throw new IOException("Failed to mkdirs " + dir); } } else { File[] files = FileUtil.listFiles(dir); List<LDir> dirList = new ArrayList<LDir>(); for (int idx = 0; idx < files.length; idx++) { if (files[idx].isDirectory()) { dirList.add(new LDir(files[idx], maxBlocksPerDir)); } else if (Block.isBlockFilename(files[idx])) { numBlocks++; } } if (dirList.size() > 0) { children = dirList.toArray(new LDir[dirList.size()]); } } } File addBlock(Block b, File src) throws IOException { //First try without creating subdirectories File file = addBlock(b, src, false, false); return (file != null) ? file : addBlock(b, src, true, true); } private File addBlock(Block b, File src, boolean createOk, boolean resetIdx ) throws IOException { if (numBlocks < maxBlocksPerDir) { final File dest = FsDatasetImpl.moveBlockFiles(b, src, dir); numBlocks += 1; return dest; } if (lastChildIdx < 0 && resetIdx) { //reset so that all children will be checked lastChildIdx = DFSUtil.getRandom().nextInt(children.length); } if (lastChildIdx >= 0 && children != null) { //Check if any child-tree has room for a block. for (int i=0; i < children.length; i++) { int idx = (lastChildIdx + i)%children.length; File file = children[idx].addBlock(b, src, false, resetIdx); if (file != null) { lastChildIdx = idx; return file; } } lastChildIdx = -1; } if (!createOk) { return null; } if (children == null || children.length == 0) { children = new LDir[maxBlocksPerDir]; for (int idx = 0; idx < maxBlocksPerDir; idx++) { final File sub = new File(dir, DataStorage.BLOCK_SUBDIR_PREFIX+idx); children[idx] = new LDir(sub, maxBlocksPerDir); } } //now pick a child randomly for creating a new set of subdirs. lastChildIdx = DFSUtil.getRandom().nextInt(children.length); return children[ lastChildIdx ].addBlock(b, src, true, false); } void getVolumeMap(String bpid, ReplicaMap volumeMap, FsVolumeImpl volume ) throws IOException { if (children != null) { for (int i = 0; i < children.length; i++) { children[i].getVolumeMap(bpid, volumeMap, volume); } } recoverTempUnlinkedBlock(); volume.addToReplicasMap(bpid, volumeMap, dir, true); } /** * Recover unlinked tmp files on datanode restart. If the original block * does not exist, then the tmp file is renamed to be the * original file name; otherwise the tmp file is deleted. */ private void recoverTempUnlinkedBlock() throws IOException { File files[] = FileUtil.listFiles(dir); for (File file : files) { if (!FsDatasetUtil.isUnlinkTmpFile(file)) { continue; } File blockFile = FsDatasetUtil.getOrigFile(file); if (blockFile.exists()) { // If the original block file still exists, then no recovery is needed. if (!file.delete()) { throw new IOException("Unable to cleanup unlinked tmp file " + file); } } else { if (!file.renameTo(blockFile)) { throw new IOException("Unable to cleanup detached file " + file); } } } } /** * check if a data diretory is healthy * @throws DiskErrorException */ void checkDirTree() throws DiskErrorException { DiskChecker.checkDir(dir); if (children != null) { for (int i = 0; i < children.length; i++) { children[i].checkDirTree(); } } } void clearPath(File f) { String root = dir.getAbsolutePath(); String dir = f.getAbsolutePath(); if (dir.startsWith(root)) { String[] dirNames = dir.substring(root.length()). split(File.separator + DataStorage.BLOCK_SUBDIR_PREFIX); if (clearPath(f, dirNames, 1)) return; } clearPath(f, null, -1); } /** * dirNames is an array of string integers derived from * usual directory structure data/subdirN/subdirXY/subdirM ... * If dirName array is non-null, we only check the child at * the children[dirNames[idx]]. This avoids iterating over * children in common case. If directory structure changes * in later versions, we need to revisit this. */ private boolean clearPath(File f, String[] dirNames, int idx) { if ((dirNames == null || idx == dirNames.length) && dir.compareTo(f) == 0) { numBlocks--; return true; } if (dirNames != null) { //guess the child index from the directory name if (idx > (dirNames.length - 1) || children == null) { return false; } int childIdx; try { childIdx = Integer.parseInt(dirNames[idx]); } catch (NumberFormatException ignored) { // layout changed? we could print a warning. return false; } return (childIdx >= 0 && childIdx < children.length) ? children[childIdx].clearPath(f, dirNames, idx+1) : false; } //guesses failed. back to blind iteration. if (children != null) { for(int i=0; i < children.length; i++) { if (children[i].clearPath(f, null, -1)){ return true; } } } return false; } @Override public String toString() { return "FSDir{dir=" + dir + ", children=" + (children == null ? null : Arrays.asList(children)) + "}"; } }