/** * 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.common; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.channels.FileLock; import java.nio.channels.OverlappingFileLockException; import java.util.ArrayList; import java.util.List; import java.util.Iterator; import java.util.Properties; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.hdfs.protocol.FSConstants; import org.apache.hadoop.hdfs.server.common.HdfsConstants.NodeType; import org.apache.hadoop.hdfs.server.common.HdfsConstants.StartupOption; import org.apache.hadoop.hdfs.util.InjectionEvent; import org.apache.hadoop.fs.FileUtil; import org.apache.hadoop.util.InjectionHandler; import org.apache.hadoop.util.ToolRunner; import org.apache.hadoop.util.VersionInfo; import com.google.common.base.Preconditions; /** * Storage information file. * <p> * Local storage information is stored in a separate file VERSION. * It contains type of the node, * the storage layout version, the namespace id, and * the fs state creation time. * <p> * Local storage can reside in multiple directories. * Each directory should contain the same VERSION file as the others. * During startup Hadoop servers (name-node and data-nodes) read their local * storage information from them. * <p> * The servers hold a lock for each storage directory while they run so that * other nodes were not able to startup sharing the same storage. * The locks are released when the servers stop (normally or abnormally). * */ public abstract class Storage extends StorageInfo { public static final Log LOG = LogFactory.getLog(Storage.class.getName()); // Constants // last layout version that did not suppot upgrades public static final int LAST_PRE_UPGRADE_LAYOUT_VERSION = -3; // this corresponds to Hadoop-0.14. public static final int LAST_UPGRADABLE_LAYOUT_VERSION = -7; protected static final String LAST_UPGRADABLE_HADOOP_VERSION = "Hadoop-0.14"; /* this should be removed when LAST_UPGRADABLE_LV goes beyond -13. * any upgrade code that uses this constant should also be removed. */ public static final int PRE_GENERATIONSTAMP_LAYOUT_VERSION = -13; private static final String STORAGE_FILE_LOCK = "in_use.lock"; protected static final String STORAGE_FILE_VERSION = "VERSION"; public static final String STORAGE_DIR_CURRENT = "current"; public final static String STORAGE_DIR_RBW = "rbw"; public final static String OLD_STORAGE_DIR_RBW = "blocksBeingWritten"; public final static String STORAGE_DIR_FINALIZED = "finalized"; protected static final String STORAGE_DIR_PREVIOUS = "previous"; private static final String STORAGE_TMP_REMOVED = "removed.tmp"; private static final String STORAGE_TMP_PREVIOUS = "previous.tmp"; private static final String STORAGE_TMP_FINALIZED = "finalized.tmp"; private static final String STORAGE_TMP_LAST_CKPT = "lastcheckpoint.tmp"; private static final String STORAGE_PREVIOUS_CKPT = "previous.checkpoint"; public static final String STORAGE_BLOCK_CRC = "blockcrc"; public static final String STORAGE_TMP_BLOCK_CRC = "blockcrc.tmp"; // meta info property names protected static final String STORAGE_TYPE = "storageType"; public static final String NAMESPACE_ID = "namespaceID"; public static final String LAYOUT_VERSION = "layoutVersion"; public static final String CHECK_TIME = "cTime"; public enum StorageState { NON_EXISTENT, NOT_FORMATTED, COMPLETE_UPGRADE, UPGRADE_DONE, RECOVER_UPGRADE, COMPLETE_FINALIZE, COMPLETE_ROLLBACK, RECOVER_ROLLBACK, COMPLETE_CHECKPOINT, RECOVER_CHECKPOINT, NORMAL, INCONSISTENT; } /** * An interface to denote storage directory type * Implementations can define a type for storage directory by implementing * this interface. */ public interface StorageDirType { public StorageDirType getStorageDirType(); public boolean isOfType(StorageDirType type); } protected NodeType storageType; // Type of the node using this storage protected List<StorageDirectory> storageDirs = new ArrayList<StorageDirectory>(); private class DirIterator implements Iterator<StorageDirectory> { StorageDirType dirType; int prevIndex; // for remove() int nextIndex; // for next() DirIterator(StorageDirType dirType) { this.dirType = dirType; this.nextIndex = 0; this.prevIndex = 0; } public boolean hasNext() { if (storageDirs.isEmpty() || nextIndex >= storageDirs.size()) return false; if (dirType != null) { while (nextIndex < storageDirs.size()) { if (getStorageDir(nextIndex).getStorageDirType().isOfType(dirType)) break; nextIndex++; } if (nextIndex >= storageDirs.size()) return false; } return true; } public StorageDirectory next() { StorageDirectory sd = getStorageDir(nextIndex); prevIndex = nextIndex; nextIndex++; if (dirType != null) { while (nextIndex < storageDirs.size()) { if (getStorageDir(nextIndex).getStorageDirType().isOfType(dirType)) break; nextIndex++; } } return sd; } public void remove() { nextIndex = prevIndex; // restore previous state storageDirs.remove(prevIndex); // remove last returned element hasNext(); // reset nextIndex to correct place } } /** * Return default iterator * This iterator returns all entires of storageDirs */ public Iterator<StorageDirectory> dirIterator() { return dirIterator(null); } /** * Return iterator based on Storage Directory Type * This iterator selects entires of storageDirs of type dirType and returns * them via the Iterator */ public Iterator<StorageDirectory> dirIterator(StorageDirType dirType) { return new DirIterator(dirType); } /** * generate storage list (debug line) */ public String listStorageDirectories() { StringBuffer buf = new StringBuffer(); for (StorageDirectory sd : storageDirs) { buf.append(sd.getRoot() + "(" + sd.getStorageDirType() + ");"); } return buf.toString(); } public static Properties getProps(File from) throws IOException { RandomAccessFile file = new RandomAccessFile(from, "rws"); FileInputStream in = null; try { in = new FileInputStream(file.getFD()); file.seek(0); Properties props = new Properties(); props.load(in); return props; } finally { if (in != null) { in.close(); } file.close(); } } public static void writeProps(File to, Properties props) throws IOException { RandomAccessFile file = new RandomAccessFile(to, "rws"); FileOutputStream out = null; try { file.seek(0); out = new FileOutputStream(file.getFD()); /* * If server is interrupted before this line, the version file will remain * unchanged. */ props.store(out, null); /* * Now the new fields are flushed to the head of the file, but file length * can still be larger then required and therefore the file can contain * whole or corrupted fields from its old contents in the end. If server * is interrupted here and restarted later these extra fields either * should not effect server behavior or should be handled by the server * correctly. */ file.setLength(out.getChannel().position()); } finally { if (out != null) { out.close(); } file.close(); } } /** * One of the storage directories. */ public class StorageDirectory implements FormatConfirmable { File root; // root directory final boolean useLock; // flag to enable storage lock FileLock lock; // storage lock StorageDirType dirType; // storage dir type public StorageDirectory(File dir) { // default dirType is null this(dir, null); } public StorageDirectory(File dir, StorageDirType dirType) { this(dir, dirType, true); } public StorageDirectory(File dir, StorageDirType dirType, boolean useLock) { this.root = dir; this.lock = null; this.dirType = dirType; this.useLock = useLock; } @Override public String toString() { return "Storage Directory " + this.root; } /** * Get root directory of this storage */ public File getRoot() { return root; } /** * Get storage directory type */ public StorageDirType getStorageDirType() { return dirType; } /** * Read version file. * * @throws IOException if file cannot be read or contains inconsistent data */ public void read() throws IOException { read(getVersionFile()); } public void read(File from) throws IOException { Properties props = getProps(from); getFields(props, this); } /** * Write version file. * * @throws IOException */ public void write() throws IOException { corruptPreUpgradeStorage(root); write(getVersionFile()); } public void write(File to) throws IOException { Properties props = new Properties(); setFields(props, this); writeProps(to, props); } /** * Clear and re-create storage directory. * <p> * Removes contents of the current directory and creates an empty directory. * Removes other contents under the root directory * * This does not fully format storage directory. * It cannot write the version file since it should be written last after * all other storage type dependent files are written. * Derived storage is responsible for setting specific storage values and * writing the version file to disk. * * @throws IOException */ public void clearDirectory() throws IOException { File rootDir = this.getRootDir(); if (rootDir.exists()) { File contents[] = rootDir.listFiles(); for (int i = 0; i < contents.length; i++) { if (contents[i].getName().equals(STORAGE_FILE_LOCK)) { continue; } if (!(FileUtil.fullyDelete(contents[i]))) throw new IOException("Cannot remove content: " + contents[i]); } } File curDir = this.getCurrentDir(); if (!curDir.mkdirs()) throw new IOException("Cannot create current directory " + curDir); } public boolean isEmpty() throws IOException { File rootDir = this.getRootDir(); if (!rootDir.exists()) { throw new IOException("Directory " + rootDir + " does not exist!"); } String contents[] = rootDir.list(); if (contents == null) { throw new IOException("Unable to list files in " + rootDir); } if (contents.length == 1 && this.useLock) { // Ignore lock file return contents[0].equals(STORAGE_FILE_LOCK); } return contents.length == 0; } /** * @return true if the storage directory should prompt the user prior * to formatting (i.e if the directory appears to contain some data) * @throws IOException if the SD cannot be accessed due to an IO error */ private boolean hasSomeData() throws IOException { // Its alright for a dir not to exist, or to exist (properly accessible) // and be completely empty. if (!root.exists()) return false; if (!root.isDirectory()) { LOG.info("Root is not a directory: " + this); // a file where you expect a directory should not cause silent // formatting return true; } if (FileUtil.listFiles(root).length == 0) { // Empty dir can format without prompt. return false; } return true; } public File getRootDir() { return root; } public File getCurrentDir() { return new File(root, STORAGE_DIR_CURRENT); } public File getVersionFile() { return new File(new File(root, STORAGE_DIR_CURRENT), STORAGE_FILE_VERSION); } public File getPreviousVersionFile() { return new File(new File(root, STORAGE_DIR_PREVIOUS), STORAGE_FILE_VERSION); } public File getPreviousDir() { return new File(root, STORAGE_DIR_PREVIOUS); } public File getPreviousTmp() { return new File(root, STORAGE_TMP_PREVIOUS); } public File getRemovedTmp() { return new File(root, STORAGE_TMP_REMOVED); } public File getFinalizedTmp() { return new File(root, STORAGE_TMP_FINALIZED); } public File getLastCheckpointTmp() { return new File(root, STORAGE_TMP_LAST_CKPT); } public File getPreviousCheckpoint() { return new File(root, STORAGE_PREVIOUS_CKPT); } /** * Check consistency of the storage directory * * @param startOpt a startup option. * * @return state {@link StorageState} of the storage directory * @throws {@link InconsistentFSStateException} if directory state is not * consistent and cannot be recovered */ public StorageState analyzeStorage(StartupOption startOpt) throws IOException { assert root != null : "root is null"; String rootPath = root.getCanonicalPath(); try { // check that storage exists if (!root.exists()) { // storage directory does not exist if (startOpt != StartupOption.FORMAT) { LOG.info("Storage directory " + rootPath + " does not exist."); return StorageState.NON_EXISTENT; } LOG.info(rootPath + " does not exist. Creating ..."); if (!root.mkdirs()) throw new IOException("Cannot create directory " + rootPath); } // or is inaccessible if (!root.isDirectory()) { LOG.info(rootPath + "is not a directory."); return StorageState.NON_EXISTENT; } if (!root.canWrite()) { LOG.info("Cannot access storage directory " + rootPath); return StorageState.NON_EXISTENT; } } catch(SecurityException ex) { LOG.info("Cannot access storage directory " + rootPath, ex); return StorageState.NON_EXISTENT; } this.lock(); // lock storage if it exists if (startOpt == HdfsConstants.StartupOption.FORMAT) return StorageState.NOT_FORMATTED; if (startOpt != HdfsConstants.StartupOption.IMPORT) { //make sure no conversion is required checkConversionNeeded(this); } // check whether current directory is valid File versionFile = getVersionFile(); boolean hasCurrent = versionFile.exists(); // check which directories exist boolean hasPrevious = getPreviousDir().exists(); boolean hasPreviousTmp = getPreviousTmp().exists(); boolean hasRemovedTmp = getRemovedTmp().exists(); boolean hasFinalizedTmp = getFinalizedTmp().exists(); boolean hasCheckpointTmp = getLastCheckpointTmp().exists(); if (!(hasPreviousTmp || hasRemovedTmp || hasFinalizedTmp || hasCheckpointTmp)) { // no temp dirs - no recovery if (hasCurrent) return StorageState.NORMAL; if (hasPrevious) throw new InconsistentFSStateException(root, "version file in current directory is missing."); return StorageState.NOT_FORMATTED; } if ((hasPreviousTmp?1:0) + (hasRemovedTmp?1:0) + (hasFinalizedTmp?1:0) + (hasCheckpointTmp?1:0) > 1) // more than one temp dirs throw new InconsistentFSStateException(root, "too many temporary directories."); // # of temp dirs == 1 should either recover or complete a transition if (hasCheckpointTmp) { return hasCurrent ? StorageState.COMPLETE_CHECKPOINT : StorageState.RECOVER_CHECKPOINT; } if (hasFinalizedTmp) { if (hasPrevious) throw new InconsistentFSStateException(root, STORAGE_DIR_PREVIOUS + " and " + STORAGE_TMP_FINALIZED + "cannot exist together."); return StorageState.COMPLETE_FINALIZE; } if (hasPreviousTmp) { if (hasPrevious) throw new InconsistentFSStateException(root, STORAGE_DIR_PREVIOUS + " and " + STORAGE_TMP_PREVIOUS + " cannot exist together."); if (hasCurrent) return StorageState.COMPLETE_UPGRADE; return StorageState.RECOVER_UPGRADE; } if (hasPrevious && hasCurrent) { return StorageState.UPGRADE_DONE; } assert hasRemovedTmp : "hasRemovedTmp must be true"; if (!(hasCurrent ^ hasPrevious)) throw new InconsistentFSStateException(root, "one and only one directory " + STORAGE_DIR_CURRENT + " or " + STORAGE_DIR_PREVIOUS + " must be present when " + STORAGE_TMP_REMOVED + " exists."); if (hasCurrent) return StorageState.COMPLETE_ROLLBACK; return StorageState.RECOVER_ROLLBACK; } /** * Complete or recover storage state from previously failed transition. * * @param curState specifies what/how the state should be recovered * @throws IOException */ public void doRecover(StorageState curState) throws IOException { File curDir = getCurrentDir(); String rootPath = root.getCanonicalPath(); switch(curState) { case COMPLETE_UPGRADE: // mv previous.tmp -> previous LOG.info("Completing previous upgrade for storage directory " + rootPath + "."); rename(getPreviousTmp(), getPreviousDir()); return; case RECOVER_UPGRADE: // mv previous.tmp -> current LOG.info("Recovering storage directory " + rootPath + " from previous upgrade."); if (curDir.exists()) deleteDir(curDir); rename(getPreviousTmp(), curDir); return; case COMPLETE_ROLLBACK: // rm removed.tmp LOG.info("Completing previous rollback for storage directory " + rootPath + "."); deleteDir(getRemovedTmp()); return; case RECOVER_ROLLBACK: // mv removed.tmp -> current LOG.info("Recovering storage directory " + rootPath + " from previous rollback."); rename(getRemovedTmp(), curDir); return; case COMPLETE_FINALIZE: // rm finalized.tmp LOG.info("Completing previous finalize for storage directory " + rootPath + "."); deleteDir(getFinalizedTmp()); return; case COMPLETE_CHECKPOINT: // mv lastcheckpoint.tmp -> previous.checkpoint LOG.info("Completing previous checkpoint for storage directory " + rootPath + "."); File prevCkptDir = getPreviousCheckpoint(); if (prevCkptDir.exists()) deleteDir(prevCkptDir); rename(getLastCheckpointTmp(), prevCkptDir); return; case RECOVER_CHECKPOINT: // mv lastcheckpoint.tmp -> current LOG.info("Recovering storage directory " + rootPath + " from failed checkpoint."); if (curDir.exists()) deleteDir(curDir); rename(getLastCheckpointTmp(), curDir); return; default: throw new IOException("Unexpected FS state: " + curState); } } /** * Lock storage to provide exclusive access. * * <p> Locking is not supported by all file systems. * E.g., NFS does not consistently support exclusive locks. * * <p> If locking is supported we guarantee exculsive access to the * storage directory. Otherwise, no guarantee is given. * * @throws IOException if locking fails */ public void lock() throws IOException { if (!useLock) { LOG.info("Locking is disabled"); return; } this.lock = tryLock(); if (lock == null) { String msg = "Cannot lock storage " + this.root + ". The directory is already locked."; LOG.info(msg); throw new IOException(msg); } } /** * Attempts to acquire an exclusive lock on the storage. * * @return A lock object representing the newly-acquired lock or * <code>null</code> if storage is already locked. * @throws IOException if locking fails. */ FileLock tryLock() throws IOException { File lockF = new File(root, STORAGE_FILE_LOCK); lockF.deleteOnExit(); RandomAccessFile file = new RandomAccessFile(lockF, "rws"); FileLock res = null; try { res = file.getChannel().tryLock(); } catch(OverlappingFileLockException oe) { file.close(); return null; } catch(IOException e) { LOG.error("Cannot create lock on " + lockF, e); file.close(); throw e; } return res; } /** * Unlock storage. * * @throws IOException */ public void unlock() throws IOException { if (this.lock == null) return; this.lock.release(); lock.channel().close(); lock = null; } @Override public boolean hasSomeJournalData() throws IOException { return hasSomeData(); } @Override public boolean hasSomeImageData() throws IOException { return hasSomeData(); } } /** * Create empty storage info of the specified type */ protected Storage(NodeType type) { super(); this.storageType = type; } protected Storage(NodeType type, int nsID, long cT) { super(FSConstants.LAYOUT_VERSION, nsID, cT); this.storageType = type; } protected Storage(NodeType type, StorageInfo storageInfo) { super(storageInfo); this.storageType = type; } public int getNumStorageDirs() { return storageDirs.size(); } public StorageDirectory getStorageDir(int idx) { return storageDirs.get(idx); } protected void addStorageDir(StorageDirectory sd) { storageDirs.add(sd); } public abstract boolean isConversionNeeded(StorageDirectory sd) throws IOException; /* * Coversion is no longer supported. So this should throw exception if * conversion is needed. */ private void checkConversionNeeded(StorageDirectory sd) throws IOException { if (isConversionNeeded(sd)) { //throw an exception checkVersionUpgradable(0); } } /** * Checks if the upgrade from the given old version is supported. If * no upgrade is supported, it throws IncorrectVersionException. * * @param oldVersion */ public static void checkVersionUpgradable(int oldVersion) throws IOException { if (oldVersion > LAST_UPGRADABLE_LAYOUT_VERSION) { String msg = "*********** Upgrade is not supported from this older" + " version of storage to the current version." + " Please upgrade to " + LAST_UPGRADABLE_HADOOP_VERSION + " or a later version and then upgrade to current" + " version. Old layout version is " + (oldVersion == 0 ? "'too old'" : (""+oldVersion)) + " and latest layout version this software version can" + " upgrade from is " + LAST_UPGRADABLE_LAYOUT_VERSION + ". ************"; LOG.error(msg); throw new IOException(msg); } } /** * Get common storage fields. * Should be overloaded if additional fields need to be get. * * @param props * @throws IOException */ protected void getFields(Properties props, StorageDirectory sd ) throws IOException { String sv, st, sid, sct; sv = props.getProperty(LAYOUT_VERSION); st = props.getProperty(STORAGE_TYPE); sid = props.getProperty(NAMESPACE_ID); sct = props.getProperty(CHECK_TIME); if (sv == null || st == null || sid == null || sct == null) throw new InconsistentFSStateException(sd.root, "file " + STORAGE_FILE_VERSION + " is invalid."); int rv = Integer.parseInt(sv); NodeType rt = NodeType.valueOf(st); int rid = Integer.parseInt(sid); long rct = Long.parseLong(sct); if (!storageType.equals(rt) || !((namespaceID == 0) || (rid == 0) || namespaceID == rid)) throw new InconsistentFSStateException(sd.root, "is incompatible with others. " + " namespaceID is " + namespaceID + " and rid is " + rid + "," + " storage type is " + storageType + " but rt is " + rt); if (rv < FSConstants.LAYOUT_VERSION) // future version throw new IncorrectVersionException(rv, "storage directory " + sd.root.getCanonicalPath()); layoutVersion = rv; storageType = rt; namespaceID = rid; cTime = rct; } /** * Set common storage fields. * Should be overloaded if additional fields need to be set. * * @param props * @throws IOException */ protected void setFields(Properties props, StorageDirectory sd ) throws IOException { props.setProperty(LAYOUT_VERSION, String.valueOf(layoutVersion)); props.setProperty(STORAGE_TYPE, storageType.toString()); props.setProperty(NAMESPACE_ID, String.valueOf(namespaceID)); props.setProperty(CHECK_TIME, String.valueOf(cTime)); } public static void rename(File from, File to) throws IOException { if (!from.renameTo(to)) throw new IOException("Failed to rename " + from.getCanonicalPath() + " to " + to.getCanonicalPath()); } public static void upgradeDirectory(StorageDirectory sd) throws IOException { File curDir = sd.getCurrentDir(); File prevDir = sd.getPreviousDir(); File tmpDir = sd.getPreviousTmp(); assert curDir.exists() : "Current directory must exist."; assert !prevDir.exists() : "prvious directory must not exist."; assert !tmpDir.exists() : "prvious.tmp directory must not exist."; // rename current to tmp rename(curDir, tmpDir); if (!curDir.mkdir()) { throw new IOException("Cannot create directory " + curDir); } } public static void completeUpgrade(StorageDirectory sd) throws IOException { // Write the version file, since saveFsImage above only makes the // fsimage, and the directory is otherwise empty. sd.write(); InjectionHandler .processEventIO(InjectionEvent.FSIMAGE_UPGRADE_AFTER_SAVE_IMAGE); File prevDir = sd.getPreviousDir(); File tmpDir = sd.getPreviousTmp(); // rename tmp to previous rename(tmpDir, prevDir); } public static void deleteDir(File dir) throws IOException { if (!FileUtil.fullyDelete(dir)) throw new IOException("Failed to delete " + dir.getCanonicalPath()); } /** * Write all data storage files. * @throws IOException */ public void writeAll() throws IOException { this.layoutVersion = FSConstants.LAYOUT_VERSION; for (Iterator<StorageDirectory> it = storageDirs.iterator(); it.hasNext();) { it.next().write(); } } /** * Unlock all storage directories. * @throws IOException */ public void unlockAll() throws IOException { for (Iterator<StorageDirectory> it = storageDirs.iterator(); it.hasNext();) { it.next().unlock(); } } /** * Check whether underlying file system supports file locking. * * @return <code>true</code> if exclusive locks are supported or * <code>false</code> otherwise. * @throws IOException * @see StorageDirectory#lock() */ public boolean isLockSupported(int idx) throws IOException { StorageDirectory sd = storageDirs.get(idx); FileLock firstLock = null; FileLock secondLock = null; try { firstLock = sd.lock; if(firstLock == null) { firstLock = sd.tryLock(); if(firstLock == null) return true; } secondLock = sd.tryLock(); if(secondLock == null) return true; } finally { if(firstLock != null && firstLock != sd.lock) { firstLock.release(); firstLock.channel().close(); } if(secondLock != null) { secondLock.release(); secondLock.channel().close(); } } return false; } public static String getBuildVersion() { return VersionInfo.getRevision(); } public static String getRegistrationID(StorageInfo storage) { return "NS-" + Integer.toString(storage.getNamespaceID()) + "-" + Integer.toString(storage.getLayoutVersion()) + "-" + Long.toString(storage.getCTime()); } protected static String getProperty(Properties props, StorageDirectory sd, String name) throws InconsistentFSStateException { String property = props.getProperty(name); if (property == null) { throw new InconsistentFSStateException(sd.root, "file " + STORAGE_FILE_VERSION + " has " + name + " missing."); } return property; } /** Validate and set storage type from {@link Properties}*/ protected void setStorageType(Properties props, StorageDirectory sd) throws InconsistentFSStateException { NodeType type = NodeType.valueOf(getProperty(props, sd, STORAGE_TYPE)); if (!storageType.equals(type)) { throw new InconsistentFSStateException(sd.root, "node type is incompatible with others."); } storageType = type; } /** Validate and set ctime from {@link Properties}*/ protected void setcTime(Properties props, StorageDirectory sd) throws InconsistentFSStateException { cTime = Long.parseLong(getProperty(props, sd, CHECK_TIME)); } /** Validate and set layout version from {@link Properties}*/ protected void setLayoutVersion(Properties props, StorageDirectory sd) throws IncorrectVersionException, InconsistentFSStateException { int lv = Integer.parseInt(getProperty(props, sd, LAYOUT_VERSION)); if (lv < FSConstants.LAYOUT_VERSION) { // future version throw new IncorrectVersionException(lv, "storage directory " + sd.root.getAbsolutePath()); } layoutVersion = lv; } /** Validate and set namespaceID version from {@link Properties}*/ protected void setNamespaceID(Properties props, StorageDirectory sd) throws InconsistentFSStateException { int nsId = Integer.parseInt(getProperty(props, sd, NAMESPACE_ID)); if (namespaceID != 0 && nsId != 0 && namespaceID != nsId) { throw new InconsistentFSStateException(sd.root, "namespaceID is incompatible with others."); } namespaceID = nsId; } // Pre-upgrade version compatibility protected abstract void corruptPreUpgradeStorage(File rootDir) throws IOException; protected void writeCorruptedData(RandomAccessFile file) throws IOException { final String messageForPreUpgradeVersion = "\nThis file is INTENTIONALLY CORRUPTED so that versions\n" + "of Hadoop prior to 0.13 (which are incompatible\n" + "with this directory layout) will fail to start.\n"; file.seek(0); file.writeInt(FSConstants.LAYOUT_VERSION); org.apache.hadoop.io.UTF8.writeString(file, ""); file.writeBytes(messageForPreUpgradeVersion); file.getFD().sync(); } public Iterable<StorageDirectory> dirIterable(final StorageDirType dirType) { return new Iterable<StorageDirectory>() { @Override public Iterator<StorageDirectory> iterator() { return dirIterator(dirType); } }; } /** * Interface for classes which need to have the user confirm their * formatting during NameNode -format and other similar operations. * * This is currently a storage directory or journal manager. */ public interface FormatConfirmable { /** * @return true if the storage seems to have some valid journal data in it, * and the user should be required to confirm the format. Otherwise, * false. * @throws IOException if the storage cannot be accessed at all. */ public boolean hasSomeJournalData() throws IOException; /** * @return true if the storage seems to have some valid image data in it, * and the user should be required to confirm the format. Otherwise, * false. * @throws IOException if the storage cannot be accessed at all. */ public boolean hasSomeImageData() throws IOException; /** * @return a string representation of the formattable item, suitable * for display to the user inside a prompt */ @Override public String toString(); } /** * Iterate over each of the {@link FormatConfirmable} objects, * potentially checking with the user whether it should be formatted. * * If running in interactive mode, will prompt the user for each * directory to allow them to format anyway. Otherwise, returns * false, unless 'force' is specified. * * @param interactive prompt the user when a dir exists * @return true if formatting should proceed * @throws IOException if some storage cannot be accessed */ public static boolean confirmFormat( Iterable<? extends FormatConfirmable> items, boolean force, boolean interactive) throws IOException { for (FormatConfirmable item : items) { if (!(item.hasSomeJournalData() || item.hasSomeImageData())) continue; if (force) { // Don't confirm, always format. System.err.println( "Data exists in " + item + ". Formatting anyway."); continue; } if (!interactive) { // Don't ask - always don't format System.err.println( "Running in non-interactive mode, and data appears to exist in " + item + ". Not formatting."); return false; } if (!ToolRunner.confirmPrompt("Re-format filesystem in " + item + " ?")) { System.err.println("Format aborted in " + item); return false; } } return true; } /** * @return the storage directory, with the precondition that this storage * has exactly one storage directory */ public StorageDirectory getSingularStorageDir() { Preconditions.checkState(storageDirs.size() == 1); return storageDirs.get(0); } }