| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.hadoop.hbase.regionserver; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertNull; |
| import static org.junit.Assert.assertTrue; |
| |
| import java.io.IOException; |
| import java.net.URI; |
| import java.util.Collection; |
| import java.util.List; |
| import org.apache.hadoop.conf.Configuration; |
| import org.apache.hadoop.fs.FSDataInputStream; |
| import org.apache.hadoop.fs.FSDataOutputStream; |
| import org.apache.hadoop.fs.FileStatus; |
| import org.apache.hadoop.fs.FileSystem; |
| import org.apache.hadoop.fs.Path; |
| import org.apache.hadoop.fs.permission.FsPermission; |
| import org.apache.hadoop.hbase.HBaseClassTestRule; |
| import org.apache.hadoop.hbase.HBaseTestingUtility; |
| import org.apache.hadoop.hbase.TableName; |
| import org.apache.hadoop.hbase.client.Admin; |
| import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; |
| import org.apache.hadoop.hbase.client.Connection; |
| import org.apache.hadoop.hbase.client.Put; |
| import org.apache.hadoop.hbase.client.RegionInfo; |
| import org.apache.hadoop.hbase.client.RegionInfoBuilder; |
| import org.apache.hadoop.hbase.client.Table; |
| import org.apache.hadoop.hbase.fs.HFileSystem; |
| import org.apache.hadoop.hbase.testclassification.LargeTests; |
| import org.apache.hadoop.hbase.testclassification.RegionServerTests; |
| import org.apache.hadoop.hbase.util.Bytes; |
| import org.apache.hadoop.hbase.util.CommonFSUtils; |
| import org.apache.hadoop.hbase.util.FSUtils; |
| import org.apache.hadoop.util.Progressable; |
| import org.junit.ClassRule; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.experimental.categories.Category; |
| import org.junit.rules.TestName; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| @Category({RegionServerTests.class, LargeTests.class}) |
| public class TestHRegionFileSystem { |
| |
| @ClassRule |
| public static final HBaseClassTestRule CLASS_RULE = |
| HBaseClassTestRule.forClass(TestHRegionFileSystem.class); |
| |
| private static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); |
| private static final Logger LOG = LoggerFactory.getLogger(TestHRegionFileSystem.class); |
| |
| public static final byte[] FAMILY_NAME = Bytes.toBytes("info"); |
| private static final byte[][] FAMILIES = { |
| Bytes.add(FAMILY_NAME, Bytes.toBytes("-A")), |
| Bytes.add(FAMILY_NAME, Bytes.toBytes("-B")) }; |
| private static final TableName TABLE_NAME = TableName.valueOf("TestTable"); |
| |
| @Rule |
| public TestName name = new TestName(); |
| |
| @Test |
| public void testBlockStoragePolicy() throws Exception { |
| TEST_UTIL = new HBaseTestingUtility(); |
| Configuration conf = TEST_UTIL.getConfiguration(); |
| TEST_UTIL.startMiniCluster(); |
| Table table = TEST_UTIL.createTable(TABLE_NAME, FAMILIES); |
| assertEquals("Should start with empty table", 0, TEST_UTIL.countRows(table)); |
| HRegionFileSystem regionFs = getHRegionFS(TEST_UTIL.getConnection(), table, conf); |
| // the original block storage policy would be HOT |
| String spA = regionFs.getStoragePolicyName(Bytes.toString(FAMILIES[0])); |
| String spB = regionFs.getStoragePolicyName(Bytes.toString(FAMILIES[1])); |
| LOG.debug("Storage policy of cf 0: [" + spA + "]."); |
| LOG.debug("Storage policy of cf 1: [" + spB + "]."); |
| assertEquals("HOT", spA); |
| assertEquals("HOT", spB); |
| |
| // Recreate table and make sure storage policy could be set through configuration |
| TEST_UTIL.shutdownMiniCluster(); |
| TEST_UTIL.getConfiguration().set(HStore.BLOCK_STORAGE_POLICY_KEY, "WARM"); |
| TEST_UTIL.startMiniCluster(); |
| table = TEST_UTIL.createTable(TABLE_NAME, FAMILIES); |
| regionFs = getHRegionFS(TEST_UTIL.getConnection(), table, conf); |
| |
| try (Admin admin = TEST_UTIL.getConnection().getAdmin()) { |
| spA = regionFs.getStoragePolicyName(Bytes.toString(FAMILIES[0])); |
| spB = regionFs.getStoragePolicyName(Bytes.toString(FAMILIES[1])); |
| LOG.debug("Storage policy of cf 0: [" + spA + "]."); |
| LOG.debug("Storage policy of cf 1: [" + spB + "]."); |
| assertEquals("WARM", spA); |
| assertEquals("WARM", spB); |
| |
| // alter table cf schema to change storage policies |
| // and make sure it could override settings in conf |
| ColumnFamilyDescriptorBuilder cfdA = |
| ColumnFamilyDescriptorBuilder.newBuilder(FAMILIES[0]); |
| // alter through setting HStore#BLOCK_STORAGE_POLICY_KEY in HColumnDescriptor |
| cfdA.setValue(HStore.BLOCK_STORAGE_POLICY_KEY, "ONE_SSD"); |
| admin.modifyColumnFamily(TABLE_NAME, cfdA.build()); |
| while (TEST_UTIL.getMiniHBaseCluster().getMaster().getAssignmentManager(). |
| getRegionStates().hasRegionsInTransition()) { |
| Thread.sleep(200); |
| LOG.debug("Waiting on table to finish schema altering"); |
| } |
| // alter through HColumnDescriptor#setStoragePolicy |
| ColumnFamilyDescriptorBuilder cfdB = |
| ColumnFamilyDescriptorBuilder.newBuilder(FAMILIES[1]); |
| cfdB.setStoragePolicy("ALL_SSD"); |
| admin.modifyColumnFamily(TABLE_NAME, cfdB.build()); |
| while (TEST_UTIL.getMiniHBaseCluster().getMaster().getAssignmentManager().getRegionStates() |
| .hasRegionsInTransition()) { |
| Thread.sleep(200); |
| LOG.debug("Waiting on table to finish schema altering"); |
| } |
| spA = regionFs.getStoragePolicyName(Bytes.toString(FAMILIES[0])); |
| spB = regionFs.getStoragePolicyName(Bytes.toString(FAMILIES[1])); |
| LOG.debug("Storage policy of cf 0: [" + spA + "]."); |
| LOG.debug("Storage policy of cf 1: [" + spB + "]."); |
| assertNotNull(spA); |
| assertEquals("ONE_SSD", spA); |
| assertNotNull(spB); |
| assertEquals("ALL_SSD", spB); |
| |
| // flush memstore snapshot into 3 files |
| for (long i = 0; i < 3; i++) { |
| Put put = new Put(Bytes.toBytes(i)); |
| put.addColumn(FAMILIES[0], Bytes.toBytes(i), Bytes.toBytes(i)); |
| table.put(put); |
| admin.flush(TABLE_NAME); |
| } |
| // there should be 3 files in store dir |
| FileSystem fs = TEST_UTIL.getDFSCluster().getFileSystem(); |
| Path storePath = regionFs.getStoreDir(Bytes.toString(FAMILIES[0])); |
| FileStatus[] storeFiles = CommonFSUtils.listStatus(fs, storePath); |
| assertNotNull(storeFiles); |
| assertEquals(3, storeFiles.length); |
| // store temp dir still exists but empty |
| Path storeTempDir = new Path(regionFs.getTempDir(), Bytes.toString(FAMILIES[0])); |
| assertTrue(fs.exists(storeTempDir)); |
| FileStatus[] tempFiles = CommonFSUtils.listStatus(fs, storeTempDir); |
| assertNull(tempFiles); |
| // storage policy of cf temp dir and 3 store files should be ONE_SSD |
| assertEquals("ONE_SSD", |
| ((HFileSystem) regionFs.getFileSystem()).getStoragePolicyName(storeTempDir)); |
| for (FileStatus status : storeFiles) { |
| assertEquals("ONE_SSD", |
| ((HFileSystem) regionFs.getFileSystem()).getStoragePolicyName(status.getPath())); |
| } |
| |
| // change storage policies by calling raw api directly |
| regionFs.setStoragePolicy(Bytes.toString(FAMILIES[0]), "ALL_SSD"); |
| regionFs.setStoragePolicy(Bytes.toString(FAMILIES[1]), "ONE_SSD"); |
| spA = regionFs.getStoragePolicyName(Bytes.toString(FAMILIES[0])); |
| spB = regionFs.getStoragePolicyName(Bytes.toString(FAMILIES[1])); |
| LOG.debug("Storage policy of cf 0: [" + spA + "]."); |
| LOG.debug("Storage policy of cf 1: [" + spB + "]."); |
| assertNotNull(spA); |
| assertEquals("ALL_SSD", spA); |
| assertNotNull(spB); |
| assertEquals("ONE_SSD", spB); |
| } finally { |
| table.close(); |
| TEST_UTIL.deleteTable(TABLE_NAME); |
| TEST_UTIL.shutdownMiniCluster(); |
| } |
| } |
| |
| private HRegionFileSystem getHRegionFS(Connection conn, Table table, Configuration conf) |
| throws IOException { |
| FileSystem fs = TEST_UTIL.getDFSCluster().getFileSystem(); |
| Path tableDir = CommonFSUtils.getTableDir(TEST_UTIL.getDefaultRootDirPath(), table.getName()); |
| List<Path> regionDirs = FSUtils.getRegionDirs(fs, tableDir); |
| assertEquals(1, regionDirs.size()); |
| List<Path> familyDirs = FSUtils.getFamilyDirs(fs, regionDirs.get(0)); |
| assertEquals(2, familyDirs.size()); |
| RegionInfo hri = |
| conn.getRegionLocator(table.getName()).getAllRegionLocations().get(0).getRegion(); |
| HRegionFileSystem regionFs = new HRegionFileSystem(conf, new HFileSystem(fs), tableDir, hri); |
| return regionFs; |
| } |
| |
| @Test |
| public void testOnDiskRegionCreation() throws IOException { |
| Path rootDir = TEST_UTIL.getDataTestDirOnTestFS(name.getMethodName()); |
| FileSystem fs = TEST_UTIL.getTestFileSystem(); |
| Configuration conf = TEST_UTIL.getConfiguration(); |
| |
| // Create a Region |
| RegionInfo hri = RegionInfoBuilder.newBuilder(TableName.valueOf(name.getMethodName())).build(); |
| HRegionFileSystem regionFs = HRegionFileSystem.createRegionOnFileSystem(conf, fs, |
| CommonFSUtils.getTableDir(rootDir, hri.getTable()), hri); |
| |
| // Verify if the region is on disk |
| Path regionDir = regionFs.getRegionDir(); |
| assertTrue("The region folder should be created", fs.exists(regionDir)); |
| |
| // Verify the .regioninfo |
| RegionInfo hriVerify = HRegionFileSystem.loadRegionInfoFileContent(fs, regionDir); |
| assertEquals(hri, hriVerify); |
| |
| // Open the region |
| regionFs = HRegionFileSystem.openRegionFromFileSystem(conf, fs, |
| CommonFSUtils.getTableDir(rootDir, hri.getTable()), hri, false); |
| assertEquals(regionDir, regionFs.getRegionDir()); |
| |
| // Delete the region |
| HRegionFileSystem.deleteRegionFromFileSystem(conf, fs, |
| CommonFSUtils.getTableDir(rootDir, hri.getTable()), hri); |
| assertFalse("The region folder should be removed", fs.exists(regionDir)); |
| |
| fs.delete(rootDir, true); |
| } |
| |
| @Test |
| public void testNonIdempotentOpsWithRetries() throws IOException { |
| Path rootDir = TEST_UTIL.getDataTestDirOnTestFS(name.getMethodName()); |
| FileSystem fs = TEST_UTIL.getTestFileSystem(); |
| Configuration conf = TEST_UTIL.getConfiguration(); |
| |
| // Create a Region |
| RegionInfo hri = RegionInfoBuilder.newBuilder(TableName.valueOf(name.getMethodName())).build(); |
| HRegionFileSystem regionFs = HRegionFileSystem.createRegionOnFileSystem(conf, fs, rootDir, hri); |
| assertTrue(fs.exists(regionFs.getRegionDir())); |
| |
| regionFs = new HRegionFileSystem(conf, new MockFileSystemForCreate(), rootDir, hri); |
| boolean result = regionFs.createDir(new Path("/foo/bar")); |
| assertTrue("Couldn't create the directory", result); |
| |
| regionFs = new HRegionFileSystem(conf, new MockFileSystem(), rootDir, hri); |
| result = regionFs.rename(new Path("/foo/bar"), new Path("/foo/bar2")); |
| assertTrue("Couldn't rename the directory", result); |
| |
| regionFs = new HRegionFileSystem(conf, new MockFileSystem(), rootDir, hri); |
| result = regionFs.deleteDir(new Path("/foo/bar")); |
| assertTrue("Couldn't delete the directory", result); |
| fs.delete(rootDir, true); |
| } |
| |
| static class MockFileSystemForCreate extends MockFileSystem { |
| @Override |
| public boolean exists(Path path) { |
| return false; |
| } |
| } |
| |
| /** |
| * a mock fs which throws exception for first 3 times, and then process the call (returns the |
| * excepted result). |
| */ |
| static class MockFileSystem extends FileSystem { |
| int retryCount; |
| final static int successRetryCount = 3; |
| |
| public MockFileSystem() { |
| retryCount = 0; |
| } |
| |
| @Override |
| public FSDataOutputStream append(Path arg0, int arg1, Progressable arg2) throws IOException { |
| throw new IOException(""); |
| } |
| |
| @Override |
| public FSDataOutputStream create(Path arg0, FsPermission arg1, boolean arg2, int arg3, |
| short arg4, long arg5, Progressable arg6) throws IOException { |
| LOG.debug("Create, " + retryCount); |
| if (retryCount++ < successRetryCount) throw new IOException("Something bad happen"); |
| return null; |
| } |
| |
| @Override |
| public boolean delete(Path arg0) throws IOException { |
| if (retryCount++ < successRetryCount) throw new IOException("Something bad happen"); |
| return true; |
| } |
| |
| @Override |
| public boolean delete(Path arg0, boolean arg1) throws IOException { |
| if (retryCount++ < successRetryCount) throw new IOException("Something bad happen"); |
| return true; |
| } |
| |
| @Override |
| public FileStatus getFileStatus(Path arg0) throws IOException { |
| FileStatus fs = new FileStatus(); |
| return fs; |
| } |
| |
| @Override |
| public boolean exists(Path path) { |
| return true; |
| } |
| |
| @Override |
| public URI getUri() { |
| throw new RuntimeException("Something bad happen"); |
| } |
| |
| @Override |
| public Path getWorkingDirectory() { |
| throw new RuntimeException("Something bad happen"); |
| } |
| |
| @Override |
| public FileStatus[] listStatus(Path arg0) throws IOException { |
| throw new IOException("Something bad happen"); |
| } |
| |
| @Override |
| public boolean mkdirs(Path arg0, FsPermission arg1) throws IOException { |
| LOG.debug("mkdirs, " + retryCount); |
| if (retryCount++ < successRetryCount) throw new IOException("Something bad happen"); |
| return true; |
| } |
| |
| @Override |
| public FSDataInputStream open(Path arg0, int arg1) throws IOException { |
| throw new IOException("Something bad happen"); |
| } |
| |
| @Override |
| public boolean rename(Path arg0, Path arg1) throws IOException { |
| LOG.debug("rename, " + retryCount); |
| if (retryCount++ < successRetryCount) throw new IOException("Something bad happen"); |
| return true; |
| } |
| |
| @Override |
| public void setWorkingDirectory(Path arg0) { |
| throw new RuntimeException("Something bad happen"); |
| } |
| } |
| |
| @Test |
| public void testTempAndCommit() throws IOException { |
| Path rootDir = TEST_UTIL.getDataTestDirOnTestFS("testTempAndCommit"); |
| FileSystem fs = TEST_UTIL.getTestFileSystem(); |
| Configuration conf = TEST_UTIL.getConfiguration(); |
| |
| // Create a Region |
| String familyName = "cf"; |
| |
| RegionInfo hri = RegionInfoBuilder.newBuilder(TableName.valueOf(name.getMethodName())).build(); |
| HRegionFileSystem regionFs = HRegionFileSystem.createRegionOnFileSystem(conf, fs, rootDir, hri); |
| |
| // New region, no store files |
| Collection<StoreFileInfo> storeFiles = regionFs.getStoreFiles(familyName); |
| assertEquals(0, storeFiles != null ? storeFiles.size() : 0); |
| |
| // Create a new file in temp (no files in the family) |
| Path buildPath = regionFs.createTempName(); |
| fs.createNewFile(buildPath); |
| storeFiles = regionFs.getStoreFiles(familyName); |
| assertEquals(0, storeFiles != null ? storeFiles.size() : 0); |
| |
| // commit the file |
| Path dstPath = regionFs.commitStoreFile(familyName, buildPath); |
| storeFiles = regionFs.getStoreFiles(familyName); |
| assertEquals(0, storeFiles != null ? storeFiles.size() : 0); |
| assertFalse(fs.exists(buildPath)); |
| |
| fs.delete(rootDir, true); |
| } |
| } |