From 6f3772e8da6f170d3459134c6701a20c6969b8fc 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 --- .../master/procedure/CreateTableProcedure.java | 39 +++++++++++++++++++++- .../master/procedure/TestCreateTableProcedure.java | 29 ++++++++++++++++ hbase-shell/src/main/ruby/shell/commands.rb | 7 +++- 3 files changed, 73 insertions(+), 2 deletions(-) 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..3dfa619 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; @@ -48,6 +53,7 @@ import org.apache.hadoop.hbase.util.FSTableDescriptors; import org.apache.hadoop.hbase.util.FSUtils; import org.apache.hadoop.hbase.util.ModifyRegionUtils; import org.apache.hadoop.hbase.util.ServerRegionReplicaUtil; +import org.apache.hadoop.util.Shell; import com.google.common.collect.Lists; @@ -237,7 +243,38 @@ public class CreateTableProcedure return false; } - return true; + return !tableExistOnCaseInsensitiveFs(env); + } + + private boolean tableExistOnCaseInsensitiveFs(MasterProcedureEnv env) + throws IOException { + + FileSystem masterFileSystem = env.getMasterServices().getMasterFileSystem().getFileSystem(); + if (!(masterFileSystem instanceof LocalFileSystem && Shell.MAC)) { + return false; + } + final MutableBoolean tableExistIgnoreCase = new MutableBoolean(false); + String tableNameString = 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 && tableNameString.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..e6f67c8 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,10 @@ 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.HRegionInfo; import org.apache.hadoop.hbase.HTableDescriptor; import org.apache.hadoop.hbase.ProcedureInfo; @@ -30,11 +33,14 @@ 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.apache.hadoop.util.Shell; 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,29 @@ public class TestCreateTableProcedure extends TestTableDDLProcedureBase { } @Test(timeout=60000) + public void testCreateExistingIgnoreCase() throws Exception { + if (!Shell.MAC) { + return; + } + 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.startMiniCluster(1); + UTIL.createTable(TableName.valueOf("testCaseInsensitive"), "f"); + UTIL.createTable(TableName.valueOf("testCaseInsensiTIVE"), "f"); + fail("Should throw TableExistsException."); + } catch (TableExistsException e) { + assertEquals(e.getMessage(), "testCaseInsensiTIVE (ignore case) already exists."); + } 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