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.regionserver.storefiletracker; 019 020import static org.junit.jupiter.api.Assertions.assertEquals; 021import static org.junit.jupiter.api.Assertions.assertNull; 022import static org.junit.jupiter.api.Assertions.assertThrows; 023import static org.junit.jupiter.api.Assertions.assertTrue; 024import static org.mockito.Mockito.mock; 025import static org.mockito.Mockito.when; 026 027import java.io.IOException; 028import org.apache.hadoop.fs.FSDataInputStream; 029import org.apache.hadoop.fs.FSDataOutputStream; 030import org.apache.hadoop.fs.FileStatus; 031import org.apache.hadoop.fs.FileSystem; 032import org.apache.hadoop.fs.Path; 033import org.apache.hadoop.hbase.HBaseCommonTestingUtil; 034import org.apache.hadoop.hbase.regionserver.HRegionFileSystem; 035import org.apache.hadoop.hbase.regionserver.StoreContext; 036import org.apache.hadoop.hbase.testclassification.RegionServerTests; 037import org.apache.hadoop.hbase.testclassification.SmallTests; 038import org.apache.hadoop.hbase.util.Bytes; 039import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 040import org.junit.jupiter.api.AfterAll; 041import org.junit.jupiter.api.BeforeEach; 042import org.junit.jupiter.api.Tag; 043import org.junit.jupiter.api.Test; 044import org.junit.jupiter.api.TestInfo; 045import org.slf4j.Logger; 046import org.slf4j.LoggerFactory; 047 048import org.apache.hbase.thirdparty.com.google.common.io.ByteStreams; 049 050import org.apache.hadoop.hbase.shaded.protobuf.generated.StoreFileTrackerProtos.StoreFileEntry; 051import org.apache.hadoop.hbase.shaded.protobuf.generated.StoreFileTrackerProtos.StoreFileList; 052 053@Tag(RegionServerTests.TAG) 054@Tag(SmallTests.TAG) 055public class TestStoreFileListFile { 056 057 private static final Logger LOG = LoggerFactory.getLogger(TestStoreFileListFile.class); 058 059 private static final HBaseCommonTestingUtil UTIL = new HBaseCommonTestingUtil(); 060 061 private Path testDir; 062 063 private StoreFileListFile storeFileListFile; 064 065 private String name; 066 067 private StoreFileListFile create() throws IOException { 068 HRegionFileSystem hfs = mock(HRegionFileSystem.class); 069 when(hfs.getFileSystem()).thenReturn(FileSystem.get(UTIL.getConfiguration())); 070 StoreContext ctx = StoreContext.getBuilder().withFamilyStoreDirectoryPath(testDir) 071 .withRegionFileSystem(hfs).build(); 072 return new StoreFileListFile(ctx); 073 } 074 075 @BeforeEach 076 public void setUp(TestInfo testInfo) throws IOException { 077 name = testInfo.getTestMethod().get().getName(); 078 testDir = UTIL.getDataTestDir(name); 079 storeFileListFile = create(); 080 } 081 082 @AfterAll 083 public static void tearDown() { 084 UTIL.cleanupTestDir(); 085 } 086 087 @Test 088 public void testEmptyLoad() throws IOException { 089 assertNull(storeFileListFile.load(false)); 090 } 091 092 private FileStatus getOnlyTrackerFile(FileSystem fs) throws IOException { 093 return fs.listStatus(new Path(testDir, StoreFileListFile.TRACK_FILE_DIR))[0]; 094 } 095 096 private byte[] readAll(FileSystem fs, Path file) throws IOException { 097 try (FSDataInputStream in = fs.open(file)) { 098 return ByteStreams.toByteArray(in); 099 } 100 } 101 102 private void write(FileSystem fs, Path file, byte[] buf, int off, int len) throws IOException { 103 try (FSDataOutputStream out = fs.create(file, true)) { 104 out.write(buf, off, len); 105 } 106 } 107 108 @Test 109 public void testLoadPartial() throws IOException { 110 StoreFileList.Builder builder = StoreFileList.newBuilder(); 111 storeFileListFile.update(builder); 112 FileSystem fs = FileSystem.get(UTIL.getConfiguration()); 113 FileStatus trackerFileStatus = getOnlyTrackerFile(fs); 114 // truncate it so we do not have enough data 115 LOG.info("Truncate file {} with size {} to {}", trackerFileStatus.getPath(), 116 trackerFileStatus.getLen(), trackerFileStatus.getLen() / 2); 117 byte[] content = readAll(fs, trackerFileStatus.getPath()); 118 write(fs, trackerFileStatus.getPath(), content, 0, content.length / 2); 119 assertNull(storeFileListFile.load(false)); 120 } 121 122 private void writeInt(byte[] buf, int off, int value) { 123 byte[] b = Bytes.toBytes(value); 124 for (int i = 0; i < 4; i++) { 125 buf[off + i] = b[i]; 126 } 127 } 128 129 @Test 130 public void testZeroFileLength() throws IOException { 131 StoreFileList.Builder builder = StoreFileList.newBuilder(); 132 storeFileListFile.update(builder); 133 FileSystem fs = FileSystem.get(UTIL.getConfiguration()); 134 FileStatus trackerFileStatus = getOnlyTrackerFile(fs); 135 // write a zero length 136 byte[] content = readAll(fs, trackerFileStatus.getPath()); 137 writeInt(content, 0, 0); 138 write(fs, trackerFileStatus.getPath(), content, 0, content.length); 139 assertThrows(IOException.class, () -> storeFileListFile.load(false)); 140 } 141 142 @Test 143 public void testBigFileLength() throws IOException { 144 StoreFileList.Builder builder = StoreFileList.newBuilder(); 145 storeFileListFile.update(builder); 146 FileSystem fs = FileSystem.get(UTIL.getConfiguration()); 147 FileStatus trackerFileStatus = getOnlyTrackerFile(fs); 148 // write a large length 149 byte[] content = readAll(fs, trackerFileStatus.getPath()); 150 writeInt(content, 0, 128 * 1024 * 1024); 151 write(fs, trackerFileStatus.getPath(), content, 0, content.length); 152 assertThrows(IOException.class, () -> storeFileListFile.load(false)); 153 } 154 155 @Test 156 public void testChecksumMismatch() throws IOException { 157 StoreFileList.Builder builder = StoreFileList.newBuilder(); 158 storeFileListFile.update(builder); 159 FileSystem fs = FileSystem.get(UTIL.getConfiguration()); 160 FileStatus trackerFileStatus = getOnlyTrackerFile(fs); 161 // flip one byte 162 byte[] content = readAll(fs, trackerFileStatus.getPath()); 163 content[5] = (byte) ~content[5]; 164 write(fs, trackerFileStatus.getPath(), content, 0, content.length); 165 assertThrows(IOException.class, () -> storeFileListFile.load(false)); 166 } 167 168 @Test 169 public void testLoadNewerTrackFiles() throws IOException, InterruptedException { 170 StoreFileList.Builder builder = StoreFileList.newBuilder(); 171 storeFileListFile.update(builder); 172 173 FileSystem fs = FileSystem.get(UTIL.getConfiguration()); 174 FileStatus trackFileStatus = getOnlyTrackerFile(fs); 175 176 builder.addStoreFile(StoreFileEntry.newBuilder().setName("hehe").setSize(10).build()); 177 storeFileListFile = create(); 178 storeFileListFile.update(builder); 179 180 // should load the list we stored the second time 181 storeFileListFile = create(); 182 StoreFileList list = storeFileListFile.load(true); 183 assertEquals(1, list.getStoreFileCount()); 184 // since read only is true, we should not delete the old track file 185 // the deletion is in background, so we will test it multiple times through HTU.waitFor and make 186 // sure that it is still there after timeout, i.e, the waitFor method returns -1 187 assertTrue(UTIL.waitFor(2000, 100, false, () -> !fs.exists(testDir)) < 0); 188 189 // this time read only is false, we should delete the old track file 190 list = storeFileListFile.load(false); 191 assertEquals(1, list.getStoreFileCount()); 192 UTIL.waitFor(5000, () -> !fs.exists(trackFileStatus.getPath())); 193 } 194 195 // This is to simulate the scenario where a 'dead' RS perform flush or compaction on a region 196 // which has already been reassigned to another RS. This is possible in real world, usually caused 197 // by a long STW GC. 198 @Test 199 public void testConcurrentUpdate() throws IOException { 200 storeFileListFile.update(StoreFileList.newBuilder()); 201 202 StoreFileListFile storeFileListFile2 = create(); 203 storeFileListFile2.update(StoreFileList.newBuilder() 204 .addStoreFile(StoreFileEntry.newBuilder().setName("hehe").setSize(10).build())); 205 206 // let's update storeFileListFile several times 207 for (int i = 0; i < 10; i++) { 208 storeFileListFile.update(StoreFileList.newBuilder() 209 .addStoreFile(StoreFileEntry.newBuilder().setName("haha-" + i).setSize(100 + i).build())); 210 } 211 212 // create a new list file, make sure we load the list generate by storeFileListFile2. 213 StoreFileListFile storeFileListFile3 = create(); 214 StoreFileList fileList = storeFileListFile3.load(true); 215 assertEquals(1, fileList.getStoreFileCount()); 216 StoreFileEntry entry = fileList.getStoreFile(0); 217 assertEquals("hehe", entry.getName()); 218 assertEquals(10, entry.getSize()); 219 } 220 221 @Test 222 public void testLoadHigherVersion() throws IOException { 223 // write a fake StoreFileList file with higher version 224 StoreFileList storeFileList = 225 StoreFileList.newBuilder().setVersion(StoreFileListFile.VERSION + 1) 226 .setTimestamp(EnvironmentEdgeManager.currentTime()).build(); 227 Path trackFileDir = new Path(testDir, StoreFileListFile.TRACK_FILE_DIR); 228 StoreFileListFile.write(FileSystem.get(UTIL.getConfiguration()), 229 new Path(trackFileDir, StoreFileListFile.TRACK_FILE_PREFIX 230 + StoreFileListFile.TRACK_FILE_SEPARATOR + EnvironmentEdgeManager.currentTime()), 231 storeFileList); 232 IOException error = assertThrows(IOException.class, () -> create().load(false)); 233 assertEquals("Higher store file list version detected, expected " + StoreFileListFile.VERSION 234 + ", got " + (StoreFileListFile.VERSION + 1), error.getMessage()); 235 } 236 237 @Test 238 public void testLoadOldPatternTrackFiles() throws IOException { 239 FileSystem fs = FileSystem.get(UTIL.getConfiguration()); 240 StoreFileList storeFileList = 241 StoreFileList.newBuilder().setTimestamp(EnvironmentEdgeManager.currentTime()) 242 .addStoreFile(StoreFileEntry.newBuilder().setName("hehe").setSize(10).build()).build(); 243 Path trackFileDir = new Path(testDir, StoreFileListFile.TRACK_FILE_DIR); 244 StoreFileListFile.write(fs, new Path(trackFileDir, StoreFileListFile.TRACK_FILE_PREFIX), 245 storeFileList); 246 247 FileStatus trackerFileStatus = getOnlyTrackerFile(fs); 248 assertEquals(StoreFileListFile.TRACK_FILE_PREFIX, trackerFileStatus.getPath().getName()); 249 250 StoreFileList list = storeFileListFile.load(true); 251 assertEquals(1, list.getStoreFileCount()); 252 } 253}