Index: src/test/org/apache/hadoop/hbase/HBaseTestingUtility.java =================================================================== --- src/test/org/apache/hadoop/hbase/HBaseTestingUtility.java (revision 936390) +++ src/test/org/apache/hadoop/hbase/HBaseTestingUtility.java (working copy) @@ -68,6 +68,7 @@ private MiniDFSCluster dfsCluster = null; private MiniHBaseCluster hbaseCluster = null; private MiniMRCluster mrCluster = null; + // If non-null, then already a cluster running. private File clusterTestBuildDir = null; private HBaseAdmin hbaseAdmin = null; @@ -98,6 +99,36 @@ } /** + * Home our cluster in a dir under build/test. Give it a random name + * so can have many concurrent clusters running if we need to. Need to + * amend the test.build.data System property. Its what minidfscluster bases + * it data dir on. Moding a System property is not the way to do concurrent + * instances -- another instance could grab the temporary + * value unintentionally -- but not anything can do about it at moment; its + * how the minidfscluster works. + * @return The calculated cluster test build directory. + */ + File setupClusterTestBuildDir() { + String oldTestBuildDir = + System.getProperty(TEST_DIRECTORY_KEY, "build/test/data"); + String randomStr = UUID.randomUUID().toString(); + String dirStr = oldTestBuildDir + "." + randomStr; + File dir = new File(dirStr).getAbsoluteFile(); + // Have it cleaned up on exit + dir.deleteOnExit(); + return dir; + } + + /** + * @throws IOException If cluster already running. + */ + void isRunningCluster() throws IOException { + if (this.clusterTestBuildDir == null) return; + throw new IOException("Cluster already running at " + + this.clusterTestBuildDir); + } + + /** * @param subdirName * @return Path to a subdirectory named subdirName under * {@link #getTestDir()}. @@ -114,16 +145,35 @@ startMiniCluster(1); } + /** + * Call this if you only want a zk cluster. + * @see #startMiniZKCluster() if you want zk + dfs + hbase mini cluster. + * @throws Exception + * @see #shutdownMiniZKCluster() + */ public void startMiniZKCluster() throws Exception { - // Note that this is done before we create the MiniHBaseCluster because we - // need to edit the config to add the ZooKeeper servers. + isRunningCluster(); + this.clusterTestBuildDir = setupClusterTestBuildDir(); + startMiniZKCluster(this.clusterTestBuildDir); + + } + + private void startMiniZKCluster(final File dir) throws Exception { this.zkCluster = new MiniZooKeeperCluster(); - int clientPort = this.zkCluster.startup(this.clusterTestBuildDir); + int clientPort = this.zkCluster.startup(dir); this.conf.set("hbase.zookeeper.property.clientPort", Integer.toString(clientPort)); } /** + * @throws IOException + * @see #startMiniZKCluster() + */ + public void shutdownMiniZKCluster() throws IOException { + if (this.zkCluster != null) this.zkCluster.shutdown(); + } + + /** * Start up a minicluster of hbase, optinally dfs, and zookeeper. * Modifies Configuration. Homes the cluster data directory under a random * subdirectory in a directory under System property test.build.data. @@ -138,27 +188,13 @@ throws Exception { LOG.info("Starting up minicluster"); // If we already put up a cluster, fail. - if (this.clusterTestBuildDir != null) { - throw new IOException("Cluster already running at " + - this.clusterTestBuildDir); - } - // Now, home our cluster in a dir under build/test. Give it a random name - // so can have many concurrent clusters running if we need to. Need to - // amend the test.build.data System property. Its what minidfscluster bases - // it data dir on. Moding a System property is not the way to do concurrent - // instances -- another instance could grab the temporary - // value unintentionally -- but not anything can do about it at moment; its - // how the minidfscluster works. - String oldTestBuildDir = + isRunningCluster(); + String oldBuildTestDir = System.getProperty(TEST_DIRECTORY_KEY, "build/test/data"); - String randomStr = UUID.randomUUID().toString(); - String clusterTestBuildDirStr = oldTestBuildDir + "." + randomStr; - this.clusterTestBuildDir = - new File(clusterTestBuildDirStr).getAbsoluteFile(); - // Have it cleaned up on exit - this.clusterTestBuildDir.deleteOnExit(); + this.clusterTestBuildDir = setupClusterTestBuildDir(); + // Set our random dir while minidfscluster is being constructed. - System.setProperty(TEST_DIRECTORY_KEY, clusterTestBuildDirStr); + System.setProperty(TEST_DIRECTORY_KEY, this.clusterTestBuildDir.getPath()); // Bring up mini dfs cluster. This spews a bunch of warnings about missing // scheme. TODO: fix. // Complaints are 'Scheme is undefined for build/test/data/dfs/name1'. @@ -167,7 +203,8 @@ // Restore System property. minidfscluster accesses content of // the TEST_DIRECTORY_KEY to make bad blocks, a feature we are not using, // but otherwise, just in constructor. - System.setProperty(TEST_DIRECTORY_KEY, oldTestBuildDir); + System.setProperty(TEST_DIRECTORY_KEY, oldBuildTestDir); + // Mangle conf so fs parameter points to minidfs we just started up FileSystem fs = this.dfsCluster.getFileSystem(); this.conf.set("fs.defaultFS", fs.getUri().toString()); @@ -175,7 +212,7 @@ // It could be created before the cluster if(this.zkCluster == null) { - startMiniZKCluster(); + startMiniZKCluster(this.clusterTestBuildDir); } // Now do the mini hbase cluster. Set the hbase.rootdir in config. @@ -193,7 +230,7 @@ /** * @throws IOException - * @see {@link #startMiniCluster(boolean, int)} + * @see {@link #startMiniCluster(int)} */ public void shutdownMiniCluster() throws IOException { LOG.info("Shutting down minicluster"); @@ -202,7 +239,7 @@ // Wait till hbase is down before going on to shutdown zk. this.hbaseCluster.join(); } - if (this.zkCluster != null) this.zkCluster.shutdown(); + shutdownMiniZKCluster(); if (this.dfsCluster != null) { // The below throws an exception per dn, AsynchronousCloseException. this.dfsCluster.shutdown(); Index: src/test/org/apache/hadoop/hbase/master/TestMaster.java =================================================================== --- src/test/org/apache/hadoop/hbase/master/TestMaster.java (revision 0) +++ src/test/org/apache/hadoop/hbase/master/TestMaster.java (revision 0) @@ -0,0 +1,187 @@ +package org.apache.hadoop.hbase.master; + +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.hbase.*; +import org.apache.hadoop.hbase.regionserver.HRegion; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.hbase.zookeeper.ZooKeeperWrapper; +import org.apache.hadoop.io.MapWritable; +import org.apache.zookeeper.WatchedEvent; +import org.apache.zookeeper.Watcher; +import org.junit.*; + +import java.io.IOException; +import java.net.DatagramSocket; +import java.net.ServerSocket; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; + +/** + * Test HMaster. + * TODO: How to do random ports. + */ +public class TestMaster implements Watcher { + private HMaster master; + private HServerInfo rootRegionServer; + private HServerInfo metaRegionServer; + private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); + private static final HRegionInfo [] EMPTY_HRI_ARRAY = new HRegionInfo [] {}; + private static final HMsg [] EMPTY_HMSG_ARRAY = new HMsg [] {}; + private ZooKeeperWrapper zkWrapper; + + + @BeforeClass public static void beforeAllTests() throws Exception { + TEST_UTIL.startMiniZKCluster(); + } + + @AfterClass public static void afterAllTests() throws IOException { + TEST_UTIL.shutdownMiniZKCluster(); + } + + + /** + * Starts up a master. + * Fakes it out by sending in startup messages from two *servers*. The two + * fictional regionservers then fake the master into thinking that they have + * deployed .META. and -ROOT- regions. + * @throws IOException + */ + @Before + public void setup() throws IOException { + // Copy config. before I start messing with it + HBaseConfiguration c = new HBaseConfiguration(TEST_UTIL.getConfiguration()); + // Disable the root and meta scanning. Means have to push regions into + // unassigned manually. + c.setBoolean("hbase.testing.master.meta.thread.run", false); + // Now do the mini hbase cluster. Set the hbase.rootdir in config. + FileSystem fs = FileSystem.get(c); + Path hbaseRootdir = fs.makeQualified(fs.getHomeDirectory()); + c.set(HConstants.HBASE_DIR, hbaseRootdir.toString()); + c.setInt(HConstants.MASTER_PORT, 0); + + // Get a zk wrapper. + this.zkWrapper = new ZooKeeperWrapper(TEST_UTIL.getConfiguration(), this); + + // Start up master. + this.master = new HMaster(c); + this.master.start(); + + // Register two servers, one to host root and other to host meta. Tell zk + // and the master about them. + this.rootRegionServer = createMockRegionServer("root_regionserver"); + addMockRegionServer(this.zkWrapper, this.rootRegionServer, this.master); + this.metaRegionServer = createMockRegionServer("meta_regionserver"); + addMockRegionServer(this.zkWrapper, this.metaRegionServer, this.master); + + // Now get root and meta assigned. + openSingleRegion(this.master, this.rootRegionServer); + // Push the meta into unassigned regions. Need to do this because we + // disabled the meta and root scanners. + this.master.setUnassigned(HRegionInfo.FIRST_META_REGIONINFO); + openSingleRegion(this.master, this.metaRegionServer); + } + + private HServerInfo createMockRegionServer(final String name) throws IOException { + int freeport = getFreePort(); + return new HServerInfo(new HServerAddress("localhost:" + freeport), + freeport, -1, name); + } + + /** + * @param wrapper + * @param server + * @param m + * @return The map the master returns on call to regionServerStartup. + * @throws IOException + */ + private MapWritable addMockRegionServer(final ZooKeeperWrapper wrapper, + final HServerInfo server, final HMaster m) + throws IOException { + wrapper.writeRSLocation(server); + return m.regionServerStartup(server); + } + + /** + * Open the region the master has currently primed. + * @param m + * @param server + * @return Last messages from the master. + * @throws IOException + */ + private HMsg [] openSingleRegion(final HMaster m, final HServerInfo server) + throws IOException { + HMsg [] msgs = m.regionServerReport(server, EMPTY_HMSG_ARRAY, EMPTY_HRI_ARRAY); + assertEquals(1, msgs.length); + HMsg msg = msgs[0]; + assertTrue(msg.isType(HMsg.Type.MSG_REGION_OPEN)); + HMsg openMsg = new HMsg(HMsg.Type.MSG_REPORT_OPEN, msg.getRegionInfo()); + return this.master.regionServerReport(server, new HMsg[] {openMsg}, + EMPTY_HRI_ARRAY); + } + + @After + public void teardown() { + if (this.master != null) this.master.shutdown(); + } + + /** + * @see HBASE-2428 + */ + @Test public void testRegionCloseWhenNoMeta() throws Exception { + // Push some random regions on to the master to assign. + HTableDescriptor htd = new HTableDescriptor("test_table"); + for(byte[] family: new byte [][] {Bytes.toBytes("one"), Bytes.toBytes("two"), + Bytes.toBytes("three")}) { + htd.addFamily(new HColumnDescriptor(family)); + } + // Add three regions to assign. + HRegionInfo hri = new HRegionInfo(htd, HConstants.EMPTY_START_ROW, + Bytes.toBytes("01")); + this.master.setUnassigned(hri); + hri = new HRegionInfo(htd, Bytes.toBytes("01"), Bytes.toBytes("02")); + this.master.setUnassigned(hri); + hri = new HRegionInfo(htd, Bytes.toBytes("02"), HConstants.EMPTY_END_ROW); + this.master.setUnassigned(hri); + // Add a new *regionserver* + HServerInfo rs = createMockRegionServer("random_regionserver"); + addMockRegionServer(this.zkWrapper, rs, this.master); + HMsg [] msgs = this.master.regionServerReport(rs, EMPTY_HMSG_ARRAY, + EMPTY_HRI_ARRAY); + } + + @Override + public void process(WatchedEvent watchedEvent) { + //To change body of implemented methods use File | Settings | File Templates. + } + + + /** + * Checks to see if a specific port is available. + * Modified from apache mina available method. + * @return some random free port. + */ + public static int getFreePort() throws IOException { + ServerSocket ss = null; + DatagramSocket ds = null; + try { + ss = new ServerSocket(0); + ss.setReuseAddress(true); + ds = new DatagramSocket(ss.getLocalPort()); + ds.setReuseAddress(true); + return ss.getLocalPort(); + } finally { + if (ds != null) { + ds.close(); + } + if (ss != null) { + try { + ss.close(); + } catch (IOException e) { + /* should not be thrown */ + } + } + } + } +} Index: src/java/org/apache/hadoop/hbase/master/HMaster.java =================================================================== --- src/java/org/apache/hadoop/hbase/master/HMaster.java (revision 936390) +++ src/java/org/apache/hadoop/hbase/master/HMaster.java (working copy) @@ -376,6 +376,14 @@ } /** + * Used testing. + * @param hri Region to set unassigned. + */ + void setUnassigned(final HRegionInfo hri) { + this.regionManager.setUnassigned(hri, false); + } + + /** * @return Location of the -ROOT- region. */ public HServerAddress getRootRegionLocation() { Index: src/java/org/apache/hadoop/hbase/master/RegionManager.java =================================================================== --- src/java/org/apache/hadoop/hbase/master/RegionManager.java (revision 936390) +++ src/java/org/apache/hadoop/hbase/master/RegionManager.java (working copy) @@ -149,6 +149,13 @@ } void start() { + if (!this.master.getConfiguration().getBoolean("hbase.testing.master.meta.thread.run", true)) { + // If testing, sometimes we don't want meta scanners to run. Set intial + // scan to be true. + this.rootScannerThread.initialScanComplete = true; + this.metaScannerThread.initialScanComplete = true; + return; + } Threads.setDaemonThreadRunning(rootScannerThread, "RegionManager.rootScanner"); Threads.setDaemonThreadRunning(metaScannerThread,