1 /**
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements.  See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership.  The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License.  You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 package org.apache.hadoop.hdfs.server.common;
19 
20 import java.io.File;
21 import java.io.FileInputStream;
22 import java.io.FileOutputStream;
23 import java.io.IOException;
24 import java.io.RandomAccessFile;
25 import java.nio.channels.FileLock;
26 import java.nio.channels.OverlappingFileLockException;
27 import java.util.ArrayList;
28 import java.util.List;
29 import java.util.Iterator;
30 import java.util.Properties;
31 
32 import org.apache.commons.logging.Log;
33 import org.apache.commons.logging.LogFactory;
34 import org.apache.hadoop.hdfs.protocol.FSConstants;
35 import org.apache.hadoop.hdfs.server.common.HdfsConstants.NodeType;
36 import org.apache.hadoop.hdfs.server.common.HdfsConstants.StartupOption;
37 import org.apache.hadoop.fs.FileUtil;
38 
39 /**
40  * Storage information file.
41  * <p>
42  * Local storage information is stored in a separate file VERSION.
43  * It contains type of the node,
44  * the storage layout version, the namespace id, and
45  * the fs state creation time.
46  * <p>
47  * Local storage can reside in multiple directories.
48  * Each directory should contain the same VERSION file as the others.
49  * During startup Hadoop servers (name-node and data-nodes) read their local
50  * storage information from them.
51  * <p>
52  * The servers hold a lock for each storage directory while they run so that
53  * other nodes were not able to startup sharing the same storage.
54  * The locks are released when the servers stop (normally or abnormally).
55  *
56  */
57 public abstract class Storage extends StorageInfo {
58   public static final Log LOG = LogFactory.getLog(Storage.class.getName());
59 
60   // Constants
61 
62   // last layout version that did not suppot upgrades
63   protected static final int LAST_PRE_UPGRADE_LAYOUT_VERSION = -3;
64 
65   // this corresponds to Hadoop-0.14.
66   public static final int LAST_UPGRADABLE_LAYOUT_VERSION = -7;
67   protected static final String LAST_UPGRADABLE_HADOOP_VERSION = "Hadoop-0.14";
68 
69   /* this should be removed when LAST_UPGRADABLE_LV goes beyond -13.
70    * any upgrade code that uses this constant should also be removed. */
71   public static final int PRE_GENERATIONSTAMP_LAYOUT_VERSION = -13;
72 
73   /** Layout versions of 203 release */
74   public static final int[] LAYOUT_VERSIONS_203 = {-19, -31};
75 
76   private   static final String STORAGE_FILE_LOCK     = "in_use.lock";
77   protected static final String STORAGE_FILE_VERSION  = "VERSION";
78   public static final String STORAGE_DIR_CURRENT   = "current";
79   private   static final String STORAGE_DIR_PREVIOUS  = "previous";
80   private   static final String STORAGE_TMP_REMOVED   = "removed.tmp";
81   private   static final String STORAGE_TMP_PREVIOUS  = "previous.tmp";
82   private   static final String STORAGE_TMP_FINALIZED = "finalized.tmp";
83   private   static final String STORAGE_TMP_LAST_CKPT = "lastcheckpoint.tmp";
84   private   static final String STORAGE_PREVIOUS_CKPT = "previous.checkpoint";
85 
86   public enum StorageState {
87     NON_EXISTENT,
88     NOT_FORMATTED,
89     COMPLETE_UPGRADE,
90     RECOVER_UPGRADE,
91     COMPLETE_FINALIZE,
92     COMPLETE_ROLLBACK,
93     RECOVER_ROLLBACK,
94     COMPLETE_CHECKPOINT,
95     RECOVER_CHECKPOINT,
96     NORMAL;
97   }
98 
99   /**
100    * An interface to denote storage directory type
101    * Implementations can define a type for storage directory by implementing
102    * this interface.
103    */
104   public interface StorageDirType {
getStorageDirType()105     public StorageDirType getStorageDirType();
isOfType(StorageDirType type)106     public boolean isOfType(StorageDirType type);
107   }
108 
109   private NodeType storageType;    // Type of the node using this storage
110   protected List<StorageDirectory> storageDirs = new ArrayList<StorageDirectory>();
111 
112   private class DirIterator implements Iterator<StorageDirectory> {
113     StorageDirType dirType;
114     int prevIndex; // for remove()
115     int nextIndex; // for next()
116 
DirIterator(StorageDirType dirType)117     DirIterator(StorageDirType dirType) {
118       this.dirType = dirType;
119       this.nextIndex = 0;
120       this.prevIndex = 0;
121     }
122 
hasNext()123     public boolean hasNext() {
124       if (storageDirs.isEmpty() || nextIndex >= storageDirs.size())
125         return false;
126       if (dirType != null) {
127         while (nextIndex < storageDirs.size()) {
128           if (getStorageDir(nextIndex).getStorageDirType().isOfType(dirType))
129             break;
130           nextIndex++;
131         }
132         if (nextIndex >= storageDirs.size())
133          return false;
134       }
135       return true;
136     }
137 
next()138     public StorageDirectory next() {
139       StorageDirectory sd = getStorageDir(nextIndex);
140       prevIndex = nextIndex;
141       nextIndex++;
142       if (dirType != null) {
143         while (nextIndex < storageDirs.size()) {
144           if (getStorageDir(nextIndex).getStorageDirType().isOfType(dirType))
145             break;
146           nextIndex++;
147         }
148       }
149       return sd;
150     }
151 
remove()152     public void remove() {
153       nextIndex = prevIndex; // restore previous state
154       storageDirs.remove(prevIndex); // remove last returned element
155       hasNext(); // reset nextIndex to correct place
156     }
157   }
158 
159   /**
160    * Return default iterator
161    * This iterator returns all entires of storageDirs
162    */
dirIterator()163   public Iterator<StorageDirectory> dirIterator() {
164     return dirIterator(null);
165   }
166 
167   /**
168    * Return iterator based on Storage Directory Type
169    * This iterator selects entires of storageDirs of type dirType and returns
170    * them via the Iterator
171    */
dirIterator(StorageDirType dirType)172   public Iterator<StorageDirectory> dirIterator(StorageDirType dirType) {
173     return new DirIterator(dirType);
174   }
175 
176   /**
177    * One of the storage directories.
178    */
179   public class StorageDirectory {
180     File              root; // root directory
181     FileLock          lock; // storage lock
182     StorageDirType dirType; // storage dir type
183 
StorageDirectory(File dir)184     public StorageDirectory(File dir) {
185       // default dirType is null
186       this(dir, null);
187     }
188 
StorageDirectory(File dir, StorageDirType dirType)189     public StorageDirectory(File dir, StorageDirType dirType) {
190       this.root = dir;
191       this.lock = null;
192       this.dirType = dirType;
193     }
194 
195     /**
196      * Get root directory of this storage
197      */
getRoot()198     public File getRoot() {
199       return root;
200     }
201 
202     /**
203      * Get storage directory type
204      */
getStorageDirType()205     public StorageDirType getStorageDirType() {
206       return dirType;
207     }
208 
209     /**
210      * Read version file.
211      *
212      * @throws IOException if file cannot be read or contains inconsistent data
213      */
read()214     public void read() throws IOException {
215       read(getVersionFile());
216     }
217 
read(File from)218     public void read(File from) throws IOException {
219       RandomAccessFile file = new RandomAccessFile(from, "rws");
220       FileInputStream in = null;
221       try {
222         in = new FileInputStream(file.getFD());
223         file.seek(0);
224         Properties props = new Properties();
225         props.load(in);
226         getFields(props, this);
227       } finally {
228         if (in != null) {
229           in.close();
230         }
231         file.close();
232       }
233     }
234 
235     /**
236      * Write version file.
237      *
238      * @throws IOException
239      */
write()240     public void write() throws IOException {
241       corruptPreUpgradeStorage(root);
242       write(getVersionFile());
243     }
244 
write(File to)245     public void write(File to) throws IOException {
246       Properties props = new Properties();
247       setFields(props, this);
248       RandomAccessFile file = new RandomAccessFile(to, "rws");
249       FileOutputStream out = null;
250       try {
251         file.seek(0);
252         out = new FileOutputStream(file.getFD());
253         /*
254          * If server is interrupted before this line,
255          * the version file will remain unchanged.
256          */
257         props.store(out, null);
258         /*
259          * Now the new fields are flushed to the head of the file, but file
260          * length can still be larger then required and therefore the file can
261          * contain whole or corrupted fields from its old contents in the end.
262          * If server is interrupted here and restarted later these extra fields
263          * either should not effect server behavior or should be handled
264          * by the server correctly.
265          */
266         file.setLength(out.getChannel().position());
267       } finally {
268         if (out != null) {
269           out.close();
270         }
271         file.close();
272       }
273     }
274 
275     /**
276      * Clear and re-create storage directory.
277      * <p>
278      * Removes contents of the current directory and creates an empty directory.
279      *
280      * This does not fully format storage directory.
281      * It cannot write the version file since it should be written last after
282      * all other storage type dependent files are written.
283      * Derived storage is responsible for setting specific storage values and
284      * writing the version file to disk.
285      *
286      * @throws IOException
287      */
clearDirectory()288     public void clearDirectory() throws IOException {
289       File curDir = this.getCurrentDir();
290       if (curDir.exists())
291         if (!(FileUtil.fullyDelete(curDir)))
292           throw new IOException("Cannot remove current directory: " + curDir);
293       if (!curDir.mkdirs())
294         throw new IOException("Cannot create directory " + curDir);
295     }
296 
297     /**
298      * Directory {@code current} contains latest files defining
299      * the file system meta-data.
300      *
301      * @return the directory path
302      */
getCurrentDir()303     public File getCurrentDir() {
304       return new File(root, STORAGE_DIR_CURRENT);
305     }
306 
307     /**
308      * File {@code VERSION} contains the following fields:
309      * <ol>
310      * <li>node type</li>
311      * <li>layout version</li>
312      * <li>namespaceID</li>
313      * <li>fs state creation time</li>
314      * <li>other fields specific for this node type</li>
315      * </ol>
316      * The version file is always written last during storage directory updates.
317      * The existence of the version file indicates that all other files have
318      * been successfully written in the storage directory, the storage is valid
319      * and does not need to be recovered.
320      *
321      * @return the version file path
322      */
getVersionFile()323     public File getVersionFile() {
324       return new File(new File(root, STORAGE_DIR_CURRENT), STORAGE_FILE_VERSION);
325     }
326 
327     /**
328      * File {@code VERSION} from the {@code previous} directory.
329      *
330      * @return the previous version file path
331      */
getPreviousVersionFile()332     public File getPreviousVersionFile() {
333       return new File(new File(root, STORAGE_DIR_PREVIOUS), STORAGE_FILE_VERSION);
334     }
335 
336     /**
337      * Directory {@code previous} contains the previous file system state,
338      * which the system can be rolled back to.
339      *
340      * @return the directory path
341      */
getPreviousDir()342     public File getPreviousDir() {
343       return new File(root, STORAGE_DIR_PREVIOUS);
344     }
345 
346     /**
347      * {@code previous.tmp} is a transient directory, which holds
348      * current file system state while the new state is saved into the new
349      * {@code current} during upgrade.
350      * If the saving succeeds {@code previous.tmp} will be moved to
351      * {@code previous}, otherwise it will be renamed back to
352      * {@code current} by the recovery procedure during startup.
353      *
354      * @return the directory path
355      */
getPreviousTmp()356     public File getPreviousTmp() {
357       return new File(root, STORAGE_TMP_PREVIOUS);
358     }
359 
360     /**
361      * {@code removed.tmp} is a transient directory, which holds
362      * current file system state while the previous state is moved into
363      * {@code current} during rollback.
364      * If the moving succeeds {@code removed.tmp} will be removed,
365      * otherwise it will be renamed back to
366      * {@code current} by the recovery procedure during startup.
367      *
368      * @return the directory path
369      */
getRemovedTmp()370     public File getRemovedTmp() {
371       return new File(root, STORAGE_TMP_REMOVED);
372     }
373 
374     /**
375      * {@code finalized.tmp} is a transient directory, which holds
376      * the {@code previous} file system state while it is being removed
377      * in response to the finalize request.
378      * Finalize operation will remove {@code finalized.tmp} when completed,
379      * otherwise the removal will resume upon the system startup.
380      *
381      * @return the directory path
382      */
getFinalizedTmp()383     public File getFinalizedTmp() {
384       return new File(root, STORAGE_TMP_FINALIZED);
385     }
386 
387     /**
388      * {@code lastcheckpoint.tmp} is a transient directory, which holds
389      * current file system state while the new state is saved into the new
390      * {@code current} during regular namespace updates.
391      * If the saving succeeds {@code lastcheckpoint.tmp} will be moved to
392      * {@code previous.checkpoint}, otherwise it will be renamed back to
393      * {@code current} by the recovery procedure during startup.
394      *
395      * @return the directory path
396      */
getLastCheckpointTmp()397     public File getLastCheckpointTmp() {
398       return new File(root, STORAGE_TMP_LAST_CKPT);
399     }
400 
401     /**
402      * {@code previous.checkpoint} is a directory, which holds the previous
403      * (before the last save) state of the storage directory.
404      * The directory is created as a reference only, it does not play role
405      * in state recovery procedures, and is recycled automatically,
406      * but it may be useful for manual recovery of a stale state of the system.
407      *
408      * @return the directory path
409      */
getPreviousCheckpoint()410     public File getPreviousCheckpoint() {
411       return new File(root, STORAGE_PREVIOUS_CKPT);
412     }
413 
414     /**
415      * Check consistency of the storage directory
416      *
417      * @param startOpt a startup option.
418      *
419      * @return state {@link StorageState} of the storage directory
420      * @throws InconsistentFSStateException if directory state is not
421      * consistent and cannot be recovered.
422      * @throws IOException
423      */
analyzeStorage(StartupOption startOpt)424     public StorageState analyzeStorage(StartupOption startOpt) throws IOException {
425       assert root != null : "root is null";
426       String rootPath = root.getCanonicalPath();
427       try { // check that storage exists
428         if (!root.exists()) {
429           // storage directory does not exist
430           if (startOpt != StartupOption.FORMAT) {
431             LOG.info("Storage directory " + rootPath + " does not exist");
432             return StorageState.NON_EXISTENT;
433           }
434           LOG.info(rootPath + " does not exist. Creating...");
435           if (!root.mkdirs())
436             throw new IOException("Cannot create directory " + rootPath);
437         }
438         // or is inaccessible
439         if (!root.isDirectory()) {
440           LOG.info(rootPath + "is not a directory");
441           return StorageState.NON_EXISTENT;
442         }
443         if (!root.canWrite()) {
444           LOG.info("Cannot access storage directory " + rootPath);
445           return StorageState.NON_EXISTENT;
446         }
447       } catch(SecurityException ex) {
448         LOG.info("Cannot access storage directory " + rootPath, ex);
449         return StorageState.NON_EXISTENT;
450       }
451 
452       this.lock(); // lock storage if it exists
453 
454       if (startOpt == HdfsConstants.StartupOption.FORMAT)
455         return StorageState.NOT_FORMATTED;
456       if (startOpt != HdfsConstants.StartupOption.IMPORT) {
457         //make sure no conversion is required
458         checkConversionNeeded(this);
459       }
460 
461       // check whether current directory is valid
462       File versionFile = getVersionFile();
463       boolean hasCurrent = versionFile.exists();
464 
465       // check which directories exist
466       boolean hasPrevious = getPreviousDir().exists();
467       boolean hasPreviousTmp = getPreviousTmp().exists();
468       boolean hasRemovedTmp = getRemovedTmp().exists();
469       boolean hasFinalizedTmp = getFinalizedTmp().exists();
470       boolean hasCheckpointTmp = getLastCheckpointTmp().exists();
471 
472       if (!(hasPreviousTmp || hasRemovedTmp
473           || hasFinalizedTmp || hasCheckpointTmp)) {
474         // no temp dirs - no recovery
475         if (hasCurrent)
476           return StorageState.NORMAL;
477         if (hasPrevious)
478           throw new InconsistentFSStateException(root,
479                               "version file in current directory is missing.");
480         return StorageState.NOT_FORMATTED;
481       }
482 
483       if ((hasPreviousTmp?1:0) + (hasRemovedTmp?1:0)
484           + (hasFinalizedTmp?1:0) + (hasCheckpointTmp?1:0) > 1)
485         // more than one temp dirs
486         throw new InconsistentFSStateException(root,
487                                                "too many temporary directories.");
488 
489       // # of temp dirs == 1 should either recover or complete a transition
490       if (hasCheckpointTmp) {
491         return hasCurrent ? StorageState.COMPLETE_CHECKPOINT
492                           : StorageState.RECOVER_CHECKPOINT;
493       }
494 
495       if (hasFinalizedTmp) {
496         if (hasPrevious)
497           throw new InconsistentFSStateException(root,
498                                                  STORAGE_DIR_PREVIOUS + " and " + STORAGE_TMP_FINALIZED
499                                                  + "cannot exist together.");
500         return StorageState.COMPLETE_FINALIZE;
501       }
502 
503       if (hasPreviousTmp) {
504         if (hasPrevious)
505           throw new InconsistentFSStateException(root,
506                                                  STORAGE_DIR_PREVIOUS + " and " + STORAGE_TMP_PREVIOUS
507                                                  + " cannot exist together.");
508         if (hasCurrent)
509           return StorageState.COMPLETE_UPGRADE;
510         return StorageState.RECOVER_UPGRADE;
511       }
512 
513       assert hasRemovedTmp : "hasRemovedTmp must be true";
514       if (!(hasCurrent ^ hasPrevious))
515         throw new InconsistentFSStateException(root,
516                                                "one and only one directory " + STORAGE_DIR_CURRENT
517                                                + " or " + STORAGE_DIR_PREVIOUS
518                                                + " must be present when " + STORAGE_TMP_REMOVED
519                                                + " exists.");
520       if (hasCurrent)
521         return StorageState.COMPLETE_ROLLBACK;
522       return StorageState.RECOVER_ROLLBACK;
523     }
524 
525     /**
526      * Complete or recover storage state from previously failed transition.
527      *
528      * @param curState specifies what/how the state should be recovered
529      * @throws IOException
530      */
doRecover(StorageState curState)531     public void doRecover(StorageState curState) throws IOException {
532       File curDir = getCurrentDir();
533       String rootPath = root.getCanonicalPath();
534       switch(curState) {
535       case COMPLETE_UPGRADE:  // mv previous.tmp -> previous
536         LOG.info("Completing previous upgrade for storage directory "
537                  + rootPath);
538         rename(getPreviousTmp(), getPreviousDir());
539         return;
540       case RECOVER_UPGRADE:   // mv previous.tmp -> current
541         LOG.info("Recovering storage directory " + rootPath
542                  + " from previous upgrade");
543         if (curDir.exists())
544           deleteDir(curDir);
545         rename(getPreviousTmp(), curDir);
546         return;
547       case COMPLETE_ROLLBACK: // rm removed.tmp
548         LOG.info("Completing previous rollback for storage directory "
549                  + rootPath);
550         deleteDir(getRemovedTmp());
551         return;
552       case RECOVER_ROLLBACK:  // mv removed.tmp -> current
553         LOG.info("Recovering storage directory " + rootPath
554                  + " from previous rollback");
555         rename(getRemovedTmp(), curDir);
556         return;
557       case COMPLETE_FINALIZE: // rm finalized.tmp
558         LOG.info("Completing previous finalize for storage directory "
559                  + rootPath);
560         deleteDir(getFinalizedTmp());
561         return;
562       case COMPLETE_CHECKPOINT: // mv lastcheckpoint.tmp -> previous.checkpoint
563         LOG.info("Completing previous checkpoint for storage directory "
564                  + rootPath);
565         File prevCkptDir = getPreviousCheckpoint();
566         if (prevCkptDir.exists())
567           deleteDir(prevCkptDir);
568         rename(getLastCheckpointTmp(), prevCkptDir);
569         return;
570       case RECOVER_CHECKPOINT:  // mv lastcheckpoint.tmp -> current
571         LOG.info("Recovering storage directory " + rootPath
572                  + " from failed checkpoint");
573         if (curDir.exists())
574           deleteDir(curDir);
575         rename(getLastCheckpointTmp(), curDir);
576         return;
577       default:
578         throw new IOException("Unexpected FS state: " + curState);
579       }
580     }
581 
582     /**
583      * Lock storage to provide exclusive access.
584      *
585      * <p> Locking is not supported by all file systems.
586      * E.g., NFS does not consistently support exclusive locks.
587      *
588      * <p> If locking is supported we guarantee exculsive access to the
589      * storage directory. Otherwise, no guarantee is given.
590      *
591      * @throws IOException if locking fails
592      */
lock()593     public void lock() throws IOException {
594       this.lock = tryLock();
595       if (lock == null) {
596         String msg = "Cannot lock storage " + this.root
597           + ". The directory is already locked.";
598         LOG.info(msg);
599         throw new IOException(msg);
600       }
601     }
602 
603     /**
604      * Attempts to acquire an exclusive lock on the storage.
605      *
606      * @return A lock object representing the newly-acquired lock or
607      * <code>null</code> if storage is already locked.
608      * @throws IOException if locking fails.
609      */
tryLock()610     FileLock tryLock() throws IOException {
611       boolean deletionHookAdded = false;
612       File lockF = new File(root, STORAGE_FILE_LOCK);
613       if (!lockF.exists()) {
614         lockF.deleteOnExit();
615         deletionHookAdded = true;
616       }
617       RandomAccessFile file = new RandomAccessFile(lockF, "rws");
618       FileLock res = null;
619       try {
620         res = file.getChannel().tryLock();
621       } catch(OverlappingFileLockException oe) {
622         file.close();
623         return null;
624       } catch(IOException e) {
625         LOG.error("Cannot create lock on " + lockF, e);
626         file.close();
627         throw e;
628       }
629       if (res != null && !deletionHookAdded) {
630         // If the file existed prior to our startup, we didn't
631         // call deleteOnExit above. But since we successfully locked
632         // the dir, we can take care of cleaning it up.
633         lockF.deleteOnExit();
634       }
635       return res;
636     }
637 
638     /**
639      * Unlock storage.
640      *
641      * @throws IOException
642      */
unlock()643     public void unlock() throws IOException {
644       if (this.lock == null)
645         return;
646       this.lock.release();
647       lock.channel().close();
648       lock = null;
649     }
650   }
651 
652   /**
653    * Create empty storage info of the specified type
654    */
Storage(NodeType type)655   protected Storage(NodeType type) {
656     super();
657     this.storageType = type;
658   }
659 
Storage(NodeType type, int nsID, long cT)660   protected Storage(NodeType type, int nsID, long cT) {
661     super(FSConstants.LAYOUT_VERSION, nsID, cT);
662     this.storageType = type;
663   }
664 
Storage(NodeType type, StorageInfo storageInfo)665   protected Storage(NodeType type, StorageInfo storageInfo) {
666     super(storageInfo);
667     this.storageType = type;
668   }
669 
getNumStorageDirs()670   public int getNumStorageDirs() {
671     return storageDirs.size();
672   }
673 
getStorageDir(int idx)674   public StorageDirectory getStorageDir(int idx) {
675     return storageDirs.get(idx);
676   }
677 
addStorageDir(StorageDirectory sd)678   protected void addStorageDir(StorageDirectory sd) {
679     storageDirs.add(sd);
680   }
681 
isConversionNeeded(StorageDirectory sd)682   public abstract boolean isConversionNeeded(StorageDirectory sd) throws IOException;
683 
684   /*
685    * Coversion is no longer supported. So this should throw exception if
686    * conversion is needed.
687    */
checkConversionNeeded(StorageDirectory sd)688   private void checkConversionNeeded(StorageDirectory sd) throws IOException {
689     if (isConversionNeeded(sd)) {
690       //throw an exception
691       checkVersionUpgradable(0);
692     }
693   }
694 
695   /**
696    * Checks if the upgrade from the given old version is supported. If
697    * no upgrade is supported, it throws IncorrectVersionException.
698    *
699    * @param oldVersion
700    */
checkVersionUpgradable(int oldVersion)701   protected static void checkVersionUpgradable(int oldVersion)
702                                      throws IOException {
703     if (oldVersion > LAST_UPGRADABLE_LAYOUT_VERSION) {
704       String msg = "*********** Upgrade is not supported from this older" +
705                    " version of storage to the current version." +
706                    " Please upgrade to " + LAST_UPGRADABLE_HADOOP_VERSION +
707                    " or a later version and then upgrade to current" +
708                    " version. Old layout version is " +
709                    (oldVersion == 0 ? "'too old'" : (""+oldVersion)) +
710                    " and latest layout version this software version can" +
711                    " upgrade from is " + LAST_UPGRADABLE_LAYOUT_VERSION +
712                    ". ************";
713       LOG.error(msg);
714       throw new IOException(msg);
715     }
716 
717   }
718 
719   /**
720    * Get common storage fields.
721    * Should be overloaded if additional fields need to be get.
722    *
723    * @param props
724    * @throws IOException
725    */
getFields(Properties props, StorageDirectory sd )726   protected void getFields(Properties props,
727                            StorageDirectory sd
728                            ) throws IOException {
729     String sv, st, sid, sct;
730     sv = props.getProperty("layoutVersion");
731     st = props.getProperty("storageType");
732     sid = props.getProperty("namespaceID");
733     sct = props.getProperty("cTime");
734     if (sv == null || st == null || sid == null || sct == null)
735       throw new InconsistentFSStateException(sd.root,
736                                              "file " + STORAGE_FILE_VERSION + " is invalid.");
737     int rv = Integer.parseInt(sv);
738     NodeType rt = NodeType.valueOf(st);
739     int rid = Integer.parseInt(sid);
740     long rct = Long.parseLong(sct);
741     if (!storageType.equals(rt) ||
742         !((namespaceID == 0) || (rid == 0) || namespaceID == rid))
743       throw new InconsistentFSStateException(sd.root,
744                                              "is incompatible with others.");
745     if (rv < FSConstants.LAYOUT_VERSION) // future version
746       throw new IncorrectVersionException(rv, "storage directory "
747                                           + sd.root.getCanonicalPath());
748     layoutVersion = rv;
749     storageType = rt;
750     namespaceID = rid;
751     cTime = rct;
752   }
753 
754   /**
755    * Set common storage fields.
756    * Should be overloaded if additional fields need to be set.
757    *
758    * @param props
759    * @throws IOException
760    */
setFields(Properties props, StorageDirectory sd )761   protected void setFields(Properties props,
762                            StorageDirectory sd
763                            ) throws IOException {
764     props.setProperty("layoutVersion", String.valueOf(layoutVersion));
765     props.setProperty("storageType", storageType.toString());
766     props.setProperty("namespaceID", String.valueOf(namespaceID));
767     props.setProperty("cTime", String.valueOf(cTime));
768   }
769 
rename(File from, File to)770   public static void rename(File from, File to) throws IOException {
771     if (!from.renameTo(to))
772       throw new IOException("Failed to rename "
773                             + from.getCanonicalPath() + " to " + to.getCanonicalPath());
774   }
775 
deleteDir(File dir)776   protected static void deleteDir(File dir) throws IOException {
777     if (!FileUtil.fullyDelete(dir))
778       throw new IOException("Failed to delete " + dir.getCanonicalPath());
779   }
780 
781   /**
782    * Write all data storage files.
783    * @throws IOException
784    */
writeAll()785   public void writeAll() throws IOException {
786     this.layoutVersion = FSConstants.LAYOUT_VERSION;
787     for (Iterator<StorageDirectory> it = storageDirs.iterator(); it.hasNext();) {
788       it.next().write();
789     }
790   }
791 
792   /**
793    * Unlock all storage directories.
794    * @throws IOException
795    */
unlockAll()796   public void unlockAll() throws IOException {
797     for (Iterator<StorageDirectory> it = storageDirs.iterator(); it.hasNext();) {
798       it.next().unlock();
799     }
800   }
801 
802   /**
803    * Check whether underlying file system supports file locking.
804    *
805    * @return <code>true</code> if exclusive locks are supported or
806    *         <code>false</code> otherwise.
807    * @throws IOException
808    * @see StorageDirectory#lock()
809    */
isLockSupported(int idx)810   public boolean isLockSupported(int idx) throws IOException {
811     StorageDirectory sd = storageDirs.get(idx);
812     FileLock firstLock = null;
813     FileLock secondLock = null;
814     try {
815       firstLock = sd.lock;
816       if(firstLock == null) {
817         firstLock = sd.tryLock();
818         if(firstLock == null)
819           return true;
820       }
821       secondLock = sd.tryLock();
822       if(secondLock == null)
823         return true;
824     } finally {
825       if(firstLock != null && firstLock != sd.lock) {
826         firstLock.release();
827         firstLock.channel().close();
828       }
829       if(secondLock != null) {
830         secondLock.release();
831         secondLock.channel().close();
832       }
833     }
834     return false;
835   }
836 
getRegistrationID(StorageInfo storage)837   public static String getRegistrationID(StorageInfo storage) {
838     return "NS-" + Integer.toString(storage.getNamespaceID())
839       + "-" + Integer.toString(storage.getLayoutVersion())
840       + "-" + Long.toString(storage.getCTime());
841   }
842 
843   // Pre-upgrade version compatibility
corruptPreUpgradeStorage(File rootDir)844   protected abstract void corruptPreUpgradeStorage(File rootDir) throws IOException;
845 
writeCorruptedData(RandomAccessFile file)846   protected void writeCorruptedData(RandomAccessFile file) throws IOException {
847     final String messageForPreUpgradeVersion =
848       "\nThis file is INTENTIONALLY CORRUPTED so that versions\n"
849       + "of Hadoop prior to 0.13 (which are incompatible\n"
850       + "with this directory layout) will fail to start.\n";
851 
852     file.seek(0);
853     file.writeInt(FSConstants.LAYOUT_VERSION);
854     org.apache.hadoop.io.UTF8.writeString(file, "");
855     file.writeBytes(messageForPreUpgradeVersion);
856     file.getFD().sync();
857   }
858 
is203LayoutVersion(int layoutVersion)859   public static boolean is203LayoutVersion(int layoutVersion) {
860     for (int lv : LAYOUT_VERSIONS_203) {
861       if (lv == layoutVersion) {
862         return true;
863       }
864     }
865     return false;
866   }
867 }
868