001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hdfs.server.common;
019
020import java.io.File;
021import java.io.FileInputStream;
022import java.io.FileOutputStream;
023import java.io.IOException;
024import java.io.RandomAccessFile;
025import java.lang.management.ManagementFactory;
026import java.nio.channels.FileLock;
027import java.nio.channels.OverlappingFileLockException;
028import java.util.ArrayList;
029import java.util.List;
030import java.util.Iterator;
031import java.util.Properties;
032
033import org.apache.commons.logging.Log;
034import org.apache.commons.logging.LogFactory;
035import org.apache.hadoop.classification.InterfaceAudience;
036import org.apache.hadoop.fs.Path;
037import org.apache.hadoop.hdfs.protocol.HdfsConstants;
038import org.apache.hadoop.hdfs.protocol.LayoutVersion;
039import org.apache.hadoop.hdfs.protocol.LayoutVersion.Feature;
040import org.apache.hadoop.hdfs.server.common.HdfsServerConstants.NodeType;
041import org.apache.hadoop.hdfs.server.common.HdfsServerConstants.StartupOption;
042import org.apache.hadoop.fs.FileUtil;
043import org.apache.hadoop.util.ToolRunner;
044import org.apache.hadoop.util.VersionInfo;
045
046import com.google.common.base.Preconditions;
047
048import com.google.common.base.Charsets;
049
050
051
052/**
053 * Storage information file.
054 * <p>
055 * Local storage information is stored in a separate file VERSION.
056 * It contains type of the node, 
057 * the storage layout version, the namespace id, and 
058 * the fs state creation time.
059 * <p>
060 * Local storage can reside in multiple directories. 
061 * Each directory should contain the same VERSION file as the others.
062 * During startup Hadoop servers (name-node and data-nodes) read their local 
063 * storage information from them.
064 * <p>
065 * The servers hold a lock for each storage directory while they run so that 
066 * other nodes were not able to startup sharing the same storage.
067 * The locks are released when the servers stop (normally or abnormally).
068 * 
069 */
070@InterfaceAudience.Private
071public abstract class Storage extends StorageInfo {
072  public static final Log LOG = LogFactory.getLog(Storage.class.getName());
073
074  // last layout version that did not support upgrades
075  public static final int LAST_PRE_UPGRADE_LAYOUT_VERSION = -3;
076  
077  // this corresponds to Hadoop-0.18
078  public static final int LAST_UPGRADABLE_LAYOUT_VERSION = -16;
079  protected static final String LAST_UPGRADABLE_HADOOP_VERSION = "Hadoop-0.18";
080  
081  /** Layout versions of 0.20.203 release */
082  public static final int[] LAYOUT_VERSIONS_203 = {-19, -31};
083
084  public    static final String STORAGE_FILE_LOCK     = "in_use.lock";
085  protected static final String STORAGE_FILE_VERSION  = "VERSION";
086  public    static final String STORAGE_DIR_CURRENT   = "current";
087  public    static final String STORAGE_DIR_PREVIOUS  = "previous";
088  public    static final String STORAGE_TMP_REMOVED   = "removed.tmp";
089  public    static final String STORAGE_TMP_PREVIOUS  = "previous.tmp";
090  public    static final String STORAGE_TMP_FINALIZED = "finalized.tmp";
091  public    static final String STORAGE_TMP_LAST_CKPT = "lastcheckpoint.tmp";
092  public    static final String STORAGE_PREVIOUS_CKPT = "previous.checkpoint";
093  
094  /**
095   * The blocksBeingWritten directory which was used in some 1.x and earlier
096   * releases.
097   */
098  public static final String STORAGE_1_BBW = "blocksBeingWritten";
099  
100  public enum StorageState {
101    NON_EXISTENT,
102    NOT_FORMATTED,
103    COMPLETE_UPGRADE,
104    RECOVER_UPGRADE,
105    COMPLETE_FINALIZE,
106    COMPLETE_ROLLBACK,
107    RECOVER_ROLLBACK,
108    COMPLETE_CHECKPOINT,
109    RECOVER_CHECKPOINT,
110    NORMAL;
111  }
112  
113  /**
114   * An interface to denote storage directory type
115   * Implementations can define a type for storage directory by implementing
116   * this interface.
117   */
118  @InterfaceAudience.Private
119  public interface StorageDirType {
120    public StorageDirType getStorageDirType();
121    public boolean isOfType(StorageDirType type);
122  }
123  
124  protected NodeType storageType;    // Type of the node using this storage 
125  protected List<StorageDirectory> storageDirs = new ArrayList<StorageDirectory>();
126  
127  private class DirIterator implements Iterator<StorageDirectory> {
128    StorageDirType dirType;
129    int prevIndex; // for remove()
130    int nextIndex; // for next()
131    
132    DirIterator(StorageDirType dirType) {
133      this.dirType = dirType;
134      this.nextIndex = 0;
135      this.prevIndex = 0;
136    }
137    
138    @Override
139    public boolean hasNext() {
140      if (storageDirs.isEmpty() || nextIndex >= storageDirs.size())
141        return false;
142      if (dirType != null) {
143        while (nextIndex < storageDirs.size()) {
144          if (getStorageDir(nextIndex).getStorageDirType().isOfType(dirType))
145            break;
146          nextIndex++;
147        }
148        if (nextIndex >= storageDirs.size())
149         return false;
150      }
151      return true;
152    }
153    
154    @Override
155    public StorageDirectory next() {
156      StorageDirectory sd = getStorageDir(nextIndex);
157      prevIndex = nextIndex;
158      nextIndex++;
159      if (dirType != null) {
160        while (nextIndex < storageDirs.size()) {
161          if (getStorageDir(nextIndex).getStorageDirType().isOfType(dirType))
162            break;
163          nextIndex++;
164        }
165      }
166      return sd;
167    }
168    
169    @Override
170    public void remove() {
171      nextIndex = prevIndex; // restore previous state
172      storageDirs.remove(prevIndex); // remove last returned element
173      hasNext(); // reset nextIndex to correct place
174    }
175  }
176  
177  /**
178   * @return A list of the given File in every available storage directory,
179   * regardless of whether it might exist.
180   */
181  public List<File> getFiles(StorageDirType dirType, String fileName) {
182    ArrayList<File> list = new ArrayList<File>();
183    Iterator<StorageDirectory> it =
184      (dirType == null) ? dirIterator() : dirIterator(dirType);
185    for ( ;it.hasNext(); ) {
186      list.add(new File(it.next().getCurrentDir(), fileName));
187    }
188    return list;
189  }
190
191
192  /**
193   * Return default iterator
194   * This iterator returns all entries in storageDirs
195   */
196  public Iterator<StorageDirectory> dirIterator() {
197    return dirIterator(null);
198  }
199  
200  /**
201   * Return iterator based on Storage Directory Type
202   * This iterator selects entries in storageDirs of type dirType and returns
203   * them via the Iterator
204   */
205  public Iterator<StorageDirectory> dirIterator(StorageDirType dirType) {
206    return new DirIterator(dirType);
207  }
208  
209  public Iterable<StorageDirectory> dirIterable(final StorageDirType dirType) {
210    return new Iterable<StorageDirectory>() {
211      @Override
212      public Iterator<StorageDirectory> iterator() {
213        return dirIterator(dirType);
214      }
215    };
216  }
217  
218  
219  /**
220   * generate storage list (debug line)
221   */
222  public String listStorageDirectories() {
223    StringBuilder buf = new StringBuilder();
224    for (StorageDirectory sd : storageDirs) {
225      buf.append(sd.getRoot() + "(" + sd.getStorageDirType() + ");");
226    }
227    return buf.toString();
228  }
229  
230  /**
231   * One of the storage directories.
232   */
233  @InterfaceAudience.Private
234  public static class StorageDirectory implements FormatConfirmable {
235    final File root;              // root directory
236    final boolean useLock;        // flag to enable storage lock
237    final StorageDirType dirType; // storage dir type
238    FileLock lock;                // storage lock
239
240    private String storageUuid = null;      // Storage directory identifier.
241    
242    public StorageDirectory(File dir) {
243      // default dirType is null
244      this(dir, null, true);
245    }
246    
247    public StorageDirectory(File dir, StorageDirType dirType) {
248      this(dir, dirType, true);
249    }
250    
251    public void setStorageUuid(String storageUuid) {
252      this.storageUuid = storageUuid;
253    }
254
255    public String getStorageUuid() {
256      return storageUuid;
257    }
258
259    /**
260     * Constructor
261     * @param dir directory corresponding to the storage
262     * @param dirType storage directory type
263     * @param useLock true - enables locking on the storage directory and false
264     *          disables locking
265     */
266    public StorageDirectory(File dir, StorageDirType dirType, boolean useLock) {
267      this.root = dir;
268      this.lock = null;
269      this.dirType = dirType;
270      this.useLock = useLock;
271    }
272    
273    /**
274     * Get root directory of this storage
275     */
276    public File getRoot() {
277      return root;
278    }
279
280    /**
281     * Get storage directory type
282     */
283    public StorageDirType getStorageDirType() {
284      return dirType;
285    }    
286
287    public void read(File from, Storage storage) throws IOException {
288      Properties props = readPropertiesFile(from);
289      storage.setFieldsFromProperties(props, this);
290    }
291
292    /**
293     * Clear and re-create storage directory.
294     * <p>
295     * Removes contents of the current directory and creates an empty directory.
296     * 
297     * This does not fully format storage directory. 
298     * It cannot write the version file since it should be written last after  
299     * all other storage type dependent files are written.
300     * Derived storage is responsible for setting specific storage values and
301     * writing the version file to disk.
302     * 
303     * @throws IOException
304     */
305    public void clearDirectory() throws IOException {
306      File curDir = this.getCurrentDir();
307      if (curDir.exists())
308        if (!(FileUtil.fullyDelete(curDir)))
309          throw new IOException("Cannot remove current directory: " + curDir);
310      if (!curDir.mkdirs())
311        throw new IOException("Cannot create directory " + curDir);
312    }
313
314    /**
315     * Directory {@code current} contains latest files defining
316     * the file system meta-data.
317     * 
318     * @return the directory path
319     */
320    public File getCurrentDir() {
321      return new File(root, STORAGE_DIR_CURRENT);
322    }
323
324    /**
325     * File {@code VERSION} contains the following fields:
326     * <ol>
327     * <li>node type</li>
328     * <li>layout version</li>
329     * <li>namespaceID</li>
330     * <li>fs state creation time</li>
331     * <li>other fields specific for this node type</li>
332     * </ol>
333     * The version file is always written last during storage directory updates.
334     * The existence of the version file indicates that all other files have
335     * been successfully written in the storage directory, the storage is valid
336     * and does not need to be recovered.
337     * 
338     * @return the version file path
339     */
340    public File getVersionFile() {
341      return new File(new File(root, STORAGE_DIR_CURRENT), STORAGE_FILE_VERSION);
342    }
343
344    /**
345     * File {@code VERSION} from the {@code previous} directory.
346     * 
347     * @return the previous version file path
348     */
349    public File getPreviousVersionFile() {
350      return new File(new File(root, STORAGE_DIR_PREVIOUS), STORAGE_FILE_VERSION);
351    }
352
353    /**
354     * Directory {@code previous} contains the previous file system state,
355     * which the system can be rolled back to.
356     * 
357     * @return the directory path
358     */
359    public File getPreviousDir() {
360      return new File(root, STORAGE_DIR_PREVIOUS);
361    }
362
363    /**
364     * {@code previous.tmp} is a transient directory, which holds
365     * current file system state while the new state is saved into the new
366     * {@code current} during upgrade.
367     * If the saving succeeds {@code previous.tmp} will be moved to
368     * {@code previous}, otherwise it will be renamed back to 
369     * {@code current} by the recovery procedure during startup.
370     * 
371     * @return the directory path
372     */
373    public File getPreviousTmp() {
374      return new File(root, STORAGE_TMP_PREVIOUS);
375    }
376
377    /**
378     * {@code removed.tmp} is a transient directory, which holds
379     * current file system state while the previous state is moved into
380     * {@code current} during rollback.
381     * If the moving succeeds {@code removed.tmp} will be removed,
382     * otherwise it will be renamed back to 
383     * {@code current} by the recovery procedure during startup.
384     * 
385     * @return the directory path
386     */
387    public File getRemovedTmp() {
388      return new File(root, STORAGE_TMP_REMOVED);
389    }
390
391    /**
392     * {@code finalized.tmp} is a transient directory, which holds
393     * the {@code previous} file system state while it is being removed
394     * in response to the finalize request.
395     * Finalize operation will remove {@code finalized.tmp} when completed,
396     * otherwise the removal will resume upon the system startup.
397     * 
398     * @return the directory path
399     */
400    public File getFinalizedTmp() {
401      return new File(root, STORAGE_TMP_FINALIZED);
402    }
403
404    /**
405     * {@code lastcheckpoint.tmp} is a transient directory, which holds
406     * current file system state while the new state is saved into the new
407     * {@code current} during regular namespace updates.
408     * If the saving succeeds {@code lastcheckpoint.tmp} will be moved to
409     * {@code previous.checkpoint}, otherwise it will be renamed back to 
410     * {@code current} by the recovery procedure during startup.
411     * 
412     * @return the directory path
413     */
414    public File getLastCheckpointTmp() {
415      return new File(root, STORAGE_TMP_LAST_CKPT);
416    }
417
418    /**
419     * {@code previous.checkpoint} is a directory, which holds the previous
420     * (before the last save) state of the storage directory.
421     * The directory is created as a reference only, it does not play role
422     * in state recovery procedures, and is recycled automatically, 
423     * but it may be useful for manual recovery of a stale state of the system.
424     * 
425     * @return the directory path
426     */
427    public File getPreviousCheckpoint() {
428      return new File(root, STORAGE_PREVIOUS_CKPT);
429    }
430
431    /**
432     * Check consistency of the storage directory
433     * 
434     * @param startOpt a startup option.
435     *  
436     * @return state {@link StorageState} of the storage directory 
437     * @throws InconsistentFSStateException if directory state is not 
438     * consistent and cannot be recovered.
439     * @throws IOException
440     */
441    public StorageState analyzeStorage(StartupOption startOpt, Storage storage)
442        throws IOException {
443      assert root != null : "root is null";
444      String rootPath = root.getCanonicalPath();
445      try { // check that storage exists
446        if (!root.exists()) {
447          // storage directory does not exist
448          if (startOpt != StartupOption.FORMAT) {
449            LOG.warn("Storage directory " + rootPath + " does not exist");
450            return StorageState.NON_EXISTENT;
451          }
452          LOG.info(rootPath + " does not exist. Creating ...");
453          if (!root.mkdirs())
454            throw new IOException("Cannot create directory " + rootPath);
455        }
456        // or is inaccessible
457        if (!root.isDirectory()) {
458          LOG.warn(rootPath + "is not a directory");
459          return StorageState.NON_EXISTENT;
460        }
461        if (!FileUtil.canWrite(root)) {
462          LOG.warn("Cannot access storage directory " + rootPath);
463          return StorageState.NON_EXISTENT;
464        }
465      } catch(SecurityException ex) {
466        LOG.warn("Cannot access storage directory " + rootPath, ex);
467        return StorageState.NON_EXISTENT;
468      }
469
470      this.lock(); // lock storage if it exists
471
472      if (startOpt == HdfsServerConstants.StartupOption.FORMAT)
473        return StorageState.NOT_FORMATTED;
474
475      if (startOpt != HdfsServerConstants.StartupOption.IMPORT) {
476        storage.checkOldLayoutStorage(this);
477      }
478
479      // check whether current directory is valid
480      File versionFile = getVersionFile();
481      boolean hasCurrent = versionFile.exists();
482
483      // check which directories exist
484      boolean hasPrevious = getPreviousDir().exists();
485      boolean hasPreviousTmp = getPreviousTmp().exists();
486      boolean hasRemovedTmp = getRemovedTmp().exists();
487      boolean hasFinalizedTmp = getFinalizedTmp().exists();
488      boolean hasCheckpointTmp = getLastCheckpointTmp().exists();
489
490      if (!(hasPreviousTmp || hasRemovedTmp
491          || hasFinalizedTmp || hasCheckpointTmp)) {
492        // no temp dirs - no recovery
493        if (hasCurrent)
494          return StorageState.NORMAL;
495        if (hasPrevious)
496          throw new InconsistentFSStateException(root,
497                              "version file in current directory is missing.");
498        return StorageState.NOT_FORMATTED;
499      }
500
501      if ((hasPreviousTmp?1:0) + (hasRemovedTmp?1:0)
502          + (hasFinalizedTmp?1:0) + (hasCheckpointTmp?1:0) > 1)
503        // more than one temp dirs
504        throw new InconsistentFSStateException(root,
505                                               "too many temporary directories.");
506
507      // # of temp dirs == 1 should either recover or complete a transition
508      if (hasCheckpointTmp) {
509        return hasCurrent ? StorageState.COMPLETE_CHECKPOINT
510                          : StorageState.RECOVER_CHECKPOINT;
511      }
512
513      if (hasFinalizedTmp) {
514        if (hasPrevious)
515          throw new InconsistentFSStateException(root,
516                                                 STORAGE_DIR_PREVIOUS + " and " + STORAGE_TMP_FINALIZED
517                                                 + "cannot exist together.");
518        return StorageState.COMPLETE_FINALIZE;
519      }
520
521      if (hasPreviousTmp) {
522        if (hasPrevious)
523          throw new InconsistentFSStateException(root,
524                                                 STORAGE_DIR_PREVIOUS + " and " + STORAGE_TMP_PREVIOUS
525                                                 + " cannot exist together.");
526        if (hasCurrent)
527          return StorageState.COMPLETE_UPGRADE;
528        return StorageState.RECOVER_UPGRADE;
529      }
530      
531      assert hasRemovedTmp : "hasRemovedTmp must be true";
532      if (!(hasCurrent ^ hasPrevious))
533        throw new InconsistentFSStateException(root,
534                                               "one and only one directory " + STORAGE_DIR_CURRENT 
535                                               + " or " + STORAGE_DIR_PREVIOUS 
536                                               + " must be present when " + STORAGE_TMP_REMOVED
537                                               + " exists.");
538      if (hasCurrent)
539        return StorageState.COMPLETE_ROLLBACK;
540      return StorageState.RECOVER_ROLLBACK;
541    }
542
543    /**
544     * Complete or recover storage state from previously failed transition.
545     * 
546     * @param curState specifies what/how the state should be recovered
547     * @throws IOException
548     */
549    public void doRecover(StorageState curState) throws IOException {
550      File curDir = getCurrentDir();
551      String rootPath = root.getCanonicalPath();
552      switch(curState) {
553      case COMPLETE_UPGRADE:  // mv previous.tmp -> previous
554        LOG.info("Completing previous upgrade for storage directory " 
555                 + rootPath);
556        rename(getPreviousTmp(), getPreviousDir());
557        return;
558      case RECOVER_UPGRADE:   // mv previous.tmp -> current
559        LOG.info("Recovering storage directory " + rootPath
560                 + " from previous upgrade");
561        if (curDir.exists())
562          deleteDir(curDir);
563        rename(getPreviousTmp(), curDir);
564        return;
565      case COMPLETE_ROLLBACK: // rm removed.tmp
566        LOG.info("Completing previous rollback for storage directory "
567                 + rootPath);
568        deleteDir(getRemovedTmp());
569        return;
570      case RECOVER_ROLLBACK:  // mv removed.tmp -> current
571        LOG.info("Recovering storage directory " + rootPath
572                 + " from previous rollback");
573        rename(getRemovedTmp(), curDir);
574        return;
575      case COMPLETE_FINALIZE: // rm finalized.tmp
576        LOG.info("Completing previous finalize for storage directory "
577                 + rootPath);
578        deleteDir(getFinalizedTmp());
579        return;
580      case COMPLETE_CHECKPOINT: // mv lastcheckpoint.tmp -> previous.checkpoint
581        LOG.info("Completing previous checkpoint for storage directory " 
582                 + rootPath);
583        File prevCkptDir = getPreviousCheckpoint();
584        if (prevCkptDir.exists())
585          deleteDir(prevCkptDir);
586        rename(getLastCheckpointTmp(), prevCkptDir);
587        return;
588      case RECOVER_CHECKPOINT:  // mv lastcheckpoint.tmp -> current
589        LOG.info("Recovering storage directory " + rootPath
590                 + " from failed checkpoint");
591        if (curDir.exists())
592          deleteDir(curDir);
593        rename(getLastCheckpointTmp(), curDir);
594        return;
595      default:
596        throw new IOException("Unexpected FS state: " + curState);
597      }
598    }
599    
600    /**
601     * @return true if the storage directory should prompt the user prior
602     * to formatting (i.e if the directory appears to contain some data)
603     * @throws IOException if the SD cannot be accessed due to an IO error
604     */
605    @Override
606    public boolean hasSomeData() throws IOException {
607      // Its alright for a dir not to exist, or to exist (properly accessible)
608      // and be completely empty.
609      if (!root.exists()) return false;
610      
611      if (!root.isDirectory()) {
612        // a file where you expect a directory should not cause silent
613        // formatting
614        return true;
615      }
616      
617      if (FileUtil.listFiles(root).length == 0) {
618        // Empty dir can format without prompt.
619        return false;
620      }
621      
622      return true;
623    }
624
625
626    /**
627     * Lock storage to provide exclusive access.
628     * 
629     * <p> Locking is not supported by all file systems.
630     * E.g., NFS does not consistently support exclusive locks.
631     * 
632     * <p> If locking is supported we guarantee exclusive access to the
633     * storage directory. Otherwise, no guarantee is given.
634     * 
635     * @throws IOException if locking fails
636     */
637    public void lock() throws IOException {
638      if (!useLock) {
639        LOG.info("Locking is disabled");
640        return;
641      }
642      FileLock newLock = tryLock();
643      if (newLock == null) {
644        String msg = "Cannot lock storage " + this.root 
645          + ". The directory is already locked";
646        LOG.info(msg);
647        throw new IOException(msg);
648      }
649      // Don't overwrite lock until success - this way if we accidentally
650      // call lock twice, the internal state won't be cleared by the second
651      // (failed) lock attempt
652      lock = newLock;
653    }
654
655    /**
656     * Attempts to acquire an exclusive lock on the storage.
657     * 
658     * @return A lock object representing the newly-acquired lock or
659     * <code>null</code> if storage is already locked.
660     * @throws IOException if locking fails.
661     */
662    FileLock tryLock() throws IOException {
663      boolean deletionHookAdded = false;
664      File lockF = new File(root, STORAGE_FILE_LOCK);
665      if (!lockF.exists()) {
666        lockF.deleteOnExit();
667        deletionHookAdded = true;
668      }
669      RandomAccessFile file = new RandomAccessFile(lockF, "rws");
670      String jvmName = ManagementFactory.getRuntimeMXBean().getName();
671      FileLock res = null;
672      try {
673        res = file.getChannel().tryLock();
674        file.write(jvmName.getBytes(Charsets.UTF_8));
675        LOG.info("Lock on " + lockF + " acquired by nodename " + jvmName);
676      } catch(OverlappingFileLockException oe) {
677        // Cannot read from the locked file on Windows.
678        String lockingJvmName = Path.WINDOWS ? "" : (" " + file.readLine());
679        LOG.error("It appears that another namenode" + lockingJvmName
680            + " has already locked the storage directory");
681        file.close();
682        return null;
683      } catch(IOException e) {
684        LOG.error("Failed to acquire lock on " + lockF + ". If this storage directory is mounted via NFS, " 
685            + "ensure that the appropriate nfs lock services are running.", e);
686        file.close();
687        throw e;
688      }
689      if (res != null && !deletionHookAdded) {
690        // If the file existed prior to our startup, we didn't
691        // call deleteOnExit above. But since we successfully locked
692        // the dir, we can take care of cleaning it up.
693        lockF.deleteOnExit();
694      }
695      return res;
696    }
697
698    /**
699     * Unlock storage.
700     * 
701     * @throws IOException
702     */
703    public void unlock() throws IOException {
704      if (this.lock == null)
705        return;
706      this.lock.release();
707      lock.channel().close();
708      lock = null;
709    }
710    
711    @Override
712    public String toString() {
713      return "Storage Directory " + this.root;
714    }
715
716    /**
717     * Check whether underlying file system supports file locking.
718     * 
719     * @return <code>true</code> if exclusive locks are supported or
720     *         <code>false</code> otherwise.
721     * @throws IOException
722     * @see StorageDirectory#lock()
723     */
724    public boolean isLockSupported() throws IOException {
725      FileLock firstLock = null;
726      FileLock secondLock = null;
727      try {
728        firstLock = lock;
729        if(firstLock == null) {
730          firstLock = tryLock();
731          if(firstLock == null)
732            return true;
733        }
734        secondLock = tryLock();
735        if(secondLock == null)
736          return true;
737      } finally {
738        if(firstLock != null && firstLock != lock) {
739          firstLock.release();
740          firstLock.channel().close();
741        }
742        if(secondLock != null) {
743          secondLock.release();
744          secondLock.channel().close();
745        }
746      }
747      return false;
748    }
749  }
750
751  /**
752   * Create empty storage info of the specified type
753   */
754  protected Storage(NodeType type) {
755    super();
756    this.storageType = type;
757  }
758  
759  protected Storage(NodeType type, StorageInfo storageInfo) {
760    super(storageInfo);
761    this.storageType = type;
762  }
763  
764  public int getNumStorageDirs() {
765    return storageDirs.size();
766  }
767  
768  public StorageDirectory getStorageDir(int idx) {
769    return storageDirs.get(idx);
770  }
771  
772  /**
773   * @return the storage directory, with the precondition that this storage
774   * has exactly one storage directory
775   */
776  public StorageDirectory getSingularStorageDir() {
777    Preconditions.checkState(storageDirs.size() == 1);
778    return storageDirs.get(0);
779  }
780  
781  protected void addStorageDir(StorageDirectory sd) {
782    storageDirs.add(sd);
783  }
784
785  /**
786   * Return true if the layout of the given storage directory is from a version
787   * of Hadoop prior to the introduction of the "current" and "previous"
788   * directories which allow upgrade and rollback.
789   */
790  public abstract boolean isPreUpgradableLayout(StorageDirectory sd)
791  throws IOException;
792
793  /**
794   * Check if the given storage directory comes from a version of Hadoop
795   * prior to when the directory layout changed (ie 0.13). If this is
796   * the case, this method throws an IOException.
797   */
798  private void checkOldLayoutStorage(StorageDirectory sd) throws IOException {
799    if (isPreUpgradableLayout(sd)) {
800      checkVersionUpgradable(0);
801    }
802  }
803
804  /**
805   * Checks if the upgrade from the given old version is supported. If
806   * no upgrade is supported, it throws IncorrectVersionException.
807   * 
808   * @param oldVersion
809   */
810  public static void checkVersionUpgradable(int oldVersion) 
811                                     throws IOException {
812    if (oldVersion > LAST_UPGRADABLE_LAYOUT_VERSION) {
813      String msg = "*********** Upgrade is not supported from this " +
814                   " older version " + oldVersion + 
815                   " of storage to the current version." + 
816                   " Please upgrade to " + LAST_UPGRADABLE_HADOOP_VERSION +
817                   " or a later version and then upgrade to current" +
818                   " version. Old layout version is " + 
819                   (oldVersion == 0 ? "'too old'" : (""+oldVersion)) +
820                   " and latest layout version this software version can" +
821                   " upgrade from is " + LAST_UPGRADABLE_LAYOUT_VERSION +
822                   ". ************";
823      LOG.error(msg);
824      throw new IOException(msg); 
825    }
826    
827  }
828  
829  /**
830   * Iterate over each of the {@link FormatConfirmable} objects,
831   * potentially checking with the user whether it should be formatted.
832   * 
833   * If running in interactive mode, will prompt the user for each
834   * directory to allow them to format anyway. Otherwise, returns
835   * false, unless 'force' is specified.
836   * 
837   * @param force format regardless of whether dirs exist
838   * @param interactive prompt the user when a dir exists
839   * @return true if formatting should proceed
840   * @throws IOException if some storage cannot be accessed
841   */
842  public static boolean confirmFormat(
843      Iterable<? extends FormatConfirmable> items,
844      boolean force, boolean interactive) throws IOException {
845    for (FormatConfirmable item : items) {
846      if (!item.hasSomeData())
847        continue;
848      if (force) { // Don't confirm, always format.
849        System.err.println(
850            "Data exists in " + item + ". Formatting anyway.");
851        continue;
852      }
853      if (!interactive) { // Don't ask - always don't format
854        System.err.println(
855            "Running in non-interactive mode, and data appears to exist in " +
856            item + ". Not formatting.");
857        return false;
858      }
859      if (!ToolRunner.confirmPrompt("Re-format filesystem in " + item + " ?")) {
860        System.err.println("Format aborted in " + item);
861        return false;
862      }
863    }
864    
865    return true;
866  }
867  
868  /**
869   * Interface for classes which need to have the user confirm their
870   * formatting during NameNode -format and other similar operations.
871   * 
872   * This is currently a storage directory or journal manager.
873   */
874  @InterfaceAudience.Private
875  public interface FormatConfirmable {
876    /**
877     * @return true if the storage seems to have some valid data in it,
878     * and the user should be required to confirm the format. Otherwise,
879     * false.
880     * @throws IOException if the storage cannot be accessed at all.
881     */
882    public boolean hasSomeData() throws IOException;
883    
884    /**
885     * @return a string representation of the formattable item, suitable
886     * for display to the user inside a prompt
887     */
888    public String toString();
889  }
890  
891  /**
892   * Get common storage fields.
893   * Should be overloaded if additional fields need to be get.
894   * 
895   * @param props
896   * @throws IOException
897   */
898  protected void setFieldsFromProperties(
899      Properties props, StorageDirectory sd) throws IOException {
900    setLayoutVersion(props, sd);
901    setNamespaceID(props, sd);
902    setStorageType(props, sd);
903    setcTime(props, sd);
904    setClusterId(props, layoutVersion, sd);
905  }
906  
907  /**
908   * Set common storage fields into the given properties object.
909   * Should be overloaded if additional fields need to be set.
910   * 
911   * @param props the Properties object to write into
912   */
913  protected void setPropertiesFromFields(Properties props, 
914                                         StorageDirectory sd)
915      throws IOException {
916    props.setProperty("layoutVersion", String.valueOf(layoutVersion));
917    props.setProperty("storageType", storageType.toString());
918    props.setProperty("namespaceID", String.valueOf(namespaceID));
919    // Set clusterID in version with federation support
920    if (versionSupportsFederation()) {
921      props.setProperty("clusterID", clusterID);
922    }
923    props.setProperty("cTime", String.valueOf(cTime));
924  }
925
926  /**
927   * Read properties from the VERSION file in the given storage directory.
928   */
929  public void readProperties(StorageDirectory sd) throws IOException {
930    Properties props = readPropertiesFile(sd.getVersionFile());
931    setFieldsFromProperties(props, sd);
932  }
933
934  /**
935   * Read properties from the the previous/VERSION file in the given storage directory.
936   */
937  public void readPreviousVersionProperties(StorageDirectory sd)
938      throws IOException {
939    Properties props = readPropertiesFile(sd.getPreviousVersionFile());
940    setFieldsFromProperties(props, sd);
941  }
942
943  /**
944   * Write properties to the VERSION file in the given storage directory.
945   */
946  public void writeProperties(StorageDirectory sd) throws IOException {
947    writeProperties(sd.getVersionFile(), sd);
948  }
949
950  public void writeProperties(File to, StorageDirectory sd) throws IOException {
951    Properties props = new Properties();
952    setPropertiesFromFields(props, sd);
953    RandomAccessFile file = new RandomAccessFile(to, "rws");
954    FileOutputStream out = null;
955    try {
956      file.seek(0);
957      out = new FileOutputStream(file.getFD());
958      /*
959       * If server is interrupted before this line, 
960       * the version file will remain unchanged.
961       */
962      props.store(out, null);
963      /*
964       * Now the new fields are flushed to the head of the file, but file 
965       * length can still be larger then required and therefore the file can 
966       * contain whole or corrupted fields from its old contents in the end.
967       * If server is interrupted here and restarted later these extra fields
968       * either should not effect server behavior or should be handled
969       * by the server correctly.
970       */
971      file.setLength(out.getChannel().position());
972    } finally {
973      if (out != null) {
974        out.close();
975      }
976      file.close();
977    }
978  }
979  
980  public static Properties readPropertiesFile(File from) throws IOException {
981    RandomAccessFile file = new RandomAccessFile(from, "rws");
982    FileInputStream in = null;
983    Properties props = new Properties();
984    try {
985      in = new FileInputStream(file.getFD());
986      file.seek(0);
987      props.load(in);
988    } finally {
989      if (in != null) {
990        in.close();
991      }
992      file.close();
993    }
994    return props;
995  }
996
997  public static void rename(File from, File to) throws IOException {
998    if (!from.renameTo(to))
999      throw new IOException("Failed to rename " 
1000                            + from.getCanonicalPath() + " to " + to.getCanonicalPath());
1001  }
1002
1003  /**
1004   * Recursively delete all the content of the directory first and then 
1005   * the directory itself from the local filesystem.
1006   * @param dir The directory to delete
1007   * @throws IOException
1008   */
1009  public static void deleteDir(File dir) throws IOException {
1010    if (!FileUtil.fullyDelete(dir))
1011      throw new IOException("Failed to delete " + dir.getCanonicalPath());
1012  }
1013  
1014  /**
1015   * Write all data storage files.
1016   * @throws IOException
1017   */
1018  public void writeAll() throws IOException {
1019    this.layoutVersion = HdfsConstants.LAYOUT_VERSION;
1020    for (Iterator<StorageDirectory> it = storageDirs.iterator(); it.hasNext();) {
1021      writeProperties(it.next());
1022    }
1023  }
1024
1025  /**
1026   * Unlock all storage directories.
1027   * @throws IOException
1028   */
1029  public void unlockAll() throws IOException {
1030    for (Iterator<StorageDirectory> it = storageDirs.iterator(); it.hasNext();) {
1031      it.next().unlock();
1032    }
1033  }
1034
1035  public static String getBuildVersion() {
1036    return VersionInfo.getRevision();
1037  }
1038
1039  public static String getRegistrationID(StorageInfo storage) {
1040    return "NS-" + Integer.toString(storage.getNamespaceID())
1041      + "-" + storage.getClusterID()
1042      + "-" + Integer.toString(storage.getLayoutVersion())
1043      + "-" + Long.toString(storage.getCTime());
1044  }
1045  
1046  String getProperty(Properties props, StorageDirectory sd,
1047      String name) throws InconsistentFSStateException {
1048    String property = props.getProperty(name);
1049    if (property == null) {
1050      throw new InconsistentFSStateException(sd.root, "file "
1051          + STORAGE_FILE_VERSION + " has " + name + " missing.");
1052    }
1053    return property;
1054  }
1055  
1056  /** Validate and set storage type from {@link Properties}*/
1057  protected void setStorageType(Properties props, StorageDirectory sd)
1058      throws InconsistentFSStateException {
1059    NodeType type = NodeType.valueOf(getProperty(props, sd, "storageType"));
1060    if (!storageType.equals(type)) {
1061      throw new InconsistentFSStateException(sd.root,
1062          "node type is incompatible with others.");
1063    }
1064    storageType = type;
1065  }
1066  
1067  /** Validate and set ctime from {@link Properties}*/
1068  protected void setcTime(Properties props, StorageDirectory sd)
1069      throws InconsistentFSStateException {
1070    cTime = Long.parseLong(getProperty(props, sd, "cTime"));
1071  }
1072
1073  /** Validate and set clusterId from {@link Properties}*/
1074  protected void setClusterId(Properties props, int layoutVersion,
1075      StorageDirectory sd) throws InconsistentFSStateException {
1076    // Set cluster ID in version that supports federation
1077    if (LayoutVersion.supports(Feature.FEDERATION, layoutVersion)) {
1078      String cid = getProperty(props, sd, "clusterID");
1079      if (!(clusterID.equals("") || cid.equals("") || clusterID.equals(cid))) {
1080        throw new InconsistentFSStateException(sd.getRoot(),
1081            "cluster Id is incompatible with others.");
1082      }
1083      clusterID = cid;
1084    }
1085  }
1086  
1087  /** Validate and set layout version from {@link Properties}*/
1088  protected void setLayoutVersion(Properties props, StorageDirectory sd)
1089      throws IncorrectVersionException, InconsistentFSStateException {
1090    int lv = Integer.parseInt(getProperty(props, sd, "layoutVersion"));
1091    if (lv < HdfsConstants.LAYOUT_VERSION) { // future version
1092      throw new IncorrectVersionException(lv, "storage directory "
1093          + sd.root.getAbsolutePath());
1094    }
1095    layoutVersion = lv;
1096  }
1097  
1098  /** Validate and set namespaceID version from {@link Properties}*/
1099  protected void setNamespaceID(Properties props, StorageDirectory sd)
1100      throws InconsistentFSStateException {
1101    int nsId = Integer.parseInt(getProperty(props, sd, "namespaceID"));
1102    if (namespaceID != 0 && nsId != 0 && namespaceID != nsId) {
1103      throw new InconsistentFSStateException(sd.root,
1104          "namespaceID is incompatible with others.");
1105    }
1106    namespaceID = nsId;
1107  }
1108  
1109  public static boolean is203LayoutVersion(int layoutVersion) {
1110    for (int lv203 : LAYOUT_VERSIONS_203) {
1111      if (lv203 == layoutVersion) {
1112        return true;
1113      }
1114    }
1115    return false;
1116  }
1117}