diff --git bin/hbase bin/hbase index 4eeb145..4674693 100755 --- bin/hbase +++ bin/hbase @@ -355,7 +355,7 @@ fi # Exec unless HBASE_NOEXEC is set. if [ "${HBASE_NOEXEC}" != "" ]; then - "$JAVA" -XX:OnOutOfMemoryError="kill -9 %p" $JAVA_HEAP_MAX $HBASE_OPTS -classpath "$CLASSPATH" $CLASS "$@" + "$JAVA" -Dproc_$COMMAND {-XX:OnOutOfMemoryError="kill -9 %p" $JAVA_HEAP_MAX $HBASE_OPTS -classpath "$CLASSPATH" $CLASS "$@" else - exec "$JAVA" -XX:OnOutOfMemoryError="kill -9 %p" $JAVA_HEAP_MAX $HBASE_OPTS -classpath "$CLASSPATH" $CLASS "$@" + exec "$JAVA" -Dproc_$COMMAND -XX:OnOutOfMemoryError="kill -9 %p" $JAVA_HEAP_MAX $HBASE_OPTS -classpath "$CLASSPATH" $CLASS "$@" fi diff --git hbase-it/src/test/java/org/apache/hadoop/hbase/IntegrationTestDataIngestWithChaosMonkey.java hbase-it/src/test/java/org/apache/hadoop/hbase/IntegrationTestDataIngestWithChaosMonkey.java new file mode 100644 index 0000000..f92a9ec --- /dev/null +++ hbase-it/src/test/java/org/apache/hadoop/hbase/IntegrationTestDataIngestWithChaosMonkey.java @@ -0,0 +1,165 @@ +/** + * 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; + +import java.io.IOException; +import java.util.Collection; +import java.util.Random; + +import junit.framework.Assert; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.hbase.util.LoadTestTool; +import org.apache.hadoop.hbase.util.SimpleStoppable; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +/** + * A system test which does large data ingestion, while killing the region servers + * and the master(s) randomly, and doing a verify afterwards. + */ +@Category(IntegrationTests.class) +public class IntegrationTestDataIngestWithChaosMonkey { + + private static final Log LOG = LogFactory.getLog(IntegrationTestDataIngestWithChaosMonkey.class); + private HBaseTestingUtility util; + private HBaseCluster cluster; + private static final String TABLE_NAME = "IntegrationTestDataIngestWithChaosMonkey"; + private static final int NUM_SLAVES_BASE = 4; //number of slaves for the smallest cluster + + @Before + public void setUp() throws Exception { + util = new HBaseTestingUtility(); + if (!util.isRealCluster()) { + util.startMiniCluster(NUM_SLAVES_BASE); + } + + cluster = util.getHBaseClusterShim(); + deleteTableIfNecessary(); + } + + @After + public void tearDown() throws Exception { + if (!util.isRealCluster()) { + util.shutdownMiniCluster(); + } + } + + private void deleteTableIfNecessary() { + try { + util.deleteTable(Bytes.toBytes(TABLE_NAME)); + } catch (Exception ex) { + //ignore + } + } + + @Test + public void testDataIngest() throws Exception { + LOG.info("Running testDataIngest"); + SimpleChaosMonkey monkey = new SimpleChaosMonkey(util); + Thread monkeyThread = new Thread(monkey); + + monkeyThread.start(); + + LoadTestTool loadTool = new LoadTestTool(); + loadTool.setConf(util.getConfiguration()); + + String numKeys = String.valueOf(estimateDataSize()); + + int ret = loadTool.run(new String[] { + "-tn", TABLE_NAME, + "-write", "10:100:20", + "-num_keys", numKeys + }); + + monkey.stop("test has finished, that's why"); + monkeyThread.join(); + + //assert that load was successful + Assert.assertEquals(0, ret); + + ret = loadTool.run(new String[] { + "-tn", TABLE_NAME, + "-read", "100:20", + "-num_keys", numKeys + }); + + //assert that verify was successful + Assert.assertEquals(0, ret); + } + + /** Estimates a data size based on the cluster size */ + protected long estimateDataSize() throws IOException { + //base is a 4 slave node cluster. + ClusterStatus status = cluster.getClusterStatus(); + int numRegionServers = status.getServersSize(); + int multiplication = Math.max(1, numRegionServers / NUM_SLAVES_BASE); + + return 1000000 * multiplication; + } + + /** + * A very simple utility which randomly selects a region server, kill's it, + * and restarts it after some time. + */ + static class SimpleChaosMonkey extends SimpleStoppable implements Runnable { + private static final long ONE_MIN = 60000; + + HBaseTestingUtility util; + + public SimpleChaosMonkey(HBaseTestingUtility util) { + this.util = util; + } + + @Override + public void run() { + try { + HBaseCluster cluster = util.getHBaseClusterShim(); + ClusterStatus status = cluster.getInitialClusterStatus(); + Collection regionServers = status.getServers(); + ServerName[] servers = regionServers.toArray(new ServerName[regionServers.size()]); + + int numRegionServers = status.getServersSize(); + Random random = new Random(); + + while (!isStopped()) { + int selected = random.nextInt(numRegionServers); + cluster.abortRegionServer(selected); + sleep(ONE_MIN); + cluster.startRegionServer(servers[selected].getHostname()); + sleep(ONE_MIN); + } + } catch(IOException ex) { + throw new RuntimeException(ex); + } + } + + void sleep(long milis) { + try { + Thread.sleep(milis); + } catch (InterruptedException ex) { + //ignore + } + } + } +} diff --git hbase-it/src/test/java/org/apache/hadoop/hbase/IntegrationTestsDriver.java hbase-it/src/test/java/org/apache/hadoop/hbase/IntegrationTestsDriver.java new file mode 100644 index 0000000..7c23823 --- /dev/null +++ hbase-it/src/test/java/org/apache/hadoop/hbase/IntegrationTestsDriver.java @@ -0,0 +1,64 @@ +/** + * 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; + +import org.apache.commons.cli.CommandLine; +import org.apache.hadoop.hbase.util.AbstractHBaseTool; +import org.apache.hadoop.util.ToolRunner; +import org.junit.internal.TextListener; +import org.junit.runner.JUnitCore; + +/** + * This class drives the Integration test suite execution + */ +public class IntegrationTestsDriver extends AbstractHBaseTool { + + public static void main(String[] args) throws Exception { + int ret = ToolRunner.run(new IntegrationTestsDriver(), args); + System.exit(ret); + } + + @Override + protected void addOptions() { + } + + @Override + protected void processOptions(CommandLine cmd) { + } + + private Class[] getIntegrationTestClasses() { + //TODO: replace this with smt to find all classes in the classpath + //having @Category(IntegrationTests.class) annotation + return new Class[] { + IntegrationTestDataIngestWithChaosMonkey.class + }; + } + + @Override + protected void doWork() throws Exception { + + //this is called from the command line, so we should set to use the real cluster + HBaseTestingUtility.setUseRealCluster(conf); + + JUnitCore junit = new JUnitCore(); + junit.addListener(new TextListener(System.out)); + junit.run(getIntegrationTestClasses()); + } + +} diff --git hbase-server/src/test/java/org/apache/hadoop/hbase/ClusterManager.java hbase-server/src/test/java/org/apache/hadoop/hbase/ClusterManager.java new file mode 100644 index 0000000..e5e8313 --- /dev/null +++ hbase-server/src/test/java/org/apache/hadoop/hbase/ClusterManager.java @@ -0,0 +1,112 @@ +/** + * 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; + +import java.io.IOException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.conf.Configured; + + +/** + * Utility to start/kill servers in a distributed environment. Assumes Unix-like + * commands are available like 'ps', 'kill', etc. Also assumes the user running + * the test has enough "power" to start & stop servers on the remote machines + * (for example, the test user could be the same user as the user the daemon is + * running as) + */ +@InterfaceAudience.Private +public abstract class ClusterManager extends Configured { + protected static final Log LOG = LogFactory.getLog(ClusterManager.class); + + private static final String SIGKILL = "SIGKILL"; + private static final String SIGSTOP = "SIGSTOP"; + private static final String SIGCONT = "SIGCONT"; + + public ClusterManager() { + } + + /** + * Type of the service daemon + */ + public static enum ServiceType { + HADOOP_NAMENODE("namenode"), + HADOOP_DATANODE("datanode"), + HBASE_MASTER("master"), + HBASE_REGIONSERVER("regionserver"); + + private String name; + + ServiceType(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return getName(); + } + } + + /** + * Start the service on the given host + */ + public abstract boolean start(ServiceType service, String hostname) throws IOException; + + /** + * Stop the service on the given host + */ + public abstract boolean stop(ServiceType service, String hostname) throws IOException; + + /** + * Restart the service on the given host + */ + public abstract boolean restart(ServiceType service, String hostname) throws IOException; + + /** + * Send the given posix signal to the service + */ + public abstract boolean signal(ServiceType service, String signal, String hostname) throws IOException; + + /** + * Kill the service running on given host + */ + public boolean kill(ServiceType service, String hostname) throws IOException { + return signal(service, SIGKILL, hostname); + } + + /** + * Suspend the service running on given host + */ + public boolean suspend(ServiceType service, String hostname) throws IOException { + return signal(service, SIGSTOP, hostname); + } + + /** + * Resume the services running on given hosts + */ + public boolean resume(ServiceType service, String hostname) throws IOException { + return signal(service, SIGCONT, hostname); + } +} \ No newline at end of file diff --git hbase-server/src/test/java/org/apache/hadoop/hbase/HBaseCluster.java hbase-server/src/test/java/org/apache/hadoop/hbase/HBaseCluster.java new file mode 100644 index 0000000..e1e742d --- /dev/null +++ hbase-server/src/test/java/org/apache/hadoop/hbase/HBaseCluster.java @@ -0,0 +1,189 @@ +/** + * Copyright 2008 The Apache Software Foundation + * + * 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; + +import java.io.Closeable; +import java.io.IOException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.conf.Configurable; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.client.AdminProtocol; +import org.apache.hadoop.hbase.client.ClientProtocol; +import org.apache.hadoop.hbase.ipc.HMasterInterface; + +/** + * This class defines methods that can help with managing HBase clusters + * from unit tests and system tests. + */ +public abstract class HBaseCluster implements Closeable, Configurable { + static final Log LOG = LogFactory.getLog(HBaseCluster.class.getName()); + protected Configuration conf; + + /** the status of the cluster before we begin */ + protected ClusterStatus initialClusterStatus; + + /** + * Construct an HBaseCluster + * @param conf Configuration to be used for cluster + */ + public HBaseCluster(Configuration conf) throws IOException, InterruptedException { + setConf(conf); + } + + @Override + public void setConf(Configuration conf) { + this.conf = conf; + } + + @Override + public Configuration getConf() { + return conf; + } + + /** + * Returns a ClusterStatus for this HBase cluster + */ + public abstract ClusterStatus getClusterStatus() throws IOException; + + /** + * Returns a ClusterStatus for this HBase cluster as observed at the + * starting of the HBaseCluster + */ + public ClusterStatus getInitialClusterStatus() throws IOException { + return initialClusterStatus; + } + + /** + * Returns an AdminProtocol interface to the regionserver + */ + public abstract AdminProtocol getAdminProtocol(ServerName serverName) throws IOException; + + /** + * Returns a ClientProtocol interface to the regionserver + */ + public abstract ClientProtocol getClientProtocol(ServerName serverName) throws IOException; + + /** + * Starts a new region server on the given hostname or if this is a mini cluster, + * starts a regionserver in-process. + * @return + */ + public abstract boolean startRegionServer(String hostname) throws IOException; + + /** + * Cause a region server to exit doing basic clean up only on its way out. + * @param serverNumber Used as index into a list. + */ + public abstract String abortRegionServer(int serverNumber) throws IOException; + + /** + * Wait for the specified region server to stop. Removes this thread from list + * of running threads. + * @param serverNumber + * @return Name of region server that just went down. + */ + public abstract String waitOnRegionServer(final int serverNumber); + + /** + * Get the master instance + * @throws MasterNotRunningException + * @throws ZooKeeperConnectionException + */ + public abstract HMasterInterface getMasterInterface() + throws ZooKeeperConnectionException, MasterNotRunningException; + + /** + * Cause a master to exit without shutting down entire cluster. + * @param serverNumber Used as index into a list. + */ + public abstract String abortMaster(int serverNumber); + + /** + * Wait for the specified master to stop. Removes this thread from list + * of running threads. + * @param serverNumber + * @return Name of master that just went down. + */ + public abstract String waitOnMaster(final int serverNumber); + + /** + * Blocks until there is an active master and that master has completed + * initialization. + * + * @return true if an active master becomes available. false if there are no + * masters left. + * @throws InterruptedException + */ + public abstract boolean waitForActiveAndReadyMaster() + throws InterruptedException; + + /** + * Wait for Mini HBase Cluster to shut down. + */ + public abstract void waitUntilShutDown(); + + /** + * Shut down the mini HBase cluster + * @throws IOException + */ + public abstract void shutdown() throws IOException; + + /** + * Restores the cluster to it's initial state if this is a real cluster, + * otherwise does nothing. + * @throws IOException + */ + public void restoreInitialState() throws IOException { + } + + /** + * @return Index into List of {@link MiniHBaseCluster#getRegionServerThreads()} + * of HRS carrying regionName. Returns -1 if none found. + */ + public int getServerWithMeta() throws IOException { + return getServerWith(HRegionInfo.FIRST_META_REGIONINFO.getRegionName()); + } + + /** + * Get the location of the specified region + * @param regionName Name of the region in bytes + * @return Index into List of {@link MiniHBaseCluster#getRegionServerThreads()} + * of HRS carrying .META.. Returns -1 if none found. + */ + public abstract int getServerWith(byte[] regionName) throws IOException; + + /** + * Counts the total numbers of regions being served by the currently online + * region servers by asking each how many regions they have. Does not look + * at META at all. Count includes catalog tables. + * @return number of regions being served by all region servers + */ +// public abstract long countServedRegions(); + + /** + * @return whether we are interacting with a real cluster as opposed to and in-process mini + * cluster or a local cluster. + */ + public boolean isRealCluster() { + return false; + } +} \ No newline at end of file diff --git hbase-server/src/test/java/org/apache/hadoop/hbase/HBaseClusterManager.java hbase-server/src/test/java/org/apache/hadoop/hbase/HBaseClusterManager.java new file mode 100644 index 0000000..482a138 --- /dev/null +++ hbase-server/src/test/java/org/apache/hadoop/hbase/HBaseClusterManager.java @@ -0,0 +1,211 @@ +/** + * 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; + +import java.io.File; +import java.io.IOException; +import java.util.Map; + +import org.apache.commons.lang.StringUtils; +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.hbase.HBaseClusterManager.CommandProvider.Operation; +import org.apache.hadoop.hbase.util.Pair; +import org.apache.hadoop.util.Shell; + +/** + * A default cluster manager for HBase. Uses SSH, and shell scripts + * to manage the cluster. + */ +@InterfaceAudience.Private +public class HBaseClusterManager extends ClusterManager { + + /** + * Executes commands over SSH + */ + static class RemoteShell extends Shell.ShellCommandExecutor { + + private String hostname; + + private String sshCmd = "/usr/bin/ssh"; + private String sshOptions = System.getenv("HBASE_SSH_OPTS"); //from conf/hbase-env.sh + + public RemoteShell(String hostname, String[] execString, File dir, Map env, + long timeout) { + super(execString, dir, env, timeout); + this.hostname = hostname; + } + + public RemoteShell(String hostname, String[] execString, File dir, Map env) { + super(execString, dir, env); + this.hostname = hostname; + } + + public RemoteShell(String hostname, String[] execString, File dir) { + super(execString, dir); + this.hostname = hostname; + } + + public RemoteShell(String hostname, String[] execString) { + super(execString); + this.hostname = hostname; + } + + @Override + protected String[] getExecString() { + return new String[] { + "bash", "-c", + StringUtils.join(new String[] { sshCmd, + sshOptions == null ? "" : sshOptions, + hostname, + "\"" + StringUtils.join(super.getExecString(), " ") + "\"" + }, " ")}; + } + + @Override + public void execute() throws IOException { + super.execute(); + } + + public void setSshCmd(String sshCmd) { + this.sshCmd = sshCmd; + } + + public void setSshOptions(String sshOptions) { + this.sshOptions = sshOptions; + } + + public String getSshCmd() { + return sshCmd; + } + + public String getSshOptions() { + return sshOptions; + } + } + + /** + * Provides command strings for services to be executed by Shell + */ + static abstract class CommandProvider { + + enum Operation { + START, STOP, RESTART, STATUS + } + + public abstract String getCommand(ServiceType service, Operation op); + + protected String findPidCommand(ServiceType service) { + return String.format("ps aux | grep %s | grep -v grep | tr -s ' ' | cut -d ' ' -f2", + service); + } + + public String signalCommand(ServiceType service, String signal) { + return String.format("%s | xargs kill -s %s", findPidCommand(service), signal); + } + } + + /** + * CommandProvider to manage the service as a System V service. + */ + static class SystemVServiceCommandProvider extends CommandProvider { + //TODO: this assumes root + @Override + public String getCommand(ServiceType service, Operation op) { + return String.format("service %s %s", service, op.toString().toLowerCase()); + } + } + + /** + * CommandProvider to manage the service using bin/hbase-* scripts + */ + static class HBaseShellCommandProvider extends CommandProvider { + private String getHBaseHome() { + return System.getenv("HBASE_HOME"); + } + + private String getConfig() { + String confDir = System.getenv("HBASE_CONF_DIR"); + if (confDir != null) { + return String.format("--config %s", confDir); + } + return ""; + } + + @Override + public String getCommand(ServiceType service, Operation op) { + return String.format("%s/bin/hbase-daemon.sh %s %s %s", getHBaseHome(), getConfig(), + op.toString().toLowerCase(), service); + } + } + + public HBaseClusterManager() { + super(); + } + + protected CommandProvider getCommandProvider(ServiceType service) { + //TODO: make it pluggable, or auto-detect the best command provider, should work with + //hadoop daemons as well + return new HBaseShellCommandProvider(); + } + + /** + * Execute the given command on the host using SSH + * @return pair of exit code and command output + */ + private Pair exec(String hostname, String... cmd) { + LOG.info("Executing remote command: " + StringUtils.join(cmd, " ") + " ,hostname:" + hostname); + RemoteShell shell = new RemoteShell(hostname, cmd); + try { + shell.execute(); + } catch(IOException ex) { + //we do not want to throw this + LOG.warn(ex.getMessage()); + } + LOG.info("Executed remote command, exit code:" + shell.getExitCode() + + " ,output:" + shell.getOutput()); + + return new Pair(shell.getExitCode(), shell.getOutput()); + } + + private boolean exec(String hostname, ServiceType service, Operation op) { + return exec(hostname, getCommandProvider(service).getCommand(service, op)).getFirst() == 0; + } + + @Override + public boolean start(ServiceType service, String hostname) throws IOException { + return exec(hostname, service, Operation.START); + } + + @Override + public boolean stop(ServiceType service, String hostname) throws IOException { + return exec(hostname, service, Operation.STOP); + } + + @Override + public boolean restart(ServiceType service, String hostname) throws IOException { + return exec(hostname, service, Operation.RESTART); + } + + @Override + public boolean signal(ServiceType service, String signal, String hostname) throws IOException { + return exec(hostname, getCommandProvider(service).signalCommand(service, signal)) + .getFirst() == 0; + } + +} diff --git hbase-server/src/test/java/org/apache/hadoop/hbase/HBaseTestingUtility.java hbase-server/src/test/java/org/apache/hadoop/hbase/HBaseTestingUtility.java index be932d7..459c673 100644 --- hbase-server/src/test/java/org/apache/hadoop/hbase/HBaseTestingUtility.java +++ hbase-server/src/test/java/org/apache/hadoop/hbase/HBaseTestingUtility.java @@ -102,7 +102,9 @@ import org.apache.zookeeper.ZooKeeper; * old HBaseTestCase and HBaseClusterTestCase functionality. * Create an instance and keep it around testing HBase. This class is * meant to be your one-stop shop for anything you might need testing. Manages - * one cluster at a time only. + * one cluster at a time only. Managed cluster can be an in-process + * {@link MiniHBaseCluster}, or a deployed cluster of type {@link RealHBaseCluster}. + * Not all methods work with the real cluster. * Depends on log4j being on classpath and * hbase-site.xml for logging and test-run configuration. It does not set * logging levels nor make changes to configuration parameters. @@ -127,7 +129,7 @@ public class HBaseTestingUtility { private boolean passedZkCluster = false; private MiniDFSCluster dfsCluster = null; - private MiniHBaseCluster hbaseCluster = null; + private HBaseCluster hbaseCluster = null; private MiniMRCluster mrCluster = null; /** If there is a mini cluster running for this testing utility instance. */ @@ -157,6 +159,11 @@ public class HBaseTestingUtility { "test.build.data.basedirectory"; /** + * Configuration that controls whether this utility assumes a running cluster + */ + public static final String IS_REAL_CLUSTER = "hbase.cluster.real"; + + /** * Default base directory for test output. */ public static final String DEFAULT_BASE_TEST_DIRECTORY = "target/test-data"; @@ -210,6 +217,7 @@ public class HBaseTestingUtility { public HBaseTestingUtility(Configuration conf) { this.conf = conf; + this.createHBaseCluster(); // a hbase checksum verification failure will cause unit tests to fail ChecksumUtil.generateExceptionForChecksumFailureForTest(true); @@ -230,6 +238,45 @@ public class HBaseTestingUtility { return this.conf; } + /** System and integration tests can be run against a "real" cluster as opposed to a mini cluster.*/ + private void createHBaseCluster() { + boolean isRealCluster = false; + isRealCluster = Boolean.parseBoolean(System.getProperty(IS_REAL_CLUSTER, "false")); + if (!isRealCluster) { + isRealCluster = conf.getBoolean(IS_REAL_CLUSTER, false); + } + + if (!isRealCluster) { + return; + } + + try { + this.hbaseCluster = new RealHBaseCluster(conf); + getHBaseAdmin(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + /** + * Sets the configuration property to use a real cluster for the tests + */ + public static void setUseRealCluster(Configuration conf) { + conf.setBoolean(IS_REAL_CLUSTER, true); + System.setProperty(IS_REAL_CLUSTER, "true"); + } + + /** + * @return whether we are interacting with a real cluster as opposed to and in-process mini + * cluster or a local cluster. + */ + public boolean isRealCluster() { + if (hbaseCluster != null) { + return hbaseCluster.isRealCluster(); + } + return false; + } + /** * @return Where to write test data on local filesystem; usually * {@link #DEFAULT_BASE_TEST_DIRECTORY} @@ -671,7 +718,7 @@ public class HBaseTestingUtility { getHBaseAdmin(); // create immediately the hbaseAdmin LOG.info("Minicluster is up"); - return this.hbaseCluster; + return (MiniHBaseCluster)this.hbaseCluster; } /** @@ -699,7 +746,11 @@ public class HBaseTestingUtility { * @see #startMiniCluster() */ public MiniHBaseCluster getMiniHBaseCluster() { - return this.hbaseCluster; + if (this.hbaseCluster instanceof MiniHBaseCluster) { + return (MiniHBaseCluster)this.hbaseCluster; + } + throw new RuntimeException(hbaseCluster + " not an instance of " + + MiniHBaseCluster.class.getName()); } /** @@ -738,7 +789,7 @@ public class HBaseTestingUtility { if (this.hbaseCluster != null) { this.hbaseCluster.shutdown(); // Wait till hbase is down before going on to shutdown zk. - this.hbaseCluster.join(); + this.hbaseCluster.waitUntilShutDown(); this.hbaseCluster = null; } } @@ -776,7 +827,7 @@ public class HBaseTestingUtility { * @throws IOException */ public void flush() throws IOException { - this.hbaseCluster.flushcache(); + getMiniHBaseCluster().flushcache(); } /** @@ -784,7 +835,7 @@ public class HBaseTestingUtility { * @throws IOException */ public void flush(byte [] tableName) throws IOException { - this.hbaseCluster.flushcache(tableName); + getMiniHBaseCluster().flushcache(tableName); } @@ -1159,9 +1210,10 @@ public class HBaseTestingUtility { HConnection conn = table.getConnection(); conn.clearRegionCache(); // assign all the new regions IF table is enabled. - if (getHBaseAdmin().isTableEnabled(table.getTableName())) { + HBaseAdmin admin = getHBaseAdmin(); + if (admin.isTableEnabled(table.getTableName())) { for(HRegionInfo hri : newRegions) { - hbaseCluster.getMaster().assignRegion(hri); + admin.assign(hri.getRegionName()); } } @@ -1273,7 +1325,7 @@ public class HBaseTestingUtility { byte [] firstrow = metaRows.get(0); LOG.debug("FirstRow=" + Bytes.toString(firstrow)); int index = hbaseCluster.getServerWith(firstrow); - return hbaseCluster.getRegionServerThreads().get(index).getRegionServer(); + return getMiniHBaseCluster().getRegionServerThreads().get(index).getRegionServer(); } /** @@ -1343,7 +1395,7 @@ public class HBaseTestingUtility { // Needed for TestImportTsv. conf.set("mapred.job.tracker", jobConf.get("mapred.job.tracker")); - // this for mrv2 support; mr1 ignores this + // this for mrv2 support; mr1 ignores this conf.set("mapreduce.framework.name", "yarn"); String rmAdress = jobConf.get("yarn.resourcemanager.address"); if (rmAdress != null) { @@ -1389,7 +1441,7 @@ public class HBaseTestingUtility { * @throws Exception */ public void expireMasterSession() throws Exception { - HMaster master = hbaseCluster.getMaster(); + HMaster master = getMiniHBaseCluster().getMaster(); expireSession(master.getZooKeeper(), false); } @@ -1399,7 +1451,7 @@ public class HBaseTestingUtility { * @throws Exception */ public void expireRegionServerSession(int index) throws Exception { - HRegionServer rs = hbaseCluster.getRegionServer(index); + HRegionServer rs = getMiniHBaseCluster().getRegionServer(index); expireSession(rs.getZooKeeper(), false); } @@ -1463,13 +1515,16 @@ public class HBaseTestingUtility { } } - /** - * Get the HBase cluster. + * Get the Mini HBase cluster. * * @return hbase cluster */ public MiniHBaseCluster getHBaseCluster() { + return getMiniHBaseCluster(); + } + + public HBaseCluster getHBaseClusterShim() { return hbaseCluster; } @@ -1647,8 +1702,8 @@ public class HBaseTestingUtility { public boolean ensureSomeRegionServersAvailable(final int num) throws IOException { boolean startedServer = false; - - for (int i=hbaseCluster.getLiveRegionServerThreads().size(); i servers = getRegionServerThreads(); + for (int i=0; i < servers.size(); i++) { + if (servers.get(i).getRegionServer().getServerName().equals(serverName)) { + return i; + } + } + return -1; + } + + @Override + public AdminProtocol getAdminProtocol(ServerName serverName) throws IOException { + return getRegionServer(getRegionServerIndex(serverName)); + } + + @Override + public ClientProtocol getClientProtocol(ServerName serverName) throws IOException { + return getRegionServer(getRegionServerIndex(serverName)); + } } diff --git hbase-server/src/test/java/org/apache/hadoop/hbase/RealHBaseCluster.java hbase-server/src/test/java/org/apache/hadoop/hbase/RealHBaseCluster.java new file mode 100644 index 0000000..b8fba79 --- /dev/null +++ hbase-server/src/test/java/org/apache/hadoop/hbase/RealHBaseCluster.java @@ -0,0 +1,184 @@ +package org.apache.hadoop.hbase; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.ClusterManager.ServiceType; +import org.apache.hadoop.hbase.client.AdminProtocol; +import org.apache.hadoop.hbase.client.ClientProtocol; +import org.apache.hadoop.hbase.client.HBaseAdmin; +import org.apache.hadoop.hbase.client.HConnection; +import org.apache.hadoop.hbase.client.HConnectionManager; +import org.apache.hadoop.hbase.ipc.HMasterInterface; + +/** + * Manages the interactions with an already deployed cluster for integration + * and system tests. + */ +public class RealHBaseCluster extends HBaseCluster { + + private HBaseAdmin admin; + + private List initialRegionServers; + + private ClusterManager clusterManager; + + public RealHBaseCluster(Configuration conf) throws IOException, InterruptedException { + this(conf, new HBaseClusterManager()); + } + + public RealHBaseCluster(Configuration conf, ClusterManager clusterManager) throws IOException, InterruptedException { + super(conf); + this.clusterManager = clusterManager; + this.admin = new HBaseAdmin(conf); + this.initialClusterStatus = getClusterStatus(); + initialRegionServers = new ArrayList(initialClusterStatus.getServers()); + } + + public void setClusterManager(ClusterManager clusterManager) { + this.clusterManager = clusterManager; + } + + public ClusterManager getClusterManager() { + return clusterManager; + } + + /** + * Returns a ClusterStatus for this HBase cluster + * @throws IOException + */ + public ClusterStatus getClusterStatus() throws IOException { + return admin.getClusterStatus(); + } + + @Override + public ClusterStatus getInitialClusterStatus() throws IOException { + return initialClusterStatus; + } + + @Override + public void close() throws IOException { + if (this.admin != null) { + admin.close(); + } + } + + @Override + public boolean startRegionServer(String hostname) throws IOException { + return clusterManager.start(ServiceType.HBASE_REGIONSERVER, hostname); + } + + @Override + public String abortRegionServer(int serverNumber) { + ServerName serverName = getRegionServerName(serverNumber); + LOG.info("Aborting RS " + serverNumber); + + try { + clusterManager.kill(ServiceType.HBASE_REGIONSERVER, serverName.getHostname()); + } catch (Exception e) { + LOG.warn("Failed to abort region server " + e); + throw new RuntimeException(e); + } + + return serverName.getServerName(); + } + + public void abortAllRegionServers() { + + } + + public void abortAllMasters() { + + } + + protected ServerName getRegionServerName(int serverNumber) { + return initialRegionServers.get(serverNumber); + } + + public AdminProtocol getAdminProtocol(ServerName serverName) throws IOException { + return admin.getConnection().getAdmin(serverName.getHostname(), serverName.getPort()); + } + + public ClientProtocol getClientProtocol(ServerName serverName) throws IOException { + return admin.getConnection().getClient(serverName.getHostname(), serverName.getPort()); + } + + @Override + public String waitOnRegionServer(int serverNumber) { + throw new RuntimeException("Not implemented yet"); + } + + public void startMaster() throws IOException { + } + + public HMasterInterface getMasterInterface() + throws ZooKeeperConnectionException, MasterNotRunningException { + HConnection conn = HConnectionManager.getConnection(conf); + return conn.getMaster(); + } + + public HMasterInterface getMasterInterface(int serverNumber) { + throw new RuntimeException("Not implemented yet"); + } + + @Override + public String abortMaster(int serverNumber) { + return null; + } + + public String stopMaster(int serverNumber) { + return null; + } + + @Override + public String waitOnMaster(int serverNumber) { + throw new RuntimeException("Not implemented yet"); + } + + @Override + public boolean waitForActiveAndReadyMaster() throws InterruptedException { + while (true) { + try { + getMasterInterface(); + return true; + } catch (MasterNotRunningException m) { + LOG.warn("Master not started yet " + m); + } catch (ZooKeeperConnectionException e) { + // TODO Auto-generated catch block + LOG.warn("Failed to connect to ZK " + e); + } + Thread.sleep(1000); + } + } + + @Override + public int getServerWith(byte[] regionName) throws IOException { + HRegionLocation regionLoc = admin.getConnection().locateRegion(regionName); + //TODO convert + + return -1; + } + + @Override + public void waitUntilShutDown() { + //Simply wait for a few seconds for now (after issuing serverManager.kill + throw new RuntimeException("Not implemented yet"); + } + + @Override + public void shutdown() throws IOException { + + } + + @Override + public boolean isRealCluster() { + return true; + } + + @Override + public void restoreInitialState() throws IOException { + //TODO: + } +} \ No newline at end of file diff --git hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestEndToEndSplitTransaction.java hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestEndToEndSplitTransaction.java index db47ccf..6edc086 100644 --- hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestEndToEndSplitTransaction.java +++ hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestEndToEndSplitTransaction.java @@ -58,6 +58,7 @@ import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.ScanRequest; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.Pair; import org.apache.hadoop.hbase.util.PairOfSameType; +import org.apache.hadoop.hbase.util.SimpleStoppable; import org.apache.hadoop.hbase.util.Threads; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -202,20 +203,6 @@ public class TestEndToEndSplitTransaction { regionChecker.verify(); } - private static class SimpleStoppable implements Stoppable { - volatile boolean stopped = false; - - @Override - public void stop(String why) { - this.stopped = true; - } - - @Override - public boolean isStopped() { - return stopped; - } - } - static class RegionSplitter extends Thread { Throwable ex; HTable table; diff --git hbase-server/src/test/java/org/apache/hadoop/hbase/util/SimpleStoppable.java hbase-server/src/test/java/org/apache/hadoop/hbase/util/SimpleStoppable.java new file mode 100644 index 0000000..8fa1568 --- /dev/null +++ hbase-server/src/test/java/org/apache/hadoop/hbase/util/SimpleStoppable.java @@ -0,0 +1,38 @@ +/** + * 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.util; + +import org.apache.hadoop.hbase.Stoppable; + +/** + * A simple implementation for a Stoppable service + */ +public class SimpleStoppable implements Stoppable { + volatile boolean stopped = false; + + @Override + public void stop(String why) { + this.stopped = true; + } + + @Override + public boolean isStopped() { + return stopped; + } +} \ No newline at end of file