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