All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.hadoop.hbase.regionserver.wal.AbstractProtobufWALReader Maven / Gradle / Ivy

There is a newer version: 3.0.0-beta-1
Show newest version
/*
 * 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.hbase.regionserver.wal;

import java.io.Closeable;
import java.io.EOFException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.security.Key;
import java.security.KeyException;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.io.IOUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.codec.Codec;
import org.apache.hadoop.hbase.io.compress.Compression;
import org.apache.hadoop.hbase.io.crypto.Cipher;
import org.apache.hadoop.hbase.io.crypto.Decryptor;
import org.apache.hadoop.hbase.io.crypto.Encryption;
import org.apache.hadoop.hbase.io.util.LRUDictionary;
import org.apache.hadoop.hbase.security.EncryptionUtil;
import org.apache.hadoop.hbase.security.User;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.CommonFSUtils;
import org.apache.hadoop.hbase.util.EncryptionTest;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.hadoop.hbase.wal.AbstractFSWALProvider;
import org.apache.hadoop.ipc.RemoteException;
import org.apache.yetus.audience.InterfaceAudience;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableList;
import org.apache.hbase.thirdparty.com.google.common.io.Closeables;
import org.apache.hbase.thirdparty.com.google.protobuf.InvalidProtocolBufferException;

import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos;
import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos.WALTrailer;

/**
 * Base class for reading protobuf based wal reader
 */
