Package org.apache.hadoop.hdfs.server.namenode

Source Code of org.apache.hadoop.hdfs.server.namenode.FileJournalManager$EditLogFile

/**
* 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 org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Comparator;
import java.util.Collections;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.hadoop.fs.FileUtil;
import org.apache.hadoop.hdfs.server.common.HdfsConstants;
import org.apache.hadoop.hdfs.server.common.HdfsConstants.StartupOption;
import org.apache.hadoop.hdfs.server.common.HdfsConstants.Transition;
import org.apache.hadoop.hdfs.server.common.StorageErrorReporter;
import org.apache.hadoop.hdfs.server.common.Storage.StorageDirectory;
import org.apache.hadoop.hdfs.server.common.StorageInfo;
import org.apache.hadoop.hdfs.server.namenode.FSEditLogLoader.EditLogValidation;
import org.apache.hadoop.hdfs.server.namenode.NNStorage.NameNodeFile;
import org.apache.hadoop.hdfs.server.namenode.NNStorageRetentionManager.StoragePurger;
import org.apache.hadoop.hdfs.server.namenode.metrics.NameNodeMetrics;
import org.apache.hadoop.hdfs.server.protocol.RemoteEditLog;
import org.apache.hadoop.hdfs.server.protocol.RemoteEditLogManifest;

import com.google.common.base.Joiner;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.Lists;

/**
* Journal manager for the common case of edits files being written
* to a storage directory.
*
* Note: this class is not thread-safe and should be externally
* synchronized.
*/
public class FileJournalManager implements JournalManager {
  private static final Log LOG = LogFactory.getLog(FileJournalManager.class);

  private final StorageDirectory sd;
  private final NameNodeMetrics metrics;
  private final StorageErrorReporter errorReporter;

  public static final Pattern EDITS_REGEX = Pattern.compile(
    NameNodeFile.EDITS.getName() + "_(\\d+)-(\\d+)");
  public static final Pattern EDITS_INPROGRESS_REGEX = Pattern.compile(
    NameNodeFile.EDITS_INPROGRESS.getName() + "_(\\d+)");

  private File currentInProgress = null;
  private long maxSeenTransaction = -1;

  StoragePurger purger
    = new NNStorageRetentionManager.DeletionStoragePurger();

  public FileJournalManager(StorageDirectory sd) {
    this(sd, null, null);
  }
 
  public FileJournalManager(StorageDirectory sd, NameNodeMetrics metrics,
      StorageErrorReporter errorReporter) {
    this.sd = sd;
    this.metrics = metrics;
    this.errorReporter = errorReporter;
  }

  @Override
  public void close() throws IOException {}

  @Override
  synchronized public EditLogOutputStream startLogSegment(long txid)
      throws IOException {
    try {
      currentInProgress = NNStorage.getInProgressEditsFile(sd, txid);
      EditLogOutputStream stm = new EditLogFileOutputStream(currentInProgress,
          metrics);
      stm.create();
      return stm;
    } catch (IOException e) {
      LOG.warn("Unable to start log segment " + txid + " at "
          + currentInProgress + ": " + e.getLocalizedMessage());
      reportErrorOnFile(currentInProgress);
      throw e;
    }
  }

  @Override
  synchronized public void finalizeLogSegment(long firstTxId, long lastTxId)
      throws IOException {
    File inprogressFile = NNStorage.getInProgressEditsFile(sd, firstTxId);

    File dstFile = NNStorage.getFinalizedEditsFile(
        sd, firstTxId, lastTxId);
      LOG.info("Finalizing edits file " + inprogressFile + " -> " + dstFile);
   
    if(dstFile.exists()) {
        throw new IllegalStateException("Can't finalize edits file "
            + inprogressFile + " since finalized file " + "already exists");
    }
    if (!inprogressFile.renameTo(dstFile)) {
      reportErrorOnFile(dstFile);
      throw new IOException("Unable to finalize edits file " + inprogressFile);
    }
    if (inprogressFile.equals(currentInProgress)) {
      currentInProgress = null;
    }
  }
 
  void reportErrorOnFile(File f) {
    if (errorReporter != null) {
      errorReporter.reportErrorOnFile(f);
    }
  }

  public StorageDirectory getStorageDirectory() {
    return sd;
  }

  @Override
  public void purgeLogsOlderThan(long minTxIdToKeep)
      throws IOException {
    File[] files = FileUtil.listFiles(sd.getCurrentDir());
    List<EditLogFile> editLogs =
      FileJournalManager.matchEditLogs(files);
    for (EditLogFile log : editLogs) {
      if (log.getFirstTxId() < minTxIdToKeep &&
          log.getLastTxId() < minTxIdToKeep) {
        LOG.info("Purging log: " + log);
        purger.purgeLog(log);
      }
    }
  }
 
