hadoop-hdfs-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From t...@apache.org
Subject svn commit: r1087573 - in /hadoop/hdfs/branches/HDFS-1073: ./ ivy/ src/java/org/apache/hadoop/hdfs/server/namenode/ src/test/hdfs/org/apache/hadoop/hdfs/server/namenode/
Date Fri, 01 Apr 2011 03:46:39 GMT
Author: todd
Date: Fri Apr  1 03:46:39 2011
New Revision: 1087573

URL: http://svn.apache.org/viewvc?rev=1087573&view=rev
Log:
HDFS-1793. Add code to inspect a storage directory with txid-based filenames. Contributed
by Todd Lipcon.

Added:
    hadoop/hdfs/branches/HDFS-1073/src/java/org/apache/hadoop/hdfs/server/namenode/FSImageTransactionalStorageInspector.java
    hadoop/hdfs/branches/HDFS-1073/src/test/hdfs/org/apache/hadoop/hdfs/server/namenode/TestFSImageStorageInspector.java
Modified:
    hadoop/hdfs/branches/HDFS-1073/CHANGES.HDFS-1073.txt
    hadoop/hdfs/branches/HDFS-1073/ivy.xml
    hadoop/hdfs/branches/HDFS-1073/ivy/libraries.properties
    hadoop/hdfs/branches/HDFS-1073/src/java/org/apache/hadoop/hdfs/server/namenode/FSImage.java
    hadoop/hdfs/branches/HDFS-1073/src/java/org/apache/hadoop/hdfs/server/namenode/NNStorage.java

Modified: hadoop/hdfs/branches/HDFS-1073/CHANGES.HDFS-1073.txt
URL: http://svn.apache.org/viewvc/hadoop/hdfs/branches/HDFS-1073/CHANGES.HDFS-1073.txt?rev=1087573&r1=1087572&r2=1087573&view=diff
==============================================================================
--- hadoop/hdfs/branches/HDFS-1073/CHANGES.HDFS-1073.txt (original)
+++ hadoop/hdfs/branches/HDFS-1073/CHANGES.HDFS-1073.txt Fri Apr  1 03:46:39 2011
@@ -9,3 +9,5 @@ HDFS-1521. Persist transaction ID on dis
 HDFS-1538. Refactor more startup and image loading code out of FSImage.
            (todd)
 HDFS-1729. Add code to detect valid length of an edits file. (todd)
+HDFS-1793. Add code to inspect a storage directory with txid-based filenames
+           (todd)

Modified: hadoop/hdfs/branches/HDFS-1073/ivy.xml
URL: http://svn.apache.org/viewvc/hadoop/hdfs/branches/HDFS-1073/ivy.xml?rev=1087573&r1=1087572&r2=1087573&view=diff
==============================================================================
--- hadoop/hdfs/branches/HDFS-1073/ivy.xml (original)
+++ hadoop/hdfs/branches/HDFS-1073/ivy.xml Fri Apr  1 03:46:39 2011
@@ -61,6 +61,7 @@
     <dependency org="commons-logging" name="commons-logging" rev="${commons-logging.version}"
conf="common->master"/>
     <dependency org="commons-daemon" name="commons-daemon" rev="${commons-daemon.version}"
conf="common->default" />
     <dependency org="log4j" name="log4j" rev="${log4j.version}" conf="common->master"/>
+    <dependency org="com.google.guava" name="guava" rev="${guava.version}" conf="common->default"
/>
     <dependency org="org.apache.hadoop" name="avro" rev="${avro.version}" conf="common->default">
       <exclude module="ant"/>
       <exclude module="jetty"/>

Modified: hadoop/hdfs/branches/HDFS-1073/ivy/libraries.properties
URL: http://svn.apache.org/viewvc/hadoop/hdfs/branches/HDFS-1073/ivy/libraries.properties?rev=1087573&r1=1087572&r2=1087573&view=diff
==============================================================================
--- hadoop/hdfs/branches/HDFS-1073/ivy/libraries.properties (original)
+++ hadoop/hdfs/branches/HDFS-1073/ivy/libraries.properties Fri Apr  1 03:46:39 2011
@@ -34,6 +34,8 @@ commons-net.version=1.4.1
 core.version=3.1.1
 coreplugin.version=1.3.2
 
+guava.version=r07
+
 hadoop-common.version=0.23.0-SNAPSHOT
 hadoop-hdfs.version=0.23.0-SNAPSHOT
 

Modified: hadoop/hdfs/branches/HDFS-1073/src/java/org/apache/hadoop/hdfs/server/namenode/FSImage.java
URL: http://svn.apache.org/viewvc/hadoop/hdfs/branches/HDFS-1073/src/java/org/apache/hadoop/hdfs/server/namenode/FSImage.java?rev=1087573&r1=1087572&r2=1087573&view=diff
==============================================================================
--- hadoop/hdfs/branches/HDFS-1073/src/java/org/apache/hadoop/hdfs/server/namenode/FSImage.java
(original)
+++ hadoop/hdfs/branches/HDFS-1073/src/java/org/apache/hadoop/hdfs/server/namenode/FSImage.java
Fri Apr  1 03:46:39 2011
@@ -71,6 +71,8 @@ public class FSImage implements NNStorag
   private static final SimpleDateFormat DATE_FORM =
       new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 
