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,