@InterfaceAudience.Private
public abstract class AbstractProtobufWALReader
  implements AbstractFSWALProvider.Initializer, Closeable {

  private static final Logger LOG = LoggerFactory.getLogger(AbstractProtobufWALReader.class);

  // public for WALFactory until we move everything to o.a.h.h.wal
  public static final byte[] PB_WAL_MAGIC = Bytes.toBytes("PWAL");

  // public for TestWALSplit
  public static final byte[] PB_WAL_COMPLETE_MAGIC = Bytes.toBytes("LAWP");

  /**
   * Configuration name of WAL Trailer's warning size. If a waltrailer's size is greater than the
   * configured size, providers should log a warning. e.g. this is used with Protobuf reader/writer.
   */
  static final String WAL_TRAILER_WARN_SIZE = "hbase.regionserver.waltrailer.warn.size";
  static final int DEFAULT_WAL_TRAILER_WARN_SIZE = 1024 * 1024; // 1MB

  private static final List WRITER_CLS_NAMES = ImmutableList.of(
    ProtobufLogWriter.class.getSimpleName(), AsyncProtobufLogWriter.class.getSimpleName(),
    "SecureProtobufLogWriter", "SecureAsyncProtobufLogWriter");

  protected Configuration conf;

  protected FileSystem fs;

  protected Path path;

  protected long fileLength;

  protected FSDataInputStream inputStream;

  protected CompressionContext compressionCtx;
  protected boolean hasCompression = false;
  protected boolean hasTagCompression = false;
  protected boolean hasValueCompression = false;
  protected Compression.Algorithm valueCompressionType;

  protected Codec.Decoder cellDecoder;
  protected WALCellCodec.ByteStringUncompressor byteStringUncompressor;

  protected long walEditsStopOffset;
  protected boolean trailerPresent;
  protected WALTrailer trailer;
  // maximum size of the wal Trailer in bytes. If a user writes/reads a trailer with size larger
  // than this size, it is written/read respectively, with a WARN message in the log.
  protected int trailerWarnSize;

  // cell codec classname
  protected String codecClsName;

  protected Decryptor decryptor;

  /**
   * Get or create the input stream used by cell decoder.
   * 

* For implementing replication, we may need to limit the bytes we can read, so here we provide a * method so sub classes can wrap the original input stream. */ protected abstract InputStream getCellCodecInputStream(FSDataInputStream stream); /** * Skip to the given position. */ protected abstract void skipTo(long position) throws IOException; @Override public void init(FileSystem fs, Path path, Configuration conf, long startPosition) throws IOException { this.conf = conf; this.path = path; this.fs = fs; this.trailerWarnSize = conf.getInt(WAL_TRAILER_WARN_SIZE, DEFAULT_WAL_TRAILER_WARN_SIZE); Pair pair = open(); FSDataInputStream stream = pair.getFirst(); FileStatus stat = pair.getSecond(); boolean initSucceeded = false; try { // read the header WALProtos.WALHeader header = readHeader(stream); // initialize metadata and fields initDecryptor(header); initCompression(header); initWALCellCodec(header, getCellCodecInputStream(stream)); // read trailer if available readTrailer(stream, stat); // this is intentional as we do not want the above methods to use the inputStream field. For // implementation tailing reader, we need to wrap the input stream when creating cell decoder, // so we need to make sure in the above methods we do not accidentally use the stored // inputStream directly and cause trouble. If a method needs to use an input stream, we just // pass the input stream in, like readHeader and readTrailer. this.inputStream = stream; // seek to the given position if it is not -1 if (startPosition >= 0 && startPosition != inputStream.getPos()) { if (compressionCtx != null) { // skip to the position, as we need to construct the compression dictionary skipTo(startPosition); } else { // just seek to the position stream.seek(startPosition); } } initSucceeded = true; } finally { if (!initSucceeded) { Closeables.close(stream, initSucceeded); inputStream = null; } } } private Pair openArchivedWAL() throws IOException { Path archivedWAL = AbstractFSWALProvider.findArchivedLog(path, conf); if (archivedWAL != null) { // try open from oldWAL dir return Pair.newPair(fs.open(archivedWAL), fs.getFileStatus(archivedWAL)); } else { return null; } } protected final Pair open() throws IOException { try { return Pair.newPair(fs.open(path), fs.getFileStatus(path)); } catch (FileNotFoundException e) { Pair pair = openArchivedWAL(); if (pair != null) { return pair; } else { throw e; } } catch (RemoteException re) { IOException ioe = re.unwrapRemoteException(FileNotFoundException.class); if (!(ioe instanceof FileNotFoundException)) { throw ioe; } Pair pair = openArchivedWAL(); if (pair != null) { return pair; } else { throw ioe; } } } protected final WALProtos.WALHeader readHeader(FSDataInputStream stream) throws IOException { byte[] magic = new byte[PB_WAL_MAGIC.length]; try { stream.readFully(magic); } catch (EOFException e) { throw new WALHeaderEOFException("EOF while reading PB WAL magic", e); } if (!Arrays.equals(PB_WAL_MAGIC, magic)) { throw new IOException("Invalid PB WAL magic " + Bytes.toStringBinary(magic) + ", expected " + Bytes.toStringBinary(PB_WAL_MAGIC)); } WALProtos.WALHeader header; try { header = ProtobufUtil.parseDelimitedFrom(stream, WALProtos.WALHeader.parser()); } catch (InvalidProtocolBufferException e) { if (ProtobufUtil.isEOF(e)) { throw new WALHeaderEOFException("EOF while reading PB header", e); } else { throw e; } } catch (EOFException e) { throw new WALHeaderEOFException("EOF while reading PB header", e); } if (header == null) { throw new WALHeaderEOFException("EOF while reading PB header"); } if (header.hasWriterClsName() && !getWriterClsNames().contains(header.getWriterClsName())) { throw new IOException("Got unknown writer class: " + header.getWriterClsName()); } return header; } private void initDecryptor(WALProtos.WALHeader header) throws IOException { if (!header.hasEncryptionKey()) { return; } EncryptionTest.testKeyProvider(conf); EncryptionTest.testCipherProvider(conf); // Retrieve a usable key byte[] keyBytes = header.getEncryptionKey().toByteArray(); Key key = null; String walKeyName = conf.get(HConstants.CRYPTO_WAL_KEY_NAME_CONF_KEY); // First try the WAL key, if one is configured if (walKeyName != null) { try { key = EncryptionUtil.unwrapWALKey(conf, walKeyName, keyBytes); } catch (KeyException e) { LOG.debug("Unable to unwrap key with WAL key '{}'", walKeyName, e); key = null; } } if (key == null) { String masterKeyName = conf.get(HConstants.CRYPTO_MASTERKEY_NAME_CONF_KEY, User.getCurrent().getShortName()); try { // Then, try the cluster master key key = EncryptionUtil.unwrapWALKey(conf, masterKeyName, keyBytes); } catch (KeyException e) { // If the current master key fails to unwrap, try the alternate, if // one is configured LOG.debug("Unable to unwrap key with current master key '{}'", masterKeyName, e); String alternateKeyName = conf.get(HConstants.CRYPTO_MASTERKEY_ALTERNATE_NAME_CONF_KEY); if (alternateKeyName != null) { try { key = EncryptionUtil.unwrapWALKey(conf, alternateKeyName, keyBytes); } catch (KeyException ex) { throw new IOException(ex); } } else { throw new IOException(e); } } } // Use the algorithm the key wants Cipher cipher = Encryption.getCipher(conf, key.getAlgorithm()); if (cipher == null) { throw new IOException("Cipher '" + key.getAlgorithm() + "' is not available"); } // Set up the decryptor for this WAL decryptor = cipher.getDecryptor(); decryptor.setKey(key); LOG.debug("Initialized secure protobuf WAL: cipher={}", cipher.getName()); } private void initCompression(WALProtos.WALHeader header) throws IOException { this.hasCompression = header.hasHasCompression() && header.getHasCompression(); if (!hasCompression) { return; } this.hasTagCompression = header.hasHasTagCompression() && header.getHasTagCompression(); this.hasValueCompression = header.hasHasValueCompression() && header.getHasValueCompression(); if (header.hasValueCompressionAlgorithm()) { try { this.valueCompressionType = Compression.Algorithm.values()[header.getValueCompressionAlgorithm()]; } catch (ArrayIndexOutOfBoundsException e) { throw new IOException("Invalid compression type", e); } } if (LOG.isDebugEnabled()) { LOG.debug( "Initializing compression context for {}: isRecoveredEdits={}" + ", hasTagCompression={}, hasValueCompression={}, valueCompressionType={}", path, CommonFSUtils.isRecoveredEdits(path), hasTagCompression, hasValueCompression, valueCompressionType); } try { compressionCtx = new CompressionContext(LRUDictionary.class, CommonFSUtils.isRecoveredEdits(path), hasTagCompression, hasValueCompression, valueCompressionType); } catch (Exception e) { throw new IOException("Failed to initialize CompressionContext", e); } } private WALCellCodec getCodec(Configuration conf, String cellCodecClsName, CompressionContext compressionContext) throws IOException { return WALCellCodec.create(conf, cellCodecClsName, compressionContext); } protected final void initWALCellCodec(WALProtos.WALHeader header, InputStream inputStream) throws IOException { String cellCodecClsName = header.hasCellCodecClsName() ? header.getCellCodecClsName() : null; if (decryptor != null && SecureWALCellCodec.class.getName().equals(cellCodecClsName)) { WALCellCodec codec = SecureWALCellCodec.getCodec(this.conf, decryptor); this.cellDecoder = codec.getDecoder(inputStream); // We do not support compression with WAL encryption this.compressionCtx = null; this.byteStringUncompressor = WALCellCodec.getNoneUncompressor(); this.hasCompression = false; this.hasTagCompression = false; this.hasValueCompression = false; } else { WALCellCodec codec = getCodec(conf, cellCodecClsName, compressionCtx); this.cellDecoder = codec.getDecoder(inputStream); if (this.hasCompression) { this.byteStringUncompressor = codec.getByteStringUncompressor(); } else { this.byteStringUncompressor = WALCellCodec.getNoneUncompressor(); } } this.codecClsName = cellCodecClsName; } protected final void readTrailer(FSDataInputStream stream, FileStatus stat) throws IOException { this.fileLength = stat.getLen(); this.walEditsStopOffset = this.fileLength; long currentPos = stream.getPos(); // we will reset walEditsStopOffset if trailer if available trailerPresent = setTrailerIfPresent(stream); if (currentPos != stream.getPos()) { // seek back stream.seek(currentPos); } } /** * To check whether a trailer is present in a WAL, it seeks to position (fileLength - * PB_WAL_COMPLETE_MAGIC.size() - Bytes.SIZEOF_INT). It reads the int value to know the size of * the trailer, and checks whether the trailer is present at the end or not by comparing the last * PB_WAL_COMPLETE_MAGIC.size() bytes. In case trailer is not present, it returns false; * otherwise, sets the trailer and sets this.walEditsStopOffset variable up to the point just * before the trailer. *

* The trailer is ignored in case: *

    *
  • fileLength is 0 or not correct (when file is under recovery, etc). *
  • the trailer size is negative. *
* In case the trailer size > this.trailerMaxSize, it is read after a WARN message. * @return true if a valid trailer is present */ private boolean setTrailerIfPresent(FSDataInputStream stream) throws IOException { try { long trailerSizeOffset = this.fileLength - (PB_WAL_COMPLETE_MAGIC.length + Bytes.SIZEOF_INT); if (trailerSizeOffset <= 0) { // no trailer possible. return false; } stream.seek(trailerSizeOffset); // read the int as trailer size. int trailerSize = stream.readInt(); ByteBuffer buf = ByteBuffer.allocate(PB_WAL_COMPLETE_MAGIC.length); stream.readFully(buf.array(), buf.arrayOffset(), buf.capacity()); if (!Arrays.equals(buf.array(), PB_WAL_COMPLETE_MAGIC)) { LOG.trace("No trailer found."); return false; } if (trailerSize < 0) { LOG.warn("Invalid trailer Size " + trailerSize + ", ignoring the trailer"); return false; } else if (trailerSize > this.trailerWarnSize) { // continue reading after warning the user. LOG.warn("Please investigate WALTrailer usage. Trailer size > maximum configured size : " + trailerSize + " > " + this.trailerWarnSize); } // seek to the position where trailer starts. long positionOfTrailer = trailerSizeOffset - trailerSize; stream.seek(positionOfTrailer); // read the trailer. buf = ByteBuffer.allocate(trailerSize);// for trailer. stream.readFully(buf.array(), buf.arrayOffset(), buf.capacity()); trailer = WALTrailer.parseFrom(buf.array()); this.walEditsStopOffset = positionOfTrailer; return true; } catch (IOException ioe) { LOG.warn("Got IOE while reading the trailer. Continuing as if no trailer is present.", ioe); } return false; } protected final boolean reachWALEditsStopOffset(long pos) { if (trailerPresent && pos > 0 && pos == walEditsStopOffset) { LOG.trace("Reached end of expected edits area at offset {}", pos); return true; } else { return false; } } /** * Returns names of the accepted writer classes */ public List getWriterClsNames() { return WRITER_CLS_NAMES; } /** * Returns the cell codec classname */ public String getCodecClsName() { return codecClsName; } public long getPosition() throws IOException { return inputStream != null ? inputStream.getPos() : -1; } public long trailerSize() { if (trailerPresent) { // sizeof PB_WAL_COMPLETE_MAGIC + sizeof trailerSize + trailer final long calculatedSize = (long) PB_WAL_COMPLETE_MAGIC.length + Bytes.SIZEOF_INT + trailer.getSerializedSize(); final long expectedSize = fileLength - walEditsStopOffset; if (expectedSize != calculatedSize) { LOG.warn("After parsing the trailer, we expect the total footer to be {} bytes, but we " + "calculate it as being {}", expectedSize, calculatedSize); } return expectedSize; } else { return -1L; } } protected final String getPositionQuietly() { try { long pos = getPosition(); return pos >= 0 ? Long.toString(pos) : ""; } catch (Exception e) { LOG.warn("failed to get position, ignoring", e); return ""; } } protected final IOException extractHiddenEof(Exception ex) { // There are two problems we are dealing with here. Hadoop stream throws generic exception // for EOF, not EOFException; and scanner further hides it inside RuntimeException. IOException ioEx = null; if (ex instanceof EOFException) { return (EOFException) ex; } else if (ex instanceof IOException) { ioEx = (IOException) ex; } else if ( ex instanceof RuntimeException && ex.getCause() != null && ex.getCause() instanceof IOException ) { ioEx = (IOException) ex.getCause(); } if ((ioEx != null) && (ioEx.getMessage() != null)) { if (ioEx.getMessage().contains("EOF")) { return ioEx; } return null; } return null; } /** * This is used to determine whether we have already reached the WALTrailer. As the size and magic * are at the end of the WAL file, it is possible that these two options are missing while * writing, so we will consider there is no trailer. And when we actually reach the WALTrailer, we * will try to decode it as WALKey and we will fail but the error could be vary as it is parsing * WALTrailer actually. * @return whether this is a WALTrailer and we should throw EOF to upper layer the file is done */ protected final boolean isWALTrailer(long startPosition) throws IOException { // We have nothing in the WALTrailer PB message now so its size is just a int length size and a // magic at the end int trailerSize = PB_WAL_COMPLETE_MAGIC.length + Bytes.SIZEOF_INT; if (fileLength - startPosition >= trailerSize) { // We still have more than trailerSize bytes before reaching the EOF so this is not a trailer. // We also test for == here because if this is a valid trailer, we can read it while opening // the reader so we should not reach here return false; } inputStream.seek(startPosition); for (int i = 0; i < 4; i++) { int r = inputStream.read(); if (r == -1) { // we have reached EOF while reading the length, and all bytes read are 0, so we assume this // is a partial trailer return true; } if (r != 0) { // the length is not 0, should not be a trailer return false; } } for (int i = 0; i < PB_WAL_COMPLETE_MAGIC.length; i++) { int r = inputStream.read(); if (r == -1) { // we have reached EOF while reading the magic, and all bytes read are matched, so we assume // this is a partial trailer return true; } if (r != (PB_WAL_COMPLETE_MAGIC[i] & 0xFF)) { // does not match magic, should not be a trailer return false; } } // in fact we should not reach here, as this means the trailer bytes are all matched and // complete, then we should not call this method... return true; } @Override public void close() { if (inputStream != null) { IOUtils.closeQuietly(inputStream); inputStream = null; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy