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