From 58b943a8421037d839a70a0e490abd52e79910ff Mon Sep 17 00:00:00 2001 From: tedyu Date: Thu, 5 Feb 2015 13:05:09 -0800 Subject: [PATCH] HBASE-12966 NPE in HMaster while recovering tables in Enabling state (Andrey Stepachev) --- .../master/handler/EnableTableHandler.java | 35 +++--- .../hadoop/hbase/HBaseTestingUtility.java | 41 +++++++ .../handler/TestEnableTableHandler.java | 101 ++++++++++++++++++ 3 files changed, 163 insertions(+), 14 deletions(-) create mode 100644 hbase-server/src/test/java/org/apache/hadoop/hbase/master/handler/TestEnableTableHandler.java diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/handler/EnableTableHandler.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/handler/EnableTableHandler.java index feb5b64f551..243ec2d8143 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/handler/EnableTableHandler.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/handler/EnableTableHandler.java @@ -26,15 +26,15 @@ import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.CoordinatedStateException; -import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.HRegionInfo; +import org.apache.hadoop.hbase.MetaTableAccessor; import org.apache.hadoop.hbase.Server; import org.apache.hadoop.hbase.ServerName; +import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.TableNotDisabledException; import org.apache.hadoop.hbase.TableNotFoundException; -import org.apache.hadoop.hbase.MetaTableAccessor; +import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.executor.EventHandler; import org.apache.hadoop.hbase.executor.EventType; import org.apache.hadoop.hbase.master.AssignmentManager; @@ -226,19 +226,26 @@ public class EnableTableHandler extends EventHandler { List onlineServers = serverManager.createDestinationServersList(); Map> bulkPlan = this.assignmentManager.getBalancer().retainAssignment(regionsToAssign, onlineServers); - LOG.info("Bulk assigning " + regionsCount + " region(s) across " + bulkPlan.size() - + " server(s), retainAssignment=true"); + if (bulkPlan != null) { + LOG.info("Bulk assigning " + regionsCount + " region(s) across " + bulkPlan.size() + + " server(s), retainAssignment=true"); - BulkAssigner ba = new GeneralBulkAssigner(this.server, bulkPlan, this.assignmentManager, true); - try { - if (ba.bulkAssign()) { - done = true; + BulkAssigner ba = + new GeneralBulkAssigner(this.server, bulkPlan, this.assignmentManager, true); + try { + if (ba.bulkAssign()) { + done = true; + } + } catch (InterruptedException e) { + LOG.warn("Enable operation was interrupted when enabling table '" + + this.tableName + "'"); + // Preserve the interrupt. + Thread.currentThread().interrupt(); } - } catch (InterruptedException e) { - LOG.warn("Enable operation was interrupted when enabling table '" - + this.tableName + "'"); - // Preserve the interrupt. - Thread.currentThread().interrupt(); + } else { + done = true; + LOG.info("Balancer was unable to find suitable servers for table " + tableName + + ", leaving unassigned"); } if (done) { // Flip the table to enabled. diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/HBaseTestingUtility.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/HBaseTestingUtility.java index 483130069e7..b6e7df99114 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/HBaseTestingUtility.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/HBaseTestingUtility.java @@ -2864,6 +2864,47 @@ public class HBaseTestingUtility extends HBaseCommonTestingUtility { } } + /** + * Waits for a table to be 'disabled'. Disabled means that table is set as 'disabled' + * Will timeout after default period (30 seconds) + * @param table Table to wait on. + * @throws InterruptedException + * @throws IOException + */ + public void waitTableDisabled(byte[] table) + throws InterruptedException, IOException { + waitTableDisabled(getHBaseAdmin(), table, 30000); + } + + public void waitTableDisabled(Admin admin, byte[] table) + throws InterruptedException, IOException { + waitTableDisabled(admin, table, 30000); + } + + /** + * Waits for a table to be 'disabled'. Disabled means that table is set as 'disabled' + * @param table Table to wait on. + * @param timeoutMillis Time to wait on it being marked disabled. + * @throws InterruptedException + * @throws IOException + */ + public void waitTableDisabled(byte[] table, long timeoutMillis) + throws InterruptedException, IOException { + waitTableDisabled(getHBaseAdmin(), table, timeoutMillis); + } + + public void waitTableDisabled(Admin admin, byte[] table, long timeoutMillis) + throws InterruptedException, IOException { + TableName tableName = TableName.valueOf(table); + long startWait = System.currentTimeMillis(); + while (!admin.isTableDisabled(tableName)) { + assertTrue("Timed out waiting for table to become disabled " + + Bytes.toStringBinary(table), + System.currentTimeMillis() - startWait < timeoutMillis); + Thread.sleep(200); + } + } + /** * Make sure that at least the specified number of region servers * are running diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/handler/TestEnableTableHandler.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/handler/TestEnableTableHandler.java new file mode 100644 index 00000000000..327c11e19ae --- /dev/null +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/handler/TestEnableTableHandler.java @@ -0,0 +1,101 @@ +/** + * + * 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.master.handler; + +import java.util.Collections; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.hbase.HBaseTestingUtility; +import org.apache.hadoop.hbase.HColumnDescriptor; +import org.apache.hadoop.hbase.HRegionInfo; +import org.apache.hadoop.hbase.HTableDescriptor; +import org.apache.hadoop.hbase.MetaTableAccessor; +import org.apache.hadoop.hbase.MiniHBaseCluster; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.client.HBaseAdmin; +import org.apache.hadoop.hbase.master.HMaster; +import org.apache.hadoop.hbase.testclassification.MediumTests; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.hbase.util.FSTableDescriptors; +import org.apache.hadoop.hbase.util.JVMClusterUtil; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +@Category({ MediumTests.class }) +public class TestEnableTableHandler { + private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); + private static final Log LOG = LogFactory.getLog(TestEnableTableHandler.class); + private static final byte[] FAMILYNAME = Bytes.toBytes("fam"); + + @Before + public void setUp() throws Exception { + TEST_UTIL.getConfiguration().set("hbase.balancer.tablesOnMaster", "hbase:meta"); + TEST_UTIL.startMiniCluster(1); + } + + @After + public void tearDown() throws Exception { + TEST_UTIL.shutdownMiniCluster(); + } + + @Test(timeout = 300000) + public void testEnableTableWithNoRegionServers() throws Exception { + final TableName tableName = TableName.valueOf("testEnableTableWithNoRegionServers"); + final MiniHBaseCluster cluster = TEST_UTIL.getHBaseCluster(); + final HMaster m = cluster.getMaster(); + final HBaseAdmin admin = TEST_UTIL.getHBaseAdmin(); + final HTableDescriptor desc = new HTableDescriptor(tableName); + desc.addFamily(new HColumnDescriptor(FAMILYNAME)); + admin.createTable(desc); + admin.disableTable(tableName); + TEST_UTIL.waitTableDisabled(tableName.getName()); + + admin.enableTable(tableName); + TEST_UTIL.waitTableEnabled(tableName); + + // disable once more + admin.disableTable(tableName); + + // now stop region servers + JVMClusterUtil.RegionServerThread rs = cluster.getRegionServerThreads().get(0); + rs.getRegionServer().stop("stop"); + cluster.waitForRegionServerToStop(rs.getRegionServer().getServerName(), 10000); + + TEST_UTIL.waitUntilAllRegionsAssigned(TableName.META_TABLE_NAME); + + admin.enableTable(tableName); + assertTrue(admin.isTableEnabled(tableName)); + + JVMClusterUtil.RegionServerThread rs2 = cluster.startRegionServer(); + m.getAssignmentManager().assign(admin.getTableRegions(tableName)); + TEST_UTIL.waitUntilAllRegionsAssigned(tableName); + List onlineRegions = admin.getOnlineRegions( + rs2.getRegionServer().getServerName()); + assertEquals(2, onlineRegions.size()); + assertEquals(tableName, onlineRegions.get(1).getTable()); + } + +}