From 9bfd845c3c519609e7d9fdeae2042ecf0d931942 Mon Sep 17 00:00:00 2001 From: Xuesen Liang Date: Sat, 7 Jan 2017 14:09:33 +0800 Subject: [PATCH] HBASE-13652 Case-insensitivity of file system affects table creation --- .../java/org/apache/hadoop/hbase/HConstants.java | 6 ++++ .../hadoop/hbase/master/MasterFileSystem.java | 5 +++ .../master/procedure/CreateTableProcedure.java | 38 +++++++++++++++++++++- .../master/procedure/TestCreateTableProcedure.java | 27 +++++++++++++++ hbase-shell/src/main/ruby/shell/commands.rb | 7 +++- 5 files changed, 81 insertions(+), 2 deletions(-) diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java index 1eec691..9795040 100644 --- a/hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java +++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java @@ -184,6 +184,12 @@ public final class HConstants { */ public static final boolean DEFAULT_MASTER_TYPE_BACKUP = false; + /** Parameter name for whether master file system is case-insensitive */ + public static final String MASTER_FS_CASE_INSENSITIVE = "hbase.master.fs.case-insensitive"; + + /** by default master file system is case-sensitive */ + public static final boolean DEFAULT_MASTER_FS_CASE_INSENSITIVE = false; + /** Name of ZooKeeper quorum configuration parameter. */ public static final String ZOOKEEPER_QUORUM = "hbase.zookeeper.quorum"; diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterFileSystem.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterFileSystem.java index a8f81ee..213be51 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterFileSystem.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterFileSystem.java @@ -190,6 +190,11 @@ public class MasterFileSystem { return clusterId; } + public boolean isCaseInsensitive() { + return conf.getBoolean(HConstants.MASTER_FS_CASE_INSENSITIVE, + HConstants.DEFAULT_MASTER_FS_CASE_INSENSITIVE); + } + /** * Get the rootdir. Make sure its wholesome and exists before returning. * @param rd diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/CreateTableProcedure.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/CreateTableProcedure.java index 0d24f51..3920054 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/CreateTableProcedure.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/CreateTableProcedure.java @@ -24,18 +24,23 @@ import java.io.OutputStream; import java.util.ArrayList; import java.util.List; +import org.apache.commons.lang.mutable.MutableBoolean; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.LocalFileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.hbase.DoNotRetryIOException; import org.apache.hadoop.hbase.HRegionInfo; import org.apache.hadoop.hbase.HTableDescriptor; import org.apache.hadoop.hbase.MetaTableAccessor; +import org.apache.hadoop.hbase.MetaTableAccessor.QueryType; +import org.apache.hadoop.hbase.MetaTableAccessor.Visitor; import org.apache.hadoop.hbase.TableExistsException; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.client.RegionReplicaUtil; +import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.client.TableState; import org.apache.hadoop.hbase.master.AssignmentManager; import org.apache.hadoop.hbase.master.MasterCoprocessorHost; @@ -237,7 +242,38 @@ public class CreateTableProcedure return false; } - return true; + return !tableExistOnCaseInsensitiveFs(env); + } + + private boolean tableExistOnCaseInsensitiveFs(MasterProcedureEnv env) throws IOException { + MasterFileSystem masterFileSystem = env.getMasterServices().getMasterFileSystem(); + if (!(masterFileSystem.getFileSystem() instanceof LocalFileSystem + && masterFileSystem.isCaseInsensitive())) { + return false; + } + + final MutableBoolean tableExistIgnoreCase = new MutableBoolean(false); + String tableNameStr = getTableName().getNameAsString(); + MetaTableAccessor.scanMeta( + env.getMasterServices().getConnection(), null, null, QueryType.ALL, new Visitor() { + @Override + public boolean visit(Result rowResult) { + HRegionInfo info = MetaTableAccessor.getHRegionInfo(rowResult); + if (info != null && tableNameStr.equalsIgnoreCase(info.getTable().getNameAsString())) { + tableExistIgnoreCase.setValue(true);; + return false; + } + return true; + } + }); + + if (tableExistIgnoreCase.isTrue()) { + setFailure("master-create-table", + new TableExistsException("Table already exists: " + getTableName() + " (ignore case).")); + return true; + } + + return false; } private void preCreate(final MasterProcedureEnv env) diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/procedure/TestCreateTableProcedure.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/procedure/TestCreateTableProcedure.java index 5c3d913..3235bbb 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/procedure/TestCreateTableProcedure.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/procedure/TestCreateTableProcedure.java @@ -18,7 +18,11 @@ package org.apache.hadoop.hbase.master.procedure; +import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.DoNotRetryIOException; +import org.apache.hadoop.hbase.HBaseConfiguration; +import org.apache.hadoop.hbase.HBaseTestingUtility; +import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.HRegionInfo; import org.apache.hadoop.hbase.HTableDescriptor; import org.apache.hadoop.hbase.ProcedureInfo; @@ -30,11 +34,13 @@ import org.apache.hadoop.hbase.testclassification.MasterTests; import org.apache.hadoop.hbase.testclassification.MediumTests; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.ModifyRegionUtils; +import org.apache.hadoop.hdfs.MiniDFSCluster; import org.junit.Test; import org.junit.experimental.categories.Category; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; @Category({MasterTests.class, MediumTests.class}) public class TestCreateTableProcedure extends TestTableDDLProcedureBase { @@ -107,6 +113,27 @@ public class TestCreateTableProcedure extends TestTableDDLProcedureBase { } @Test(timeout=60000) + public void testCreateExistingIgnoreCase() throws Exception { + HBaseTestingUtility UTIL = new HBaseTestingUtility(); + try { + Configuration dfsConf = HBaseConfiguration.create(); + dfsConf.set("fs.defaultFS", "file:///"); + MiniDFSCluster.Builder dfsClusterBuilder = new MiniDFSCluster.Builder(dfsConf); + UTIL.setDFSCluster(dfsClusterBuilder.build()); + UTIL.getConfiguration().set("fs.defaultFS", "file:///"); + UTIL.getConfiguration().setBoolean(HConstants.MASTER_FS_CASE_INSENSITIVE, true); + UTIL.startMiniCluster(1); + UTIL.createTable(TableName.valueOf("testCaseInsensitive"), "f"); + UTIL.createTable(TableName.valueOf("testCaseInsensiTIVE"), "f"); + fail("Should throw TableExistsException."); + } catch (TableExistsException e) { + assertEquals(e.getMessage(), "Table already exists: testCaseInsensiTIVE (ignore case)."); + } finally { + UTIL.shutdownMiniCluster(); + } + } + + @Test(timeout=60000) public void testRecoveryAndDoubleExecution() throws Exception { final TableName tableName = TableName.valueOf("testRecoveryAndDoubleExecution"); diff --git a/hbase-shell/src/main/ruby/shell/commands.rb b/hbase-shell/src/main/ruby/shell/commands.rb index 98fcf60..90c0f8f 100644 --- a/hbase-shell/src/main/ruby/shell/commands.rb +++ b/hbase-shell/src/main/ruby/shell/commands.rb @@ -134,7 +134,12 @@ module Shell end end if cause.kind_of?(org.apache.hadoop.hbase.TableExistsException) then - raise "Table already exists: #{args.first}!" + message = cause.getMessage + if message.include? "ignore case" then + raise "Table already exists: #{args.first}! (on case-insensitive filesystem)" + else + raise "Table already exists: #{args.first}!" + end end # To be safe, here only AccessDeniedException is considered. In future # we might support more in more generic approach when possible. -- 2.10.0