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; 019 020import static org.junit.jupiter.api.Assertions.assertEquals; 021import static org.junit.jupiter.api.Assertions.assertFalse; 022import static org.junit.jupiter.api.Assertions.assertNotNull; 023import static org.junit.jupiter.api.Assertions.assertNull; 024import static org.junit.jupiter.api.Assertions.assertTrue; 025 026import java.io.IOException; 027import java.net.URI; 028import java.util.List; 029import org.apache.hadoop.conf.Configuration; 030import org.apache.hadoop.fs.FSDataInputStream; 031import org.apache.hadoop.fs.FSDataOutputStream; 032import org.apache.hadoop.fs.FileStatus; 033import org.apache.hadoop.fs.FileSystem; 034import org.apache.hadoop.fs.Path; 035import org.apache.hadoop.fs.permission.FsPermission; 036import org.apache.hadoop.hbase.HBaseTestingUtil; 037import org.apache.hadoop.hbase.TableName; 038import org.apache.hadoop.hbase.client.Admin; 039import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 040import org.apache.hadoop.hbase.client.Connection; 041import org.apache.hadoop.hbase.client.Put; 042import org.apache.hadoop.hbase.client.RegionInfo; 043import org.apache.hadoop.hbase.client.RegionInfoBuilder; 044import org.apache.hadoop.hbase.client.Table; 045import org.apache.hadoop.hbase.fs.HFileSystem; 046import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTracker; 047import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory; 048import org.apache.hadoop.hbase.testclassification.LargeTests; 049import org.apache.hadoop.hbase.testclassification.RegionServerTests; 050import org.apache.hadoop.hbase.util.Bytes; 051import org.apache.hadoop.hbase.util.CommonFSUtils; 052import org.apache.hadoop.hbase.util.FSUtils; 053import org.apache.hadoop.util.Progressable; 054import org.junit.jupiter.api.BeforeEach; 055import org.junit.jupiter.api.Tag; 056import org.junit.jupiter.api.Test; 057import org.junit.jupiter.api.TestInfo; 058import org.slf4j.Logger; 059import org.slf4j.LoggerFactory; 060 061@Tag(RegionServerTests.TAG) 062@Tag(LargeTests.TAG) 063public class TestHRegionFileSystem { 064 065 private static HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil(); 066 private static final Logger LOG = LoggerFactory.getLogger(TestHRegionFileSystem.class); 067 068 public static final byte[] FAMILY_NAME = Bytes.toBytes("info"); 069 private static final byte[][] FAMILIES = 070 { Bytes.add(FAMILY_NAME, Bytes.toBytes("-A")), Bytes.add(FAMILY_NAME, Bytes.toBytes("-B")) }; 071 private static final TableName TABLE_NAME = TableName.valueOf("TestTable"); 072 private String name; 073 074 @BeforeEach 075 public void setTestName(TestInfo testInfo) { 076 this.name = testInfo.getTestMethod().get().getName(); 077 } 078 079 @Test 080 public void testBlockStoragePolicy() throws Exception { 081 TEST_UTIL = new HBaseTestingUtil(); 082 Configuration conf = TEST_UTIL.getConfiguration(); 083 TEST_UTIL.startMiniCluster(); 084 Table table = TEST_UTIL.createTable(TABLE_NAME, FAMILIES); 085 assertEquals(0, TEST_UTIL.countRows(table), "Should start with empty table"); 086 HRegionFileSystem regionFs = getHRegionFS(TEST_UTIL.getConnection(), table, conf); 087 // the original block storage policy would be HOT 088 String spA = regionFs.getStoragePolicyName(Bytes.toString(FAMILIES[0])); 089 String spB = regionFs.getStoragePolicyName(Bytes.toString(FAMILIES[1])); 090 LOG.debug("Storage policy of cf 0: [" + spA + "]."); 091 LOG.debug("Storage policy of cf 1: [" + spB + "]."); 092 assertEquals("HOT", spA); 093 assertEquals("HOT", spB); 094 095 // Recreate table and make sure storage policy could be set through configuration 096 TEST_UTIL.shutdownMiniCluster(); 097 TEST_UTIL.getConfiguration().set(HStore.BLOCK_STORAGE_POLICY_KEY, "WARM"); 098 TEST_UTIL.startMiniCluster(); 099 table = TEST_UTIL.createTable(TABLE_NAME, FAMILIES); 100 regionFs = getHRegionFS(TEST_UTIL.getConnection(), table, conf); 101 102 try (Admin admin = TEST_UTIL.getConnection().getAdmin()) { 103 spA = regionFs.getStoragePolicyName(Bytes.toString(FAMILIES[0])); 104 spB = regionFs.getStoragePolicyName(Bytes.toString(FAMILIES[1])); 105 LOG.debug("Storage policy of cf 0: [" + spA + "]."); 106 LOG.debug("Storage policy of cf 1: [" + spB + "]."); 107 assertEquals("WARM", spA); 108 assertEquals("WARM", spB); 109 110 // alter table cf schema to change storage policies 111 // and make sure it could override settings in conf 112 ColumnFamilyDescriptorBuilder cfdA = ColumnFamilyDescriptorBuilder.newBuilder(FAMILIES[0]); 113 // alter through setting HStore#BLOCK_STORAGE_POLICY_KEY in HColumnDescriptor 114 cfdA.setValue(HStore.BLOCK_STORAGE_POLICY_KEY, "ONE_SSD"); 115 admin.modifyColumnFamily(TABLE_NAME, cfdA.build()); 116 while ( 117 TEST_UTIL.getMiniHBaseCluster().getMaster().getAssignmentManager().hasRegionsInTransition() 118 ) { 119 Thread.sleep(200); 120 LOG.debug("Waiting on table to finish schema altering"); 121 } 122 // alter through HColumnDescriptor#setStoragePolicy 123 ColumnFamilyDescriptorBuilder cfdB = ColumnFamilyDescriptorBuilder.newBuilder(FAMILIES[1]); 124 cfdB.setStoragePolicy("ALL_SSD"); 125 admin.modifyColumnFamily(TABLE_NAME, cfdB.build()); 126 while ( 127 TEST_UTIL.getMiniHBaseCluster().getMaster().getAssignmentManager().hasRegionsInTransition() 128 ) { 129 Thread.sleep(200); 130 LOG.debug("Waiting on table to finish schema altering"); 131 } 132 spA = regionFs.getStoragePolicyName(Bytes.toString(FAMILIES[0])); 133 spB = regionFs.getStoragePolicyName(Bytes.toString(FAMILIES[1])); 134 LOG.debug("Storage policy of cf 0: [" + spA + "]."); 135 LOG.debug("Storage policy of cf 1: [" + spB + "]."); 136 assertNotNull(spA); 137 assertEquals("ONE_SSD", spA); 138 assertNotNull(spB); 139 assertEquals("ALL_SSD", spB); 140 141 // flush memstore snapshot into 3 files 142 for (long i = 0; i < 3; i++) { 143 Put put = new Put(Bytes.toBytes(i)); 144 put.addColumn(FAMILIES[0], Bytes.toBytes(i), Bytes.toBytes(i)); 145 table.put(put); 146 admin.flush(TABLE_NAME); 147 } 148 // there should be 3 files in store dir 149 FileSystem fs = TEST_UTIL.getDFSCluster().getFileSystem(); 150 Path storePath = regionFs.getStoreDir(Bytes.toString(FAMILIES[0])); 151 FileStatus[] storeFiles = CommonFSUtils.listStatus(fs, storePath); 152 assertNotNull(storeFiles); 153 assertEquals(3, storeFiles.length); 154 // store temp dir still exists but empty 155 Path storeTempDir = new Path(regionFs.getTempDir(), Bytes.toString(FAMILIES[0])); 156 assertTrue(fs.exists(storeTempDir)); 157 FileStatus[] tempFiles = CommonFSUtils.listStatus(fs, storeTempDir); 158 assertNull(tempFiles); 159 // storage policy of cf temp dir and 3 store files should be ONE_SSD 160 assertEquals("ONE_SSD", 161 ((HFileSystem) regionFs.getFileSystem()).getStoragePolicyName(storeTempDir)); 162 for (FileStatus status : storeFiles) { 163 assertEquals("ONE_SSD", 164 ((HFileSystem) regionFs.getFileSystem()).getStoragePolicyName(status.getPath())); 165 } 166 167 // change storage policies by calling raw api directly 168 regionFs.setStoragePolicy(Bytes.toString(FAMILIES[0]), "ALL_SSD"); 169 regionFs.setStoragePolicy(Bytes.toString(FAMILIES[1]), "ONE_SSD"); 170 spA = regionFs.getStoragePolicyName(Bytes.toString(FAMILIES[0])); 171 spB = regionFs.getStoragePolicyName(Bytes.toString(FAMILIES[1])); 172 LOG.debug("Storage policy of cf 0: [" + spA + "]."); 173 LOG.debug("Storage policy of cf 1: [" + spB + "]."); 174 assertNotNull(spA); 175 assertEquals("ALL_SSD", spA); 176 assertNotNull(spB); 177 assertEquals("ONE_SSD", spB); 178 } finally { 179 table.close(); 180 TEST_UTIL.deleteTable(TABLE_NAME); 181 TEST_UTIL.shutdownMiniCluster(); 182 } 183 } 184 185 private HRegionFileSystem getHRegionFS(Connection conn, Table table, Configuration conf) 186 throws IOException { 187 FileSystem fs = TEST_UTIL.getDFSCluster().getFileSystem(); 188 Path tableDir = CommonFSUtils.getTableDir(TEST_UTIL.getDefaultRootDirPath(), table.getName()); 189 List<Path> regionDirs = FSUtils.getRegionDirs(fs, tableDir); 190 assertEquals(1, regionDirs.size()); 191 List<Path> familyDirs = FSUtils.getFamilyDirs(fs, regionDirs.get(0)); 192 assertEquals(2, familyDirs.size()); 193 RegionInfo hri = 194 conn.getRegionLocator(table.getName()).getAllRegionLocations().get(0).getRegion(); 195 HRegionFileSystem regionFs = new HRegionFileSystem(conf, new HFileSystem(fs), tableDir, hri); 196 return regionFs; 197 } 198 199 @Test 200 public void testOnDiskRegionCreation() throws IOException { 201 Path rootDir = TEST_UTIL.getDataTestDirOnTestFS(name); 202 FileSystem fs = TEST_UTIL.getTestFileSystem(); 203 Configuration conf = TEST_UTIL.getConfiguration(); 204 205 // Create a Region 206 RegionInfo hri = RegionInfoBuilder.newBuilder(TableName.valueOf(name)).build(); 207 HRegionFileSystem regionFs = HRegionFileSystem.createRegionOnFileSystem(conf, fs, 208 CommonFSUtils.getTableDir(rootDir, hri.getTable()), hri); 209 210 // Verify if the region is on disk 211 Path regionDir = regionFs.getRegionDir(); 212 assertTrue(fs.exists(regionDir), "The region folder should be created"); 213 214 // Verify the .regioninfo 215 RegionInfo hriVerify = HRegionFileSystem.loadRegionInfoFileContent(fs, regionDir); 216 assertEquals(hri, hriVerify); 217 218 // Open the region 219 regionFs = HRegionFileSystem.openRegionFromFileSystem(conf, fs, 220 CommonFSUtils.getTableDir(rootDir, hri.getTable()), hri, false); 221 assertEquals(regionDir, regionFs.getRegionDir()); 222 223 // Delete the region 224 HRegionFileSystem.deleteRegionFromFileSystem(conf, fs, 225 CommonFSUtils.getTableDir(rootDir, hri.getTable()), hri); 226 assertFalse(fs.exists(regionDir), "The region folder should be removed"); 227 228 fs.delete(rootDir, true); 229 } 230 231 @Test 232 public void testNonIdempotentOpsWithRetries() throws IOException { 233 Path rootDir = TEST_UTIL.getDataTestDirOnTestFS(name); 234 FileSystem fs = TEST_UTIL.getTestFileSystem(); 235 Configuration conf = TEST_UTIL.getConfiguration(); 236 237 // Create a Region 238 RegionInfo hri = RegionInfoBuilder.newBuilder(TableName.valueOf(name)).build(); 239 HRegionFileSystem regionFs = HRegionFileSystem.createRegionOnFileSystem(conf, fs, rootDir, hri); 240 assertTrue(fs.exists(regionFs.getRegionDir())); 241 242 regionFs = new HRegionFileSystem(conf, new MockFileSystemForCreate(), rootDir, hri); 243 boolean result = regionFs.createDir(new Path("/foo/bar")); 244 assertTrue(result, "Couldn't create the directory"); 245 246 regionFs = new HRegionFileSystem(conf, new MockFileSystem(), rootDir, hri); 247 result = regionFs.rename(new Path("/foo/bar"), new Path("/foo/bar2")); 248 assertTrue(result, "Couldn't rename the directory"); 249 250 regionFs = new HRegionFileSystem(conf, new MockFileSystem(), rootDir, hri); 251 result = regionFs.deleteDir(new Path("/foo/bar")); 252 assertTrue(result, "Couldn't delete the directory"); 253 fs.delete(rootDir, true); 254 } 255 256 static class MockFileSystemForCreate extends MockFileSystem { 257 @Override 258 public boolean exists(Path path) { 259 return false; 260 } 261 } 262 263 /** 264 * a mock fs which throws exception for first 3 times, and then process the call (returns the 265 * excepted result). 266 */ 267 static class MockFileSystem extends FileSystem { 268 int retryCount; 269 final static int successRetryCount = 3; 270 271 public MockFileSystem() { 272 retryCount = 0; 273 } 274 275 @Override 276 public FSDataOutputStream append(Path arg0, int arg1, Progressable arg2) throws IOException { 277 throw new IOException(""); 278 } 279 280 @Override 281 public FSDataOutputStream create(Path arg0, FsPermission arg1, boolean arg2, int arg3, 282 short arg4, long arg5, Progressable arg6) throws IOException { 283 LOG.debug("Create, " + retryCount); 284 if (retryCount++ < successRetryCount) throw new IOException("Something bad happen"); 285 return null; 286 } 287 288 @Override 289 public boolean delete(Path arg0) throws IOException { 290 if (retryCount++ < successRetryCount) throw new IOException("Something bad happen"); 291 return true; 292 } 293 294 @Override 295 public boolean delete(Path arg0, boolean arg1) throws IOException { 296 if (retryCount++ < successRetryCount) throw new IOException("Something bad happen"); 297 return true; 298 } 299 300 @Override 301 public FileStatus getFileStatus(Path arg0) throws IOException { 302 FileStatus fs = new FileStatus(); 303 return fs; 304 } 305 306 @Override 307 public boolean exists(Path path) { 308 return true; 309 } 310 311 @Override 312 public URI getUri() { 313 throw new RuntimeException("Something bad happen"); 314 } 315 316 @Override 317 public Path getWorkingDirectory() { 318 throw new RuntimeException("Something bad happen"); 319 } 320 321 @Override 322 public FileStatus[] listStatus(Path arg0) throws IOException { 323 throw new IOException("Something bad happen"); 324 } 325 326 @Override 327 public boolean mkdirs(Path arg0, FsPermission arg1) throws IOException { 328 LOG.debug("mkdirs, " + retryCount); 329 if (retryCount++ < successRetryCount) throw new IOException("Something bad happen"); 330 return true; 331 } 332 333 @Override 334 public FSDataInputStream open(Path arg0, int arg1) throws IOException { 335 throw new IOException("Something bad happen"); 336 } 337 338 @Override 339 public boolean rename(Path arg0, Path arg1) throws IOException { 340 LOG.debug("rename, " + retryCount); 341 if (retryCount++ < successRetryCount) throw new IOException("Something bad happen"); 342 return true; 343 } 344 345 @Override 346 public void setWorkingDirectory(Path arg0) { 347 throw new RuntimeException("Something bad happen"); 348 } 349 } 350 351 @Test 352 public void testTempAndCommit() throws IOException { 353 Path rootDir = TEST_UTIL.getDataTestDirOnTestFS("testTempAndCommit"); 354 FileSystem fs = TEST_UTIL.getTestFileSystem(); 355 Configuration conf = TEST_UTIL.getConfiguration(); 356 357 // Create a Region 358 String familyName = "cf"; 359 360 RegionInfo hri = RegionInfoBuilder.newBuilder(TableName.valueOf(name)).build(); 361 HRegionFileSystem regionFs = HRegionFileSystem.createRegionOnFileSystem(conf, fs, rootDir, hri); 362 StoreContext storeContext = StoreContext.getBuilder() 363 .withColumnFamilyDescriptor(ColumnFamilyDescriptorBuilder.of(familyName)) 364 .withFamilyStoreDirectoryPath( 365 new Path(regionFs.getTableDir(), new Path(hri.getRegionNameAsString(), familyName))) 366 .withRegionFileSystem(regionFs).build(); 367 StoreFileTracker sft = StoreFileTrackerFactory.create(conf, false, storeContext); 368 // New region, no store files 369 List<StoreFileInfo> storeFiles = sft.load(); 370 assertEquals(0, storeFiles != null ? storeFiles.size() : 0); 371 372 // Create a new file in temp (no files in the family) 373 Path buildPath = regionFs.createTempName(); 374 fs.createNewFile(buildPath); 375 storeFiles = sft.load(); 376 assertEquals(0, storeFiles != null ? storeFiles.size() : 0); 377 378 // commit the file 379 Path dstPath = regionFs.commitStoreFile(familyName, buildPath); 380 storeFiles = sft.load(); 381 assertEquals(0, storeFiles != null ? storeFiles.size() : 0); 382 assertFalse(fs.exists(buildPath)); 383 384 fs.delete(rootDir, true); 385 } 386}