001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    
019    package org.apache.hadoop.hdfs.server.namenode;
020    
021    import java.io.File;
022    import java.io.FileInputStream;
023    import java.io.IOException;
024    import java.io.BufferedInputStream;
025    import java.io.EOFException;
026    import java.io.DataInputStream;
027    import org.apache.commons.logging.Log;
028    import org.apache.commons.logging.LogFactory;
029    import org.apache.hadoop.hdfs.protocol.HdfsConstants;
030    import org.apache.hadoop.hdfs.server.common.Storage;
031    import org.apache.hadoop.io.IOUtils;
032    import org.apache.hadoop.hdfs.protocol.HdfsConstants;
033    
034    import com.google.common.annotations.VisibleForTesting;
035    import com.google.common.base.Preconditions;
036    import com.google.common.base.Throwables;
037    
038    /**
039     * An implementation of the abstract class {@link EditLogInputStream}, which
040     * reads edits from a local file.
041     */
042    public class EditLogFileInputStream extends EditLogInputStream {
043      private final File file;
044      private final long firstTxId;
045      private final long lastTxId;
046      private final boolean isInProgress;
047      static private enum State {
048        UNINIT,
049        OPEN,
050        CLOSED
051      }
052      private State state = State.UNINIT;
053      private FileInputStream fStream = null;
054      private int logVersion = 0;
055      private FSEditLogOp.Reader reader = null;
056      private FSEditLogLoader.PositionTrackingInputStream tracker = null;
057      private DataInputStream dataIn = null;
058      static final Log LOG = LogFactory.getLog(EditLogInputStream.class);
059      
060      /**
061       * Open an EditLogInputStream for the given file.
062       * The file is pretransactional, so has no txids
063       * @param name filename to open
064       * @throws LogHeaderCorruptException if the header is either missing or
065       *         appears to be corrupt/truncated
066       * @throws IOException if an actual IO error occurs while reading the
067       *         header
068       */
069      EditLogFileInputStream(File name)
070          throws LogHeaderCorruptException, IOException {
071        this(name, HdfsConstants.INVALID_TXID, HdfsConstants.INVALID_TXID, false);
072      }
073    
074      /**
075       * Open an EditLogInputStream for the given file.
076       * @param name filename to open
077       * @param firstTxId first transaction found in file
078       * @param lastTxId last transaction id found in file
079       * @throws LogHeaderCorruptException if the header is either missing or
080       *         appears to be corrupt/truncated
081       * @throws IOException if an actual IO error occurs while reading the
082       *         header
083       */
084      public EditLogFileInputStream(File name, long firstTxId, long lastTxId,
085          boolean isInProgress) {
086        this.file = name;
087        this.firstTxId = firstTxId;
088        this.lastTxId = lastTxId;
089        this.isInProgress = isInProgress;
090      }
091    
092      private void init() throws LogHeaderCorruptException, IOException {
093        Preconditions.checkState(state == State.UNINIT);
094        BufferedInputStream bin = null;
095        try {
096          fStream = new FileInputStream(file);
097          bin = new BufferedInputStream(fStream);
098          tracker = new FSEditLogLoader.PositionTrackingInputStream(bin);
099          dataIn = new DataInputStream(tracker);
100          try {
101            logVersion = readLogVersion(dataIn);
102          } catch (EOFException eofe) {
103            throw new LogHeaderCorruptException("No header found in log");
104          }
105          reader = new FSEditLogOp.Reader(dataIn, tracker, logVersion);
106          state = State.OPEN;
107        } finally {
108          if (reader == null) {
109            IOUtils.cleanup(LOG, dataIn, tracker, bin, fStream);
110            state = State.CLOSED;
111          }
112        }
113      }
114    
115      @Override
116      public long getFirstTxId() {
117        return firstTxId;
118      }
119      
120      @Override
121      public long getLastTxId() {
122        return lastTxId;
123      }
124    
125      @Override
126      public String getName() {
127        return file.getPath();
128      }
129    
130      private FSEditLogOp nextOpImpl(boolean skipBrokenEdits) throws IOException {
131        FSEditLogOp op = null;
132        switch (state) {
133        case UNINIT:
134          try {
135            init();
136          } catch (Throwable e) {
137            LOG.error("caught exception initializing " + this, e);
138            if (skipBrokenEdits) {
139              return null;
140            }
141            Throwables.propagateIfPossible(e, IOException.class);
142          }
143          Preconditions.checkState(state != State.UNINIT);
144          return nextOpImpl(skipBrokenEdits);
145        case OPEN:
146          op = reader.readOp(skipBrokenEdits);
147          if ((op != null) && (op.hasTransactionId())) {
148            long txId = op.getTransactionId();
149            if ((txId >= lastTxId) &&
150                (lastTxId != HdfsConstants.INVALID_TXID)) {
151              //
152              // Sometimes, the NameNode crashes while it's writing to the
153              // edit log.  In that case, you can end up with an unfinalized edit log
154              // which has some garbage at the end.
155              // JournalManager#recoverUnfinalizedSegments will finalize these
156              // unfinished edit logs, giving them a defined final transaction 
157              // ID.  Then they will be renamed, so that any subsequent
158              // readers will have this information.
159              //
160              // Since there may be garbage at the end of these "cleaned up"
161              // logs, we want to be sure to skip it here if we've read everything
162              // we were supposed to read out of the stream.
163              // So we force an EOF on all subsequent reads.
164              //
165              long skipAmt = file.length() - tracker.getPos();
166              if (skipAmt > 0) {
167                if (LOG.isDebugEnabled()) {
168                    LOG.debug("skipping " + skipAmt + " bytes at the end " +
169                      "of edit log  '" + getName() + "': reached txid " + txId +
170                      " out of " + lastTxId);
171                }
172                tracker.clearLimit();
173                IOUtils.skipFully(tracker, skipAmt);
174              }
175            }
176          }
177          break;
178          case CLOSED:
179            break; // return null
180        }
181        return op;
182      }
183    
184      @Override
185      protected FSEditLogOp nextOp() throws IOException {
186        return nextOpImpl(false);
187      }
188    
189      @Override
190      protected FSEditLogOp nextValidOp() {
191        try {
192          return nextOpImpl(true);
193        } catch (Throwable e) {
194          LOG.error("nextValidOp: got exception while reading " + this, e);
195          return null;
196        }
197      }
198    
199      @Override
200      public int getVersion() throws IOException {
201        if (state == State.UNINIT) {
202          init();
203        }
204        return logVersion;
205      }
206    
207      @Override
208      public long getPosition() {
209        if (state == State.OPEN) {
210          return tracker.getPos();
211        } else {
212          return 0;
213        }
214      }
215    
216      @Override
217      public void close() throws IOException {
218        if (state == State.OPEN) {
219          dataIn.close();
220        }
221        state = State.CLOSED;
222      }
223    
224      @Override
225      public long length() throws IOException {
226        // file size + size of both buffers
227        return file.length();
228      }
229      
230      @Override
231      public boolean isInProgress() {
232        return isInProgress;
233      }
234      
235      @Override
236      public String toString() {
237        return getName();
238      }
239    
240      static FSEditLogLoader.EditLogValidation validateEditLog(File file) throws IOException {
241        EditLogFileInputStream in;
242        try {
243          in = new EditLogFileInputStream(file);
244          in.getVersion(); // causes us to read the header
245        } catch (LogHeaderCorruptException e) {
246          // If the header is malformed or the wrong value, this indicates a corruption
247          LOG.warn("Log file " + file + " has no valid header", e);
248          return new FSEditLogLoader.EditLogValidation(0,
249              HdfsConstants.INVALID_TXID, true);
250        }
251        
252        try {
253          return FSEditLogLoader.validateEditLog(in);
254        } finally {
255          IOUtils.closeStream(in);
256        }
257      }
258    
259      /**
260       * Read the header of fsedit log
261       * @param in fsedit stream
262       * @return the edit log version number
263       * @throws IOException if error occurs
264       */
265      @VisibleForTesting
266      static int readLogVersion(DataInputStream in)
267          throws IOException, LogHeaderCorruptException {
268        int logVersion;
269        try {
270          logVersion = in.readInt();
271        } catch (EOFException eofe) {
272          throw new LogHeaderCorruptException(
273              "Reached EOF when reading log header");
274        }
275        if (logVersion < HdfsConstants.LAYOUT_VERSION || // future version
276            logVersion > Storage.LAST_UPGRADABLE_LAYOUT_VERSION) { // unsupported
277          throw new LogHeaderCorruptException(
278              "Unexpected version of the file system log file: "
279              + logVersion + ". Current version = "
280              + HdfsConstants.LAYOUT_VERSION + ".");
281        }
282        return logVersion;
283      }
284      
285      /**
286       * Exception indicating that the header of an edits log file is
287       * corrupted. This can be because the header is not present,
288       * or because the header data is invalid (eg claims to be
289       * over a newer version than the running NameNode)
290       */
291      static class LogHeaderCorruptException extends IOException {
292        private static final long serialVersionUID = 1L;
293    
294        private LogHeaderCorruptException(String msg) {
295          super(msg);
296        }
297      }
298    }