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 */
018package org.apache.hadoop.hbase.io.hfile;
019
020import java.io.ByteArrayInputStream;
021import java.io.DataInputStream;
022import java.io.DataOutputStream;
023import java.io.IOException;
024import java.io.SequenceInputStream;
025import java.util.ArrayList;
026import java.util.Collection;
027import java.util.Comparator;
028import java.util.List;
029import java.util.Map;
030import java.util.Objects;
031import java.util.Set;
032import java.util.SortedMap;
033import java.util.TreeMap;
034import org.apache.commons.io.IOUtils;
035import org.apache.hadoop.conf.Configuration;
036import org.apache.hadoop.fs.Path;
037import org.apache.hadoop.hbase.Cell;
038import org.apache.hadoop.hbase.CellUtil;
039import org.apache.hadoop.hbase.ExtendedCell;
040import org.apache.hadoop.hbase.KeyValue;
041import org.apache.hadoop.hbase.protobuf.ProtobufMagic;
042import org.apache.hadoop.hbase.security.SecurityUtil;
043import org.apache.hadoop.hbase.util.Bytes;
044import org.apache.yetus.audience.InterfaceAudience;
045import org.slf4j.Logger;
046import org.slf4j.LoggerFactory;
047
048import org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations;
049
050import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
051import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos;
052import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos.BytesBytesPair;
053import org.apache.hadoop.hbase.shaded.protobuf.generated.HFileProtos;
054
055/**
056 * Metadata Map of attributes for HFile written out as HFile Trailer. Created by the Writer and
057 * added to the tail of the file just before close. Metadata includes core attributes such as last
058 * key seen, comparator used writing the file, etc. Clients can add their own attributes via
059 * {@link #append(byte[], byte[], boolean)} and they'll be persisted and available at read time.
060 * Reader creates the HFileInfo on open by reading the tail of the HFile. The parse of the HFile
061 * trailer also creates a {@link HFileContext}, a read-only data structure that includes bulk of the
062 * HFileInfo and extras that is safe to pass around when working on HFiles.
063 * @see HFileContext
064 */
065@InterfaceAudience.Private
066public class HFileInfo implements SortedMap<byte[], byte[]> {
067
068  private static final Logger LOG = LoggerFactory.getLogger(HFileInfo.class);
069
070  static final String RESERVED_PREFIX = "hfile.";
071  static final byte[] RESERVED_PREFIX_BYTES = Bytes.toBytes(RESERVED_PREFIX);
072  static final byte[] LASTKEY = Bytes.toBytes(RESERVED_PREFIX + "LASTKEY");
073  static final byte[] AVG_KEY_LEN = Bytes.toBytes(RESERVED_PREFIX + "AVG_KEY_LEN");
074  static final byte[] AVG_VALUE_LEN = Bytes.toBytes(RESERVED_PREFIX + "AVG_VALUE_LEN");
075  static final byte[] CREATE_TIME_TS = Bytes.toBytes(RESERVED_PREFIX + "CREATE_TIME_TS");
076  static final byte[] TAGS_COMPRESSED = Bytes.toBytes(RESERVED_PREFIX + "TAGS_COMPRESSED");
077  static final byte[] KEY_OF_BIGGEST_CELL = Bytes.toBytes(RESERVED_PREFIX + "KEY_OF_BIGGEST_CELL");
078  static final byte[] LEN_OF_BIGGEST_CELL = Bytes.toBytes(RESERVED_PREFIX + "LEN_OF_BIGGEST_CELL");
079  public static final byte[] MAX_TAGS_LEN = Bytes.toBytes(RESERVED_PREFIX + "MAX_TAGS_LEN");
080  private final SortedMap<byte[], byte[]> map = new TreeMap<>(Bytes.BYTES_COMPARATOR);
081
082  /**
083   * We can read files whose major version is v2 IFF their minor version is at least 3.
084   */
085  private static final int MIN_V2_MINOR_VERSION_WITH_PB = 3;
086
087  /** Maximum minor version supported by this HFile format */
088  // We went to version 2 when we moved to pb'ing fileinfo and the trailer on
089  // the file. This version can read Writables version 1.
090  static final int MAX_MINOR_VERSION = 3;
091
092  /** Last key in the file. Filled in when we read in the file info */
093  private ExtendedCell lastKeyCell = null;
094  /** Average key length read from file info */
095  private int avgKeyLen = -1;
096  /** Average value length read from file info */
097  private int avgValueLen = -1;
098  /** Biggest Cell in the file, key only. Filled in when we read in the file info */
099  private Cell biggestCell = null;
100  /** Length of the biggest Cell */
101  private long lenOfBiggestCell = -1;
102  private boolean includesMemstoreTS = false;
103  private boolean decodeMemstoreTS = false;
104
105  /**
106   * Blocks read from the load-on-open section, excluding data root index, meta index, and file
107   * info.
108   */
109  private List<HFileBlock> loadOnOpenBlocks = new ArrayList<>();
110
111  /**
112   * The iterator will track all blocks in load-on-open section, since we use the
113   * {@link org.apache.hadoop.hbase.io.ByteBuffAllocator} to manage the ByteBuffers in block now, so
114   * we must ensure that deallocate all ByteBuffers in the end.
115   */
116  private HFileBlock.BlockIterator blockIter;
117
118  private HFileBlockIndex.CellBasedKeyBlockIndexReader dataIndexReader;
119  private HFileBlockIndex.ByteArrayKeyBlockIndexReader metaIndexReader;
120
121  private FixedFileTrailer trailer;
122  private HFileContext hfileContext;
123  private boolean initialized = false;
124
125  public HFileInfo() {
126    super();
127  }
128
129  public HFileInfo(ReaderContext context, Configuration conf) throws IOException {
130    this.initTrailerAndContext(context, conf);
131  }
132
133  /**
134   * Append the given key/value pair to the file info, optionally checking the key prefix.
135   * @param k           key to add
136   * @param v           value to add
137   * @param checkPrefix whether to check that the provided key does not start with the reserved
138   *                    prefix
139   * @return this file info object
140   * @throws IOException          if the key or value is invalid
141   * @throws NullPointerException if {@code key} or {@code value} is {@code null}
142   */
143  public HFileInfo append(final byte[] k, final byte[] v, final boolean checkPrefix)
144    throws IOException {
145    Objects.requireNonNull(k, "key cannot be null");
146    Objects.requireNonNull(v, "value cannot be null");
147
148    if (checkPrefix && isReservedFileInfoKey(k)) {
149      throw new IOException("Keys with a " + HFileInfo.RESERVED_PREFIX + " are reserved");
150    }
151    put(k, v);
152    return this;
153  }
154
155  /** Return true if the given file info key is reserved for internal use. */
156  public static boolean isReservedFileInfoKey(byte[] key) {
157    return Bytes.startsWith(key, HFileInfo.RESERVED_PREFIX_BYTES);
158  }
159
160  @Override
161  public void clear() {
162    this.map.clear();
163  }
164
165  @Override
166  public Comparator<? super byte[]> comparator() {
167    return map.comparator();
168  }
169
170  @Override
171  public boolean containsKey(Object key) {
172    return map.containsKey(key);
173  }
174
175  @Override
176  public boolean containsValue(Object value) {
177    return map.containsValue(value);
178  }
179
180  @Override
181  public Set<java.util.Map.Entry<byte[], byte[]>> entrySet() {
182    return map.entrySet();
183  }
184
185  @Override
186  public boolean equals(Object o) {
187    return map.equals(o);
188  }
189
190  @Override
191  public byte[] firstKey() {
192    return map.firstKey();
193  }
194
195  @Override
196  public byte[] get(Object key) {
197    return map.get(key);
198  }
199
200  @Override
201  public int hashCode() {
202    return map.hashCode();
203  }
204
205  @Override
206  public SortedMap<byte[], byte[]> headMap(byte[] toKey) {
207    return this.map.headMap(toKey);
208  }
209
210  @Override
211  public boolean isEmpty() {
212    return map.isEmpty();
213  }
214
215  @Override
216  public Set<byte[]> keySet() {
217    return map.keySet();
218  }
219
220  @Override
221  public byte[] lastKey() {
222    return map.lastKey();
223  }
224
225  @Override
226  public byte[] put(byte[] key, byte[] value) {
227    return this.map.put(key, value);
228  }
229
230  @Override
231  public void putAll(Map<? extends byte[], ? extends byte[]> m) {
232    this.map.putAll(m);
233  }
234
235  @Override
236  public byte[] remove(Object key) {
237    return this.map.remove(key);
238  }
239
240  @Override
241  public int size() {
242    return map.size();
243  }
244
245  @Override
246  public SortedMap<byte[], byte[]> subMap(byte[] fromKey, byte[] toKey) {
247    return this.map.subMap(fromKey, toKey);
248  }
249
250  @Override
251  public SortedMap<byte[], byte[]> tailMap(byte[] fromKey) {
252    return this.map.tailMap(fromKey);
253  }
254
255  @Override
256  public Collection<byte[]> values() {
257    return map.values();
258  }
259
260  /**
261   * Write out this instance on the passed in <code>out</code> stream. We write it as a protobuf.
262   * @see #read(DataInputStream)
263   */
264  void write(final DataOutputStream out) throws IOException {
265    HFileProtos.FileInfoProto.Builder builder = HFileProtos.FileInfoProto.newBuilder();
266    for (Map.Entry<byte[], byte[]> e : this.map.entrySet()) {
267      HBaseProtos.BytesBytesPair.Builder bbpBuilder = HBaseProtos.BytesBytesPair.newBuilder();
268      bbpBuilder.setFirst(UnsafeByteOperations.unsafeWrap(e.getKey()));
269      bbpBuilder.setSecond(UnsafeByteOperations.unsafeWrap(e.getValue()));
270      builder.addMapEntry(bbpBuilder.build());
271    }
272    out.write(ProtobufMagic.PB_MAGIC);
273    builder.build().writeDelimitedTo(out);
274  }
275
276  /**
277   * Populate this instance with what we find on the passed in <code>in</code> stream. Can
278   * deserialize protobuf of old Writables format.
279   * @see #write(DataOutputStream)
280   */
281  void read(final DataInputStream in) throws IOException {
282    // This code is tested over in TestHFileReaderV1 where we read an old hfile w/ this new code.
283    int pblen = ProtobufUtil.lengthOfPBMagic();
284    byte[] pbuf = new byte[pblen];
285    if (in.markSupported()) {
286      in.mark(pblen);
287    }
288    int read = in.read(pbuf);
289    if (read != pblen) {
290      throw new IOException("read=" + read + ", wanted=" + pblen);
291    }
292    if (ProtobufUtil.isPBMagicPrefix(pbuf)) {
293      parsePB(HFileProtos.FileInfoProto.parseDelimitedFrom(in));
294    } else {
295      if (in.markSupported()) {
296        in.reset();
297        parseWritable(in);
298      } else {
299        // We cannot use BufferedInputStream, it consumes more than we read from the underlying IS
300        ByteArrayInputStream bais = new ByteArrayInputStream(pbuf);
301        SequenceInputStream sis = new SequenceInputStream(bais, in); // Concatenate input streams
302        // TODO: Am I leaking anything here wrapping the passed in stream? We are not calling
303        // close on the wrapped streams but they should be let go after we leave this context?
304        // I see that we keep a reference to the passed in inputstream but since we no longer
305        // have a reference to this after we leave, we should be ok.
306        parseWritable(new DataInputStream(sis));
307      }
308    }
309  }
310
311  /**
312   * Now parse the old Writable format. It was a list of Map entries. Each map entry was a key and a
313   * value of a byte []. The old map format had a byte before each entry that held a code which was
314   * short for the key or value type. We know it was a byte [] so in below we just read and dump it.
315   */
316  void parseWritable(final DataInputStream in) throws IOException {
317    // First clear the map.
318    // Otherwise we will just accumulate entries every time this method is called.
319    this.map.clear();
320    // Read the number of entries in the map
321    int entries = in.readInt();
322    // Then read each key/value pair
323    for (int i = 0; i < entries; i++) {
324      byte[] key = Bytes.readByteArray(in);
325      // We used to read a byte that encoded the class type.
326      // Read and ignore it because it is always byte [] in hfile
327      in.readByte();
328      byte[] value = Bytes.readByteArray(in);
329      this.map.put(key, value);
330    }
331  }
332
333  /**
334   * Fill our map with content of the pb we read off disk
335   * @param fip protobuf message to read
336   */
337  void parsePB(final HFileProtos.FileInfoProto fip) {
338    this.map.clear();
339    for (BytesBytesPair pair : fip.getMapEntryList()) {
340      this.map.put(pair.getFirst().toByteArray(), pair.getSecond().toByteArray());
341    }
342  }
343
344  public void initTrailerAndContext(ReaderContext context, Configuration conf) throws IOException {
345    try {
346      boolean isHBaseChecksum = context.getInputStreamWrapper().shouldUseHBaseChecksum();
347      trailer = FixedFileTrailer.readFromStream(
348        context.getInputStreamWrapper().getStream(isHBaseChecksum), context.getFileSize());
349      Path path = context.getFilePath();
350      checkFileVersion(path);
351      this.hfileContext = createHFileContext(context, path, trailer, conf);
352      context.getInputStreamWrapper().unbuffer();
353    } catch (Throwable t) {
354      IOUtils.closeQuietly(context.getInputStreamWrapper(),
355        e -> LOG.warn("failed to close input stream wrapper", e));
356      throw new CorruptHFileException(
357        "Problem reading HFile Trailer from file " + context.getFilePath(), t);
358    }
359  }
360
361  /**
362   * should be called after initTrailerAndContext
363   */
364  public void initMetaAndIndex(HFile.Reader reader) throws IOException {
365    if (initialized) {
366      return;
367    }
368
369    ReaderContext context = reader.getContext();
370    try {
371      HFileBlock.FSReader blockReader = reader.getUncachedBlockReader();
372      // Initialize an block iterator, and parse load-on-open blocks in the following.
373      blockIter = blockReader.blockRange(trailer.getLoadOnOpenDataOffset(),
374        context.getFileSize() - trailer.getTrailerSize());
375      // Data index. We also read statistics about the block index written after
376      // the root level.
377      HFileBlock dataBlockRootIndex = blockIter.nextBlockWithBlockType(BlockType.ROOT_INDEX);
378      HFileBlock metaBlockIndex = blockIter.nextBlockWithBlockType(BlockType.ROOT_INDEX);
379      loadMetaInfo(blockIter, hfileContext);
380
381      HFileIndexBlockEncoder indexBlockEncoder =
382        HFileIndexBlockEncoderImpl.createFromFileInfo(this);
383      this.dataIndexReader = new HFileBlockIndex.CellBasedKeyBlockIndexReaderV2(
384        trailer.createComparator(), trailer.getNumDataIndexLevels(), indexBlockEncoder);
385      dataIndexReader.readMultiLevelIndexRoot(dataBlockRootIndex, trailer.getDataIndexCount());
386      reader.setDataBlockIndexReader(dataIndexReader);
387      // Meta index.
388      this.metaIndexReader = new HFileBlockIndex.ByteArrayKeyBlockIndexReader(1);
389      metaIndexReader.readRootIndex(metaBlockIndex, trailer.getMetaIndexCount());
390      reader.setMetaBlockIndexReader(metaIndexReader);
391
392      reader.setDataBlockEncoder(HFileDataBlockEncoderImpl.createFromFileInfo(this));
393      // Load-On-Open info
394      HFileBlock b;
395      while ((b = blockIter.nextBlock()) != null) {
396        loadOnOpenBlocks.add(b);
397      }
398      // close the block reader
399      context.getInputStreamWrapper().unbuffer();
400    } catch (Throwable t) {
401      IOUtils.closeQuietly(context.getInputStreamWrapper(),
402        e -> LOG.warn("failed to close input stream wrapper", e));
403      throw new CorruptHFileException(
404        "Problem reading data index and meta index from file " + context.getFilePath(), t);
405    }
406    initialized = true;
407  }
408
409  private HFileContext createHFileContext(ReaderContext readerContext, Path path,
410    FixedFileTrailer trailer, Configuration conf) throws IOException {
411    return new HFileContextBuilder().withHBaseCheckSum(true).withHFileName(path.getName())
412      .withCompression(trailer.getCompressionCodec())
413      .withDecompressionContext(
414        trailer.getCompressionCodec().getHFileDecompressionContextForConfiguration(conf))
415      .withCellComparator(FixedFileTrailer.createComparator(trailer.getComparatorClassName()))
416      .withEncryptionContext(SecurityUtil.createEncryptionContext(conf, path, trailer,
417        readerContext.getManagedKeyDataCache(), readerContext.getSystemKeyCache()))
418      .build();
419  }
420
421  private void loadMetaInfo(HFileBlock.BlockIterator blockIter, HFileContext hfileContext)
422    throws IOException {
423    read(blockIter.nextBlockWithBlockType(BlockType.FILE_INFO).getByteStream());
424    byte[] creationTimeBytes = get(HFileInfo.CREATE_TIME_TS);
425    hfileContext.setFileCreateTime(creationTimeBytes == null ? 0 : Bytes.toLong(creationTimeBytes));
426    byte[] tmp = get(HFileInfo.MAX_TAGS_LEN);
427    // max tag length is not present in the HFile means tags were not at all written to file.
428    if (tmp != null) {
429      hfileContext.setIncludesTags(true);
430      tmp = get(HFileInfo.TAGS_COMPRESSED);
431      if (tmp != null && Bytes.toBoolean(tmp)) {
432        hfileContext.setCompressTags(true);
433      }
434    }
435    // parse meta info
436    if (get(HFileInfo.LASTKEY) != null) {
437      lastKeyCell = new KeyValue.KeyOnlyKeyValue(get(HFileInfo.LASTKEY));
438    }
439    if (get(HFileInfo.KEY_OF_BIGGEST_CELL) != null) {
440      biggestCell = new KeyValue.KeyOnlyKeyValue(get(HFileInfo.KEY_OF_BIGGEST_CELL));
441      lenOfBiggestCell = Bytes.toLong(get(HFileInfo.LEN_OF_BIGGEST_CELL));
442    }
443    avgKeyLen = Bytes.toInt(get(HFileInfo.AVG_KEY_LEN));
444    avgValueLen = Bytes.toInt(get(HFileInfo.AVG_VALUE_LEN));
445    byte[] keyValueFormatVersion = get(HFileWriterImpl.KEY_VALUE_VERSION);
446    includesMemstoreTS = keyValueFormatVersion != null
447      && Bytes.toInt(keyValueFormatVersion) == HFileWriterImpl.KEY_VALUE_VER_WITH_MEMSTORE;
448    hfileContext.setIncludesMvcc(includesMemstoreTS);
449    if (includesMemstoreTS) {
450      decodeMemstoreTS = Bytes.toLong(get(HFileWriterImpl.MAX_MEMSTORE_TS_KEY)) > 0;
451    }
452  }
453
454  /**
455   * File version check is a little sloppy. We read v3 files but can also read v2 files if their
456   * content has been pb'd; files written with 0.98.
457   */
458  private void checkFileVersion(Path path) {
459    int majorVersion = trailer.getMajorVersion();
460    if (majorVersion == getMajorVersion()) {
461      return;
462    }
463    int minorVersion = trailer.getMinorVersion();
464    if (majorVersion == 2 && minorVersion >= MIN_V2_MINOR_VERSION_WITH_PB) {
465      return;
466    }
467    // We can read v3 or v2 versions of hfile.
468    throw new IllegalArgumentException("Invalid HFile version: major=" + trailer.getMajorVersion()
469      + ", minor=" + trailer.getMinorVersion() + ": expected at least " + "major=2 and minor="
470      + MAX_MINOR_VERSION + ", path=" + path);
471  }
472
473  public void close() {
474    if (blockIter != null) {
475      blockIter.freeBlocks();
476    }
477  }
478
479  public int getMajorVersion() {
480    return 3;
481  }
482
483  public void setTrailer(FixedFileTrailer trailer) {
484    this.trailer = trailer;
485  }
486
487  public FixedFileTrailer getTrailer() {
488    return this.trailer;
489  }
490
491  public HFileBlockIndex.CellBasedKeyBlockIndexReader getDataBlockIndexReader() {
492    return this.dataIndexReader;
493  }
494
495  public HFileBlockIndex.ByteArrayKeyBlockIndexReader getMetaBlockIndexReader() {
496    return this.metaIndexReader;
497  }
498
499  public HFileContext getHFileContext() {
500    return this.hfileContext;
501  }
502
503  public List<HFileBlock> getLoadOnOpenBlocks() {
504    return loadOnOpenBlocks;
505  }
506
507  public ExtendedCell getLastKeyCell() {
508    return lastKeyCell;
509  }
510
511  public int getAvgKeyLen() {
512    return avgKeyLen;
513  }
514
515  public int getAvgValueLen() {
516    return avgValueLen;
517  }
518
519  public String getKeyOfBiggestCell() {
520    return CellUtil.toString(biggestCell, false);
521  }
522
523  public long getLenOfBiggestCell() {
524    return lenOfBiggestCell;
525  }
526
527  public boolean shouldIncludeMemStoreTS() {
528    return includesMemstoreTS;
529  }
530
531  public boolean isDecodeMemstoreTS() {
532    return decodeMemstoreTS;
533  }
534}