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