+  private static final int FIRST_TXNID_BASED_LAYOUT_VERSION=-29;
+  
   // checkpoint states
   enum CheckpointStates{START, ROLLED_EDITS, UPLOAD_START, UPLOAD_DONE; }
 
@@ -493,7 +495,39 @@ public class FSImage implements NNStorag
   }
 
   private FSImageStorageInspector inspectStorageDirs() throws IOException {
-    FSImageStorageInspector inspector = new FSImageOldStorageInspector();
+    int minLayoutVersion = Integer.MAX_VALUE; // the newest
+    int maxLayoutVersion = Integer.MIN_VALUE; // the oldest
+    
+    // First determine what range of layout versions we're going to inspect
+    for (Iterator<StorageDirectory> it = storage.dirIterator();
+         it.hasNext();) {
+      StorageDirectory sd = it.next();
+      if (!sd.getVersionFile().exists()) {
+        LOG.info("Storage directory " + sd + " contains no VERSION file. Skipping...");
+        continue;
+      }
+      sd.read(); // sets layoutVersion
+      minLayoutVersion = Math.min(minLayoutVersion, storage.getLayoutVersion());
+      maxLayoutVersion = Math.max(maxLayoutVersion, storage.getLayoutVersion());
+    }
+    
+    if (minLayoutVersion > maxLayoutVersion) {
+      throw new IOException("No storage directories contained VERSION information");
+    }
+    assert minLayoutVersion <= maxLayoutVersion;
+    
+    // If we have any storage directories with the new layout version
+    // (ie edits_<txnid>) then use the new inspector, which will ignore
+    // the old format dirs.
+    FSImageStorageInspector inspector;
+    if (minLayoutVersion <= FIRST_TXNID_BASED_LAYOUT_VERSION) {
+      inspector = new FSImageTransactionalStorageInspector();
+      if (maxLayoutVersion > FIRST_TXNID_BASED_LAYOUT_VERSION) {
+        LOG.warn("Ignoring one or more storage directories with old layouts");
+      }
+    } else {
+      inspector = new FSImageOldStorageInspector();
+    }
 
     // Process each of the storage directories to find the pair of
     // newest image file and edit file

Added: hadoop/hdfs/branches/HDFS-1073/src/java/org/apache/hadoop/hdfs/server/namenode/FSImageTransactionalStorageInspector.java
URL: http://svn.apache.org/viewvc/hadoop/hdfs/branches/HDFS-1073/src/java/org/apache/hadoop/hdfs/server/namenode/FSImageTransactionalStorageInspector.java?rev=1087573&view=auto
==============================================================================
--- hadoop/hdfs/branches/HDFS-1073/src/java/org/apache/hadoop/hdfs/server/namenode/FSImageTransactionalStorageInspector.java
(added)
+++ hadoop/hdfs/branches/HDFS-1073/src/java/org/apache/hadoop/hdfs/server/namenode/FSImageTransactionalStorageInspector.java
Fri Apr  1 03:46:39 2011
@@ -0,0 +1,516 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.hdfs.server.namenode;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.hdfs.server.common.Storage.StorageDirectory;
+import org.apache.hadoop.hdfs.server.namenode.NNStorage.NameNodeDirType;
+import org.apache.hadoop.hdfs.server.namenode.NNStorage.NameNodeFile;
+
+import com.google.common.collect.Lists;
+
+class FSImageTransactionalStorageInspector extends FSImageStorageInspector {
+  public static final Log LOG = LogFactory.getLog(
+    FSImageTransactionalStorageInspector.class);
+
+  private boolean needToSave = false;
+  private boolean isUpgradeFinalized = true;
+  
+  List<FoundFSImage> foundImages = new ArrayList<FoundFSImage>();
+  List<FoundEditLog> foundEditLogs = new ArrayList<FoundEditLog>();
+  SortedMap<Long, LogGroup> logGroups = new TreeMap<Long, LogGroup>();
+  
+  private static final Pattern IMAGE_REGEX = Pattern.compile(
+    NameNodeFile.IMAGE.getName() + "_(\\d+)");
+  private static final Pattern EDITS_REGEX = Pattern.compile(
+    NameNodeFile.EDITS.getName() + "_(\\d+)-(\\d+)");
+  private static final Pattern EDITS_INPROGRESS_REGEX = Pattern.compile(
+    NameNodeFile.EDITS_INPROGRESS.getName() + "_(\\d+)");
+
+  @Override
+  public void inspectDirectory(StorageDirectory sd) throws IOException {
+    // Was the directory just formatted?
+    if (!sd.getVersionFile().exists()) {
+      LOG.info("No version file in " + sd.getRoot());
+      needToSave |= true;
+      return;
+    }
+    
+    File currentDir = sd.getCurrentDir();
+
+    for (File f : currentDir.listFiles()) {
+      LOG.debug("Checking file " + f);
+      String name = f.getName();
+      
+      // Check for fsimage_*
+      Matcher imageMatch = IMAGE_REGEX.matcher(name);
+      if (imageMatch.matches()) {
+        if (sd.getStorageDirType().isOfType(NameNodeDirType.IMAGE)) {
+          try {
+            long txid = Long.valueOf(imageMatch.group(1));
+            foundImages.add(new FoundFSImage(sd, f, txid));
+          } catch (NumberFormatException nfe) {
+            LOG.error("Image file " + f + " has improperly formatted " +
+                      "transaction ID");
+            // skip
+          }
+        } else {
+          LOG.warn("Found image file at " + f + " but storage directory is " +
+                   "not configured to contain images.");
+        }
+      }
+      
+      // Check for edits
+      Matcher editsMatch = EDITS_REGEX.matcher(name);
+      if (editsMatch.matches()) {
+        if (sd.getStorageDirType().isOfType(NameNodeDirType.EDITS)) {
+          try {
+            long startTxId = Long.valueOf(editsMatch.group(1));
+            long endTxId = Long.valueOf(editsMatch.group(2));
+            addEditLog(new FoundEditLog(sd, f, startTxId, endTxId));
+          } catch (NumberFormatException nfe) {
+            LOG.error("Edits file " + f + " has improperly formatted " +
+                      "transaction ID");
+            // skip
+          }          
+        } else {
+          LOG.warn("Found edits file at " + f + " but storage directory is " +
+                   "not configured to contain edits.");
+        }
+      }
+      
+      // Check for in-progress edits
+      Matcher inProgressEditsMatch = EDITS_INPROGRESS_REGEX.matcher(name);
+      if (inProgressEditsMatch.matches()) {
+        if (sd.getStorageDirType().isOfType(NameNodeDirType.EDITS)) {
+          try {
+            long startTxId = Long.valueOf(inProgressEditsMatch.group(1));
+            addEditLog(
+              new FoundEditLog(sd, f, startTxId, FoundEditLog.UNKNOWN_END));
+          } catch (NumberFormatException nfe) {
+            LOG.error("In-progress edits file " + f + " has improperly " +
+                      "formatted transaction ID");
+            // skip
+          }          
+        } else {
+          LOG.warn("Found in-progress edits file at " + f + " but " +
+                   "storage directory is not configured to contain edits.");
+        }
+      }
+    }
+
+    // set finalized flag
+    isUpgradeFinalized = isUpgradeFinalized && !sd.getPreviousDir().exists();
+  }
+
+
+  private void addEditLog(FoundEditLog foundEditLog) {
+    foundEditLogs.add(foundEditLog);
+    LogGroup group = logGroups.get(foundEditLog.startTxId);
+    if (group == null) {
+      group = new LogGroup(foundEditLog.startTxId);
+      logGroups.put(foundEditLog.startTxId, group);
+    }
+    group.add(foundEditLog);
+  }
+
+
+  @Override
+  public boolean isUpgradeFinalized() {
+    return isUpgradeFinalized;
+  }
+  
+  /**
+   * @return the image that has the most recent associated transaction ID.
+   * If there are multiple storage directories which contain equal images 
+   * the storage directory that was inspected first will be preferred.
+   * 
+   * Returns null if no images were found.
+   * 
+   * TODO this is only used by unit tests I think?
+   */
+  FoundFSImage getLatestImage() {
+    FoundFSImage ret = null;
+    for (FoundFSImage img : foundImages) {
+      if (ret == null || img.txId > ret.txId) {
+        ret = img;
+      }
+    }
+    return ret;
+  }
+
+  @Override
+  public LoadPlan createLoadPlan() throws IOException {
+    if (foundImages.isEmpty()) {
+      throw new FileNotFoundException("No valid image files found");
+    }
+
+    FoundFSImage recoveryImage = getLatestImage();
+    long expectedTxId = recoveryImage.txId + 1;
+    
+    List<FoundEditLog> recoveryLogs = new ArrayList<FoundEditLog>();
+    
+    Map<Long, LogGroup> usefulGroups = logGroups.tailMap(expectedTxId);
+    LOG.debug("Excluded " + (logGroups.size() - usefulGroups.size()) + 
+        " groups of logs because they start with a txid less than image " +
+        "txid " + recoveryImage.txId);
+    
+    for (Map.Entry<Long, LogGroup> entry : usefulGroups.entrySet()) {
+      long logStartTxId = entry.getKey();
+      LogGroup logGroup = entry.getValue();
+      
+      logGroup.planRecovery();
+      
+      if (expectedTxId != -1 && logStartTxId != expectedTxId) {
+        throw new IOException("Expected next log group would start at txid " +
+            expectedTxId + " but starts at txid " + logStartTxId);
+      }
+      
+      // We can pick any of the non-corrupt logs here
+      recoveryLogs.add(logGroup.getBestNonCorruptLog());
+      
+      // If this log group was finalized, we know to expect the next
+      // log group to start at the following txid (ie no gaps)
+      if (logGroup.hasKnownLastTxId()) {
+        expectedTxId = logGroup.getLastTxId() + 1;
+      } else {
+        // the log group was in-progress so we don't know what ID
+        // the next group should start from.
+        expectedTxId = -1;
+      }
+    }
+
+    return new TransactionalLoadPlan(recoveryImage, recoveryLogs,
+        Lists.newArrayList(usefulGroups.values()));
+  }
+
+  @Override
+  public boolean needToSave() {
+    return false; // TODO do we need to do this ever?
+  }
+
+  /**
+   * A group of logs that all start at the same txid.
+   * 
+   * Handles determining which logs are corrupt and which should be considered
+   * candidates for loading.
+   */
+  static class LogGroup {
+    long startTxId;
+    List<FoundEditLog> logs = new ArrayList<FoundEditLog>();;
+    private Set<Long> endTxIds = new TreeSet<Long>();
+    private boolean hasInProgress = false;
+    private boolean hasFinalized = false;
+        
+    LogGroup(long startTxId) {
+      this.startTxId = startTxId;
+    }
+    
+    FoundEditLog getBestNonCorruptLog() {
+      for (FoundEditLog log : logs) {
+        if (!log.isCorrupt()) {
+          return log;
+        }
+      }
+      // We should never get here, because we don't get to the planning stage
+      // without calling planRecovery first, and if we've called planRecovery,
+      // we would have already thrown if there were no non-corrupt logs!
+      throw new IllegalStateException(
+        "No non-corrupt logs for txid " + startTxId);
+    }
+
+    /**
+     * @return true if we can determine the last txid in this log group.
+     */
+    boolean hasKnownLastTxId() {
+      for (FoundEditLog log : logs) {
+        if (!log.isInProgress()) {
+          return true;
+        }
+      }
+      return false;
+    }
+
+    /**
+     * @return the last txid included in the logs in this group
+     * @throws IllegalStateException if it is unknown -
+     *                               {@see #hasKnownLastTxId()}
+     */
+    long getLastTxId() {
+      for (FoundEditLog log : logs) {
+        if (!log.isInProgress()) {
+          return log.lastTxId;
+        }
+      }
+      throw new IllegalStateException("LogGroup only has in-progress logs");
+    }
+
+    
+    void add(FoundEditLog log) {
+      assert log.getStartTxId() == startTxId;
+      logs.add(log);
+      
+      if (log.isInProgress()) {
+        hasInProgress = true;
+      } else {
+        hasFinalized = true;
+        endTxIds.add(log.lastTxId);
+      }
+    }
+    
+    void planRecovery() throws IOException {
+      assert hasInProgress || hasFinalized;
+      
+      checkConsistentEndTxIds();
+        
+      if (hasFinalized && hasInProgress) {
+        planMixedLogRecovery();
+      } else if (!hasFinalized && hasInProgress) {
+        planAllInProgressRecovery();
+      } else if (hasFinalized && !hasInProgress) {
+        LOG.debug("No recovery necessary for logs starting at txid " +
+                  startTxId);
+      }
+    }
+
+    /**
+     * Recovery case for when some logs in the group were in-progress, and
+     * others were finalized. This happens when one of the storage
+     * directories fails.
+     *
+     * The in-progress logs in this case should be considered corrupt.
+     */
+    private void planMixedLogRecovery() throws IOException {
+      for (FoundEditLog log : logs) {
+        if (log.isInProgress()) {
+          LOG.warn("Log at " + log.getFile() + " is in progress, but " +
+                   "other logs starting at the same txid " + startTxId +
+                   " are finalized. Moving aside.");
+          log.markCorrupt();
+        }
+      }
+    }
+    
+    /**
+     * Recovery case for when all of the logs in the group were in progress.
+     * This happens if the NN completely crashes and restarts. In this case
+     * we check the non-zero lengths of each log file, and any logs that are
+     * less than the max of these lengths are considered corrupt.
+     */
+    private void planAllInProgressRecovery() throws IOException {
+      // We only have in-progress logs. We need to figure out which logs have
+      // the latest data to reccover them
+      LOG.warn("Logs beginning at txid " + startTxId + " were are all " +
+               "in-progress (probably truncated due to a previous NameNode " +
+               "crash)");
+      if (logs.size() == 1) {
+        // Only one log, it's our only choice!
+        return;
+      }
+
+      long maxLength = Long.MIN_VALUE;
+      for (FoundEditLog log : logs) {
+        long validLength = log.getValidLength();
+        LOG.warn("  Log " + log.getFile() + " valid length=" + validLength);
+        maxLength = Math.max(maxLength, validLength);
+      }        
+
+      for (FoundEditLog log : logs) {
+        if (log.getValidLength() < maxLength) {
+          LOG.warn("Marking log at " + log.getFile() + " as corrupt since " +
+              "it is shorter than " + maxLength + " bytes");
+          log.markCorrupt();
+        }
+      }
+    }
+
+    /**
+     * Check for the case when we have multiple finalized logs and they have
+     * different ending transaction IDs. This violates an invariant that all
+     * log directories should roll together. We should abort in this case.
+     */
+    private void checkConsistentEndTxIds() throws IOException {
+      if (hasFinalized && endTxIds.size() > 1) {
+        throw new IOException("More than one ending txid was found " +
+            "for logs starting at txid " + startTxId + ". " +
+            "Found: " + StringUtils.join(endTxIds, ','));
+      }
+    }
+
+    void recover() throws IOException {
+      for (FoundEditLog log : logs) {
+        if (log.isCorrupt()) {
+          log.moveAsideCorruptFile();
+        }
+      }
+    }    
+  }
+  /**
+   * Record of an image that has been located and had its filename parsed.
+   */
+  static class FoundFSImage {
+    final StorageDirectory sd;    
+    final long txId;
+    private final File file;
+    
+    FoundFSImage(StorageDirectory sd, File file, long txId) {
+      assert txId >= 0 : "Invalid txid on " + file +": " + txId;
+      
+      this.sd = sd;
+      this.txId = txId;
+      this.file = file;
+    } 
+    
+    File getFile() {
+      return file;
+    }
+
+    public long getTxId() {
+      return txId;
+    }
+  }
+  
+  /**
+   * Record of an edit log that has been located and had its filename parsed.
+   */
+  static class FoundEditLog {
+    final StorageDirectory sd;
+    File file;
+    final long startTxId;
+    final long lastTxId;
+    
+    private long cachedValidLength = -1;
+    private boolean isCorrupt = false;
+    
+    static final long UNKNOWN_END = -1;
+    
+    FoundEditLog(StorageDirectory sd, File file,
+        long startTxId, long endTxId) {
+      assert endTxId == UNKNOWN_END || endTxId >= startTxId;
+      assert startTxId > 0;
+      assert file != null;
+      
+      this.sd = sd;
+      this.startTxId = startTxId;
+      this.lastTxId = endTxId;
+      this.file = file;
+    }
+    
+    long getStartTxId() {
+      return startTxId;
+    }
+    
+    long getLastTxId() {
+      return lastTxId;
+    }
+
+    long getValidLength() throws IOException {
+      if (cachedValidLength == -1) {
+        cachedValidLength = EditLogFileInputStream.getValidLength(file);
+      }
+      return cachedValidLength;
+    }
+
+    boolean isInProgress() {
+      return (lastTxId == UNKNOWN_END);
+    }
+
+    File getFile() {
+      return file;
+    }
+    
+    void markCorrupt() {
+      isCorrupt = true;
+    }
+    
+    boolean isCorrupt() {
+      return isCorrupt;
+    }
+
+    void moveAsideCorruptFile() throws IOException {
+      assert isCorrupt;
+    
+      File src = file;
+      File dst = new File(src.getParent(), src.getName() + ".corrupt");
+      boolean success = src.renameTo(dst);
+      if (!success) {
+        throw new IOException(
+          "Couldn't rename corrupt log " + src + " to " + dst);
+      }
+      file = dst;
+    }
+  }
+
+  static class TransactionalLoadPlan extends LoadPlan {
+    final FoundFSImage image;
+    final List<FoundEditLog> editLogs;
+    final List<LogGroup> logGroupsToRecover;
+    
+    public TransactionalLoadPlan(FoundFSImage image,
+        List<FoundEditLog> editLogs,
+        List<LogGroup> logGroupsToRecover) {
+      super();
+      this.image = image;
+      this.editLogs = editLogs;
+      this.logGroupsToRecover = logGroupsToRecover;
+    }
+
+    @Override
+    boolean doRecovery() throws IOException {
+      for (LogGroup g : logGroupsToRecover) {
+        g.recover();
+      }
+      return false;
+    }
+
+    @Override
+    File getImageFile() {
+      return image.getFile();
+    }
+
+    @Override
+    List<File> getEditsFiles() {
+      List<File> ret = new ArrayList<File>();
+      for (FoundEditLog log : editLogs) {
+        ret.add(log.getFile());
+      }
+      return ret;
+    }
+
+    @Override
+    StorageDirectory getStorageDirectoryForProperties() {
+      return image.sd;
+    }
+  }
+}

Modified: hadoop/hdfs/branches/HDFS-1073/src/java/org/apache/hadoop/hdfs/server/namenode/NNStorage.java
URL: http://svn.apache.org/viewvc/hadoop/hdfs/branches/HDFS-1073/src/java/org/apache/hadoop/hdfs/server/namenode/NNStorage.java?rev=1087573&r1=1087572&r2=1087573&view=diff
==============================================================================
--- hadoop/hdfs/branches/HDFS-1073/src/java/org/apache/hadoop/hdfs/server/namenode/NNStorage.java
(original)
+++ hadoop/hdfs/branches/HDFS-1073/src/java/org/apache/hadoop/hdfs/server/namenode/NNStorage.java
Fri Apr  1 03:46:39 2011
@@ -71,7 +71,8 @@ public class NNStorage extends Storage i
     TIME      ("fstime"),
     EDITS     ("edits"),
     IMAGE_NEW ("fsimage.ckpt"),
-    EDITS_NEW ("edits.new");
+    EDITS_NEW ("edits.new"), // from "old" pre-HDFS-1073 format
+    EDITS_INPROGRESS ("edits_inprogress");
 
     private String fileName = null;
     private NameNodeFile(String name) { this.fileName = name; }

Added: hadoop/hdfs/branches/HDFS-1073/src/test/hdfs/org/apache/hadoop/hdfs/server/namenode/TestFSImageStorageInspector.java
URL: http://svn.apache.org/viewvc/hadoop/hdfs/branches/HDFS-1073/src/test/hdfs/org/apache/hadoop/hdfs/server/namenode/TestFSImageStorageInspector.java?rev=1087573&view=auto
==============================================================================
--- hadoop/hdfs/branches/HDFS-1073/src/test/hdfs/org/apache/hadoop/hdfs/server/namenode/TestFSImageStorageInspector.java
(added)
+++ hadoop/hdfs/branches/HDFS-1073/src/test/hdfs/org/apache/hadoop/hdfs/server/namenode/TestFSImageStorageInspector.java
Fri Apr  1 03:46:39 2011
@@ -0,0 +1,363 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.hdfs.server.namenode;
+
+import static org.junit.Assert.*;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.hdfs.server.common.Storage.StorageDirType;
+import org.apache.hadoop.hdfs.server.common.Storage.StorageDirectory;
+import org.apache.hadoop.hdfs.server.namenode.NNStorage.NameNodeDirType;
+import org.apache.hadoop.hdfs.server.namenode.FSImageTransactionalStorageInspector.FoundEditLog;
+import org.apache.hadoop.hdfs.server.namenode.FSImageTransactionalStorageInspector.FoundFSImage;
+import org.apache.hadoop.hdfs.server.namenode.FSImageTransactionalStorageInspector.TransactionalLoadPlan;
+import org.apache.hadoop.hdfs.server.namenode.FSImageTransactionalStorageInspector.LogGroup;
+import org.apache.hadoop.hdfs.server.namenode.FSImageStorageInspector.LoadPlan;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class TestFSImageStorageInspector {
+  private static final Log LOG = LogFactory.getLog(
+      TestFSImageStorageInspector.class);
+
+  /**
+   * Simple test with image, edits, and inprogress edits
+   */
+  @Test
+  public void testCurrentStorageInspector() throws IOException {
+    FSImageTransactionalStorageInspector inspector = 
+        new FSImageTransactionalStorageInspector();
+    
+    StorageDirectory mockDir = mockDirectory(
+        NameNodeDirType.IMAGE_AND_EDITS,
+        false,
+        "/foo/current/fsimage_123",
+        "/foo/current/edits_123-456",
+        "/foo/current/fsimage_456",
+        "/foo/current/edits_inprogress_457");
+
+    inspector.inspectDirectory(mockDir);
+    
+    assertEquals(2, inspector.foundEditLogs.size());
+    assertEquals(2, inspector.foundImages.size());
+    assertTrue(inspector.foundEditLogs.get(1).isInProgress());
+    
+    FoundFSImage latestImage = inspector.getLatestImage();
+    assertEquals(456, latestImage.txId);
+    assertSame(mockDir, latestImage.sd);
+    assertTrue(inspector.isUpgradeFinalized());
+    
+    LoadPlan plan = inspector.createLoadPlan();
+    LOG.info("Plan: " + plan);
+    
+    assertEquals(new File("/foo/current/fsimage_456"), plan.getImageFile());
+    assertArrayEquals(new File[] {
+        new File("/foo/current/edits_inprogress_457") },
+        plan.getEditsFiles().toArray(new File[0]));
+  }
+  
+  /**
+   * Test that we check for gaps in txids when devising a load plan.
+   */
+  @Test
+  public void testPlanWithGaps() throws IOException {
+    FSImageTransactionalStorageInspector inspector =
+        new FSImageTransactionalStorageInspector();
+    
+    StorageDirectory mockDir = mockDirectory(
+        NameNodeDirType.IMAGE_AND_EDITS,
+        false,
+        "/foo/current/fsimage_123",
+        "/foo/current/fsimage_456",
+        "/foo/current/edits_457-900",
+        "/foo/current/edits_901-950",
+        "/foo/current/edits_952-1000"); // <-- missing edit 951!
+
+    inspector.inspectDirectory(mockDir);
+    try {
+      inspector.createLoadPlan();
+      fail("Didn't throw IOE trying to load with gaps in edits");
+    } catch (IOException ioe) {
+      assertTrue(ioe.getMessage().contains(
+          "would start at txid 951 but starts at txid 952"));
+    }
+  }
+  
+  /**
+   * Test the case where an in-progress log comes in the middle of a sequence
+   * of logs
+   */
+  @Test
+  public void testPlanWithInProgressInMiddle() throws IOException {
+    FSImageTransactionalStorageInspector inspector =
+        new FSImageTransactionalStorageInspector();
+    
+    StorageDirectory mockDir = mockDirectory(
+        NameNodeDirType.IMAGE_AND_EDITS,
+        false,
+        "/foo/current/fsimage_123",
+        "/foo/current/fsimage_456",
+        "/foo/current/edits_457-900",
+        "/foo/current/edits_inprogress_901", // <-- inprogress in middle
+        "/foo/current/edits_952-1000");
+
+    inspector.inspectDirectory(mockDir);
+    LoadPlan plan = inspector.createLoadPlan();
+    LOG.info("Plan: " + plan);
+    
+    assertEquals(new File("/foo/current/fsimage_456"), plan.getImageFile());
+    assertArrayEquals(new File[] {
+        new File("/foo/current/edits_457-900"),
+        new File("/foo/current/edits_inprogress_901"),
+        new File("/foo/current/edits_952-1000") },
+        plan.getEditsFiles().toArray(new File[0]));
+
+
+  }
+
+  
+  /**
+   * Test case for the usual case where no recovery of a log group is necessary
+   * (i.e all logs have the same start and end txids and finalized)
+   */
+  @Test
+  public void testLogGroupRecoveryNoop() throws IOException {
+    FSImageTransactionalStorageInspector inspector =
+        new FSImageTransactionalStorageInspector();
+
+    inspector.inspectDirectory(
+        mockDirectoryWithEditLogs("/foo1/current/edits_123-456"));
+    inspector.inspectDirectory(
+        mockDirectoryWithEditLogs("/foo2/current/edits_123-456"));
+    inspector.inspectDirectory(
+        mockDirectoryWithEditLogs("/foo3/current/edits_123-456"));
+
+    LogGroup lg = inspector.logGroups.get(123L);
+    assertEquals(3, lg.logs.size());
+    
+    lg.planRecovery();
+    
+    assertFalse(lg.logs.get(0).isCorrupt());
+    assertFalse(lg.logs.get(1).isCorrupt());
+    assertFalse(lg.logs.get(2).isCorrupt());
+  }
+  
+  /**
+   * Test case where we have some in-progress and some finalized logs
+   * for a given txid.
+   */
+  @Test
+  public void testLogGroupRecoveryMixed() throws IOException {
+    FSImageTransactionalStorageInspector inspector =
+        new FSImageTransactionalStorageInspector();
+
+    inspector.inspectDirectory(
+        mockDirectoryWithEditLogs("/foo1/current/edits_123-456"));
+    inspector.inspectDirectory(
+        mockDirectoryWithEditLogs("/foo2/current/edits_123-456"));
+    inspector.inspectDirectory(
+        mockDirectoryWithEditLogs("/foo3/current/edits_inprogress_123"));
+    inspector.inspectDirectory(mockDirectory(
+        NameNodeDirType.IMAGE,
+        false,
+        "/foo4/current/fsimage_122"));
+
+    LogGroup lg = inspector.logGroups.get(123L);
+    assertEquals(3, lg.logs.size());
+    FoundEditLog inProgressLog = lg.logs.get(2);
+    assertTrue(inProgressLog.isInProgress());
+    
+    LoadPlan plan = inspector.createLoadPlan();
+
+    // Check that it was marked corrupt.
+    assertFalse(lg.logs.get(0).isCorrupt());
+    assertFalse(lg.logs.get(1).isCorrupt());
+    assertTrue(lg.logs.get(2).isCorrupt());
+
+    
+    // Calling recover should move it aside
+    inProgressLog = spy(inProgressLog);
+    Mockito.doNothing().when(inProgressLog).moveAsideCorruptFile();
+    lg.logs.set(2, inProgressLog);
+    
+    plan.doRecovery();
+    
+    Mockito.verify(inProgressLog).moveAsideCorruptFile();
+  }
+  
+  /**
+   * Test case where we have finalized logs with different end txids
+   */
+  @Test
+  public void testLogGroupRecoveryInconsistentEndTxIds() throws IOException {
+    FSImageTransactionalStorageInspector inspector =
+        new FSImageTransactionalStorageInspector();
+    inspector.inspectDirectory(
+        mockDirectoryWithEditLogs("/foo1/current/edits_123-456"));
+    inspector.inspectDirectory(
+        mockDirectoryWithEditLogs("/foo2/current/edits_123-678"));
+
+    LogGroup lg = inspector.logGroups.get(123L);
+    assertEquals(2, lg.logs.size());
+
+    try {
+      lg.planRecovery();
+      fail("Didn't throw IOE on inconsistent end txids");
+    } catch (IOException ioe) {
+      assertTrue(ioe.getMessage().contains("More than one ending txid"));
+    }
+  }
+
+  /**
+   * Test case where we have only in-progress logs and need to synchronize
+   * based on valid length.
+   */
+  @Test
+  public void testLogGroupRecoveryInProgress() throws IOException {
+    FSImageTransactionalStorageInspector inspector =
+        new FSImageTransactionalStorageInspector();
+    inspector.inspectDirectory(
+        mockDirectoryWithEditLogs("/foo1/current/edits_inprogress_123"));
+    inspector.inspectDirectory(
+        mockDirectoryWithEditLogs("/foo2/current/edits_inprogress_123"));
+    inspector.inspectDirectory(
+        mockDirectoryWithEditLogs("/foo3/current/edits_inprogress_123"));
+
+    LogGroup lg = inspector.logGroups.get(123L);
+    assertEquals(3, lg.logs.size());
+    
+    // Inject spies to return the lengths we would like to see
+    long validLengths[] = new long[] { 2000, 2000, 1000 };
+    for (int i = 0; i < 3; i++) {
+      FoundEditLog inProgressLog = lg.logs.get(i);
+      assertTrue(inProgressLog.isInProgress());
+      
+      inProgressLog = spy(inProgressLog);
+      doReturn(validLengths[i]).when(inProgressLog).getValidLength();
+      lg.logs.set(i, inProgressLog);      
+    }
+
+    lg.planRecovery();
+    
+    // Check that the short one was marked corrupt
+    assertFalse(lg.logs.get(0).isCorrupt());
+    assertFalse(lg.logs.get(1).isCorrupt());
+    assertTrue(lg.logs.get(2).isCorrupt());
+    
+    // Calling recover should move it aside
+    FoundEditLog badLog = lg.logs.get(2);
+    Mockito.doNothing().when(badLog).moveAsideCorruptFile();
+    
+    lg.recover();
+    
+    Mockito.verify(badLog).moveAsideCorruptFile();
+  }
+  
+  /**
+   * Test when edits and image are in separate directories.
+   */
+  @Test
+  public void testCurrentSplitEditsAndImage() throws IOException {
+    FSImageTransactionalStorageInspector inspector =
+        new FSImageTransactionalStorageInspector();
+    
+    StorageDirectory mockImageDir = mockDirectory(
+        NameNodeDirType.IMAGE,
+        false,
+        "/foo/current/fsimage_123");
+    StorageDirectory mockImageDir2 = mockDirectory(
+        NameNodeDirType.IMAGE,
+        false,
+        "/foo2/current/fsimage_456");
+    StorageDirectory mockEditsDir = mockDirectory(
+        NameNodeDirType.EDITS,
+        false,
+        "/foo3/current/edits_123-456",
+        "/foo3/current/edits_inprogress_457");
+    
+    inspector.inspectDirectory(mockImageDir);
+    inspector.inspectDirectory(mockEditsDir);
+    inspector.inspectDirectory(mockImageDir2);
+
+    assertEquals(2, inspector.foundEditLogs.size());
+    assertEquals(2, inspector.foundImages.size());
+    assertTrue(inspector.foundEditLogs.get(1).isInProgress());
+    assertTrue(inspector.isUpgradeFinalized());    
+
+    // Check plan
+    TransactionalLoadPlan plan =
+      (TransactionalLoadPlan)inspector.createLoadPlan();
+    FoundFSImage pickedImage = plan.image;
+    assertEquals(456, pickedImage.txId);
+    assertSame(mockImageDir2, pickedImage.sd);
+    assertEquals(new File("/foo2/current/fsimage_456"), plan.getImageFile());
+    assertArrayEquals(new File[] {
+        new File("/foo3/current/edits_inprogress_457")
+      }, plan.getEditsFiles().toArray(new File[0]));
+
+  }
+
+  private StorageDirectory mockDirectoryWithEditLogs(String... fileNames) {
+    return mockDirectory(NameNodeDirType.EDITS, false, fileNames);
+  }
+  
+  /**
+   * Make a mock storage directory that returns some set of file contents.
+   * @param type type of storage dir
+   * @param previousExists should we mock that the previous/ dir exists?
+   * @param fileNames the names of files contained in current/
+   */
+  private StorageDirectory mockDirectory(
+      StorageDirType type,
+      boolean previousExists,
+      String...  fileNames) {
+    StorageDirectory sd = mock(StorageDirectory.class);
+    
+    doReturn(type).when(sd).getStorageDirType();
+
+    // Version file should always exist
+    File mockVersionFile = mock(File.class);
+    doReturn(true).when(mockVersionFile).exists();
+    doReturn(mockVersionFile).when(sd).getVersionFile();
+    
+    // Previous dir optionally exists
+    File mockPreviousDir = mock(File.class);
+    doReturn(previousExists).when(mockPreviousDir).exists();
+    doReturn(mockPreviousDir).when(sd).getPreviousDir();   
+
+    // Return a mock 'current' directory which has the given paths
+    File[] files = new File[fileNames.length];
+    for (int i = 0; i < fileNames.length; i++) {
+      files[i] = new File(fileNames[i]);
+    }
+    
+    File mockDir = mock(File.class);
+    doReturn(files).when(mockDir).listFiles();
+    doReturn(mockDir).when(sd).getCurrentDir();
+    
+    return sd;
+  }
+}



Mime
View raw message