  @Override
  public void setCommittedTxId(final long txid, final boolean force) {
  }

  /**
   * Find all editlog segments starting at or above the given txid.
   * Include inprogress segments. Notice that the segments do not have to be
   * contiguous. JournalSet handles the holes between segments.
   *
   * @param fromTxId the txnid which to start looking
   * @return a list of remote edit logs
   * @throws IOException if edit logs cannot be listed.
   */
  public RemoteEditLogManifest getEditLogManifest(long firstTxId) throws IOException {
    File currentDir = sd.getCurrentDir();
    List<EditLogFile> allLogFiles = matchEditLogs(
        FileUtil.listFiles(currentDir));
    if (LOG.isDebugEnabled()) {
      LOG.debug(allLogFiles);
    }
   
    List<RemoteEditLog> ret = new ArrayList<RemoteEditLog>(
        allLogFiles.size());

    for (EditLogFile elf : allLogFiles) {
      if (elf.isCorrupt()) continue;  
      if (elf.getFirstTxId() >= firstTxId) {
        ret.add(new RemoteEditLog(elf.firstTxId,
            elf.lastTxId,
            elf.isInProgress));
      } else if ((firstTxId > elf.getFirstTxId()) &&
                 (firstTxId <= elf.getLastTxId())) {
        throw new IOException("Asked for firstTxId " + firstTxId
            + " which is in the middle of file " + elf.file);
      }
    }
    Collections.sort(ret);
    return new RemoteEditLogManifest(ret);
  }

  static List<EditLogFile> matchEditLogs(File[] filesInStorage) {
    List<EditLogFile> ret = new ArrayList<EditLogFile>();
    for (File f : filesInStorage) {
      String name = f.getName();
      // Check for edits
      Matcher editsMatch = EDITS_REGEX.matcher(name);
      if (editsMatch.matches()) {
        try {
          long startTxId = Long.valueOf(editsMatch.group(1));
          long endTxId = Long.valueOf(editsMatch.group(2));
          ret.add(new EditLogFile(f, startTxId, endTxId));
        } catch (NumberFormatException nfe) {
          LOG.error("Edits file " + f + " has improperly formatted " +
                    "transaction ID");
          // skip
        }         
      }
     
      // Check for in-progress edits
      Matcher inProgressEditsMatch = EDITS_INPROGRESS_REGEX.matcher(name);
      if (inProgressEditsMatch.matches()) {
        try {
          long startTxId = Long.valueOf(inProgressEditsMatch.group(1));
          ret.add(
              new EditLogFile(f, startTxId, HdfsConstants.INVALID_TXID, true));
        } catch (NumberFormatException nfe) {
          LOG.error("In-progress edits file " + f + " has improperly " +
                    "formatted transaction ID");
          // skip
        }
      }
    }
    return ret;
  }
 
  @Override
  synchronized public void selectInputStreams(
      Collection<EditLogInputStream> streams, long fromTxId,
      boolean inProgressOk, boolean validateInProgressSegments)
      throws IOException {
    List<EditLogFile> elfs = matchEditLogs(FileUtil.listFiles(sd.getCurrentDir()));
    if (LOG.isDebugEnabled()) {
      LOG.debug(this + ": selecting input streams starting at " + fromTxId
          + (inProgressOk ? " (inProgress ok) " : " (excluding inProgress) ")
          + "from among " + elfs.size() + " candidate file(s)");
    }
    addStreamsToCollectionFromFiles(elfs, streams, fromTxId, inProgressOk,
        validateInProgressSegments);
  }

  void addStreamsToCollectionFromFiles(Collection<EditLogFile> elfs,
      Collection<EditLogInputStream> streams, long fromTxId,
      boolean inProgressOk, boolean validateInProgressSegments)
      throws IOException {
    for (EditLogFile elf : elfs) {
      if (elf.isInProgress()) {
        if (!inProgressOk) {
          LOG.debug("passing over " + elf + " because it is in progress "
              + "and we are ignoring in-progress logs.");
          continue;
        }
        if (validateInProgressSegments) {
          try {
            elf.validateLog();
          } catch (IOException e) {
            LOG.error("got IOException while trying to validate header of "
                + elf + ".  Skipping.", e);
            continue;
          }
        } else {
          LOG.info("Skipping validation of edit segment: " + elf);
        }
      }
      if (elf.lastTxId != HdfsConstants.INVALID_TXID && elf.lastTxId < fromTxId) {
        LOG.info("passing over " + elf + " because it ends at " + elf.lastTxId
            + ", but we only care about transactions " + "as new as "
            + fromTxId);
        continue;
      }
      EditLogFileInputStream elfis = new EditLogFileInputStream(elf.getFile(),
          elf.getFirstTxId(), elf.getLastTxId(), elf.isInProgress());
      elfis.setJournalManager(this);
      streams.add(elfis);
    }
  }

