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 static org.junit.Assert.assertEquals; 021import static org.junit.Assert.assertTrue; 022import static org.junit.Assert.fail; 023 024import java.io.DataOutputStream; 025import java.io.IOException; 026import java.nio.ByteBuffer; 027import java.util.ArrayList; 028import java.util.Collection; 029import java.util.List; 030import org.apache.hadoop.conf.Configuration; 031import org.apache.hadoop.hbase.Cell; 032import org.apache.hadoop.hbase.HBaseClassTestRule; 033import org.apache.hadoop.hbase.HBaseConfiguration; 034import org.apache.hadoop.hbase.HConstants; 035import org.apache.hadoop.hbase.KeyValue; 036import org.apache.hadoop.hbase.io.ByteArrayOutputStream; 037import org.apache.hadoop.hbase.io.ByteBuffAllocator; 038import org.apache.hadoop.hbase.io.HeapSize; 039import org.apache.hadoop.hbase.io.compress.Compression.Algorithm; 040import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding; 041import org.apache.hadoop.hbase.io.encoding.HFileBlockDefaultEncodingContext; 042import org.apache.hadoop.hbase.io.encoding.HFileBlockEncodingContext; 043import org.apache.hadoop.hbase.nio.ByteBuff; 044import org.apache.hadoop.hbase.testclassification.IOTests; 045import org.apache.hadoop.hbase.testclassification.MediumTests; 046import org.apache.hadoop.hbase.util.ChecksumType; 047import org.apache.hadoop.hbase.util.RedundantKVGenerator; 048import org.junit.ClassRule; 049import org.junit.Test; 050import org.junit.experimental.categories.Category; 051import org.junit.runner.RunWith; 052import org.junit.runners.Parameterized; 053import org.junit.runners.Parameterized.Parameters; 054 055@RunWith(Parameterized.class) 056@Category({ IOTests.class, MediumTests.class }) 057public class TestHFileDataBlockEncoder { 058 059 @ClassRule 060 public static final HBaseClassTestRule CLASS_RULE = 061 HBaseClassTestRule.forClass(TestHFileDataBlockEncoder.class); 062 063 private final Configuration conf = HBaseConfiguration.create(); 064 private final RedundantKVGenerator generator = new RedundantKVGenerator(); 065 private HFileDataBlockEncoder blockEncoder; 066 private boolean includesMemstoreTS; 067 068 /** 069 * Create test for given data block encoding configuration. 070 * @param blockEncoder What kind of encoding policy will be used. 071 */ 072 public TestHFileDataBlockEncoder(HFileDataBlockEncoder blockEncoder, boolean includesMemstoreTS) { 073 this.blockEncoder = blockEncoder; 074 this.includesMemstoreTS = includesMemstoreTS; 075 System.err.println("Encoding: " + blockEncoder.getDataBlockEncoding() + ", includesMemstoreTS: " 076 + includesMemstoreTS); 077 } 078 079 /** 080 * Test putting and taking out blocks into cache with different encoding options. 081 */ 082 @Test 083 public void testEncodingWithCache() throws IOException { 084 testEncodingWithCacheInternals(false); 085 testEncodingWithCacheInternals(true); 086 } 087 088 private void testEncodingWithCacheInternals(boolean useTag) throws IOException { 089 List<KeyValue> kvs = generator.generateTestKeyValues(60, useTag); 090 HFileBlock block = getSampleHFileBlock(kvs, useTag); 091 HFileBlock cacheBlock = createBlockOnDisk(conf, kvs, block, useTag); 092 093 LruBlockCache blockCache = new LruBlockCache(8 * 1024 * 1024, 32 * 1024); 094 BlockCacheKey cacheKey = new BlockCacheKey("test", 0); 095 blockCache.cacheBlock(cacheKey, cacheBlock); 096 097 HeapSize heapSize = blockCache.getBlock(cacheKey, false, false, true); 098 assertTrue(heapSize instanceof HFileBlock); 099 100 HFileBlock returnedBlock = (HFileBlock) heapSize; 101 ; 102 103 if (blockEncoder.getDataBlockEncoding() == DataBlockEncoding.NONE) { 104 assertEquals(block.getBufferReadOnly(), returnedBlock.getBufferReadOnly()); 105 } else { 106 if (BlockType.ENCODED_DATA != returnedBlock.getBlockType()) { 107 System.out.println(blockEncoder); 108 } 109 assertEquals(BlockType.ENCODED_DATA, returnedBlock.getBlockType()); 110 } 111 } 112 113 /** Test for HBASE-5746. */ 114 @Test 115 public void testHeaderSizeInCacheWithoutChecksum() throws Exception { 116 testHeaderSizeInCacheWithoutChecksumInternals(false); 117 testHeaderSizeInCacheWithoutChecksumInternals(true); 118 } 119 120 private void testHeaderSizeInCacheWithoutChecksumInternals(boolean useTags) throws IOException { 121 int headerSize = HConstants.HFILEBLOCK_HEADER_SIZE_NO_CHECKSUM; 122 // Create some KVs and create the block with old-style header. 123 List<KeyValue> kvs = generator.generateTestKeyValues(60, useTags); 124 ByteBuffer keyValues = RedundantKVGenerator.convertKvToByteBuffer(kvs, includesMemstoreTS); 125 int size = keyValues.limit(); 126 ByteBuffer buf = ByteBuffer.allocate(size + headerSize); 127 buf.position(headerSize); 128 keyValues.rewind(); 129 buf.put(keyValues); 130 HFileContext hfileContext = 131 new HFileContextBuilder().withHBaseCheckSum(false).withIncludesMvcc(includesMemstoreTS) 132 .withIncludesTags(useTags).withBlockSize(0).withChecksumType(ChecksumType.NULL).build(); 133 HFileBlock block = new HFileBlock(BlockType.DATA, size, size, -1, ByteBuff.wrap(buf), 134 HFileBlock.FILL_HEADER, 0, 0, -1, hfileContext, ByteBuffAllocator.HEAP); 135 HFileBlock cacheBlock = createBlockOnDisk(conf, kvs, block, useTags); 136 assertEquals(headerSize, cacheBlock.getDummyHeaderForVersion().length); 137 } 138 139 /** 140 * Test encoding. 141 */ 142 @Test 143 public void testEncoding() throws IOException { 144 testEncodingInternals(false); 145 testEncodingInternals(true); 146 } 147 148 /** 149 * Test encoding with offheap keyvalue. This test just verifies if the encoders work with DBB and 150 * does not use the getXXXArray() API 151 */ 152 @Test 153 public void testEncodingWithOffheapKeyValue() throws IOException { 154 // usually we have just block without headers, but don't complicate that 155 try { 156 List<Cell> kvs = generator.generateTestExtendedOffheapKeyValues(60, true); 157 HFileContext meta = new HFileContextBuilder().withIncludesMvcc(includesMemstoreTS) 158 .withIncludesTags(true).withHBaseCheckSum(true).withCompression(Algorithm.NONE) 159 .withBlockSize(0).withChecksumType(ChecksumType.NULL).build(); 160 writeBlock(conf, kvs, meta, true); 161 } catch (IllegalArgumentException e) { 162 fail("No exception should have been thrown"); 163 } 164 } 165 166 private void testEncodingInternals(boolean useTag) throws IOException { 167 // usually we have just block without headers, but don't complicate that 168 List<KeyValue> kvs = generator.generateTestKeyValues(60, useTag); 169 HFileBlock block = getSampleHFileBlock(kvs, useTag); 170 HFileBlock blockOnDisk = createBlockOnDisk(conf, kvs, block, useTag); 171 172 if (blockEncoder.getDataBlockEncoding() != DataBlockEncoding.NONE) { 173 assertEquals(BlockType.ENCODED_DATA, blockOnDisk.getBlockType()); 174 assertEquals(blockEncoder.getDataBlockEncoding().getId(), 175 blockOnDisk.getDataBlockEncodingId()); 176 } else { 177 assertEquals(BlockType.DATA, blockOnDisk.getBlockType()); 178 } 179 } 180 181 private HFileBlock getSampleHFileBlock(List<KeyValue> kvs, boolean useTag) { 182 ByteBuffer keyValues = RedundantKVGenerator.convertKvToByteBuffer(kvs, includesMemstoreTS); 183 int size = keyValues.limit(); 184 ByteBuffer buf = ByteBuffer.allocate(size + HConstants.HFILEBLOCK_HEADER_SIZE); 185 buf.position(HConstants.HFILEBLOCK_HEADER_SIZE); 186 keyValues.rewind(); 187 buf.put(keyValues); 188 HFileContext meta = new HFileContextBuilder().withIncludesMvcc(includesMemstoreTS) 189 .withIncludesTags(useTag).withHBaseCheckSum(true).withCompression(Algorithm.NONE) 190 .withBlockSize(0).withChecksumType(ChecksumType.NULL).build(); 191 HFileBlock b = new HFileBlock(BlockType.DATA, size, size, -1, ByteBuff.wrap(buf), 192 HFileBlock.FILL_HEADER, 0, 0, -1, meta, ByteBuffAllocator.HEAP); 193 return b; 194 } 195 196 private HFileBlock createBlockOnDisk(Configuration conf, List<KeyValue> kvs, HFileBlock block, 197 boolean useTags) throws IOException { 198 int size; 199 HFileBlockEncodingContext context = 200 new HFileBlockDefaultEncodingContext(conf, blockEncoder.getDataBlockEncoding(), 201 HConstants.HFILEBLOCK_DUMMY_HEADER, block.getHFileContext()); 202 203 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 204 baos.write(block.getDummyHeaderForVersion()); 205 DataOutputStream dos = new DataOutputStream(baos); 206 blockEncoder.startBlockEncoding(context, dos); 207 for (KeyValue kv : kvs) { 208 blockEncoder.encode(kv, context, dos); 209 } 210 blockEncoder.endBlockEncoding(context, dos, baos.getBuffer(), BlockType.DATA); 211 byte[] encodedBytes = baos.toByteArray(); 212 size = encodedBytes.length - block.getDummyHeaderForVersion().length; 213 return new HFileBlock(context.getBlockType(), size, size, -1, 214 ByteBuff.wrap(ByteBuffer.wrap(encodedBytes)), HFileBlock.FILL_HEADER, 0, 215 block.getOnDiskDataSizeWithHeader(), -1, block.getHFileContext(), ByteBuffAllocator.HEAP); 216 } 217 218 private void writeBlock(Configuration conf, List<Cell> kvs, HFileContext fileContext, 219 boolean useTags) throws IOException { 220 HFileBlockEncodingContext context = new HFileBlockDefaultEncodingContext(conf, 221 blockEncoder.getDataBlockEncoding(), HConstants.HFILEBLOCK_DUMMY_HEADER, fileContext); 222 223 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 224 baos.write(HConstants.HFILEBLOCK_DUMMY_HEADER); 225 DataOutputStream dos = new DataOutputStream(baos); 226 blockEncoder.startBlockEncoding(context, dos); 227 for (Cell kv : kvs) { 228 blockEncoder.encode(kv, context, dos); 229 } 230 } 231 232 /** Returns All possible data block encoding configurations */ 233 @Parameters 234 public static Collection<Object[]> getAllConfigurations() { 235 List<Object[]> configurations = new ArrayList<>(); 236 237 for (DataBlockEncoding diskAlgo : DataBlockEncoding.values()) { 238 for (boolean includesMemstoreTS : new boolean[] { false, true }) { 239 HFileDataBlockEncoder dbe = (diskAlgo == DataBlockEncoding.NONE) 240 ? NoOpDataBlockEncoder.INSTANCE 241 : new HFileDataBlockEncoderImpl(diskAlgo); 242 configurations.add(new Object[] { dbe, new Boolean(includesMemstoreTS) }); 243 } 244 } 245 246 return configurations; 247 } 248}