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