  @Override
  synchronized public void recoverUnfinalizedSegments() throws IOException {
    File currentDir = sd.getCurrentDir();
    List<EditLogFile> allLogFiles = matchEditLogs(currentDir.listFiles());
    for(EditLogFile elf : allLogFiles) {
      LOG.info("Found edit file: " + elf);
    }  
    // make sure journal is aware of max seen transaction before moving corrupt
    // files aside
    findMaxTransaction();

    for (EditLogFile elf : allLogFiles) {
      if (elf.getFile().equals(currentInProgress)) {
        continue;
      }
      if (elf.isInProgress()) {
        // If the file is zero-length, we likely just crashed after opening the
        // file, but before writing anything to it. Safe to delete it.
        if (elf.getFile().length() == 0) {
          LOG.info("Deleting zero-length edit log file " + elf);
          if (!elf.getFile().delete()) {
            throw new IOException("Unable to delete file " + elf.getFile());
          }
          continue;
        }
       
        elf.validateLog();
        if (elf.isCorrupt()) {
          elf.moveAsideCorruptFile();
          continue;
        }
        finalizeLogSegment(elf.getFirstTxId(), elf.getLastTxId());
      }
    }
  }
 
  public List<EditLogFile> getLogFiles(long fromTxId) throws IOException {
    return getLogFiles(fromTxId, true);
  }

  /**
   * Get all edit log segments
   *
   * @param fromTxId starting txid
   * @param enforceBoundary should we throw an exception if the requested
   *        txid is not the starting id of a segment
   */
  public List<EditLogFile> getLogFiles(long fromTxId, boolean enforceBoundary)
      throws IOException {
    File currentDir = sd.getCurrentDir();
    List<EditLogFile> allLogFiles = matchEditLogs(currentDir.listFiles());
    List<EditLogFile> logFiles = new ArrayList<EditLogFile>();
   
    for (EditLogFile elf : allLogFiles) {
      if (enforceBoundary && fromTxId > elf.getFirstTxId()
          && fromTxId <= elf.getLastTxId()) {
        throw new IOException("Asked for fromTxId " + fromTxId
            + " which is in middle of file " + elf.file);
      }
      if (fromTxId <= elf.getFirstTxId()) {
        logFiles.add(elf);
      }
    }
   
    Collections.sort(logFiles, EditLogFile.COMPARE_BY_START_TXID);

    return logFiles;
  }

  /**
   * Find the maximum transaction in the journal.
   * This gets stored in a member variable, as corrupt edit logs
   * will be moved aside, but we still need to remember their first
   * tranaction id in the case that it was the maximum transaction in
   * the journal.
   */
  private long findMaxTransaction()
      throws IOException {
    for (EditLogFile elf : getLogFiles(0)) {
      if (elf.isInProgress()) {
        maxSeenTransaction = Math.max(elf.getFirstTxId(), maxSeenTransaction);
      }
      maxSeenTransaction = Math.max(elf.getLastTxId(), maxSeenTransaction);
    }
    return maxSeenTransaction;
  }

  @Override
  public String toString() {
    return String.format("file:/%s", sd.getRoot());
  }

  /**
   * Record of an edit log that has been located and had its filename parsed.
   */
  public static class EditLogFile {
    private File file;
    private final long firstTxId;
    private long lastTxId;

    private boolean isCorrupt = false;
    private final boolean isInProgress;

    final static Comparator<EditLogFile> COMPARE_BY_START_TXID
      = new Comparator<EditLogFile>() {
      public int compare(EditLogFile a, EditLogFile b) {
        return ComparisonChain.start()
        .compare(a.getFirstTxId(), b.getFirstTxId())
        .compare(a.getLastTxId(), b.getLastTxId())
        .result();
      }
    };

    EditLogFile(File file,
        long firstTxId, long lastTxId) {
      this(file, firstTxId, lastTxId, false);
      assert (lastTxId != HdfsConstants.INVALID_TXID)
        && (lastTxId >= firstTxId);
    }
   
    EditLogFile(File file, long firstTxId,
                long lastTxId, boolean isInProgress) {
      boolean checkTxIds = true;
      checkTxIds &= ((lastTxId == HdfsConstants.INVALID_TXID && isInProgress)
        || (lastTxId != HdfsConstants.INVALID_TXID && lastTxId >= firstTxId));
      checkTxIds &= ((firstTxId > -1) || (firstTxId == HdfsConstants.INVALID_TXID));
      if (!checkTxIds)
        throw new IllegalArgumentException("Illegal transaction ids: "
            + firstTxId + ", " + lastTxId + " in progress: " + isInProgress);
      if(file == null)
        throw new IllegalArgumentException("File can not be NULL");
     
      this.firstTxId = firstTxId;
      this.lastTxId = lastTxId;
      this.file = file;
      this.isInProgress = isInProgress;
    }
   
