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