001/* 002 * Copyright The Apache Software Foundation 003 * 004 * Licensed to the Apache Software Foundation (ASF) under one or more 005 * contributor license agreements. See the NOTICE file distributed with this 006 * work for additional information regarding copyright ownership. The ASF 007 * licenses this file to you under the Apache License, Version 2.0 (the 008 * "License"); you may not use this file except in compliance with the License. 009 * 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, WITHOUT 015 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 016 * License for the specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.hadoop.hbase.io.hfile.bucket; 020 021import static org.junit.Assert.assertEquals; 022import static org.junit.Assert.assertNotEquals; 023import static org.junit.Assert.assertTrue; 024import java.io.BufferedWriter; 025import java.io.FileOutputStream; 026import java.io.OutputStreamWriter; 027import java.nio.file.FileSystems; 028import java.nio.file.Files; 029import java.nio.file.attribute.FileTime; 030import java.time.Instant; 031import java.util.Arrays; 032import org.apache.hadoop.fs.Path; 033import org.apache.hadoop.hbase.HBaseClassTestRule; 034import org.apache.hadoop.hbase.HBaseTestingUtility; 035import org.apache.hadoop.hbase.io.hfile.BlockCacheKey; 036import org.apache.hadoop.hbase.io.hfile.CacheTestUtils; 037import org.apache.hadoop.hbase.io.hfile.Cacheable; 038import org.apache.hadoop.hbase.testclassification.SmallTests; 039import org.junit.ClassRule; 040import org.junit.Test; 041import org.junit.experimental.categories.Category; 042import org.junit.runner.RunWith; 043import org.junit.runners.Parameterized; 044 045/** 046 * Basic test for check file's integrity before start BucketCache in fileIOEngine 047 */ 048@RunWith(Parameterized.class) 049@Category(SmallTests.class) 050public class TestVerifyBucketCacheFile { 051 @ClassRule 052 public static final HBaseClassTestRule CLASS_RULE = 053 HBaseClassTestRule.forClass(TestVerifyBucketCacheFile.class); 054 055 @Parameterized.Parameters(name = "{index}: blockSize={0}, bucketSizes={1}") 056 public static Iterable<Object[]> data() { 057 return Arrays.asList(new Object[][] { { 8192, null }, { 16 * 1024, 058 new int[] { 2 * 1024 + 1024, 4 * 1024 + 1024, 8 * 1024 + 1024, 16 * 1024 + 1024, 059 28 * 1024 + 1024, 32 * 1024 + 1024, 64 * 1024 + 1024, 96 * 1024 + 1024, 060 128 * 1024 + 1024 } } }); 061 } 062 063 @Parameterized.Parameter(0) 064 public int constructedBlockSize; 065 066 @Parameterized.Parameter(1) 067 public int[] constructedBlockSizes; 068 069 final long capacitySize = 32 * 1024 * 1024; 070 final int writeThreads = BucketCache.DEFAULT_WRITER_THREADS; 071 final int writerQLen = BucketCache.DEFAULT_WRITER_QUEUE_ITEMS; 072 073 /** 074 * Test cache file or persistence file does not exist whether BucketCache starts normally 075 * (1) Start BucketCache and add some blocks, then shutdown BucketCache and persist cache 076 * to file. Restart BucketCache and it can restore cache from file. 077 * (2) Delete bucket cache file after shutdown BucketCache. Restart BucketCache and it can't 078 * restore cache from file, the cache file and persistence file would be deleted before 079 * BucketCache start normally. 080 * (3) Delete persistence file after shutdown BucketCache. Restart BucketCache and it can't 081 * restore cache from file, the cache file and persistence file would be deleted before 082 * BucketCache start normally. 083 * @throws Exception the exception 084 */ 085 @Test 086 public void testRetrieveFromFile() throws Exception { 087 HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 088 Path testDir = TEST_UTIL.getDataTestDir(); 089 TEST_UTIL.getTestFileSystem().mkdirs(testDir); 090 091 BucketCache bucketCache = 092 new BucketCache("file:" + testDir + "/bucket.cache", capacitySize, constructedBlockSize, 093 constructedBlockSizes, writeThreads, writerQLen, testDir + "/bucket.persistence"); 094 long usedSize = bucketCache.getAllocator().getUsedSize(); 095 assertEquals(0, usedSize); 096 CacheTestUtils.HFileBlockPair[] blocks = 097 CacheTestUtils.generateHFileBlocks(constructedBlockSize, 1); 098 // Add blocks 099 for (CacheTestUtils.HFileBlockPair block : blocks) { 100 cacheAndWaitUntilFlushedToBucket(bucketCache, block.getBlockName(), block.getBlock()); 101 } 102 usedSize = bucketCache.getAllocator().getUsedSize(); 103 assertNotEquals(0, usedSize); 104 // 1.persist cache to file 105 bucketCache.shutdown(); 106 // restore cache from file 107 bucketCache = 108 new BucketCache("file:" + testDir + "/bucket.cache", capacitySize, constructedBlockSize, 109 constructedBlockSizes, writeThreads, writerQLen, testDir + "/bucket.persistence"); 110 assertEquals(usedSize, bucketCache.getAllocator().getUsedSize()); 111 // persist cache to file 112 bucketCache.shutdown(); 113 114 // 2.delete bucket cache file 115 final java.nio.file.Path cacheFile = 116 FileSystems.getDefault().getPath(testDir.toString(), "bucket.cache"); 117 assertTrue(Files.deleteIfExists(cacheFile)); 118 // can't restore cache from file 119 bucketCache = 120 new BucketCache("file:" + testDir + "/bucket.cache", capacitySize, constructedBlockSize, 121 constructedBlockSizes, writeThreads, writerQLen, testDir + "/bucket.persistence"); 122 assertEquals(0, bucketCache.getAllocator().getUsedSize()); 123 assertEquals(0, bucketCache.backingMap.size()); 124 // Add blocks 125 for (CacheTestUtils.HFileBlockPair block : blocks) { 126 cacheAndWaitUntilFlushedToBucket(bucketCache, block.getBlockName(), block.getBlock()); 127 } 128 usedSize = bucketCache.getAllocator().getUsedSize(); 129 assertNotEquals(0, usedSize); 130 // persist cache to file 131 bucketCache.shutdown(); 132 133 // 3.delete backingMap persistence file 134 final java.nio.file.Path mapFile = 135 FileSystems.getDefault().getPath(testDir.toString(), "bucket.persistence"); 136 assertTrue(Files.deleteIfExists(mapFile)); 137 // can't restore cache from file 138 bucketCache = 139 new BucketCache("file:" + testDir + "/bucket.cache", capacitySize, constructedBlockSize, 140 constructedBlockSizes, writeThreads, writerQLen, testDir + "/bucket.persistence"); 141 assertEquals(0, bucketCache.getAllocator().getUsedSize()); 142 assertEquals(0, bucketCache.backingMap.size()); 143 144 TEST_UTIL.cleanupTestDir(); 145 } 146 147 /** 148 * Test whether BucketCache is started normally after modifying the cache file. 149 * Start BucketCache and add some blocks, then shutdown BucketCache and persist cache to file. 150 * Restart BucketCache after modify cache file's data, and it can't restore cache from file, 151 * the cache file and persistence file would be deleted before BucketCache start normally. 152 * @throws Exception the exception 153 */ 154 @Test 155 public void testModifiedBucketCacheFileData() throws Exception { 156 HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 157 Path testDir = TEST_UTIL.getDataTestDir(); 158 TEST_UTIL.getTestFileSystem().mkdirs(testDir); 159 160 BucketCache bucketCache = 161 new BucketCache("file:" + testDir + "/bucket.cache", capacitySize, constructedBlockSize, 162 constructedBlockSizes, writeThreads, writerQLen, testDir + "/bucket.persistence"); 163 long usedSize = bucketCache.getAllocator().getUsedSize(); 164 assertEquals(0, usedSize); 165 166 CacheTestUtils.HFileBlockPair[] blocks = 167 CacheTestUtils.generateHFileBlocks(constructedBlockSize, 1); 168 // Add blocks 169 for (CacheTestUtils.HFileBlockPair block : blocks) { 170 cacheAndWaitUntilFlushedToBucket(bucketCache, block.getBlockName(), block.getBlock()); 171 } 172 usedSize = bucketCache.getAllocator().getUsedSize(); 173 assertNotEquals(0, usedSize); 174 // persist cache to file 175 bucketCache.shutdown(); 176 177 // modified bucket cache file 178 String file = testDir + "/bucket.cache"; 179 try(BufferedWriter out = new BufferedWriter(new OutputStreamWriter( 180 new FileOutputStream(file, false)))) { 181 out.write("test bucket cache"); 182 } 183 // can't restore cache from file 184 bucketCache = 185 new BucketCache("file:" + testDir + "/bucket.cache", capacitySize, constructedBlockSize, 186 constructedBlockSizes, writeThreads, writerQLen, testDir + "/bucket.persistence"); 187 assertEquals(0, bucketCache.getAllocator().getUsedSize()); 188 assertEquals(0, bucketCache.backingMap.size()); 189 190 TEST_UTIL.cleanupTestDir(); 191 } 192 193 /** 194 * Test whether BucketCache is started normally after modifying the cache file's last modified 195 * time. First Start BucketCache and add some blocks, then shutdown BucketCache and persist 196 * cache to file. Then Restart BucketCache after modify cache file's last modified time, and 197 * it can't restore cache from file, the cache file and persistence file would be deleted 198 * before BucketCache start normally. 199 * @throws Exception the exception 200 */ 201 @Test 202 public void testModifiedBucketCacheFileTime() throws Exception { 203 HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 204 Path testDir = TEST_UTIL.getDataTestDir(); 205 TEST_UTIL.getTestFileSystem().mkdirs(testDir); 206 207 BucketCache bucketCache = 208 new BucketCache("file:" + testDir + "/bucket.cache", capacitySize, constructedBlockSize, 209 constructedBlockSizes, writeThreads, writerQLen, testDir + "/bucket.persistence"); 210 long usedSize = bucketCache.getAllocator().getUsedSize(); 211 assertEquals(0, usedSize); 212 213 CacheTestUtils.HFileBlockPair[] blocks = 214 CacheTestUtils.generateHFileBlocks(constructedBlockSize, 1); 215 // Add blocks 216 for (CacheTestUtils.HFileBlockPair block : blocks) { 217 cacheAndWaitUntilFlushedToBucket(bucketCache, block.getBlockName(), block.getBlock()); 218 } 219 usedSize = bucketCache.getAllocator().getUsedSize(); 220 assertNotEquals(0, usedSize); 221 // persist cache to file 222 bucketCache.shutdown(); 223 224 // modified bucket cache file LastModifiedTime 225 final java.nio.file.Path file = 226 FileSystems.getDefault().getPath(testDir.toString(), "bucket.cache"); 227 Files.setLastModifiedTime(file, FileTime.from(Instant.now().plusMillis(1_000))); 228 // can't restore cache from file 229 bucketCache = 230 new BucketCache("file:" + testDir + "/bucket.cache", capacitySize, constructedBlockSize, 231 constructedBlockSizes, writeThreads, writerQLen, testDir + "/bucket.persistence"); 232 assertEquals(0, bucketCache.getAllocator().getUsedSize()); 233 assertEquals(0, bucketCache.backingMap.size()); 234 235 TEST_UTIL.cleanupTestDir(); 236 } 237 238 private void waitUntilFlushedToBucket(BucketCache cache, BlockCacheKey cacheKey) 239 throws InterruptedException { 240 while (!cache.backingMap.containsKey(cacheKey) || cache.ramCache.containsKey(cacheKey)) { 241 Thread.sleep(100); 242 } 243 } 244 245 // BucketCache.cacheBlock is async, it first adds block to ramCache and writeQueue, then writer 246 // threads will flush it to the bucket and put reference entry in backingMap. 247 private void cacheAndWaitUntilFlushedToBucket(BucketCache cache, BlockCacheKey cacheKey, 248 Cacheable block) throws InterruptedException { 249 cache.cacheBlock(cacheKey, block); 250 waitUntilFlushedToBucket(cache, cacheKey); 251 } 252}