    public long getFirstTxId() {
      return firstTxId;
    }
   
    public long getLastTxId() {
      return lastTxId;
    }

    /**
     * Count the number of valid transactions in a log.
     * This will update the lastTxId of the EditLogFile or
     * mark it as corrupt if it is.
     */
    public void validateLog() throws IOException {
      EditLogValidation val = EditLogFileInputStream.validateEditLog(file);
      if (val.getNumTransactions() == 0) {
        markCorrupt();
      } else {
        this.lastTxId = val.getEndTxId();
      }
    }

    public boolean isInProgress() {
      return isInProgress;
    }

    public File getFile() {
      return file;
    }
   
    void markCorrupt() {
      isCorrupt = true;
    }
   
    boolean isCorrupt() {
      return isCorrupt;
    }
   
    public void moveAsideCorruptFile() throws IOException {
      assert isCorrupt;
      renameSelf(".corrupt");
    }

    public void moveAsideEmptyFile() throws IOException {
      assert lastTxId == HdfsConstants.INVALID_TXID;
      renameSelf(".empty");
    }
     
    private void renameSelf(String newSuffix) throws IOException {
      File src = file;
      File dst = new File(src.getParent(), src.getName() + newSuffix);
      boolean success = src.renameTo(dst);
      if (!success) {
        throw new IOException(
          "Couldn't rename log " + src + " to " + dst);
      }
      file = dst;
    }
   
    @Override
    public String toString() {
      return String.format("EditLogFile(file=%s,first=%019d,last=%019d,"
                           +"inProgress=%b,corrupt=%b)", file.toString(),
                           firstTxId, lastTxId, isInProgress(), isCorrupt);
    }
   
    public EditLogFile(String colonSeparated) {
      String[] list = colonSeparated.split(":");
      firstTxId = Long.valueOf(list[0]);
      lastTxId = Long.valueOf(list[1]);
      isInProgress = Boolean.valueOf(list[2]);
      isCorrupt = Boolean.valueOf(list[3]);
    }
   
    public String toColonSeparatedString() {
      Joiner joiner = Joiner.on(":");
      return joiner.join(firstTxId, lastTxId, isInProgress, isCorrupt);
    }
  }
 
  @Override
  public void transitionJournal(StorageInfo si, Transition transition,
      StartupOption startOpt) throws IOException {
    // Transitioning file journals is done by the StorageDirectory
    // format code, since they may share their directory with
    // checkpoints, etc.
    throw new UnsupportedOperationException();
  }

  @Override
  public boolean hasSomeJournalData() {
    // Formatting file journals is done by the StorageDirectory
    // format code, since they may share their directory with
    // checkpoints, etc.
    throw new UnsupportedOperationException();
  }
 
  @Override
  public boolean hasSomeImageData() {
    // Formatting file journals is done by the StorageDirectory
    // format code, since they may share their directory with
    // checkpoints, etc.
    throw new UnsupportedOperationException();
  }
 
  public EditLogFile getLogFile(long startTxId) throws IOException {
    return getLogFile(sd.getCurrentDir(), startTxId);
  }
 
  public static EditLogFile getLogFile(File dir, long startTxId)
      throws IOException {
    List<EditLogFile> files = matchEditLogs(FileUtil.listFiles(dir));
    List<EditLogFile> ret = Lists.newLinkedList();
    for (EditLogFile elf : files) {
      if (elf.getFirstTxId() == startTxId) {
        ret.add(elf);
      }
    }
   
    if (ret.isEmpty()) {
      // no matches
      return null;
    } else if (ret.size() == 1) {
      return ret.get(0);
    } else {
      throw new IllegalStateException("More than one log segment in " +
          dir + " starting at txid " + startTxId + ": " +
          Joiner.on(", ").join(ret));
    }
  }
 
  @Override
  public String toHTMLString() {
    return this.toString();
  }

  @Override
  public boolean hasImageStorage() {
    // FileJournalManager is not used directly to handle images.
    // We use NNStorage instead
    return false;
  }

  @Override
  public RemoteStorageState analyzeJournalStorage() {
    // this is done directly through storage directory
    throw new UnsupportedOperationException();
  }
}
TOP

Related Classes of org.apache.hadoop.hdfs.server.namenode.FileJournalManager$EditLogFile

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.