From add0c0c5a02d3d3a2864a5713545d7d06217d0eb Mon Sep 17 00:00:00 2001 From: Doug Cutting Date: Mon, 7 May 2007 19:58:53 +0000 Subject: [PATCH] HADOOP-1325. First complete, functioning version of HBase. Contributed by Jim Kellerman. git-svn-id: https://svn.apache.org/repos/asf/lucene/hadoop/trunk/src/contrib/hbase@535970 13f79535-47bb-0310-9956-ffa450edef68 --- bin/hbase | 207 +++ bin/hbase-config.sh | 62 + conf/hbase-default.xml | 38 +- conf/regionservers | 1 + .../apache/hadoop/hbase/HAbstractScanner.java | 79 +- .../hadoop/hbase/HBaseConfiguration.java | 1 + src/java/org/apache/hadoop/hbase/HClient.java | 495 ++++--- .../org/apache/hadoop/hbase/HConstants.java | 53 +- .../org/apache/hadoop/hbase/HGlobals.java | 10 +- .../hbase/HInternalScannerInterface.java | 44 + src/java/org/apache/hadoop/hbase/HLog.java | 175 ++- .../org/apache/hadoop/hbase/HLogEdit.java | 10 +- src/java/org/apache/hadoop/hbase/HLogKey.java | 6 + src/java/org/apache/hadoop/hbase/HMaster.java | 1146 ++++++++++------- .../apache/hadoop/hbase/HMasterInterface.java | 14 +- .../org/apache/hadoop/hbase/HMemcache.java | 70 +- src/java/org/apache/hadoop/hbase/HMsg.java | 2 +- src/java/org/apache/hadoop/hbase/HRegion.java | 317 +++-- .../org/apache/hadoop/hbase/HRegionInfo.java | 36 +- .../apache/hadoop/hbase/HRegionInterface.java | 2 +- .../apache/hadoop/hbase/HRegionServer.java | 463 ++++--- .../apache/hadoop/hbase/HRegiondirReader.java | 219 ++++ .../apache/hadoop/hbase/HServerAddress.java | 2 +- .../org/apache/hadoop/hbase/HServerInfo.java | 7 +- src/java/org/apache/hadoop/hbase/HStore.java | 234 ++-- .../org/apache/hadoop/hbase/HStoreKey.java | 45 +- .../apache/hadoop/hbase/HTableDescriptor.java | 87 +- .../org/apache/hadoop/hbase/LabelledData.java | 4 +- src/java/org/apache/hadoop/hbase/Leases.java | 2 + src/java/org/apache/hadoop/hbase/package.html | 28 + .../apache/hadoop/hbase/MiniHBaseCluster.java | 136 +- src/test/org/apache/hadoop/hbase/TestGet.java | 231 ++++ .../apache/hadoop/hbase/TestHBaseCluster.java | 289 +++++ .../apache/hadoop/hbase/TestHMemcache.java | 31 +- .../org/apache/hadoop/hbase/TestHRegion.java | 98 +- .../org/apache/hadoop/hbase/TestScanner.java | 319 +++++ .../org/apache/hadoop/hbase/TestToString.java | 29 + 37 files changed, 3714 insertions(+), 1278 deletions(-) create mode 100644 bin/hbase create mode 100644 bin/hbase-config.sh create mode 100644 conf/regionservers create mode 100644 src/java/org/apache/hadoop/hbase/HInternalScannerInterface.java create mode 100644 src/java/org/apache/hadoop/hbase/HRegiondirReader.java create mode 100644 src/java/org/apache/hadoop/hbase/package.html create mode 100644 src/test/org/apache/hadoop/hbase/TestGet.java create mode 100644 src/test/org/apache/hadoop/hbase/TestHBaseCluster.java create mode 100644 src/test/org/apache/hadoop/hbase/TestScanner.java create mode 100644 src/test/org/apache/hadoop/hbase/TestToString.java diff --git a/bin/hbase b/bin/hbase new file mode 100644 index 00000000000..225c304baf6 --- /dev/null +++ b/bin/hbase @@ -0,0 +1,207 @@ +#! /bin/sh +# +# The hbase command script. Based on the hadoop command script putting +# in hbase classes, libs and configurations ahead of hadoop's. +# +# TODO: Narrow the amount of duplicated code. +# +# Environment Variables: +# +# JAVA_HOME The java implementation to use. Overrides JAVA_HOME. +# +# HBASE_HEAPSIZE The maximum amount of heap to use, in MB. +# Default is 1000. +# +# HBASE_OPTS Extra Java runtime options. +# +# HBASE_CONF_DIR Alternate conf dir. Default is ${HBASE_HOME}/conf. +# +# HADOOP_CONF_DIR Alternate conf dir. Default is ${HADOOP_HOME}/conf. +# +# HADOOP_HOME Hadoop home directory. +# +# HADOOP_ROOT_LOGGER The root appender. Default is INFO,console +# + +bin=`dirname "$0"` +bin=`cd "$bin"; pwd` + +# This will set HBASE_HOME, HADOOP_HOME, etc. +. "$bin"/hbase-config.sh + +cygwin=false +case "`uname`" in +CYGWIN*) cygwin=true;; +esac + +# if no args specified, show usage +if [ $# = 0 ]; then + echo "Usage: hbase [--hadoop=hadoopdir] " + echo "where is one of:" + echo " client run a hbase client" + echo " reader run a hbase region directory reader" + echo " logreader output content of a logfile" + echo " master run a hbase HMaster node" + echo " regionserver run a hbase HRegionServer node" + echo " or" + echo " CLASSNAME run the class named CLASSNAME" + echo "Most commands print help when invoked w/o parameters." + exit 1 +fi + +# get arguments +COMMAND=$1 +shift + +# Source the hadoop-env.sh. Will have JAVA_HOME defined. There is no +# hbase-env.sh as yet. +if [ -f "${HADOOP_CONF_DIR}/hadoop-env.sh" ]; then + . "${HADOOP_CONF_DIR}/hadoop-env.sh" +fi + +# some Java parameters +if [ "$JAVA_HOME" != "" ]; then + #echo "run java in $JAVA_HOME" + JAVA_HOME=$JAVA_HOME +fi + +if [ "$JAVA_HOME" = "" ]; then + echo "Error: JAVA_HOME is not set." + exit 1 +fi + +JAVA=$JAVA_HOME/bin/java +JAVA_HEAP_MAX=-Xmx1000m + +# check envvars which might override default args +if [ "$HBASE_HEAPSIZE" != "" ]; then + #echo "run with heapsize $HBASE_HEAPSIZE" + JAVA_HEAP_MAX="-Xmx""$HBASE_HEAPSIZE""m" + #echo $JAVA_HEAP_MAX +fi + +# CLASSPATH initially contains $HBASE_CONF_DIR +# Add HADOOP_CONF_DIR if its been defined. +CLASSPATH="${HBASE_CONF_DIR}" +if [ ! "$HADOOP_CONF_DIR" = "" ]; then + CLASSPATH="${CLASSPATH}:${HADOOP_CONF_DIR}" +fi +CLASSPATH=${CLASSPATH}:$JAVA_HOME/lib/tools.jar + +# for developers, add hbase and hadoop classes to CLASSPATH +if [ -d "$HADOOP_HOME/build/contrib/hbase/classes" ]; then + CLASSPATH=${CLASSPATH}:$HADOOP_HOME/build/contrib/hbase/classes +fi +if [ -d "$HADOOP_HOME/build/classes" ]; then + CLASSPATH=${CLASSPATH}:$HADOOP_HOME/build/classes +fi +if [ -d "$HADOOP_HOME/build/webapps" ]; then + CLASSPATH=${CLASSPATH}:$HADOOP_HOME/build +fi +if [ -d "$HADOOP_HOME/build/test/classes" ]; then + CLASSPATH=${CLASSPATH}:$HADOOP_HOME/build/test/classes +fi + +# so that filenames w/ spaces are handled correctly in loops below +IFS= + +# for releases, add core hbase, hadoop jar & webapps to CLASSPATH +for f in "$HBASE_HOME/hadoop-hbase-*.jar"; do + CLASSPATH=${CLASSPATH}:$f; +done +for f in "$HADOOP_HOME/build/contrib/hbase/hadoop-hbase-*.jar"; do + CLASSPATH=${CLASSPATH}:$f; +done +if [ -d "$HADOOP_HOME/webapps" ]; then + CLASSPATH=${CLASSPATH}:$HADOOP_HOME +fi +for f in $HADOOP_HOME/hadoop-*-core.jar; do + CLASSPATH=${CLASSPATH}:$f; +done + +# add hbase and hadoop libs to CLASSPATH +for f in $HBASE_HOME/lib/*.jar; do + CLASSPATH=${CLASSPATH}:$f; +done +for f in $HADOOP_HOME/lib/*.jar; do + CLASSPATH=${CLASSPATH}:$f; +done + +for f in $HADOOP_HOME/lib/jetty-ext/*.jar; do + CLASSPATH=${CLASSPATH}:$f; +done + + +# default log directory & file +# TODO: Should we log to hadoop or under hbase? +if [ "$HADOOP_LOG_DIR" = "" ]; then + HADOOP_LOG_DIR="$HADOOP_HOME/logs" +fi +if [ "$HADOOP_LOGFILE" = "" ]; then + HADOOP_LOGFILE='hbase.log' +fi + +# cygwin path translation +if $cygwin; then + CLASSPATH=`cygpath -p -w "$CLASSPATH"` + HADOOP_HOME=`cygpath -d "$HADOOP_HOME"` + HBASE_HOME=`cygpath -d "$HBASE_HOME"` + HADOOP_LOG_DIR=`cygpath -d "$HADOOP_LOG_DIR"` +fi + +# TODO: Can this be put into separate script so don't have to duplicate +# hadoop command script code? +# setup 'java.library.path' for native-hadoop code if necessary +JAVA_LIBRARY_PATH='' +if [ -d "${HADOOP_HOME}/build/native" -o -d "${HADOOP_HOME}/lib/native" ]; then + JAVA_PLATFORM=`CLASSPATH=${CLASSPATH} ${JAVA} org.apache.hadoop.util.PlatformName | sed -e "s/ /_/g"` + + if [ -d "$HADOOP_HOME/build/native" ]; then + JAVA_LIBRARY_PATH=${HADOOP_HOME}/build/native/${JAVA_PLATFORM}/lib + fi + + if [ -d "${HADOOP_HOME}/lib/native" ]; then + if [ "x$JAVA_LIBRARY_PATH" != "x" ]; then + JAVA_LIBRARY_PATH=${JAVA_LIBRARY_PATH}:${HADOOP_HOME}/lib/native/${JAVA_PLATFORM} + else + JAVA_LIBRARY_PATH=${HADOOP_HOME}/lib/native/${JAVA_PLATFORM} + fi + fi +fi + +# cygwin path translation +if $cygwin; then + JAVA_LIBRARY_PATH=`cygpath -p "$JAVA_LIBRARY_PATH"` +fi + +# restore ordinary behaviour +unset IFS + +# figure out which class to run +if [ "$COMMAND" = "client" ] ; then + CLASS='org.apache.hadoop.hbase.HClient' +elif [ "$COMMAND" = "reader" ] ; then + CLASS='org.apache.hadoop.hbase.HRegiondirReader' +elif [ "$COMMAND" = "logreader" ] ; then + CLASS='org.apache.hadoop.hbase.HLog' +elif [ "$COMMAND" = "master" ] ; then + CLASS='org.apache.hadoop.hbase.HMaster' +elif [ "$COMMAND" = "regionserver" ] ; then + CLASS='org.apache.hadoop.hbase.HRegionServer' +else + CLASS=$COMMAND +fi + + +HBASE_OPTS="$HBASE_OPTS -Dhadoop.log.dir=$HADOOP_LOG_DIR" +HBASE_OPTS="$HBASE_OPTS -Dhadoop.log.file=$HADOOP_LOGFILE" +HBASE_OPTS="$HBASE_OPTS -Dhadoop.home.dir=$HADOOP_HOME" +HBASE_OPTS="$HBASE_OPTS -Dhadoop.id.str=$HADOOP_IDENT_STRING" +HBASE_OPTS="$HBASE_OPTS -Dhadoop.root.logger=${HADOOP_ROOT_LOGGER:-INFO,console}" +HBASE_OPTS="$HBASE_OPTS -Dhbase.home.dir=$HBASE_HOME" +if [ "x$JAVA_LIBRARY_PATH" != "x" ]; then + HBASE_OPTS="$HBASE_OPTS -Djava.library.path=$JAVA_LIBRARY_PATH" +fi + +# run it +exec "$JAVA" $JAVA_HEAP_MAX $HBASE_OPTS -classpath "$CLASSPATH" $CLASS "$@" diff --git a/bin/hbase-config.sh b/bin/hbase-config.sh new file mode 100644 index 00000000000..1eaad12a22a --- /dev/null +++ b/bin/hbase-config.sh @@ -0,0 +1,62 @@ +# included in all the hbase scripts with source command +# should not be executable directly +# also should not be passed any arguments, since we need original $* + +# resolve links - $0 may be a softlink + +this="$0" +while [ -h "$this" ]; do + ls=`ls -ld "$this"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '.*/.*' > /dev/null; then + this="$link" + else + this=`dirname "$this"`/"$link" + fi +done + +# convert relative path to absolute path +bin=`dirname "$this"` +script=`basename "$this"` +bin=`cd "$bin"; pwd` +this="$bin/$script" + +# the root of the hbase installation +export HBASE_HOME=`dirname "$this"`/.. + +#check to see if the conf dir or hadoop home are given as an optional arguments +while [ $# -gt 1 ] +do + case $1 in + --config=*) + HADOOP_CONF_DIR=`echo $1|sed 's/[^=]*=\(.*\)/\1/'` + shift + ;; + --hbaseconfig=*) + HBASE_CONF_DIR=`echo $1|sed 's/[^=]*=\(.*\)/\1/'` + shift + ;; + + --hadoop=*) + HADOOP_HOME=`echo $1|sed 's/[^=]*=\(.*\)/\1/'` + shift + ;; + --hosts=*) + regionservers=`echo $1|sed 's/[^=]*=\(.*\)/\1/'` + shift + ;; + + *) + break + ;; + esac +done + +# If no hadoop home specified, then we assume its above this directory. +HADOOP_HOME="${HADOOP_HOME:-$HBASE_HOME/../../../}" +# Allow alternate hadoop conf dir location. +HADOOP_CONF_DIR="${HADOOP_CONF_DIR:-$HADOOP_HOME/conf}" +# Allow alternate hbase conf dir location. +HBASE_CONF_DIR="${HBASE_CONF_DIR:-$HBASE_HOME/conf}" +# List of hbase regions servers. +HBASE_REGIONSERVERS="${HBASE_REGIONSERVERS:-$HBASE_HOME/conf/regionservers}" diff --git a/conf/hbase-default.xml b/conf/hbase-default.xml index 565a6106a3f..efb6ca5171c 100644 --- a/conf/hbase-default.xml +++ b/conf/hbase-default.xml @@ -42,7 +42,43 @@ hbase.master.meta.thread.rescanfrequency 60000 How long the HMaster sleeps (in milliseconds) between scans of - the META table. + the root and meta tables. + + hbase.master.lease.period + 30000 + HMaster server lease period in milliseconds. Default is + 30 seconds. + + + hbase.server.thread.wakefrequency + 10000 + Time to sleep in between searches for work (in milliseconds). + Used as sleep interval by service threads such as META scanner and log roller. + + + + hbase.regionserver.lease.period + 30000 + HRegion server lease period in milliseconds. Default is + 30 seconds. + + + hbase.regionserver.handler.count + 10 + Count of RPC Server instances spun up on RegionServers + Same property is used by the HMaster for count of master handlers. + Default is 10. + + + + hbase.regionserver.msginterval + 15000 + Interval between messages from the RegionServer to HMaster + in milliseconds. Default is 15. Set this value low if you want unit + tests to be responsive. + + + diff --git a/conf/regionservers b/conf/regionservers new file mode 100644 index 00000000000..2fbb50c4a8d --- /dev/null +++ b/conf/regionservers @@ -0,0 +1 @@ +localhost diff --git a/src/java/org/apache/hadoop/hbase/HAbstractScanner.java b/src/java/org/apache/hadoop/hbase/HAbstractScanner.java index e04c9ac76c2..4479c687951 100644 --- a/src/java/org/apache/hadoop/hbase/HAbstractScanner.java +++ b/src/java/org/apache/hadoop/hbase/HAbstractScanner.java @@ -30,7 +30,7 @@ import org.apache.hadoop.io.Text; * Abstract base class that implements the HScannerInterface. * Used by the concrete HMemcacheScanner and HStoreScanners ******************************************************************************/ -public abstract class HAbstractScanner implements HScannerInterface { +public abstract class HAbstractScanner implements HInternalScannerInterface { // Pattern to determine if a column key is a regex @@ -51,6 +51,7 @@ public abstract class HAbstractScanner implements HScannerInterface { // 3. Simple match: compare column family + column key literally private class ColumnMatcher { + private boolean wildCardmatch; private MATCH_TYPE matchType; private String family; private Pattern columnMatcher; @@ -69,14 +70,17 @@ public abstract class HAbstractScanner implements HScannerInterface { if(columnkey == null || columnkey.length() == 0) { this.matchType = MATCH_TYPE.FAMILY_ONLY; this.family = column.substring(0, colpos); + this.wildCardmatch = true; } else if(isRegexPattern.matcher(columnkey).matches()) { this.matchType = MATCH_TYPE.REGEX; this.columnMatcher = Pattern.compile(column); + this.wildCardmatch = true; } else { this.matchType = MATCH_TYPE.SIMPLE; this.col = col; + this.wildCardmatch = false; } } catch(Exception e) { throw new IOException("Column: " + column + ": " + e.getMessage()); @@ -99,8 +103,12 @@ public abstract class HAbstractScanner implements HScannerInterface { throw new IOException("Invalid match type: " + this.matchType); } } + + boolean isWildCardMatch() { + return this.wildCardmatch; + } } - + protected TreeMap> okCols; // Holds matchers for each column family protected boolean scannerClosed = false; // True when scanning is done @@ -109,14 +117,17 @@ public abstract class HAbstractScanner implements HScannerInterface { protected BytesWritable vals[]; // Values that correspond to those keys protected long timestamp; // The timestamp to match entries against + private boolean wildcardMatch; + private boolean multipleMatchers; protected DataOutputBuffer outbuf = new DataOutputBuffer(); protected DataInputBuffer inbuf = new DataInputBuffer(); /** Constructor for abstract base class */ HAbstractScanner(long timestamp, Text[] targetCols) throws IOException { - this.timestamp = timestamp; + this.wildcardMatch = false; + this.multipleMatchers = false; this.okCols = new TreeMap>(); for(int i = 0; i < targetCols.length; i++) { Text family = HStoreKey.extractFamily(targetCols[i]); @@ -124,7 +135,14 @@ public abstract class HAbstractScanner implements HScannerInterface { if(matchers == null) { matchers = new Vector(); } - matchers.add(new ColumnMatcher(targetCols[i])); + ColumnMatcher matcher = new ColumnMatcher(targetCols[i]); + if(matcher.isWildCardMatch()) { + this.wildcardMatch = true; + } + matchers.add(matcher); + if(matchers.size() > 1) { + this.multipleMatchers = true; + } okCols.put(family, matchers); } } @@ -170,6 +188,19 @@ public abstract class HAbstractScanner implements HScannerInterface { /** Mechanism used to shut down the whole scan */ public abstract void close() throws IOException; + /* (non-Javadoc) + * @see org.apache.hadoop.hbase.HInternalScannerInterface#isWildcardScanner() + */ + public boolean isWildcardScanner() { + return this.wildcardMatch; + } + + /* (non-Javadoc) + * @see org.apache.hadoop.hbase.HInternalScannerInterface#isMultipleMatchScanner() + */ + public boolean isMultipleMatchScanner() { + return this.multipleMatchers; + } /** * Get the next set of values for this scanner. * @@ -179,7 +210,7 @@ public abstract class HAbstractScanner implements HScannerInterface { * * @see org.apache.hadoop.hbase.HScannerInterface#next(org.apache.hadoop.hbase.HStoreKey, java.util.TreeMap) */ - public boolean next(HStoreKey key, TreeMap results) + public boolean next(HStoreKey key, TreeMap results) throws IOException { // Find the next row label (and timestamp) @@ -187,7 +218,7 @@ public abstract class HAbstractScanner implements HScannerInterface { Text chosenRow = null; long chosenTimestamp = -1; for(int i = 0; i < keys.length; i++) { - while((keys[i] != null) + if((keys[i] != null) && (columnMatch(i)) && (keys[i].getTimestamp() <= this.timestamp) && ((chosenRow == null) @@ -210,23 +241,31 @@ public abstract class HAbstractScanner implements HScannerInterface { for(int i = 0; i < keys.length; i++) { // Fetch the data - - while((keys[i] != null) - && (keys[i].getRow().compareTo(chosenRow) == 0) - && (keys[i].getTimestamp() == chosenTimestamp)) { - if(columnMatch(i)) { - outbuf.reset(); - vals[i].write(outbuf); - byte byteresults[] = outbuf.getData(); - inbuf.reset(byteresults, outbuf.getLength()); - BytesWritable tmpval = new BytesWritable(); - tmpval.readFields(inbuf); - results.put(new Text(keys[i].getColumn()), tmpval.get()); - insertedItem = true; + while((keys[i] != null) + && (keys[i].getRow().compareTo(chosenRow) == 0)) { + + // If we are doing a wild card match or there are multiple matchers + // per column, we need to scan all the older versions of this row + // to pick up the rest of the family members + + if(!wildcardMatch + && !multipleMatchers + && (keys[i].getTimestamp() != chosenTimestamp)) { + break; } - if (! getNext(i)) { + if(columnMatch(i)) { + + // We only want the first result for any specific family member + + if(!results.containsKey(keys[i].getColumn())) { + results.put(new Text(keys[i].getColumn()), vals[i]); + insertedItem = true; + } + } + + if(!getNext(i)) { closeSubScanner(i); } } diff --git a/src/java/org/apache/hadoop/hbase/HBaseConfiguration.java b/src/java/org/apache/hadoop/hbase/HBaseConfiguration.java index ed9c5ebd846..4b019c7f10f 100644 --- a/src/java/org/apache/hadoop/hbase/HBaseConfiguration.java +++ b/src/java/org/apache/hadoop/hbase/HBaseConfiguration.java @@ -21,5 +21,6 @@ public class HBaseConfiguration extends Configuration { public HBaseConfiguration() { super(); addDefaultResource("hbase-default.xml"); + addDefaultResource("hbase-site.xml"); } } diff --git a/src/java/org/apache/hadoop/hbase/HClient.java b/src/java/org/apache/hadoop/hbase/HClient.java index f0c53ac6200..e1df337cd2a 100644 --- a/src/java/org/apache/hadoop/hbase/HClient.java +++ b/src/java/org/apache/hadoop/hbase/HClient.java @@ -19,28 +19,30 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; +import java.util.NoSuchElementException; import java.util.Random; import java.util.TreeMap; import java.util.TreeSet; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.io.BytesWritable; import org.apache.hadoop.io.DataInputBuffer; import org.apache.hadoop.io.Text; import org.apache.hadoop.ipc.RPC; -import org.apache.log4j.Logger; /******************************************************************************* * HClient manages a connection to a single HRegionServer. ******************************************************************************/ public class HClient implements HConstants { - private final Logger LOG = - Logger.getLogger(this.getClass().getName()); + private final Log LOG = LogFactory.getLog(this.getClass().getName()); - private static final Text[] metaColumns = { - META_COLUMN_FAMILY + private static final Text[] META_COLUMNS = { + COLUMN_FAMILY }; - private static final Text startRow = new Text(); + + private static final Text EMPTY_START_ROW = new Text(); private boolean closed; private long clientTimeout; @@ -83,7 +85,7 @@ public class HClient implements HConstants { this.closed = false; this.conf = conf; - this.clientTimeout = conf.getLong("hbase.client.timeout.length", 10 * 1000); + this.clientTimeout = conf.getLong("hbase.client.timeout.length", 30 * 1000); this.numTimeouts = conf.getInt("hbase.client.timeout.number", 5); this.numRetries = conf.getInt("hbase.client.retries.number", 2); @@ -98,40 +100,61 @@ public class HClient implements HConstants { this.currentServer = null; this.rand = new Random(); } - - public synchronized void createTable(HTableDescriptor desc) throws IOException { - if(closed) { + + /** + * Check client is open. + */ + private synchronized void checkOpen() { + if (this.closed) { throw new IllegalStateException("client is not open"); } - if(master == null) { - locateRootRegion(); + } + + private synchronized void checkMaster() throws IOException { + if (this.master != null) { + return; } - master.createTable(desc); + HServerAddress masterLocation = + new HServerAddress(this.conf.get(MASTER_ADDRESS)); + this.master = (HMasterInterface)RPC.getProxy(HMasterInterface.class, + HMasterInterface.versionID, masterLocation.getInetSocketAddress(), this.conf); + } + + public synchronized void createTable(HTableDescriptor desc) + throws IOException { + checkOpen(); + checkMaster(); + locateRootRegion(); + this.master.createTable(desc); } public synchronized void deleteTable(Text tableName) throws IOException { - if(closed) { - throw new IllegalStateException("client is not open"); - } - if(master == null) { - locateRootRegion(); - } - master.deleteTable(tableName); + checkOpen(); + checkMaster(); + locateRootRegion(); + this.master.deleteTable(tableName); + } + + public synchronized void shutdown() throws IOException { + checkOpen(); + checkMaster(); + this.master.shutdown(); } public synchronized void openTable(Text tableName) throws IOException { - if(closed) { - throw new IllegalStateException("client is not open"); + if(tableName == null || tableName.getLength() == 0) { + throw new IllegalArgumentException("table name cannot be null or zero length"); } - - tableServers = tablesToServers.get(tableName); - if(tableServers == null ) { // We don't know where the table is + checkOpen(); + this.tableServers = tablesToServers.get(tableName); + if(this.tableServers == null ) { // We don't know where the table is findTableInMeta(tableName); // Load the information from meta } } private void findTableInMeta(Text tableName) throws IOException { - TreeMap metaServers = tablesToServers.get(META_TABLE_NAME); + TreeMap metaServers = + this.tablesToServers.get(META_TABLE_NAME); if(metaServers == null) { // Don't know where the meta is loadMetaFromRoot(tableName); @@ -139,18 +162,51 @@ public class HClient implements HConstants { // All we really wanted was the meta or root table return; } - metaServers = tablesToServers.get(META_TABLE_NAME); + metaServers = this.tablesToServers.get(META_TABLE_NAME); } - tableServers = new TreeMap(); - for(Iterator i = metaServers.tailMap(tableName).values().iterator(); - i.hasNext(); ) { + this.tableServers = new TreeMap(); + for(int tries = 0; + this.tableServers.size() == 0 && tries < this.numRetries; + tries++) { - TableInfo t = i.next(); + Text firstMetaRegion = null; + if(metaServers.containsKey(tableName)) { + firstMetaRegion = tableName; + + } else { + firstMetaRegion = metaServers.headMap(tableName).lastKey(); + } + for(Iterator i + = metaServers.tailMap(firstMetaRegion).values().iterator(); + i.hasNext(); ) { - scanOneMetaRegion(t, tableName); + TableInfo t = i.next(); + + scanOneMetaRegion(t, tableName); + } + if(this.tableServers.size() == 0) { + // Table not assigned. Sleep and try again + + if(LOG.isDebugEnabled()) { + LOG.debug("Sleeping. Table " + tableName + + " not currently being served."); + } + try { + Thread.sleep(this.clientTimeout); + + } catch(InterruptedException e) { + } + if(LOG.isDebugEnabled()) { + LOG.debug("Wake. Retry finding table " + tableName); + } + } } - tablesToServers.put(tableName, tableServers); + if(this.tableServers.size() == 0) { + throw new IOException("failed to scan " + META_TABLE_NAME + " after " + + this.numRetries + " retries"); + } + this.tablesToServers.put(tableName, this.tableServers); } /* @@ -169,24 +225,23 @@ public class HClient implements HConstants { * could be. */ private void locateRootRegion() throws IOException { - if(master == null) { - HServerAddress masterLocation = - new HServerAddress(this.conf.get(MASTER_ADDRESS)); - master = (HMasterInterface)RPC.getProxy(HMasterInterface.class, - HMasterInterface.versionID, - masterLocation.getInetSocketAddress(), conf); - } + checkMaster(); - int tries = 0; HServerAddress rootRegionLocation = null; - do { + for(int tries = 0; rootRegionLocation == null && tries < numRetries; tries++){ int localTimeouts = 0; while(rootRegionLocation == null && localTimeouts < numTimeouts) { rootRegionLocation = master.findRootRegion(); if(rootRegionLocation == null) { try { - Thread.sleep(clientTimeout); + if (LOG.isDebugEnabled()) { + LOG.debug("Sleeping. Waiting for root region."); + } + Thread.sleep(this.clientTimeout); + if (LOG.isDebugEnabled()) { + LOG.debug("Wake. Retry finding root region."); + } } catch(InterruptedException iex) { } localTimeouts++; @@ -201,17 +256,18 @@ public class HClient implements HConstants { HRegionInterface rootRegion = getHRegionConnection(rootRegionLocation); if(rootRegion.getRegionInfo(HGlobals.rootRegionInfo.regionName) != null) { - tableServers = new TreeMap(); - tableServers.put(startRow, new TableInfo(HGlobals.rootRegionInfo, rootRegionLocation)); - tablesToServers.put(ROOT_TABLE_NAME, tableServers); + this.tableServers = new TreeMap(); + this.tableServers.put(EMPTY_START_ROW, + new TableInfo(HGlobals.rootRegionInfo, rootRegionLocation)); + + this.tablesToServers.put(ROOT_TABLE_NAME, this.tableServers); break; } rootRegionLocation = null; - - } while(rootRegionLocation == null && tries++ < numRetries); + } - if(rootRegionLocation == null) { - closed = true; + if (rootRegionLocation == null) { + this.closed = true; throw new IOException("unable to locate root region server"); } } @@ -220,38 +276,78 @@ public class HClient implements HConstants { * Scans the root region to find all the meta regions */ private void scanRoot() throws IOException { - tableServers = new TreeMap(); - TableInfo t = tablesToServers.get(ROOT_TABLE_NAME).get(startRow); - scanOneMetaRegion(t, META_TABLE_NAME); - tablesToServers.put(META_TABLE_NAME, tableServers); + this.tableServers = new TreeMap(); + TableInfo t = this.tablesToServers.get(ROOT_TABLE_NAME).get(EMPTY_START_ROW); + for(int tries = 0; + scanOneMetaRegion(t, META_TABLE_NAME) == 0 && tries < this.numRetries; + tries++) { + + // The table is not yet being served. Sleep and retry. + + if(LOG.isDebugEnabled()) { + LOG.debug("Sleeping. Table " + META_TABLE_NAME + + " not currently being served."); + } + try { + Thread.sleep(this.clientTimeout); + + } catch(InterruptedException e) { + } + if(LOG.isDebugEnabled()) { + LOG.debug("Wake. Retry finding table " + META_TABLE_NAME); + } + } + if(this.tableServers.size() == 0) { + throw new IOException("failed to scan " + ROOT_TABLE_NAME + " after " + + this.numRetries + " retries"); + } + this.tablesToServers.put(META_TABLE_NAME, this.tableServers); } /* * Scans a single meta region * @param t the table we're going to scan * @param tableName the name of the table we're looking for + * @return returns the number of servers that are serving the table */ - private void scanOneMetaRegion(TableInfo t, Text tableName) throws IOException { + private int scanOneMetaRegion(TableInfo t, Text tableName) + throws IOException { + HRegionInterface server = getHRegionConnection(t.serverAddress); + int servers = 0; long scannerId = -1L; try { - scannerId = server.openScanner(t.regionInfo.regionName, metaColumns, tableName); - + scannerId = + server.openScanner(t.regionInfo.regionName, META_COLUMNS, tableName); + DataInputBuffer inbuf = new DataInputBuffer(); while(true) { + HRegionInfo regionInfo = null; + String serverAddress = null; HStoreKey key = new HStoreKey(); - LabelledData[] values = server.next(scannerId, key); if(values.length == 0) { + if(servers == 0) { + // If we didn't find any servers then the table does not exist + + throw new NoSuchElementException("table '" + tableName + + "' does not exist"); + } + + // We found at least one server for the table and now we're done. + break; } + byte[] bytes = null; TreeMap results = new TreeMap(); for(int i = 0; i < values.length; i++) { - results.put(values[i].getLabel(), values[i].getData().get()); + bytes = new byte[values[i].getData().getSize()]; + System.arraycopy(values[i].getData().get(), 0, bytes, 0, bytes.length); + results.put(values[i].getLabel(), bytes); } - HRegionInfo regionInfo = new HRegionInfo(); - byte[] bytes = results.get(META_COL_REGIONINFO); + regionInfo = new HRegionInfo(); + bytes = results.get(COL_REGIONINFO); inbuf.reset(bytes, bytes.length); regionInfo.readFields(inbuf); @@ -259,15 +355,26 @@ public class HClient implements HConstants { // We're done break; } - - bytes = results.get(META_COL_SERVER); - String serverName = new String(bytes, UTF8_ENCODING); + + bytes = results.get(COL_SERVER); + if(bytes == null || bytes.length == 0) { + // We need to rescan because the table we want is unassigned. - tableServers.put(regionInfo.startKey, - new TableInfo(regionInfo, new HServerAddress(serverName))); + if(LOG.isDebugEnabled()) { + LOG.debug("no server address for " + regionInfo.toString()); + } + servers = 0; + this.tableServers.clear(); + break; + } + servers += 1; + serverAddress = new String(bytes, UTF8_ENCODING); + this.tableServers.put(regionInfo.startKey, + new TableInfo(regionInfo, new HServerAddress(serverAddress))); } - + return servers; + } finally { if(scannerId != -1L) { server.close(scannerId); @@ -280,23 +387,24 @@ public class HClient implements HConstants { // See if we already have a connection - HRegionInterface server = servers.get(regionServer.toString()); + HRegionInterface server = this.servers.get(regionServer.toString()); if(server == null) { // Get a connection server = (HRegionInterface)RPC.waitForProxy(HRegionInterface.class, - HRegionInterface.versionID, regionServer.getInetSocketAddress(), conf); + HRegionInterface.versionID, regionServer.getInetSocketAddress(), + this.conf); - servers.put(regionServer.toString(), server); + this.servers.put(regionServer.toString(), server); } return server; } - /** Close the connection to the HRegionServer */ + /** Close the connection */ public synchronized void close() throws IOException { - if(! closed) { + if(! this.closed) { RPC.stopClient(); - closed = true; + this.closed = true; } } @@ -307,65 +415,75 @@ public class HClient implements HConstants { * catalog table that just contains table names and their descriptors. * Right now, it only exists as part of the META table's region info. */ - public synchronized HTableDescriptor[] listTables() throws IOException { + public synchronized HTableDescriptor[] listTables() + throws IOException { TreeSet uniqueTables = new TreeSet(); - TreeMap metaTables = tablesToServers.get(META_TABLE_NAME); + TreeMap metaTables = + this.tablesToServers.get(META_TABLE_NAME); + if(metaTables == null) { // Meta is not loaded yet so go do that loadMetaFromRoot(META_TABLE_NAME); metaTables = tablesToServers.get(META_TABLE_NAME); } - for(Iteratorit = metaTables.values().iterator(); it.hasNext(); ) { - TableInfo t = it.next(); + for (TableInfo t: metaTables.values()) { HRegionInterface server = getHRegionConnection(t.serverAddress); long scannerId = -1L; try { - scannerId = server.openScanner(t.regionInfo.regionName, metaColumns, startRow); - HStoreKey key = new HStoreKey(); + scannerId = server.openScanner(t.regionInfo.regionName, + META_COLUMNS, EMPTY_START_ROW); + HStoreKey key = new HStoreKey(); DataInputBuffer inbuf = new DataInputBuffer(); while(true) { LabelledData[] values = server.next(scannerId, key); if(values.length == 0) { break; } - for(int i = 0; i < values.length; i++) { - if(values[i].getLabel().equals(META_COL_REGIONINFO)) { + if(values[i].getLabel().equals(COL_REGIONINFO)) { byte[] bytes = values[i].getData().get(); inbuf.reset(bytes, bytes.length); HRegionInfo info = new HRegionInfo(); info.readFields(inbuf); - // Only examine the rows where the startKey is zero length - + // Only examine the rows where the startKey is zero length if(info.startKey.getLength() == 0) { uniqueTables.add(info.tableDesc); } } } } - } finally { if(scannerId != -1L) { server.close(scannerId); } } } - return (HTableDescriptor[]) uniqueTables.toArray(new HTableDescriptor[uniqueTables.size()]); + return (HTableDescriptor[])uniqueTables. + toArray(new HTableDescriptor[uniqueTables.size()]); } private synchronized TableInfo getTableInfo(Text row) { - if(tableServers == null) { + if(row == null || row.getLength() == 0) { + throw new IllegalArgumentException("row key cannot be null or zero length"); + } + if(this.tableServers == null) { throw new IllegalStateException("Must open table first"); } // Only one server will have the row we are looking for - Text serverKey = tableServers.tailMap(row).firstKey(); - return tableServers.get(serverKey); + Text serverKey = null; + if(this.tableServers.containsKey(row)) { + serverKey = row; + + } else { + serverKey = this.tableServers.headMap(row).lastKey(); + } + return this.tableServers.get(serverKey); } /** Get a single value for the specified row and column */ @@ -416,7 +534,7 @@ public class HClient implements HConstants { * Return the specified columns. */ public synchronized HScannerInterface obtainScanner(Text[] columns, Text startRow) throws IOException { - if(tableServers == null) { + if(this.tableServers == null) { throw new IllegalStateException("Must open table first"); } return new ClientScanner(columns, startRow); @@ -427,14 +545,14 @@ public class HClient implements HConstants { TableInfo info = getTableInfo(row); long lockid; try { - currentServer = getHRegionConnection(info.serverAddress); - currentRegion = info.regionInfo.regionName; - clientid = rand.nextLong(); - lockid = currentServer.startUpdate(currentRegion, clientid, row); + this.currentServer = getHRegionConnection(info.serverAddress); + this.currentRegion = info.regionInfo.regionName; + this.clientid = rand.nextLong(); + lockid = currentServer.startUpdate(this.currentRegion, this.clientid, row); } catch(IOException e) { - currentServer = null; - currentRegion = null; + this.currentServer = null; + this.currentRegion = null; throw e; } return lockid; @@ -443,16 +561,17 @@ public class HClient implements HConstants { /** Change a value for the specified column */ public void put(long lockid, Text column, byte val[]) throws IOException { try { - currentServer.put(currentRegion, clientid, lockid, column, new BytesWritable(val)); + this.currentServer.put(this.currentRegion, this.clientid, lockid, column, + new BytesWritable(val)); } catch(IOException e) { try { - currentServer.abort(currentRegion, clientid, lockid); + this.currentServer.abort(this.currentRegion, this.clientid, lockid); } catch(IOException e2) { } - currentServer = null; - currentRegion = null; + this.currentServer = null; + this.currentRegion = null; throw e; } } @@ -460,16 +579,16 @@ public class HClient implements HConstants { /** Delete the value for a column */ public void delete(long lockid, Text column) throws IOException { try { - currentServer.delete(currentRegion, clientid, lockid, column); + this.currentServer.delete(this.currentRegion, this.clientid, lockid, column); } catch(IOException e) { try { - currentServer.abort(currentRegion, clientid, lockid); + this.currentServer.abort(this.currentRegion, this.clientid, lockid); } catch(IOException e2) { } - currentServer = null; - currentRegion = null; + this.currentServer = null; + this.currentRegion = null; throw e; } } @@ -477,11 +596,10 @@ public class HClient implements HConstants { /** Abort a row mutation */ public void abort(long lockid) throws IOException { try { - currentServer.abort(currentRegion, clientid, lockid); - + this.currentServer.abort(this.currentRegion, this.clientid, lockid); } catch(IOException e) { - currentServer = null; - currentRegion = null; + this.currentServer = null; + this.currentRegion = null; throw e; } } @@ -489,11 +607,11 @@ public class HClient implements HConstants { /** Finalize a row mutation */ public void commit(long lockid) throws IOException { try { - currentServer.commit(currentRegion, clientid, lockid); + this.currentServer.commit(this.currentRegion, this.clientid, lockid); } finally { - currentServer = null; - currentRegion = null; + this.currentServer = null; + this.currentRegion = null; } } @@ -515,7 +633,19 @@ public class HClient implements HConstants { this.columns = columns; this.startRow = startRow; this.closed = false; - Collection info = tableServers.tailMap(startRow).values(); + + Text firstServer = null; + if(this.startRow == null || this.startRow.getLength() == 0) { + firstServer = tableServers.firstKey(); + + } else if(tableServers.containsKey(startRow)) { + firstServer = startRow; + + } else { + firstServer = tableServers.headMap(startRow).lastKey(); + } + Collection info = tableServers.tailMap(firstServer).values(); + this.regions = info.toArray(new TableInfo[info.size()]); this.currentRegion = -1; this.server = null; @@ -528,19 +658,20 @@ public class HClient implements HConstants { * Returns false if there are no more scanners. */ private boolean nextScanner() throws IOException { - if(scannerId != -1L) { - server.close(scannerId); - scannerId = -1L; + if(this.scannerId != -1L) { + this.server.close(this.scannerId); + this.scannerId = -1L; } - currentRegion += 1; - if(currentRegion == regions.length) { + this.currentRegion += 1; + if(this.currentRegion == this.regions.length) { close(); return false; } try { - server = getHRegionConnection(regions[currentRegion].serverAddress); - scannerId = server.openScanner(regions[currentRegion].regionInfo.regionName, - columns, startRow); + this.server = getHRegionConnection(this.regions[currentRegion].serverAddress); + this.scannerId = this.server.openScanner( + this.regions[currentRegion].regionInfo.regionName, this.columns, + this.startRow); } catch(IOException e) { close(); @@ -553,16 +684,18 @@ public class HClient implements HConstants { * @see org.apache.hadoop.hbase.HScannerInterface#next(org.apache.hadoop.hbase.HStoreKey, java.util.TreeMap) */ public boolean next(HStoreKey key, TreeMap results) throws IOException { - if(closed) { + if(this.closed) { return false; } LabelledData[] values = null; do { - values = server.next(scannerId, key); + values = this.server.next(this.scannerId, key); } while(values.length == 0 && nextScanner()); for(int i = 0; i < values.length; i++) { - results.put(values[i].getLabel(), values[i].getData().get()); + byte[] bytes = new byte[values[i].getData().getSize()]; + System.arraycopy(values[i].getData().get(), 0, bytes, 0, bytes.length); + results.put(values[i].getLabel(), bytes); } return values.length != 0; } @@ -571,38 +704,112 @@ public class HClient implements HConstants { * @see org.apache.hadoop.hbase.HScannerInterface#close() */ public void close() throws IOException { - if(scannerId != -1L) { - server.close(scannerId); + if(this.scannerId != -1L) { + this.server.close(this.scannerId); } - server = null; - closed = true; + this.server = null; + this.closed = true; } } private void printUsage() { - System.err.println("Usage: java " + this.getClass().getName() + - " [--master=hostname:port]"); + printUsage(null); } - private int doCommandLine(final String args[]) { - // Process command-line args. TODO: Better cmd-line processing - // (but hopefully something not as painful as cli options). - for (String cmd: args) { - if (cmd.equals("-h") || cmd.startsWith("--h")) { - printUsage(); - return 0; - } - - final String masterArgKey = "--master="; - if (cmd.startsWith(masterArgKey)) { - this.conf.set(MASTER_ADDRESS, - cmd.substring(masterArgKey.length())); - } + private void printUsage(final String message) { + if (message != null && message.length() > 0) { + System.err.println(message); } - + System.err.println("Usage: java " + this.getClass().getName() + + " [--master=host:port] "); + System.err.println("Options:"); + System.err.println(" master Specify host and port of HBase " + + "cluster master. If not present,"); + System.err.println(" address is read from configuration."); + System.err.println("Commands:"); + System.err.println(" shutdown Shutdown the HBase cluster."); + System.err.println(" createTable Takes table name, column families, " + + "and maximum versions."); + System.err.println(" deleteTable Takes a table name."); + System.err.println(" iistTables List all tables."); + System.err.println("Example Usage:"); + System.err.println(" % java " + this.getClass().getName() + " shutdown"); + System.err.println(" % java " + this.getClass().getName() + + " createTable webcrawl contents: anchors: 10"); + } + + int doCommandLine(final String args[]) { + // Process command-line args. TODO: Better cmd-line processing + // (but hopefully something not as painful as cli options). int errCode = -1; + if (args.length < 1) { + printUsage(); + return errCode; + } try { - locateRootRegion(); + for (int i = 0; i < args.length; i++) { + String cmd = args[i]; + if (cmd.equals("-h") || cmd.startsWith("--h")) { + printUsage(); + errCode = 0; + break; + } + + final String masterArgKey = "--master="; + if (cmd.startsWith(masterArgKey)) { + this.conf.set(MASTER_ADDRESS, cmd.substring(masterArgKey.length())); + continue; + } + + if (cmd.equals("shutdown")) { + shutdown(); + errCode = 0; + break; + } + + if (cmd.equals("listTables")) { + HTableDescriptor [] tables = listTables(); + for (int ii = 0; ii < tables.length; ii++) { + System.out.println(tables[ii].getName()); + } + errCode = 0; + break; + } + + if (cmd.equals("createTable")) { + if (i + 3 > args.length) { + throw new IllegalArgumentException("Must supply a table name " + + ", at least one column family and maximum number of versions"); + } + int maxVersions = (Integer.parseInt(args[args.length - 1])); + HTableDescriptor desc = + new HTableDescriptor(args[i + 1], maxVersions); + boolean addedFamily = false; + for (int ii = i + 2; ii < (args.length - 1); ii++) { + desc.addFamily(new Text(args[ii])); + addedFamily = true; + } + if (!addedFamily) { + throw new IllegalArgumentException("Must supply at least one " + + "column family"); + } + createTable(desc); + errCode = 0; + break; + } + + if (cmd.equals("deleteTable")) { + if (i + 1 > args.length) { + throw new IllegalArgumentException("Must supply a table name"); + } + deleteTable(new Text(args[i + 1])); + errCode = 0; + break; + } + + printUsage(); + break; + } } catch (Exception e) { e.printStackTrace(); } diff --git a/src/java/org/apache/hadoop/hbase/HConstants.java b/src/java/org/apache/hadoop/hbase/HConstants.java index bcbba03cae5..9d0dc384fbf 100644 --- a/src/java/org/apache/hadoop/hbase/HConstants.java +++ b/src/java/org/apache/hadoop/hbase/HConstants.java @@ -15,30 +15,40 @@ */ package org.apache.hadoop.hbase; +import org.apache.hadoop.io.BytesWritable; import org.apache.hadoop.io.Text; -/******************************************************************************* +/** * HConstants holds a bunch of HBase-related constants - ******************************************************************************/ + */ public interface HConstants { // Configuration parameters - // TODO: URL for hbase master, like hdfs URLs with host and port. - // Or, like jdbc URLs: + // TODO: URL for hbase master like hdfs URLs with host and port. + // Like jdbc URLs? URLs could be used to refer to table cells? // jdbc:mysql://[host][,failoverhost...][:port]/[database] // jdbc:mysql://[host][,failoverhost...][:port]/[database][?propertyName1][=propertyValue1][&propertyName2][=propertyValue2]... - static final String MASTER_ADDRESS = "hbase.master"; + // Key into HBaseConfiguration for the hbase.master address. // TODO: Support 'local': i.e. default of all running in single - // process. Same for regionserver. + // process. Same for regionserver. TODO: Is having HBase homed + // on port 60k OK? + static final String MASTER_ADDRESS = "hbase.master"; static final String DEFAULT_MASTER_ADDRESS = "localhost:60000"; + + // Key for hbase.regionserver address. static final String REGIONSERVER_ADDRESS = "hbase.regionserver"; - static final String DEFAULT_REGIONSERVER_ADDRESS = - "localhost:60010"; + static final String DEFAULT_REGIONSERVER_ADDRESS = "localhost:60010"; + + static final String THREAD_WAKE_FREQUENCY = "hbase.server.thread.wakefrequency"; static final String HREGION_DIR = "hbase.regiondir"; static final String DEFAULT_HREGION_DIR = "/hbase"; static final String HREGIONDIR_PREFIX = "hregion_"; + + // TODO: Someone may try to name a column family 'log'. If they + // do, it will clash with the HREGION log dir subdirectory. FIX. + static final String HREGION_LOGDIR_NAME = "log"; // Always store the location of the root table's HRegion. // This HRegion is never split. @@ -46,22 +56,29 @@ public interface HConstants { // region name = table + startkey + regionid. This is the row key. // each row in the root and meta tables describes exactly 1 region // Do we ever need to know all the information that we are storing? - - static final Text ROOT_TABLE_NAME = new Text("--ROOT--"); - static final Text ROOT_COLUMN_FAMILY = new Text("info:"); - static final Text ROOT_COL_REGIONINFO = new Text(ROOT_COLUMN_FAMILY + "regioninfo"); - static final Text ROOT_COL_SERVER = new Text(ROOT_COLUMN_FAMILY + "server"); - static final Text ROOT_COL_STARTCODE = new Text(ROOT_COLUMN_FAMILY + "serverstartcode"); + // The root tables' name. + static final Text ROOT_TABLE_NAME = new Text("--ROOT--"); + + // The META tables' name. static final Text META_TABLE_NAME = new Text("--META--"); - static final Text META_COLUMN_FAMILY = new Text(ROOT_COLUMN_FAMILY); - static final Text META_COL_REGIONINFO = new Text(ROOT_COL_REGIONINFO); - static final Text META_COL_SERVER = new Text(ROOT_COL_SERVER); - static final Text META_COL_STARTCODE = new Text(ROOT_COL_STARTCODE); + + // Defines for the column names used in both ROOT and META HBase 'meta' + // tables. + static final Text COLUMN_FAMILY = new Text("info:"); + static final Text COL_REGIONINFO = new Text(COLUMN_FAMILY + "regioninfo"); + static final Text COL_SERVER = new Text(COLUMN_FAMILY + "server"); + static final Text COL_STARTCODE = new Text(COLUMN_FAMILY + "serverstartcode"); // Other constants static final long DESIRED_MAX_FILE_SIZE = 128 * 1024 * 1024; // 128MB static final String UTF8_ENCODING = "UTF-8"; + static final BytesWritable DELETE_BYTES = + new BytesWritable("HBASE::DELETEVAL".getBytes()); + + static final BytesWritable COMPLETE_CACHEFLUSH = + new BytesWritable("HBASE::CACHEFLUSH".getBytes()); + } diff --git a/src/java/org/apache/hadoop/hbase/HGlobals.java b/src/java/org/apache/hadoop/hbase/HGlobals.java index a83c36f3b83..8ccb5b0229d 100644 --- a/src/java/org/apache/hadoop/hbase/HGlobals.java +++ b/src/java/org/apache/hadoop/hbase/HGlobals.java @@ -15,9 +15,9 @@ */ package org.apache.hadoop.hbase; -/******************************************************************************* +/** * Global values used for finding and scanning the root and meta tables. - ******************************************************************************/ + */ public class HGlobals implements HConstants { static HTableDescriptor rootTableDesc = null; @@ -26,13 +26,11 @@ public class HGlobals implements HConstants { static { rootTableDesc = new HTableDescriptor(ROOT_TABLE_NAME.toString(), 1); - rootTableDesc.addFamily(ROOT_COLUMN_FAMILY); + rootTableDesc.addFamily(COLUMN_FAMILY); rootRegionInfo = new HRegionInfo(0L, rootTableDesc, null, null); metaTableDesc = new HTableDescriptor(META_TABLE_NAME.toString(), 1); - metaTableDesc.addFamily(META_COLUMN_FAMILY); + metaTableDesc.addFamily(COLUMN_FAMILY); } - - } diff --git a/src/java/org/apache/hadoop/hbase/HInternalScannerInterface.java b/src/java/org/apache/hadoop/hbase/HInternalScannerInterface.java new file mode 100644 index 00000000000..a4899d28554 --- /dev/null +++ b/src/java/org/apache/hadoop/hbase/HInternalScannerInterface.java @@ -0,0 +1,44 @@ +/** + * Copyright 2007 The Apache Software Foundation + * + * Licensed 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.TreeMap; + +import org.apache.hadoop.io.BytesWritable; +import org.apache.hadoop.io.Text; + +/** + * Internally, we need to be able to determine if the scanner is doing wildcard + * column matches (when only a column family is specified or if a column regex + * is specified) or if multiple members of the same column family were specified. + * + * If so, we need to ignore the timestamp to ensure that we get all the family + * members, as they may have been last updated at different times. + * + * This interface exposes two APIs for querying the scanner. + */ +public interface HInternalScannerInterface { + + public boolean next(HStoreKey key, TreeMap results) throws IOException; + public void close() throws IOException; + /** Returns true if the scanner is matching a column family or regex */ + public boolean isWildcardScanner(); + + /** Returns true if the scanner is matching multiple column family members */ + public boolean isMultipleMatchScanner(); + +} diff --git a/src/java/org/apache/hadoop/hbase/HLog.java b/src/java/org/apache/hadoop/hbase/HLog.java index 1ef3695271b..608a7a72c24 100644 --- a/src/java/org/apache/hadoop/hbase/HLog.java +++ b/src/java/org/apache/hadoop/hbase/HLog.java @@ -18,40 +18,49 @@ package org.apache.hadoop.hbase; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.io.*; +import org.apache.hadoop.io.SequenceFile.Reader; import org.apache.hadoop.fs.*; import org.apache.hadoop.conf.*; import java.io.*; import java.util.*; -/******************************************************************************* +/** * HLog stores all the edits to the HStore. * * It performs logfile-rolling, so external callers are not aware that the * underlying file is being rolled. * - * A single HLog is used by several HRegions simultaneously. + *

A single HLog is used by several HRegions simultaneously. * - * Each one is identified by a unique long int. HRegions do not need to declare - * themselves before using the HLog; they simply include their HRegion-id in the - * append() or completeCacheFlush() calls. + *

Each HRegion is identified by a unique long int. HRegions do not need to + * declare themselves before using the HLog; they simply include their + * HRegion-id in the {@link #append(Text, Text, Text, TreeMap, long)} or + * {@link #completeCacheFlush(Text, Text, long)} calls. * - * An HLog consists of multiple on-disk files, which have a chronological order. + *

An HLog consists of multiple on-disk files, which have a chronological + * order. As data is flushed to other (better) on-disk structures, the log + * becomes obsolete. We can destroy all the log messages for a given + * HRegion-id up to the most-recent CACHEFLUSH message from that HRegion. * - * As data is flushed to other (better) on-disk structures, the log becomes - * obsolete. We can destroy all the log messages for a given HRegion-id up to - * the most-recent CACHEFLUSH message from that HRegion. - * - * It's only practical to delete entire files. Thus, we delete an entire + *

It's only practical to delete entire files. Thus, we delete an entire * on-disk file F when all of the messages in F have a log-sequence-id that's * older (smaller) than the most-recent CACHEFLUSH message for every HRegion * that has a message in F. - ******************************************************************************/ -public class HLog { + * + *

TODO: Vuk Ercegovac also pointed out that keeping HBase HRegion edit logs + * in HDFS is currently flawed. HBase writes edits to logs and to a memcache. + * The 'atomic' write to the log is meant to serve as insurance against + * abnormal RegionServer exit: on startup, the log is rerun to reconstruct an + * HRegion's last wholesome state. But files in HDFS do not 'exist' until they + * are cleanly closed -- something that will not happen if RegionServer exits + * without running its 'close'. + */ +public class HLog implements HConstants { private static final Log LOG = LogFactory.getLog(HLog.class); static final String HLOG_DATFILE = "hlog.dat."; - static final Text METACOLUMN = new Text("METACOLUMN"); + static final Text METACOLUMN = new Text("METACOLUMN:"); static final Text METAROW = new Text("METAROW"); FileSystem fs; @@ -66,28 +75,40 @@ public class HLog { long oldestOutstandingSeqNum = -1; boolean closed = false; - long logSeqNum = 0; + transient long logSeqNum = 0; long filenum = 0; - int numEntries = 0; + transient int numEntries = 0; Integer rollLock = new Integer(0); /** * Bundle up a bunch of log files (which are no longer being written to), * into a new file. Delete the old log files when ready. + * @param srcDir Directory of log files to bundle: + * e.g. ${REGIONDIR}/log_HOST_PORT + * @param dstFile Destination file: + * e.g. ${REGIONDIR}/oldlogfile_HOST_PORT + * @param fs FileSystem + * @param conf HBaseConfiguration + * @throws IOException */ - public static void consolidateOldLog(Path srcDir, Path dstFile, FileSystem fs, Configuration conf) throws IOException { - LOG.debug("consolidating log files"); + public static void consolidateOldLog(Path srcDir, Path dstFile, + FileSystem fs, Configuration conf) + throws IOException { + if(LOG.isDebugEnabled()) { + LOG.debug("consolidating log files"); + } Path logfiles[] = fs.listPaths(srcDir); - SequenceFile.Writer newlog = SequenceFile.createWriter(fs, conf, dstFile, HLogKey.class, HLogEdit.class); + SequenceFile.Writer newlog = SequenceFile.createWriter(fs, conf, dstFile, + HLogKey.class, HLogEdit.class); try { for(int i = 0; i < logfiles.length; i++) { - SequenceFile.Reader in = new SequenceFile.Reader(fs, logfiles[i], conf); + SequenceFile.Reader in = + new SequenceFile.Reader(fs, logfiles[i], conf); try { HLogKey key = new HLogKey(); HLogEdit val = new HLogEdit(); - while(in.next(key, val)) { newlog.append(key, val); } @@ -111,11 +132,13 @@ public class HLog { } } } - LOG.debug("log file consolidation completed"); + if(LOG.isDebugEnabled()) { + LOG.debug("log file consolidation completed"); + } } /** - * Create an edit log at the given location. + * Create an edit log at the given dir location. * * You should never have to load an existing log. If there is a log * at startup, it should have already been processed and deleted by @@ -125,19 +148,16 @@ public class HLog { this.fs = fs; this.dir = dir; this.conf = conf; - this.logSeqNum = 0; - if(fs.exists(dir)) { + if (fs.exists(dir)) { throw new IOException("Target HLog directory already exists: " + dir); } fs.mkdirs(dir); - rollWriter(); } /** - * Roll the log writer. That is, start writing log messages to - * a new file. + * Roll the log writer. That is, start writing log messages to a new file. * * The 'rollLock' prevents us from entering rollWriter() more than * once at a time. @@ -170,7 +190,9 @@ public class HLog { } } - LOG.debug("closing current log writer and getting a new one"); + if(LOG.isDebugEnabled()) { + LOG.debug("closing current log writer and getting a new one"); + } // Close the current writer (if any), and grab a new one. @@ -178,14 +200,16 @@ public class HLog { writer.close(); if(filenum > 0) { - outputfiles.put(logSeqNum-1, computeFilename(filenum-1)); + outputfiles.put(logSeqNum - 1, computeFilename(filenum - 1)); } } Path newPath = computeFilename(filenum++); this.writer = SequenceFile.createWriter(fs, conf, newPath, HLogKey.class, HLogEdit.class); - LOG.debug("new log writer created"); + if(LOG.isDebugEnabled()) { + LOG.debug("new log writer created"); + } // Can we delete any of the old log files? // First, compute the oldest relevant log operation @@ -203,7 +227,9 @@ public class HLog { // Next, remove all files with a final ID that's older // than the oldest pending region-operation. - LOG.debug("removing old log files"); + if(LOG.isDebugEnabled()) { + LOG.debug("removing old log files"); + } for(Iterator it = outputfiles.keySet().iterator(); it.hasNext(); ) { long maxSeqNum = it.next().longValue(); @@ -226,7 +252,9 @@ public class HLog { fs.delete(p); } - LOG.debug("old log files deleted"); + if(LOG.isDebugEnabled()) { + LOG.debug("old log files deleted"); + } this.numEntries = 0; } @@ -247,21 +275,29 @@ public class HLog { } /** - * Append a set of edits to the log. - * Log edits are keyed by regionName, rowname, and log-sequence-id. + * Append a set of edits to the log. Log edits are keyed by regionName, + * rowname, and log-sequence-id. * * Later, if we sort by these keys, we obtain all the relevant edits for - * a given key-range of the HRegion. Any edits that do not have a matching - * COMPLETE_CACHEFLUSH message can be discarded. + * a given key-range of the HRegion (TODO). Any edits that do not have a + * matching {@link HConstants#COMPLETE_CACHEFLUSH} message can be discarded. * - * Logs cannot be restarted once closed, or once the HLog process dies. + *

Logs cannot be restarted once closed, or once the HLog process dies. * Each time the HLog starts, it must create a new log. This means that * other systems should process the log appropriately upon each startup * (and prior to initializing HLog). * * We need to seize a lock on the writer so that writes are atomic. + * @param regionName + * @param tableName + * @param row + * @param columns + * @param timestamp + * @throws IOException */ - public synchronized void append(Text regionName, Text tableName, Text row, TreeMap columns, long timestamp) throws IOException { + public synchronized void append(Text regionName, Text tableName, Text row, + TreeMap columns, long timestamp) + throws IOException { if(closed) { throw new IOException("Cannot append; log is closed"); } @@ -272,19 +308,16 @@ public class HLog { // most recent flush for every regionName. However, for regions // that don't have any flush yet, the relevant operation is the // first one that's been added. - - if(regionToLastFlush.get(regionName) == null) { + if (regionToLastFlush.get(regionName) == null) { regionToLastFlush.put(regionName, seqNum[0]); } int counter = 0; - for(Iterator it = columns.keySet().iterator(); it.hasNext(); ) { - Text column = it.next(); - byte[] val = columns.get(column); - HLogKey logKey = new HLogKey(regionName, tableName, row, seqNum[counter++]); - HLogEdit logEdit = new HLogEdit(column, val, timestamp); + for (Text column: columns.keySet()) { + HLogKey logKey = + new HLogKey(regionName, tableName, row, seqNum[counter++]); + HLogEdit logEdit = new HLogEdit(column, columns.get(column), timestamp); writer.append(logKey, logEdit); - numEntries++; } } @@ -317,40 +350,76 @@ public class HLog { * Set a flag so that we do not roll the log between the start * and complete of a cache-flush. Otherwise the log-seq-id for * the flush will not appear in the correct logfile. + * @return sequence ID to pass {@link #completeCacheFlush(Text, Text, long)} + * @see {@link #completeCacheFlush(Text, Text, long)} */ public synchronized long startCacheFlush() { - while(insideCacheFlush) { + while (insideCacheFlush) { try { wait(); } catch (InterruptedException ie) { } } - insideCacheFlush = true; notifyAll(); return obtainSeqNum(); } /** Complete the cache flush */ - public synchronized void completeCacheFlush(Text regionName, Text tableName, long logSeqId) throws IOException { + public synchronized void completeCacheFlush(final Text regionName, + final Text tableName, final long logSeqId) + throws IOException { if(closed) { return; } if(! insideCacheFlush) { - throw new IOException("Impossible situation: inside completeCacheFlush(), but 'insideCacheFlush' flag is false"); + throw new IOException("Impossible situation: inside " + + "completeCacheFlush(), but 'insideCacheFlush' flag is false"); } writer.append(new HLogKey(regionName, tableName, HLog.METAROW, logSeqId), - new HLogEdit(HLog.METACOLUMN, HStoreKey.COMPLETE_CACHEFLUSH, System.currentTimeMillis())); + new HLogEdit(HLog.METACOLUMN, COMPLETE_CACHEFLUSH, + System.currentTimeMillis())); numEntries++; // Remember the most-recent flush for each region. // This is used to delete obsolete log files. - regionToLastFlush.put(regionName, logSeqId); insideCacheFlush = false; notifyAll(); } + + /** + * Pass a log file and it will dump out a text version on + * stdout. + * @param args + * @throws IOException + */ + public static void main(String[] args) throws IOException { + if (args.length < 1) { + System.err.println("Usage: java org.apache.hbase.HLog "); + System.exit(-1); + } + Configuration conf = new HBaseConfiguration(); + FileSystem fs = FileSystem.get(conf); + Path logfile = new Path(args[0]); + if (!fs.exists(logfile)) { + throw new FileNotFoundException(args[0] + " does not exist"); + } + if (!fs.isFile(logfile)) { + throw new IOException(args[0] + " is not a file"); + } + Reader log = new SequenceFile.Reader(fs, logfile, conf); + try { + HLogKey key = new HLogKey(); + HLogEdit val = new HLogEdit(); + while(log.next(key, val)) { + System.out.println(key.toString() + " " + val.toString()); + } + } finally { + log.close(); + } + } } \ No newline at end of file diff --git a/src/java/org/apache/hadoop/hbase/HLogEdit.java b/src/java/org/apache/hadoop/hbase/HLogEdit.java index 873afbbe0b7..6bed0f9a541 100644 --- a/src/java/org/apache/hadoop/hbase/HLogEdit.java +++ b/src/java/org/apache/hadoop/hbase/HLogEdit.java @@ -34,9 +34,9 @@ public class HLogEdit implements Writable { public HLogEdit() { } - public HLogEdit(Text column, byte[] bval, long timestamp) { + public HLogEdit(Text column, BytesWritable bval, long timestamp) { this.column.set(column); - this.val = new BytesWritable(bval); + this.val = bval; this.timestamp = timestamp; } @@ -52,6 +52,12 @@ public class HLogEdit implements Writable { return this.timestamp; } + @Override + public String toString() { + return getColumn().toString() + " " + this.getTimestamp() + " " + + new String(getVal().get()).trim(); + } + ////////////////////////////////////////////////////////////////////////////// // Writable ////////////////////////////////////////////////////////////////////////////// diff --git a/src/java/org/apache/hadoop/hbase/HLogKey.java b/src/java/org/apache/hadoop/hbase/HLogKey.java index 673e229128d..ac4891a025b 100644 --- a/src/java/org/apache/hadoop/hbase/HLogKey.java +++ b/src/java/org/apache/hadoop/hbase/HLogKey.java @@ -66,6 +66,12 @@ public class HLogKey implements WritableComparable { public long getLogSeqNum() { return logSeqNum; } + + @Override + public String toString() { + return getTablename().toString() + " " + getRegionName().toString() + " " + + getRow().toString() + " " + getLogSeqNum(); + } ////////////////////////////////////////////////////////////////////////////// // Comparable diff --git a/src/java/org/apache/hadoop/hbase/HMaster.java b/src/java/org/apache/hadoop/hbase/HMaster.java index c6e9ff696ca..f3f7cbdef7f 100644 --- a/src/java/org/apache/hadoop/hbase/HMaster.java +++ b/src/java/org/apache/hadoop/hbase/HMaster.java @@ -23,34 +23,38 @@ import org.apache.hadoop.io.*; import org.apache.hadoop.fs.*; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.ipc.*; +import org.apache.hadoop.util.StringUtils; import java.io.*; import java.util.*; -/******************************************************************************* +/** * HMaster is the "master server" for a HBase. * There is only one HMaster for a single HBase deployment. - ******************************************************************************/ -public class HMaster implements HConstants, HMasterInterface, HMasterRegionInterface { + */ +public class HMaster implements HConstants, HMasterInterface, + HMasterRegionInterface, Runnable { - public long getProtocolVersion(String protocol, - long clientVersion) throws IOException { + public long getProtocolVersion(String protocol, long clientVersion) + throws IOException { if (protocol.equals(HMasterInterface.class.getName())) { return HMasterInterface.versionID; - } else if (protocol.equals(HMasterRegionInterface.class.getName())){ + } else if (protocol.equals(HMasterRegionInterface.class.getName())) { return HMasterRegionInterface.versionID; } else { throw new IOException("Unknown protocol to name node: " + protocol); } } - private final Log LOG = LogFactory.getLog(this.getClass().getName()); + private static final Log LOG = + LogFactory.getLog(org.apache.hadoop.hbase.HMaster.class.getName()); - private boolean closed; + private volatile boolean closed; private Path dir; private Configuration conf; private FileSystem fs; private Random rand; + private long threadWakeFrequency; private long maxRegionOpenTime; // The 'msgQueue' is used to assign work to the client processor thread @@ -67,170 +71,227 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter private HServerAddress rootRegionLocation; - ////////////////////////////////////////////////////////////////////////////// - // The ROOT/META scans, performed each time a meta region comes on-line - // Since both have identical structure, we only need one class to get the work - // done, however we have to create separate objects for each. - ////////////////////////////////////////////////////////////////////////////// + /** + * Columns in the 'meta' ROOT and META tables. + */ + private static final Text METACOLUMNS[] = { + COLUMN_FAMILY + }; + + static final String MASTER_NOT_RUNNING = "Master not running"; private boolean rootScanned; private int numMetaRegions; - + /** - * How do we know if all regions are assigned? + * Base HRegion scanner class. Holds utilty common to ROOT and + * META HRegion scanners. * - * 1. After the initial scan of the root and meta regions, all regions known - * at that time will have been or are in the process of being assigned. + *

How do we know if all regions are assigned? After the initial scan of + * the ROOT and META regions, all regions known at + * that time will have been or are in the process of being assigned.

* - * 2. When a region is split the region server notifies the master of the split - * and the new regions are assigned. But suppose the master loses the split - * message? We need to periodically rescan the root and meta regions. - * - * - If we rescan, any regions that are new but not assigned will have no - * server info. Any regions that are not being served by the same server - * will get re-assigned. + *

When a region is split the region server notifies the master of the + * split and the new regions are assigned. But suppose the master loses the + * split message? We need to periodically rescan the ROOT and + * META regions. + *

    + *
  • If we rescan, any regions that are new but not assigned will have + * no server info. Any regions that are not being served by the same + * server will get re-assigned.
  • * - * - Thus a periodic rescan of the root region will find any new meta - * regions where we missed the meta split message or we failed to detect - * a server death and consequently need to assign the region to a new - * server. + *
  • Thus a periodic rescan of the root region will find any new + * META regions where we missed the META split + * message or we failed to detect a server death and consequently need to + * assign the region to a new server.
  • * - * - if we keep track of all the known meta regions, then we can rescan - * them periodically. If we do this then we can detect an regions for - * which we missed a region split message. - * - * Thus just keeping track of all the meta regions permits periodic rescanning - * which will detect unassigned regions (new or otherwise) without the need to - * keep track of every region. + *
  • if we keep track of all the known META regions, then + * we can rescan them periodically. If we do this then we can detect any + * regions for which we missed a region split message.
  • + *
+ * + * Thus just keeping track of all the META regions permits + * periodic rescanning which will detect unassigned regions (new or + * otherwise) without the need to keep track of every region.

* - * So the root region scanner needs to wake up - * 1. when the master receives notification that the root region has been opened. - * 2. periodically after the first scan + *

So the ROOT region scanner needs to wake up: + *

    + *
  1. when the master receives notification that the ROOT + * region has been opened.
  2. + *
  3. periodically after the first scan
  4. + *
* - * The meta scanner needs to wake up: - * 1. when a meta region comes on line - * 2. periodically to rescan the known meta regions + * The META scanner needs to wake up: + *
    + *
  1. when a META region comes on line
  2. + * periodically to rescan the known META regions + *
* - * A meta region is not 'known' until it has been scanned once. - * + *

A META region is not 'known' until it has been scanned + * once. */ - private class RootScanner implements Runnable { - private final Text cols[] = { - ROOT_COLUMN_FAMILY - }; - private final Text firstRow = new Text(); - private HRegionInterface rootServer; + private abstract class BaseScanner implements Runnable { + private final Text FIRST_ROW = new Text(); - public RootScanner() { - rootServer = null; + protected boolean scanRegion(final MetaRegion region) + throws IOException { + boolean scannedRegion = false; + HRegionInterface server = null; + long scannerId = -1L; + if (LOG.isDebugEnabled()) { + LOG.debug("scanning meta region " + region.regionName); + } + + try { + server = client.getHRegionConnection(region.server); + scannerId = server.openScanner(region.regionName, METACOLUMNS, FIRST_ROW); + + DataInputBuffer inbuf = new DataInputBuffer(); + while (true) { + TreeMap results = new TreeMap(); + HStoreKey key = new HStoreKey(); + + LabelledData[] values = server.next(scannerId, key); + + if (values.length == 0) { + break; + } + + for (int i = 0; i < values.length; i++) { + results.put(values[i].getLabel(), values[i].getData().get()); + } + + HRegionInfo info = getRegionInfo(COL_REGIONINFO, results, inbuf); + String serverName = getServerName(COL_SERVER, results); + long startCode = getStartCode(COL_STARTCODE, results); + + if(LOG.isDebugEnabled()) { + LOG.debug("row: " + info.toString() + ", server: " + serverName + + ", startCode: " + startCode); + } + + // Note Region has been assigned. + checkAssigned(info, serverName, startCode); + + scannedRegion = true; + } + } finally { + try { + if (scannerId != -1L) { + server.close(scannerId); + } + } catch (IOException e) { + e.printStackTrace(); + } + scannerId = -1L; + } + if (LOG.isDebugEnabled()) { + LOG.debug("scan of meta region " + region.regionName + " complete"); + } + return scannedRegion; } - public void run() { - while((!closed)) { - rootScanned = false; - waitForRootRegion(); + protected HRegionInfo getRegionInfo(final Text key, + final TreeMap data, final DataInputBuffer in) + throws IOException { + byte[] bytes = data.get(key); + if (bytes == null || bytes.length == 0) { + throw new IOException("no value for " + key); + } + in.reset(bytes, bytes.length); + HRegionInfo info = new HRegionInfo(); + info.readFields(in); + return info; + } + + protected String getServerName(final Text key, + final TreeMap data) + throws UnsupportedEncodingException { + byte [] bytes = data.get(key); + String name = (bytes != null && bytes.length != 0)? + new String(bytes, UTF8_ENCODING): null; + return (name != null)? name.trim(): name; + } - rootServer = null; - long scannerId = -1L; - - try { - rootServer = client.getHRegionConnection(rootRegionLocation); - scannerId = rootServer.openScanner(HGlobals.rootRegionInfo.regionName, cols, firstRow); - - } catch(IOException iex) { - try { - iex.printStackTrace(); - if(scannerId != -1L) { - rootServer.close(scannerId); - } - - } catch(IOException iex2) { - } - closed = true; - break; + protected long getStartCode(final Text key, + final TreeMap data) + throws NumberFormatException, UnsupportedEncodingException { + long startCode = -1L; + byte [] bytes = data.get(key); + if(bytes != null && bytes.length != 0) { + startCode = Long.valueOf(new String(bytes, UTF8_ENCODING).trim()); + } + return startCode; + } + + protected void checkAssigned(final HRegionInfo info, + final String serverName, final long startCode) { + HServerInfo storedInfo = null; + if(serverName != null) { + storedInfo = serversToServerInfo.get(serverName); + } + if(storedInfo == null || storedInfo.getStartCode() != startCode) { + // The current assignment is no good; load the region. + unassignedRegions.put(info.regionName, info); + assignAttempts.put(info.regionName, 0L); + if(LOG.isDebugEnabled()) { + LOG.debug("region unassigned: " + info.regionName); } - try { - LOG.debug("starting root region scan"); - - DataInputBuffer inbuf = new DataInputBuffer(); - while(true) { - TreeMap results = new TreeMap(); - HStoreKey key = new HStoreKey(); - LabelledData[] values = rootServer.next(scannerId, key); - if(values.length == 0) { - break; - } - for(int i = 0; i < values.length; i++) { - results.put(values[i].getLabel(), values[i].getData().get()); - } - byte[] bytes = results.get(ROOT_COL_REGIONINFO); - if(bytes == null || bytes.length == 0) { - LOG.fatal("no value for " + ROOT_COL_REGIONINFO); - stop(); - } - inbuf.reset(bytes, bytes.length); - HRegionInfo info = new HRegionInfo(); - info.readFields(inbuf); - - String serverName = null; - bytes = results.get(ROOT_COL_SERVER); - if(bytes != null && bytes.length != 0) { - serverName = new String(bytes, UTF8_ENCODING); - } - - long startCode = -1L; - bytes = results.get(ROOT_COL_STARTCODE); - if(bytes != null && bytes.length != 0) { - startCode = Long.valueOf(new String(bytes, UTF8_ENCODING)); - } - - // Note META region to load. - - HServerInfo storedInfo = null; - if(serverName != null) { - serversToServerInfo.get(serverName); - } - if(storedInfo == null - || storedInfo.getStartCode() != startCode) { - - // The current assignment is no good; load the region. - - unassignedRegions.put(info.regionName, info); - assignAttempts.put(info.regionName, 0L); - - LOG.debug("region unassigned: " + info.regionName); - } - + } + } + } + + /** + * Scanner for the ROOT HRegion. + */ + private class RootScanner extends BaseScanner { + public void run() { + if (LOG.isDebugEnabled()) { + LOG.debug("Running ROOT scanner"); + } + try { + while(!closed) { + // rootRegionLocation will be filled in when we get an 'open region' + // regionServerReport message from the HRegionServer that has been + // allocated the ROOT region below. If we get back false, then + // HMaster has closed. + if (waitForRootRegionOrClose()) { + continue; + } + rootScanned = false; + // Make a MetaRegion instance for ROOT region to pass scanRegion. + MetaRegion mr = new MetaRegion(); + mr.regionName = HGlobals.rootRegionInfo.regionName; + mr.server = HMaster.this.rootRegionLocation; + mr.startKey = null; + if (scanRegion(mr)) { numMetaRegions += 1; } - - } catch(Exception iex) { - iex.printStackTrace(); - - } finally { + rootScanned = true; try { - if(scannerId != -1L) { - rootServer.close(scannerId); + if (LOG.isDebugEnabled()) { + LOG.debug("RootScanner going to sleep"); } - - } catch(IOException iex2) { + Thread.sleep(metaRescanInterval); + if (LOG.isDebugEnabled()) { + LOG.debug("RootScanner woke up"); + } + } catch(InterruptedException e) { + // Catch and go around again. If interrupt, its spurious or we're + // being shutdown. Go back up to the while test. } - scannerId = -1L; } + } catch(IOException e) { + e.printStackTrace(); + closed = true; } - rootScanned = true; - try { - Thread.sleep(metaRescanInterval); - - } catch(InterruptedException e) { - } + LOG.debug("ROOT scanner exiting"); } } private RootScanner rootScanner; private Thread rootScannerThread; - /** Contains information the meta scanner needs to process a "new" meta region */ private class MetaRegion { public HServerAddress server; public Text regionName; @@ -245,165 +306,89 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter private boolean allMetaRegionsScanned; /** - * MetaScanner scans a region either in the META table. + * MetaScanner META table. * - * When a meta server comes on line, a MetaRegion object is queued up by - * regionServerReport() and this thread wakes up. + * When a META server comes on line, a MetaRegion object is + * queued up by regionServerReport() and this thread wakes up. * * It's important to do this work in a separate thread, or else the blocking * action would prevent other work from getting done. */ - private class MetaScanner implements Runnable { - private final Text cols[] = { - META_COLUMN_FAMILY - }; - private final Text firstRow = new Text(); - - public MetaScanner() { - } - - private void scanRegion(MetaRegion region) { - HRegionInterface server = null; - long scannerId = -1L; - - LOG.debug("scanning meta region: " + region.regionName); - - try { - server = client.getHRegionConnection(region.server); - scannerId = server.openScanner(region.regionName, cols, firstRow); - - } catch(IOException iex) { - try { - if(scannerId != -1L) { - server.close(scannerId); - scannerId = -1L; - } - stop(); - - } catch(IOException iex2) { - } - return; - } - - DataInputBuffer inbuf = new DataInputBuffer(); - try { - while(true) { - TreeMap results = new TreeMap(); - HStoreKey key = new HStoreKey(); - - LabelledData[] values = server.next(scannerId, key); - - if(values.length == 0) { - break; - } - - for(int i = 0; i < values.length; i++) { - results.put(values[i].getLabel(), values[i].getData().get()); - } - - byte bytes[] = results.get(META_COL_REGIONINFO); - if(bytes == null || bytes.length == 0) { - LOG.fatal("no value for " + META_COL_REGIONINFO); - stop(); - } - inbuf.reset(bytes, bytes.length); - HRegionInfo info = new HRegionInfo(); - info.readFields(inbuf); - - String serverName = null; - bytes = results.get(META_COL_SERVER); - if(bytes != null && bytes.length != 0) { - serverName = new String(bytes, UTF8_ENCODING); - } - - long startCode = -1L; - bytes = results.get(META_COL_STARTCODE); - if(bytes != null && bytes.length != 0) { - startCode = Long.valueOf(new String(bytes, UTF8_ENCODING)); - } - - // Note HRegion to load. - - HServerInfo storedInfo = null; - if(serverName != null) { - serversToServerInfo.get(serverName); - } - if(storedInfo == null - || storedInfo.getStartCode() != startCode) { - - // The current assignment is no good; load the region. - - unassignedRegions.put(info.regionName, info); - assignAttempts.put(info.regionName, 0L); - LOG.debug("region unassigned: " + info.regionName); - } - } - - } catch(Exception iex) { - iex.printStackTrace(); - - } finally { - try { - if(scannerId != -1L) { - server.close(scannerId); - } - - } catch(IOException iex2) { - iex2.printStackTrace(); - } - scannerId = -1L; - } - } - + private class MetaScanner extends BaseScanner { public void run() { - while((!closed)) { + while (!closed) { + if (LOG.isDebugEnabled()) { + LOG.debug("Running META scanner"); + } MetaRegion region = null; - - while(region == null) { - synchronized(metaRegionsToScan) { - if(metaRegionsToScan.size() != 0) { + while (region == null && !closed) { + synchronized (metaRegionsToScan) { + if (metaRegionsToScan.size() != 0) { region = metaRegionsToScan.remove(0); } - if(region == null) { + if (region == null) { try { + if (LOG.isDebugEnabled()) { + LOG.debug("MetaScanner going into wait"); + } metaRegionsToScan.wait(); - - } catch(InterruptedException e) { + if (LOG.isDebugEnabled()) { + LOG.debug("MetaScanner woke up"); + } + } catch (InterruptedException e) { + // Catch and go around again. We've been woken because there + // are new meta regions available or because we are being + // shut down. } } } } - - scanRegion(region); - if(closed) { - break; - } - knownMetaRegions.put(region.startKey, region); - if(rootScanned && knownMetaRegions.size() == numMetaRegions) { - LOG.debug("all meta regions scanned"); - allMetaRegionsScanned = true; - metaRegionsScanned(); + if (closed) { + continue; } + try { + scanRegion(region); + knownMetaRegions.put(region.startKey, region); + if (rootScanned && knownMetaRegions.size() == numMetaRegions) { + if(LOG.isDebugEnabled()) { + LOG.debug("all meta regions scanned"); + } + allMetaRegionsScanned = true; + metaRegionsScanned(); + } - do { - try { - Thread.sleep(metaRescanInterval); - - } catch(InterruptedException ex) { - } - if(!allMetaRegionsScanned) { - break; // A meta region must have split - } - - // Rescan the known meta regions every so often - - Vector v = new Vector(); - v.addAll(knownMetaRegions.values()); - - for(Iterator i = v.iterator(); i.hasNext(); ) { - scanRegion(i.next()); - } - } while(true); + do { + try { + if (LOG.isDebugEnabled()) { + LOG.debug("Sleep for meta rescan interval"); + } + Thread.sleep(metaRescanInterval); + if (LOG.isDebugEnabled()) { + LOG.debug("Sleep for meta rescan interval"); + } + } catch(InterruptedException ex) { + // Catch and go around again. + } + if(!allMetaRegionsScanned // A meta region must have split + || closed) { // We're shutting down + break; + } + + // Rescan the known meta regions every so often + Vector v = new Vector(); + v.addAll(knownMetaRegions.values()); + for(Iterator i = v.iterator(); i.hasNext(); ) { + scanRegion(i.next()); + } + } while(true); + + } catch(IOException e) { + e.printStackTrace(); + closed = true; + } + } + if(LOG.isDebugEnabled()) { + LOG.debug("META scanner exiting"); } } @@ -412,11 +397,19 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter } public synchronized void waitForMetaScan() { - while(!allMetaRegionsScanned) { + while(!closed && !allMetaRegionsScanned) { try { + if (LOG.isDebugEnabled()) { + LOG.debug("Wait for all meta regions scanned"); + } wait(); - + if (LOG.isDebugEnabled()) { + LOG.debug("Wake from wait for all meta regions scanned"); + } } catch(InterruptedException e) { + if (LOG.isDebugEnabled()) { + LOG.debug("Wake from wait for all meta regions scanned (IE)"); + } } } } @@ -425,11 +418,6 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter private MetaScanner metaScanner; private Thread metaScannerThread; - // Client processing - - private ClientProcessor clientProcessor; - private Thread clientProcessorThread; - // The 'unassignedRegions' table maps from a region name to a HRegionInfo record, // which includes the region's table, its id, and its start/end keys. // @@ -449,9 +437,8 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter private SortedMap> killList; - // 'serversToServerInfo' maps from the String to its HServerInfo - - private SortedMap serversToServerInfo; + private SortedMap serversToServerInfo = + Collections.synchronizedSortedMap(new TreeMap()); /** Build the HMaster out of a raw configuration item. */ public HMaster(Configuration conf) throws IOException { @@ -468,7 +455,8 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter * * @throws IOException */ - public HMaster(Path dir, HServerAddress address, Configuration conf) throws IOException { + public HMaster(Path dir, HServerAddress address, Configuration conf) + throws IOException { this.closed = true; this.dir = dir; this.conf = conf; @@ -481,12 +469,10 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter fs.mkdirs(dir); } - Path rootRegionDir = HStoreFile.getHRegionDir(dir, HGlobals.rootRegionInfo.regionName); + Path rootRegionDir = + HStoreFile.getHRegionDir(dir, HGlobals.rootRegionInfo.regionName); if(! fs.exists(rootRegionDir)) { - LOG.debug("bootstrap: creating root and meta regions"); - - // Bootstrap! Need to create the root region and the first meta region. - + LOG.info("bootstrap: creating ROOT and first META regions"); try { HRegion root = createNewHRegion(HGlobals.rootTableDesc, 0L); HRegion meta = createNewHRegion(HGlobals.metaTableDesc, 1L); @@ -501,13 +487,16 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter } } + this.threadWakeFrequency = conf.getLong(THREAD_WAKE_FREQUENCY, 10 * 1000); this.maxRegionOpenTime = conf.getLong("hbase.hbasemaster.maxregionopen", 30 * 1000); this.msgQueue = new Vector(); - this.serverLeases = new Leases(conf.getLong("hbase.master.lease.period", 30 * 1000), + this.serverLeases = new Leases( + conf.getLong("hbase.master.lease.period", 30 * 1000), conf.getLong("hbase.master.lease.thread.wakefrequency", 15 * 1000)); this.server = RPC.getServer(this, address.getBindAddress(), - address.getPort(), conf.getInt("hbase.hregionserver.handler.count", 10), false, conf); + address.getPort(), conf.getInt("hbase.regionserver.handler.count", 10), + false, conf); // The rpc-server port can be ephemeral... ensure we have the correct info @@ -539,11 +528,6 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter this.metaScanner = new MetaScanner(); this.metaScannerThread = new Thread(metaScanner, "HMaster.metaScanner"); - // Process updates to meta asychronously - - this.clientProcessor = new ClientProcessor(); - this.clientProcessorThread = new Thread(clientProcessor, "HMaster.clientProcessor"); - this.unassignedRegions = Collections.synchronizedSortedMap(new TreeMap()); @@ -558,75 +542,118 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter Collections.synchronizedSortedMap( new TreeMap>()); - this.serversToServerInfo = - Collections.synchronizedSortedMap(new TreeMap()); - // We're almost open for business this.closed = false; + LOG.info("HMaster initialized on " + address.toString()); + } + + public boolean isMasterRunning() { + return !closed; + } + + public void run() { + Thread.currentThread().setName("HMaster"); try { // Start things up - this.rootScannerThread.start(); this.metaScannerThread.start(); - this.clientProcessorThread.start(); // Start the server last so everything else is running before we start // receiving requests - this.server.start(); - } catch(IOException e) { - // Something happend during startup. Shut things down. - + // Something happened during startup. Shut things down. this.closed = true; - throw e; + e.printStackTrace(); } - LOG.info("HMaster started"); - } - /** Turn off the HMaster. Turn off all the threads, close files, etc. */ - public void stop() throws IOException { - closed = true; + // Main processing loop + for(PendingOperation op = null; !closed; ) { + synchronized(msgQueue) { + while(msgQueue.size() == 0 && serversToServerInfo.size() != 0) { + try { + msgQueue.wait(threadWakeFrequency); + } catch(InterruptedException iex) { + } + } + if(msgQueue.size() == 0 || closed) { + continue; + } + op = msgQueue.remove(msgQueue.size()-1); + } + try { + if (LOG.isDebugEnabled()) { + LOG.debug("Processing " + op.toString()); + } + op.process(); + } catch(Exception ex) { + msgQueue.insertElementAt(op, 0); + } + } + /* + * Clean up and close up shop + */ + + // Wake other threads so they notice the close + + rootScannerThread.interrupt(); + metaScannerThread.interrupt(); + server.stop(); // Stop server + serverLeases.close(); // Turn off the lease monitor try { - client.close(); - + fs.close(); + client.close(); // Shut down the client } catch(IOException iex) { + // Print if ever there is an interrupt (Just for kicks. Remove if it + // ever happens). + iex.printStackTrace(); + } + + // Join up with all threads + + try { + // Wait for the root scanner to finish. + rootScannerThread.join(); + } catch(Exception iex) { + // Print if ever there is an interrupt (Just for kicks. Remove if it + // ever happens). + iex.printStackTrace(); + } + try { + // Join the thread till it finishes. + metaScannerThread.join(); + } catch(Exception iex) { + // Print if ever there is an interrupt (Just for kicks. Remove if it + // ever happens). + iex.printStackTrace(); + } + try { + // Join until its finished. TODO: Maybe do in parallel in its own thread + // as is done in TaskTracker if its taking a long time to go down. + server.join(); + } catch(InterruptedException iex) { + // Print if ever there is an interrupt (Just for kicks. Remove if it + // ever happens). + iex.printStackTrace(); + } + if(LOG.isDebugEnabled()) { + LOG.debug("HMaster main thread exiting"); } - server.stop(); - LOG.info("shutting down HMaster"); } - /** returns the HMaster server address */ - public HServerAddress getMasterAddress() { - return address; - } - - /** Call this to wait for everything to finish */ - public void join() { - try { - server.join(); - - } catch(InterruptedException iex) { + /** + * Turn off the HMaster. Sets a flag so that the main thread know to shut + * things down in an orderly fashion. + */ + public void shutdown() throws IOException { + closed = true; + synchronized(msgQueue) { + msgQueue.clear(); // Empty the queue + msgQueue.notifyAll(); // Wake main thread } - try { - clientProcessorThread.join(); - - } catch(Exception iex) { - } - try { - metaScannerThread.join(); - - } catch(Exception iex) { - } - try { - rootScannerThread.join(); - - } catch(Exception iex) { - } - LOG.info("HMaster stopped"); } ////////////////////////////////////////////////////////////////////////////// @@ -635,20 +662,19 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter /** HRegionServers call this method upon startup. */ public void regionServerStartup(HServerInfo serverInfo) throws IOException { - String server = serverInfo.getServerAddress().toString(); + String server = serverInfo.getServerAddress().toString().trim(); HServerInfo storedInfo = null; - LOG.debug("received start message from: " + server); + if(LOG.isDebugEnabled()) { + LOG.debug("received start message from: " + server); + } // If we get the startup message but there's an old server by that // name, then we can timeout the old one right away and register // the new one. - - storedInfo = serversToServerInfo.get(server); - - if(storedInfo != null) { - serversToServerInfo.remove(server); + storedInfo = serversToServerInfo.remove(server); + if(storedInfo != null && !closed) { synchronized(msgQueue) { msgQueue.add(new PendingServerShutdown(storedInfo)); msgQueue.notifyAll(); @@ -659,19 +685,33 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter serversToServerInfo.put(server, serverInfo); - Text serverLabel = new Text(server); - serverLeases.createLease(serverLabel, serverLabel, new ServerExpirer(server)); + if(!closed) { + Text serverLabel = new Text(server); + serverLeases.createLease(serverLabel, serverLabel, new ServerExpirer(server)); + } } /** HRegionServers call this method repeatedly. */ public HMsg[] regionServerReport(HServerInfo serverInfo, HMsg msgs[]) throws IOException { - String server = serverInfo.getServerAddress().toString(); + String server = serverInfo.getServerAddress().toString().trim(); + + if(closed) { + // We're shutting down. Tell the server to go away. + serversToServerInfo.remove(server); + + HMsg returnMsgs[] = { + new HMsg(HMsg.MSG_REGIONSERVER_STOP) + }; + return returnMsgs; + } HServerInfo storedInfo = serversToServerInfo.get(server); if(storedInfo == null) { - LOG.debug("received server report from unknown server: " + server); + if(LOG.isDebugEnabled()) { + LOG.debug("received server report from unknown server: " + server); + } // The HBaseMaster may have been restarted. // Tell the RegionServer to start over and call regionServerStartup() @@ -691,10 +731,13 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter // // The answer is to ask A to shut down for good. - LOG.debug("region server race condition detected: " + server); + if(LOG.isDebugEnabled()) { + LOG.debug("region server race condition detected: " + server); + } - HMsg returnMsgs[] = new HMsg[1]; - returnMsgs[0] = new HMsg(HMsg.MSG_REGIONSERVER_ALREADY_RUNNING); + HMsg returnMsgs[] = { + new HMsg(HMsg.MSG_REGIONSERVER_STOP) + }; return returnMsgs; } else { @@ -720,7 +763,9 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter // Process the kill list - TreeMap regionsToKill = killList.get(info.toString()); + TreeMap regionsToKill = + killList.remove(info.getServerAddress().toString()); + if(regionsToKill != null) { for(Iterator i = regionsToKill.values().iterator(); i.hasNext(); ) { @@ -741,8 +786,10 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter if(regionInfo == null) { - LOG.debug("region server " + info.getServerAddress().toString() - + "should not have opened region " + region.regionName); + if(LOG.isDebugEnabled()) { + LOG.debug("region server " + info.getServerAddress().toString() + + "should not have opened region " + region.regionName); + } // This Region should not have been opened. // Ask the server to shut it down, but don't report it as closed. @@ -753,8 +800,10 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter } else { - LOG.debug(info.getServerAddress().toString() + " serving " - + region.regionName); + if(LOG.isDebugEnabled()) { + LOG.debug(info.getServerAddress().toString() + " serving " + + region.regionName); + } // Remove from unassigned list so we don't assign it to someone else @@ -797,8 +846,10 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter break; case HMsg.MSG_REPORT_CLOSE: - LOG.debug(info.getServerAddress().toString() + " no longer serving " - + region.regionName); + if(LOG.isDebugEnabled()) { + LOG.debug(info.getServerAddress().toString() + " no longer serving " + + region.regionName); + } if(region.regionName.compareTo(HGlobals.rootRegionInfo.regionName) == 0) { // Root region rootRegionLocation = null; @@ -835,7 +886,9 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter break; case HMsg.MSG_NEW_REGION: - LOG.debug("new region " + region.regionName); + if(LOG.isDebugEnabled()) { + LOG.debug("new region " + region.regionName); + } if(region.regionName.find(META_TABLE_NAME.toString()) == 0) { // A meta region has split. @@ -865,15 +918,17 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter long now = System.currentTimeMillis(); for(Iterator it = unassignedRegions.keySet().iterator(); - it.hasNext(); ) { + it.hasNext(); ) { Text curRegionName = it.next(); HRegionInfo regionInfo = unassignedRegions.get(curRegionName); long assignedTime = assignAttempts.get(curRegionName); if(now - assignedTime > maxRegionOpenTime) { - LOG.debug("assigning region " + regionInfo.regionName + " to server " - + info.getServerAddress().toString()); + if(LOG.isDebugEnabled()) { + LOG.debug("assigning region " + regionInfo.regionName + " to server " + + info.getServerAddress().toString()); + } returnMsgs.add(new HMsg(HMsg.MSG_REGION_OPEN, regionInfo)); @@ -893,12 +948,44 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter notifyAll(); } - private synchronized void waitForRootRegion() { - while(rootRegionLocation == null) { + /** + * Wait until rootRegionLocation has been set or until the + * closed flag has been set. + * @return True if rootRegionLocation was populated. + */ + private synchronized boolean waitForRootRegionOrClose() { + while (!closed && rootRegionLocation == null) { try { + if (LOG.isDebugEnabled()) { + LOG.debug("Wait for root region (or close)"); + } wait(); - + if (LOG.isDebugEnabled()) { + LOG.debug("Wake from wait for root region (or close)"); + } } catch(InterruptedException e) { + if (LOG.isDebugEnabled()) { + LOG.debug("Wake from wait for root region (or close) (IE)"); + } + } + } + return this.rootRegionLocation == null; + } + + private synchronized void waitForRootRegion() { + while (rootRegionLocation == null) { + try { + if (LOG.isDebugEnabled()) { + LOG.debug("Wait for root region"); + } + wait(); + if (LOG.isDebugEnabled()) { + LOG.debug("Wake from wait for root region"); + } + } catch(InterruptedException e) { + if (LOG.isDebugEnabled()) { + LOG.debug("Wake from wait for root region (IE)"); + } } } } @@ -907,37 +994,9 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter // Some internal classes to manage msg-passing and client operations ////////////////////////////////////////////////////////////////////////////// - private class ClientProcessor implements Runnable { - public ClientProcessor() { - } - - public void run() { - while(!closed) { - PendingOperation op = null; - - synchronized(msgQueue) { - while(msgQueue.size() == 0) { - try { - msgQueue.wait(); - - } catch(InterruptedException iex) { - } - } - op = msgQueue.remove(msgQueue.size()-1); - } - try { - op.process(); - - } catch(Exception ex) { - msgQueue.insertElementAt(op, 0); - } - } - } - } - private abstract class PendingOperation { protected final Text[] columns = { - META_COLUMN_FAMILY + COLUMN_FAMILY }; protected final Text startRow = new Text(); protected long clientId; @@ -981,16 +1040,25 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter results.put(values[i].getLabel(), values[i].getData().get()); } - String serverName = - new String(results.get(META_COL_SERVER), UTF8_ENCODING); + byte[] bytes = results.get(COL_SERVER); + String serverName = null; + if(bytes == null || bytes.length == 0) { + // No server + continue; + } + serverName = new String(bytes, UTF8_ENCODING); if(deadServer.compareTo(serverName) != 0) { // This isn't the server you're looking for - move along continue; } - long startCode = - Long.valueOf(new String(results.get(META_COL_STARTCODE), UTF8_ENCODING)); + bytes = results.get(COL_STARTCODE); + if(bytes == null || bytes.length == 0) { + // No start code + continue; + } + long startCode = Long.valueOf(new String(bytes, UTF8_ENCODING)); if(oldStartCode != startCode) { // Close but no cigar @@ -999,12 +1067,17 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter // Bingo! Found it. - byte hRegionInfoBytes[] = results.get(META_COL_REGIONINFO); - inbuf.reset(hRegionInfoBytes, hRegionInfoBytes.length); + bytes = results.get(COL_REGIONINFO); + if(bytes == null || bytes.length == 0) { + throw new IOException("no value for " + COL_REGIONINFO); + } + inbuf.reset(bytes, bytes.length); HRegionInfo info = new HRegionInfo(); info.readFields(inbuf); - LOG.debug(serverName + " was serving " + info.regionName); + if(LOG.isDebugEnabled()) { + LOG.debug(serverName + " was serving " + info.regionName); + } // Add to our to do lists @@ -1029,8 +1102,8 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter for(int i = 0; i < toDoList.size(); i++) { long lockid = server.startUpdate(regionName, clientId, toDoList.get(i).getRow()); - server.delete(regionName, clientId, lockid, META_COL_SERVER); - server.delete(regionName, clientId, lockid, META_COL_STARTCODE); + server.delete(regionName, clientId, lockid, COL_SERVER); + server.delete(regionName, clientId, lockid, COL_STARTCODE); server.commit(regionName, clientId, lockid); } @@ -1049,7 +1122,9 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter } public void process() throws IOException { - LOG.debug("server shutdown: " + deadServer); + if(LOG.isDebugEnabled()) { + LOG.debug("server shutdown: " + deadServer); + } // Scan the ROOT region @@ -1107,30 +1182,40 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter metaScanner.waitForMetaScan(); - LOG.debug("region closed: " + regionInfo.regionName); + if(LOG.isDebugEnabled()) { + LOG.debug("region closed: " + regionInfo.regionName); + } // Mark the Region as unavailable in the appropriate meta table Text metaRegionName; HRegionInterface server; - if(rootRegion) { + if (rootRegion) { metaRegionName = HGlobals.rootRegionInfo.regionName; waitForRootRegion(); // Make sure root region available server = client.getHRegionConnection(rootRegionLocation); } else { - Text metaStartRow = knownMetaRegions.headMap(regionInfo.regionName).lastKey(); - MetaRegion r = knownMetaRegions.get(metaStartRow); + MetaRegion r = null; + if(knownMetaRegions.containsKey(regionInfo.regionName)) { + r = knownMetaRegions.get(regionInfo.regionName); + + } else { + r = knownMetaRegions.get( + knownMetaRegions.headMap(regionInfo.regionName).lastKey()); + } metaRegionName = r.regionName; server = client.getHRegionConnection(r.server); } long lockid = server.startUpdate(metaRegionName, clientId, regionInfo.regionName); - server.delete(metaRegionName, clientId, lockid, META_COL_SERVER); - server.delete(metaRegionName, clientId, lockid, META_COL_STARTCODE); + server.delete(metaRegionName, clientId, lockid, COL_SERVER); + server.delete(metaRegionName, clientId, lockid, COL_STARTCODE); server.commit(metaRegionName, clientId, lockid); if(reassignRegion) { - LOG.debug("reassign region: " + regionInfo.regionName); + if(LOG.isDebugEnabled()) { + LOG.debug("reassign region: " + regionInfo.regionName); + } unassignedRegions.put(regionInfo.regionName, regionInfo); assignAttempts.put(regionInfo.regionName, 0L); @@ -1169,6 +1254,7 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter String.valueOf(info.getStartCode()).getBytes(UTF8_ENCODING)); } catch(UnsupportedEncodingException e) { + e.printStackTrace(); } } @@ -1180,7 +1266,10 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter metaScanner.waitForMetaScan(); - LOG.debug(regionName + " open on " + serverAddress.toString()); + if(LOG.isDebugEnabled()) { + LOG.debug(regionName + " open on " + + new String(serverAddress.get(), UTF8_ENCODING)); + } // Register the newly-available Region's location. @@ -1192,14 +1281,23 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter server = client.getHRegionConnection(rootRegionLocation); } else { - Text metaStartRow = knownMetaRegions.headMap(regionName).lastKey(); - MetaRegion r = knownMetaRegions.get(metaStartRow); + MetaRegion r = null; + if(knownMetaRegions.containsKey(regionName)) { + r = knownMetaRegions.get(regionName); + + } else { + r = knownMetaRegions.get( + knownMetaRegions.headMap(regionName).lastKey()); + } metaRegionName = r.regionName; server = client.getHRegionConnection(r.server); } + if(LOG.isDebugEnabled()) { + LOG.debug("updating row " + regionName + " in table " + metaRegionName); + } long lockid = server.startUpdate(metaRegionName, clientId, regionName); - server.put(metaRegionName, clientId, lockid, META_COL_SERVER, serverAddress); - server.put(metaRegionName, clientId, lockid, META_COL_STARTCODE, startCode); + server.put(metaRegionName, clientId, lockid, COL_SERVER, serverAddress); + server.put(metaRegionName, clientId, lockid, COL_STARTCODE, startCode); server.commit(metaRegionName, clientId, lockid); } } @@ -1208,7 +1306,15 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter // HMasterInterface ////////////////////////////////////////////////////////////////////////////// + /** returns the HMaster server address */ + public HServerAddress getMasterAddress() { + return address; + } + public void createTable(HTableDescriptor desc) throws IOException { + if (!isMasterRunning()) { + throw new IllegalStateException(MASTER_NOT_RUNNING); + } HRegionInfo newRegion = new HRegionInfo(rand.nextLong(), desc, null, null); // We can not access any meta region if they have not already been assigned @@ -1218,13 +1324,19 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter // 1. Check to see if table already exists - Text metaStartRow = knownMetaRegions.headMap(newRegion.regionName).lastKey(); - MetaRegion m = knownMetaRegions.get(metaStartRow); + MetaRegion m = null; + if(knownMetaRegions.containsKey(newRegion.regionName)) { + m = knownMetaRegions.get(newRegion.regionName); + + } else { + m = knownMetaRegions.get( + knownMetaRegions.headMap(newRegion.regionName).lastKey()); + } Text metaRegionName = m.regionName; HRegionInterface server = client.getHRegionConnection(m.server); - BytesWritable bytes = server.get(metaRegionName, desc.getName(), META_COL_REGIONINFO); + BytesWritable bytes = server.get(metaRegionName, desc.getName(), COL_REGIONINFO); if(bytes != null && bytes.getSize() != 0) { byte[] infoBytes = bytes.get(); DataInputBuffer inbuf = new DataInputBuffer(); @@ -1250,7 +1362,7 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter long clientId = rand.nextLong(); long lockid = server.startUpdate(metaRegionName, clientId, regionName); - server.put(metaRegionName, clientId, lockid, META_COL_REGIONINFO, + server.put(metaRegionName, clientId, lockid, COL_REGIONINFO, new BytesWritable(byteValue.toByteArray())); server.commit(metaRegionName, clientId, lockid); @@ -1263,7 +1375,9 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter unassignedRegions.put(regionName, info); assignAttempts.put(regionName, 0L); - LOG.debug("created table " + desc.getName()); + if(LOG.isDebugEnabled()) { + LOG.debug("created table " + desc.getName()); + } } /** @@ -1283,8 +1397,9 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter Path regionDir = HStoreFile.getHRegionDir(dir, info.regionName); fs.mkdirs(regionDir); - return new HRegion(dir, new HLog(fs, new Path(regionDir, "log"), conf), fs, - conf, info, null, null); + return new HRegion(dir, + new HLog(fs, new Path(regionDir, HREGION_LOGDIR_NAME), conf), + fs, conf, info, null, null); } /** @@ -1307,48 +1422,65 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter table.getRegionInfo().write(s); - meta.put(writeid, META_COL_REGIONINFO, bytes.toByteArray()); + meta.put(writeid, COL_REGIONINFO, new BytesWritable(bytes.toByteArray())); meta.commit(writeid); } public void deleteTable(Text tableName) throws IOException { - Text[] columns = { - META_COLUMN_FAMILY - }; + if (!isMasterRunning()) { + throw new IllegalStateException(MASTER_NOT_RUNNING); + } // We can not access any meta region if they have not already been assigned // and scanned. metaScanner.waitForMetaScan(); - for(Iterator it = knownMetaRegions.tailMap(tableName).values().iterator(); + Text firstMetaRegion = null; + if(knownMetaRegions.size() == 1) { + firstMetaRegion = knownMetaRegions.firstKey(); + + } else if(knownMetaRegions.containsKey(tableName)) { + firstMetaRegion = tableName; + + } else { + firstMetaRegion = knownMetaRegions.headMap(tableName).lastKey(); + } + for(Iterator it = + knownMetaRegions.tailMap(firstMetaRegion).values().iterator(); it.hasNext(); ) { // Find all the regions that make up this table - long clientId = rand.nextLong(); MetaRegion m = it.next(); HRegionInterface server = client.getHRegionConnection(m.server); + Vector rowsToDelete = new Vector(); + long scannerId = -1L; try { - scannerId = server.openScanner(m.regionName, columns, tableName); + scannerId = server.openScanner(m.regionName, METACOLUMNS, tableName); - Vector rowsToDelete = new Vector(); DataInputBuffer inbuf = new DataInputBuffer(); + byte[] bytes; while(true) { LabelledData[] values = null; HStoreKey key = new HStoreKey(); values = server.next(scannerId, key); - if(values.length == 0) { + if(values == null || values.length == 0) { break; } TreeMap results = new TreeMap(); for(int i = 0; i < values.length; i++) { - results.put(values[i].getLabel(), values[i].getData().get()); + bytes = new byte[values[i].getData().getSize()]; + System.arraycopy(values[i].getData().get(), 0, bytes, 0, bytes.length); + results.put(values[i].getLabel(), bytes); + } + bytes = results.get(COL_REGIONINFO); + if(bytes == null || bytes.length == 0) { + break; } - byte bytes[] = results.get(META_COL_REGIONINFO); inbuf.reset(bytes, bytes.length); HRegionInfo info = new HRegionInfo(); info.readFields(inbuf); @@ -1356,33 +1488,37 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter if(info.tableDesc.getName().compareTo(tableName) > 0) { break; // Beyond any more entries for this table } + + rowsToDelete.add(info.regionName); // Is it being served? - String serverName = - new String(results.get(META_COL_SERVER), UTF8_ENCODING); + bytes = results.get(COL_SERVER); + if(bytes != null && bytes.length != 0) { + String serverName = new String(bytes, UTF8_ENCODING); + + bytes = results.get(COL_STARTCODE); + if(bytes != null && bytes.length != 0) { + long startCode = Long.valueOf(new String(bytes, UTF8_ENCODING)); - long startCode = - Long.valueOf(new String(results.get(META_COL_STARTCODE), UTF8_ENCODING)); + HServerInfo s = serversToServerInfo.get(serverName); + if(s != null && s.getStartCode() == startCode) { - HServerInfo s = serversToServerInfo.get(serverName); - if(s != null && s.getStartCode() == startCode) { + // It is being served. + // Tell the server to stop it and not report back. - // It is being served. Tell the server to stop it and not report back - - TreeMap regionsToKill = killList.get(serverName); - if(regionsToKill == null) { - regionsToKill = new TreeMap(); + TreeMap regionsToKill = + killList.get(serverName); + + if(regionsToKill == null) { + regionsToKill = new TreeMap(); + } + regionsToKill.put(info.regionName, info); + killList.put(serverName, regionsToKill); + } } - regionsToKill.put(info.regionName, info); - killList.put(serverName, regionsToKill); } } - for(Iterator row = rowsToDelete.iterator(); row.hasNext(); ) { - long lockid = server.startUpdate(m.regionName, clientId, row.next()); - server.delete(m.regionName, clientId, lockid, columns[0]); - server.commit(m.regionName, clientId, lockid); - } } catch(IOException e) { e.printStackTrace(); @@ -1398,8 +1534,27 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter } scannerId = -1L; } + for(Iterator row = rowsToDelete.iterator(); row.hasNext(); ) { + Text rowName = row.next(); + if(LOG.isDebugEnabled()) { + LOG.debug("deleting columns in row: " + rowName); + } + try { + long clientId = rand.nextLong(); + long lockid = server.startUpdate(m.regionName, clientId, rowName); + server.delete(m.regionName, clientId, lockid, COL_REGIONINFO); + server.delete(m.regionName, clientId, lockid, COL_SERVER); + server.delete(m.regionName, clientId, lockid, COL_STARTCODE); + server.commit(m.regionName, clientId, lockid); + + } catch(Exception e) { + e.printStackTrace(); + } + } + } + if(LOG.isDebugEnabled()) { + LOG.debug("deleted table: " + tableName); } - LOG.debug("deleted table: " + tableName); } public HServerAddress findRootRegion() { @@ -1418,7 +1573,9 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter } public void leaseExpired() { - LOG.debug(server + " lease expired"); + if(LOG.isDebugEnabled()) { + LOG.debug(server + " lease expired"); + } HServerInfo storedInfo = serversToServerInfo.remove(server); synchronized(msgQueue) { @@ -1428,30 +1585,53 @@ public class HMaster implements HConstants, HMasterInterface, HMasterRegionInter } } - private static void printUsage() { + private static void printUsageAndExit() { System.err.println("Usage: java org.apache.hbase.HMaster " + - "[--bind=hostname:port]"); + "[--bind=hostname:port] start|stop"); + System.exit(0); } public static void main(String [] args) throws IOException { + if (args.length < 1) { + printUsageAndExit(); + } + Configuration conf = new HBaseConfiguration(); // Process command-line args. TODO: Better cmd-line processing // (but hopefully something not as painful as cli options). + final String addressArgKey = "--bind="; for (String cmd: args) { - if (cmd.equals("-h") || cmd.startsWith("--h")) { - printUsage(); - return; + if (cmd.startsWith(addressArgKey)) { + conf.set(MASTER_ADDRESS, cmd.substring(addressArgKey.length())); + continue; } - final String addressArgKey = "--bind="; - if (cmd.startsWith(addressArgKey)) { - conf.set(MASTER_ADDRESS, - cmd.substring(addressArgKey.length())); + if (cmd.equals("start")) { + try { + (new Thread(new HMaster(conf))).start(); + } catch (Throwable t) { + LOG.error( "Can not start master because "+ + StringUtils.stringifyException(t) ); + System.exit(-1); + } + break; } + + if (cmd.equals("stop")) { + try { + HClient client = new HClient(conf); + client.shutdown(); + } catch (Throwable t) { + LOG.error( "Can not stop master because " + + StringUtils.stringifyException(t) ); + System.exit(-1); + } + break; + } + + // Print out usage if we get to here. + printUsageAndExit(); } - - new HMaster(conf); } -} - +} \ No newline at end of file diff --git a/src/java/org/apache/hadoop/hbase/HMasterInterface.java b/src/java/org/apache/hadoop/hbase/HMasterInterface.java index 9da08778435..bb4c9ac06a2 100644 --- a/src/java/org/apache/hadoop/hbase/HMasterInterface.java +++ b/src/java/org/apache/hadoop/hbase/HMasterInterface.java @@ -20,10 +20,11 @@ import org.apache.hadoop.ipc.VersionedProtocol; import java.io.IOException; -/******************************************************************************* - * Clients interact with the HMasterInterface to gain access to meta-level HBase - * functionality, like finding an HRegionServer and creating/destroying tables. - ******************************************************************************/ +/** + * Clients interact with the HMasterInterface to gain access to meta-level + * HBase functionality, like finding an HRegionServer and creating/destroying + * tables. + */ public interface HMasterInterface extends VersionedProtocol { public static final long versionID = 1L; // initial version @@ -33,6 +34,11 @@ public interface HMasterInterface extends VersionedProtocol { public void createTable(HTableDescriptor desc) throws IOException; public void deleteTable(Text tableName) throws IOException; + + /** + * Shutdown an HBase cluster. + */ + public void shutdown() throws IOException; ////////////////////////////////////////////////////////////////////////////// // These are the method calls of last resort when trying to find an HRegion diff --git a/src/java/org/apache/hadoop/hbase/HMemcache.java b/src/java/org/apache/hadoop/hbase/HMemcache.java index d98a2fbf60a..23d3121df97 100644 --- a/src/java/org/apache/hadoop/hbase/HMemcache.java +++ b/src/java/org/apache/hadoop/hbase/HMemcache.java @@ -75,11 +75,15 @@ public class HMemcache { throw new IOException("Snapshot in progress!"); } if(memcache.size() == 0) { - LOG.debug("memcache empty. Skipping snapshot"); + if(LOG.isDebugEnabled()) { + LOG.debug("memcache empty. Skipping snapshot"); + } return retval; } - LOG.debug("starting memcache snapshot"); + if(LOG.isDebugEnabled()) { + LOG.debug("starting memcache snapshot"); + } retval.memcacheSnapshot = memcache; this.snapshot = memcache; @@ -87,7 +91,9 @@ public class HMemcache { memcache = new TreeMap(); retval.sequenceId = log.startCacheFlush(); - LOG.debug("memcache snapshot complete"); + if(LOG.isDebugEnabled()) { + LOG.debug("memcache snapshot complete"); + } return retval; @@ -108,7 +114,9 @@ public class HMemcache { if(snapshot == null) { throw new IOException("Snapshot not present!"); } - LOG.debug("deleting snapshot"); + if(LOG.isDebugEnabled()) { + LOG.debug("deleting snapshot"); + } for(Iterator> it = history.iterator(); it.hasNext(); ) { @@ -121,7 +129,9 @@ public class HMemcache { } this.snapshot = null; - LOG.debug("snapshot deleted"); + if(LOG.isDebugEnabled()) { + LOG.debug("snapshot deleted"); + } } finally { this.locker.writeLock().unlock(); @@ -133,15 +143,15 @@ public class HMemcache { * * Operation uses a write lock. */ - public void add(Text row, TreeMap columns, long timestamp) { + public void add(Text row, TreeMap columns, long timestamp) { this.locker.writeLock().lock(); try { for(Iterator it = columns.keySet().iterator(); it.hasNext(); ) { Text column = it.next(); - byte[] val = columns.get(column); + BytesWritable val = columns.get(column); HStoreKey key = new HStoreKey(row, column, timestamp); - memcache.put(key, new BytesWritable(val)); + memcache.put(key, val); } } finally { @@ -154,11 +164,11 @@ public class HMemcache { * * We only need a readlock here. */ - public byte[][] get(HStoreKey key, int numVersions) { - Vector results = new Vector(); + public BytesWritable[] get(HStoreKey key, int numVersions) { + Vector results = new Vector(); this.locker.readLock().lock(); try { - Vector result = get(memcache, key, numVersions-results.size()); + Vector result = get(memcache, key, numVersions-results.size()); results.addAll(0, result); for(int i = history.size()-1; i >= 0; i--) { @@ -174,22 +184,22 @@ public class HMemcache { return null; } else { - return (byte[][]) results.toArray(new byte[results.size()][]); + return results.toArray(new BytesWritable[results.size()]); } } finally { this.locker.readLock().unlock(); } } - + /** * Return all the available columns for the given key. The key indicates a * row and timestamp, but not a column name. * * The returned object should map column names to byte arrays (byte[]). */ - public TreeMap getFull(HStoreKey key) throws IOException { - TreeMap results = new TreeMap(); + public TreeMap getFull(HStoreKey key) throws IOException { + TreeMap results = new TreeMap(); this.locker.readLock().lock(); try { internalGetFull(memcache, key, results); @@ -205,7 +215,7 @@ public class HMemcache { } void internalGetFull(TreeMap map, HStoreKey key, - TreeMap results) { + TreeMap results) { SortedMap tailMap = map.tailMap(key); @@ -216,7 +226,7 @@ public class HMemcache { if(results.get(itCol) == null && key.matchesWithoutColumn(itKey)) { BytesWritable val = tailMap.get(itKey); - results.put(itCol, val.get()); + results.put(itCol, val); } else if(key.getRow().compareTo(itKey.getRow()) > 0) { break; @@ -233,8 +243,8 @@ public class HMemcache { * TODO - This is kinda slow. We need a data structure that allows for * proximity-searches, not just precise-matches. */ - Vector get(TreeMap map, HStoreKey key, int numVersions) { - Vector result = new Vector(); + Vector get(TreeMap map, HStoreKey key, int numVersions) { + Vector result = new Vector(); HStoreKey curKey = new HStoreKey(key.getRow(), key.getColumn(), key.getTimestamp()); SortedMap tailMap = map.tailMap(curKey); @@ -242,7 +252,7 @@ public class HMemcache { HStoreKey itKey = it.next(); if(itKey.matchesRowCol(curKey)) { - result.add(tailMap.get(itKey).get()); + result.add(tailMap.get(itKey)); curKey.setVersion(itKey.getTimestamp() - 1); } @@ -256,7 +266,7 @@ public class HMemcache { /** * Return a scanner over the keys in the HMemcache */ - public HScannerInterface getScanner(long timestamp, Text targetCols[], Text firstRow) + public HInternalScannerInterface getScanner(long timestamp, Text targetCols[], Text firstRow) throws IOException { return new HMemcacheScanner(timestamp, targetCols, firstRow); @@ -280,13 +290,14 @@ public class HMemcache { locker.readLock().lock(); try { this.backingMaps = new TreeMap[history.size() + 1]; - int i = 0; - for(Iterator> it = history.iterator(); - it.hasNext(); ) { - - backingMaps[i++] = it.next(); + + //NOTE: Since we iterate through the backing maps from 0 to n, we need + // to put the memcache first, the newest history second, ..., etc. + + backingMaps[0] = memcache; + for(int i = history.size() - 1; i > 0; i--) { + backingMaps[i] = history.elementAt(i); } - backingMaps[backingMaps.length - 1] = memcache; this.keyIterators = new Iterator[backingMaps.length]; this.keys = new HStoreKey[backingMaps.length]; @@ -295,7 +306,7 @@ public class HMemcache { // Generate list of iterators HStoreKey firstKey = new HStoreKey(firstRow); - for(i = 0; i < backingMaps.length; i++) { + for(int i = 0; i < backingMaps.length; i++) { if(firstRow.getLength() != 0) { keyIterators[i] = backingMaps[i].tailMap(firstKey).keySet().iterator(); @@ -327,7 +338,8 @@ public class HMemcache { * @return - true if this is the first row */ boolean findFirstRow(int i, Text firstRow) { - return ((firstRow.getLength() == 0) || (keys[i].getRow().equals(firstRow))); + return ((firstRow.getLength() == 0) + || (keys[i].getRow().toString().startsWith(firstRow.toString()))); } /** diff --git a/src/java/org/apache/hadoop/hbase/HMsg.java b/src/java/org/apache/hadoop/hbase/HMsg.java index 044a85395fd..a385130e662 100644 --- a/src/java/org/apache/hadoop/hbase/HMsg.java +++ b/src/java/org/apache/hadoop/hbase/HMsg.java @@ -28,7 +28,7 @@ public class HMsg implements Writable { public static final byte MSG_REGION_CLOSE = 2; public static final byte MSG_REGION_MERGE = 3; public static final byte MSG_CALL_SERVER_STARTUP = 4; - public static final byte MSG_REGIONSERVER_ALREADY_RUNNING = 5; + public static final byte MSG_REGIONSERVER_STOP = 5; public static final byte MSG_REGION_CLOSE_WITHOUT_REPORT = 6; public static final byte MSG_REGION_CLOSE_AND_DELETE = 7; diff --git a/src/java/org/apache/hadoop/hbase/HRegion.java b/src/java/org/apache/hadoop/hbase/HRegion.java index 0cbbcd98d92..80dae05e068 100644 --- a/src/java/org/apache/hadoop/hbase/HRegion.java +++ b/src/java/org/apache/hadoop/hbase/HRegion.java @@ -24,25 +24,37 @@ import org.apache.hadoop.conf.*; import java.io.*; import java.util.*; -/******************************************************************************* - * HRegion stores data for a certain region of a table. It stores all columns +/** + * HRegion stores data for a certain region of a table. It stores all columns * for each row. A given table consists of one or more HRegions. * - * We maintain multiple HStores for a single HRegion. + *

We maintain multiple HStores for a single HRegion. * - * An HStore is a set of rows with some column data; together, they make up all - * the data for the rows. + *

An HStore is a set of rows with some column data; together, + * they make up all the data for the rows. * - * Each HRegion has a 'startKey' and 'endKey'. + *

Each HRegion has a 'startKey' and 'endKey'. * - * The first is inclusive, the second is exclusive (except for the final region) - * The endKey of region 0 is the same as startKey for region 1 (if it exists). - * The startKey for the first region is null. - * The endKey for the final region is null. + *

The first is inclusive, the second is exclusive (except for + * the final region) The endKey of region 0 is the same as + * startKey for region 1 (if it exists). The startKey for the + * first region is null. The endKey for the final region is null. * - * The HStores have no locking built-in. All row-level locking and row-level - * atomicity is provided by the HRegion. - ******************************************************************************/ + *

The HStores have no locking built-in. All row-level locking + * and row-level atomicity is provided by the HRegion. + * + *

An HRegion is defined by its table and its key extent. + * + *

It consists of at least one HStore. The number of HStores should be + * configurable, so that data which is accessed together is stored in the same + * HStore. Right now, we approximate that by building a single HStore for + * each column family. (This config info will be communicated via the + * tabledesc.) + * + * The HTableDescriptor contains metainfo about the HRegion's table. + * regionName is a unique identifier for this HRegion. (startKey, endKey] + * defines the keyspace for this HRegion. + */ public class HRegion implements HConstants { static String SPLITDIR = "splits"; static String MERGEDIR = "merges"; @@ -53,8 +65,9 @@ public class HRegion implements HConstants { private static final Log LOG = LogFactory.getLog(HRegion.class); /** - * Merge two HRegions. They must be available on the current HRegionServer. - * Returns a brand-new active HRegion, also running on the current HRegionServer. + * Merge two HRegions. They must be available on the current + * HRegionServer. Returns a brand-new active HRegion, also + * running on the current HRegionServer. */ public static HRegion closeAndMerge(HRegion srcA, HRegion srcB) throws IOException { @@ -110,7 +123,9 @@ public class HRegion implements HConstants { // Flush each of the sources, and merge their files into a single // target for each column family. - LOG.debug("flushing and getting file names for region " + srcA.getRegionName()); + if(LOG.isDebugEnabled()) { + LOG.debug("flushing and getting file names for region " + srcA.getRegionName()); + } TreeSet alreadyMerged = new TreeSet(); TreeMap> filesToMerge = new TreeMap>(); @@ -124,7 +139,9 @@ public class HRegion implements HConstants { v.add(src); } - LOG.debug("flushing and getting file names for region " + srcB.getRegionName()); + if(LOG.isDebugEnabled()) { + LOG.debug("flushing and getting file names for region " + srcB.getRegionName()); + } for(Iterator it = srcB.flushcache(true).iterator(); it.hasNext(); ) { HStoreFile src = it.next(); @@ -136,7 +153,9 @@ public class HRegion implements HConstants { v.add(src); } - LOG.debug("merging stores"); + if(LOG.isDebugEnabled()) { + LOG.debug("merging stores"); + } for(Iterator it = filesToMerge.keySet().iterator(); it.hasNext(); ) { Text colFamily = it.next(); @@ -152,8 +171,10 @@ public class HRegion implements HConstants { // Now close the source HRegions for good, and repeat the above to take care // of any last-minute inserts - LOG.debug("flushing changes since start of merge for region " - + srcA.getRegionName()); + if(LOG.isDebugEnabled()) { + LOG.debug("flushing changes since start of merge for region " + + srcA.getRegionName()); + } filesToMerge.clear(); for(Iterator it = srcA.close().iterator(); it.hasNext(); ) { @@ -169,8 +190,10 @@ public class HRegion implements HConstants { } } - LOG.debug("flushing changes since start of merge for region " - + srcB.getRegionName()); + if(LOG.isDebugEnabled()) { + LOG.debug("flushing changes since start of merge for region " + + srcB.getRegionName()); + } for(Iterator it = srcB.close().iterator(); it.hasNext(); ) { HStoreFile src = it.next(); @@ -185,7 +208,9 @@ public class HRegion implements HConstants { } } - LOG.debug("merging changes since start of merge"); + if(LOG.isDebugEnabled()) { + LOG.debug("merging changes since start of merge"); + } for(Iterator it = filesToMerge.keySet().iterator(); it.hasNext(); ) { Text colFamily = it.next(); @@ -217,8 +242,8 @@ public class HRegion implements HConstants { TreeMap rowsToLocks = new TreeMap(); TreeMap locksToRows = new TreeMap(); TreeMap stores = new TreeMap(); - TreeMap> targetColumns - = new TreeMap>(); + TreeMap> targetColumns + = new TreeMap>(); HMemcache memcache = new HMemcache(); @@ -251,40 +276,25 @@ public class HRegion implements HConstants { ////////////////////////////////////////////////////////////////////////////// /** - * An HRegion is defined by its table and its key extent. - * - * It consists of at least one HStore. The number of HStores should be - * configurable, so that data which is accessed together is stored in the same - * HStore. Right now, we approximate that by building a single HStore for - * each column family. (This config info will be communicated via the - * tabledesc.) + * HRegion constructor. * - * The HLog is the outbound log for any updates to the HRegion. (There's a - * single HLog for all the HRegions on a single HRegionServer.) - * - * The HTableDescriptor contains metainfo about the HRegion's table. - * - * regionName is a unique identifier for this HRegion. - * - * (startKey, endKey] defines the keyspace for this HRegion. NULL values - * indicate we're at the start or end of the table. - * - * fs is the filesystem. regiondir is where the HRegion is stored. - * - * logfile is a logfile from the previous execution that's custom-computed for - * this HRegion. The HRegionServer computes and sorts the appropriate log - * info for this HRegion. - * - * conf is global configuration settings. - * - * If there are initial files (implying that the HRegion is new), then read - * them from the supplied path. - * - * If there is a previous log file (implying that the HRegion has been - * written-to before), then read it from the supplied path. + * @param log The HLog is the outbound log for any updates to the HRegion + * (There's a single HLog for all the HRegions on a single HRegionServer.) + * The log file is a logfile from the previous execution that's + * custom-computed for this HRegion. The HRegionServer computes and sorts the + * appropriate log info for this HRegion. If there is a previous log file + * (implying that the HRegion has been written-to before), then read it from + * the supplied path. + * @param fs is the filesystem. + * @param dir dir is where the HRegion is stored. + * @param conf is global configuration settings. + * @param initialFiles If there are initial files (implying that the HRegion + * is new), then read them from the supplied path. + * @throws IOException */ public HRegion(Path dir, HLog log, FileSystem fs, Configuration conf, - HRegionInfo regionInfo, Path initialFiles, Path oldLogFile) throws IOException { + HRegionInfo regionInfo, Path initialFiles, Path oldLogFile) + throws IOException { this.dir = dir; this.log = log; @@ -308,33 +318,28 @@ public class HRegion implements HConstants { } // Load in all the HStores. - for(Iterator it = this.regionInfo.tableDesc.families().iterator(); - it.hasNext(); ) { - + it.hasNext(); ) { Text colFamily = HStoreKey.extractFamily(it.next()); - stores.put(colFamily, new HStore(dir, this.regionInfo.regionName, colFamily, - this.regionInfo.tableDesc.getMaxVersions(), fs, oldLogFile, conf)); + stores.put(colFamily, new HStore(dir, this.regionInfo.regionName, + colFamily, this.regionInfo.tableDesc.getMaxVersions(), fs, + oldLogFile, conf)); } // Get rid of any splits or merges that were lost in-progress - Path splits = new Path(regiondir, SPLITDIR); - if(fs.exists(splits)) { + if (fs.exists(splits)) { fs.delete(splits); } - Path merges = new Path(regiondir, MERGEDIR); - if(fs.exists(merges)) { + if (fs.exists(merges)) { fs.delete(merges); } this.maxUnflushedEntries = conf.getInt("hbase.hregion.maxunflushed", 10000); // HRegion is ready to go! - this.writestate.writesOngoing = false; - LOG.info("region " + this.regionInfo.regionName + " available"); } @@ -445,8 +450,10 @@ public class HRegion implements HConstants { for(Iterator it = hstoreFilesToSplit.iterator(); it.hasNext(); ) { HStoreFile hsf = it.next(); - LOG.debug("splitting HStore " + hsf.getRegionName() + "/" + hsf.getColFamily() - + "/" + hsf.fileId()); + if(LOG.isDebugEnabled()) { + LOG.debug("splitting HStore " + hsf.getRegionName() + "/" + + hsf.getColFamily() + "/" + hsf.fileId()); + } HStoreFile dstA = new HStoreFile(conf, splits, regionAInfo.regionName, hsf.getColFamily(), Math.abs(rand.nextLong())); @@ -466,8 +473,10 @@ public class HRegion implements HConstants { HStoreFile hsf = it.next(); if(! alreadySplit.contains(hsf)) { - LOG.debug("splitting HStore " + hsf.getRegionName() + "/" + hsf.getColFamily() - + "/" + hsf.fileId()); + if(LOG.isDebugEnabled()) { + LOG.debug("splitting HStore " + hsf.getRegionName() + "/" + + hsf.getColFamily() + "/" + hsf.fileId()); + } HStoreFile dstA = new HStoreFile(conf, splits, regionAInfo.regionName, hsf.getColFamily(), Math.abs(rand.nextLong())); @@ -672,7 +681,9 @@ public class HRegion implements HConstants { } if(! shouldFlush) { - LOG.debug("not flushing cache for region " + this.regionInfo.regionName); + if(LOG.isDebugEnabled()) { + LOG.debug("not flushing cache for region " + this.regionInfo.regionName); + } return null; } else { @@ -715,7 +726,9 @@ public class HRegion implements HConstants { Vector internalFlushcache() throws IOException { Vector allHStoreFiles = new Vector(); - LOG.debug("flushing cache for region " + this.regionInfo.regionName); + if(LOG.isDebugEnabled()) { + LOG.debug("flushing cache for region " + this.regionInfo.regionName); + } // We pass the log to the HMemcache, so we can lock down // both simultaneously. We only have to do this for a moment: @@ -728,7 +741,9 @@ public class HRegion implements HConstants { // object stored that must be explicitly cleaned up using // a call to deleteSnapshot(). - LOG.debug("starting memcache snapshot"); + if(LOG.isDebugEnabled()) { + LOG.debug("starting memcache snapshot"); + } HMemcache.Snapshot retval = memcache.snapshotMemcacheForLog(log); TreeMap memcacheSnapshot = retval.memcacheSnapshot; @@ -745,7 +760,9 @@ public class HRegion implements HConstants { // A. Flush memcache to all the HStores. - LOG.debug("flushing memcache to HStores"); + if(LOG.isDebugEnabled()) { + LOG.debug("flushing memcache to HStores"); + } for(Iterator it = stores.values().iterator(); it.hasNext(); ) { HStore hstore = it.next(); @@ -760,7 +777,9 @@ public class HRegion implements HConstants { // and that all updates to the log for this regionName that have lower // log-sequence-ids can be safely ignored. - LOG.debug("writing flush cache complete to log"); + if(LOG.isDebugEnabled()) { + LOG.debug("writing flush cache complete to log"); + } log.completeCacheFlush(this.regionInfo.regionName, regionInfo.tableDesc.getName(), logCacheFlushId); @@ -768,11 +787,15 @@ public class HRegion implements HConstants { // C. Delete the now-irrelevant memcache snapshot; its contents have been // dumped to disk-based HStores. - LOG.debug("deleting memcache snapshot"); + if(LOG.isDebugEnabled()) { + LOG.debug("deleting memcache snapshot"); + } memcache.deleteSnapshot(); - LOG.debug("cache flush complete for region " + this.regionInfo.regionName); + if(LOG.isDebugEnabled()) { + LOG.debug("cache flush complete for region " + this.regionInfo.regionName); + } this.commitsSinceFlush = 0; return allHStoreFiles; @@ -783,8 +806,8 @@ public class HRegion implements HConstants { ////////////////////////////////////////////////////////////////////////////// /** Fetch a single data item. */ - public byte[] get(Text row, Text column) throws IOException { - byte results[][] = get(row, column, Long.MAX_VALUE, 1); + public BytesWritable get(Text row, Text column) throws IOException { + BytesWritable[] results = get(row, column, Long.MAX_VALUE, 1); if(results == null) { return null; @@ -794,12 +817,12 @@ public class HRegion implements HConstants { } /** Fetch multiple versions of a single data item */ - public byte[][] get(Text row, Text column, int numVersions) throws IOException { + public BytesWritable[] get(Text row, Text column, int numVersions) throws IOException { return get(row, column, Long.MAX_VALUE, numVersions); } /** Fetch multiple versions of a single data item, with timestamp. */ - public byte[][] get(Text row, Text column, long timestamp, int numVersions) + public BytesWritable[] get(Text row, Text column, long timestamp, int numVersions) throws IOException { if(writestate.closed) { @@ -825,11 +848,11 @@ public class HRegion implements HConstants { // Private implementation: get the value for the indicated HStoreKey - private byte[][] get(HStoreKey key, int numVersions) throws IOException { + private BytesWritable[] get(HStoreKey key, int numVersions) throws IOException { // Check the memcache - byte[][] result = memcache.get(key, numVersions); + BytesWritable[] result = memcache.get(key, numVersions); if(result != null) { return result; } @@ -855,10 +878,10 @@ public class HRegion implements HConstants { * determine which column groups are useful for that row. That would let us * avoid a bunch of disk activity. */ - public TreeMap getFull(Text row) throws IOException { + public TreeMap getFull(Text row) throws IOException { HStoreKey key = new HStoreKey(row, System.currentTimeMillis()); - TreeMap memResult = memcache.getFull(key); + TreeMap memResult = memcache.getFull(key); for(Iterator it = stores.keySet().iterator(); it.hasNext(); ) { Text colFamily = it.next(); HStore targetStore = stores.get(colFamily); @@ -871,13 +894,13 @@ public class HRegion implements HConstants { * Return an iterator that scans over the HRegion, returning the indicated * columns. This Iterator must be closed by the caller. */ - public HScannerInterface getScanner(Text cols[], Text firstRow) throws IOException { + public HInternalScannerInterface getScanner(Text[] cols, Text firstRow) throws IOException { TreeSet families = new TreeSet(); for(int i = 0; i < cols.length; i++) { families.add(HStoreKey.extractFamily(cols[i])); } - HStore storelist[] = new HStore[families.size()]; + HStore[] storelist = new HStore[families.size()]; int i = 0; for(Iterator it = families.iterator(); it.hasNext(); ) { Text family = it.next(); @@ -917,21 +940,11 @@ public class HRegion implements HConstants { * This method really just tests the input, then calls an internal localput() * method. */ - public void put(long lockid, Text targetCol, byte[] val) throws IOException { - if(val.length == HStoreKey.DELETE_BYTES.length) { - boolean matches = true; - for(int i = 0; i < val.length; i++) { - if(val[i] != HStoreKey.DELETE_BYTES[i]) { - matches = false; - break; - } - } - - if(matches) { + public void put(long lockid, Text targetCol, BytesWritable val) throws IOException { + if(val.getSize() == DELETE_BYTES.getSize() + && val.compareTo(DELETE_BYTES) == 0) { throw new IOException("Cannot insert value: " + val); - } } - localput(lockid, targetCol, val); } @@ -939,7 +952,7 @@ public class HRegion implements HConstants { * Delete a value or write a value. This is a just a convenience method for put(). */ public void delete(long lockid, Text targetCol) throws IOException { - localput(lockid, targetCol, HStoreKey.DELETE_BYTES); + localput(lockid, targetCol, DELETE_BYTES); } /** @@ -949,7 +962,7 @@ public class HRegion implements HConstants { * a per-row pending area, until a commit() or abort() call is received. * (Or until the user's write-lock expires.) */ - void localput(long lockid, Text targetCol, byte[] val) throws IOException { + void localput(long lockid, Text targetCol, BytesWritable val) throws IOException { checkColumn(targetCol); Text row = getRowFromLock(lockid); @@ -971,9 +984,9 @@ public class HRegion implements HConstants { + " unexpected aborted by another thread"); } - TreeMap targets = targetColumns.get(lockid); + TreeMap targets = targetColumns.get(lockid); if(targets == null) { - targets = new TreeMap(); + targets = new TreeMap(); targetColumns.put(lockid, targets); } targets.put(targetCol, val); @@ -1132,42 +1145,79 @@ public class HRegion implements HConstants { /******************************************************************************* * HScanner is an iterator through a bunch of rows in an HRegion. ******************************************************************************/ - private class HScanner implements HScannerInterface { - HScannerInterface scanners[] = null; - TreeMap resultSets[] = null; - HStoreKey keys[] = null; + private class HScanner implements HInternalScannerInterface { + private HInternalScannerInterface[] scanners; + private TreeMap[] resultSets; + private HStoreKey[] keys; + private boolean wildcardMatch; + private boolean multipleMatchers; /** Create an HScanner with a handle on many HStores. */ @SuppressWarnings("unchecked") - public HScanner(Text cols[], Text firstRow, HMemcache memcache, HStore stores[]) throws IOException { + public HScanner(Text[] cols, Text firstRow, HMemcache memcache, HStore[] stores) throws IOException { long scanTime = System.currentTimeMillis(); - this.scanners = new HScannerInterface[stores.length + 1]; - this.keys = new HStoreKey[scanners.length]; + this.scanners = new HInternalScannerInterface[stores.length + 1]; this.resultSets = new TreeMap[scanners.length]; + this.keys = new HStoreKey[scanners.length]; + this.wildcardMatch = false; + this.multipleMatchers = false; // Advance to the first key in each store. // All results will match the required column-set and scanTime. + + // NOTE: the memcache scanner should be the first scanner - for(int i = 0; i < stores.length; i++) { - scanners[i] = stores[i].getScanner(scanTime, cols, firstRow); + HInternalScannerInterface scanner = + memcache.getScanner(scanTime, cols, firstRow); + + if(scanner.isWildcardScanner()) { + this.wildcardMatch = true; + } + if(scanner.isMultipleMatchScanner()) { + this.multipleMatchers = true; + } + scanners[0] = scanner; + + for(int i = 0; i < stores.length; i++) { + scanner = stores[i].getScanner(scanTime, cols, firstRow); + if(scanner.isWildcardScanner()) { + this.wildcardMatch = true; + } + if(scanner.isMultipleMatchScanner()) { + this.multipleMatchers = true; + } + scanners[i + 1] = scanner; } - scanners[scanners.length-1] = memcache.getScanner(scanTime, cols, firstRow); for(int i = 0; i < scanners.length; i++) { keys[i] = new HStoreKey(); - resultSets[i] = new TreeMap(); + resultSets[i] = new TreeMap(); - if(! scanners[i].next(keys[i], resultSets[i])) { + if(scanners[i] != null && ! scanners[i].next(keys[i], resultSets[i])) { closeScanner(i); } } } + /* (non-Javadoc) + * @see org.apache.hadoop.hbase.HInternalScannerInterface#isWildcardScanner() + */ + public boolean isWildcardScanner() { + return wildcardMatch; + } + + /* (non-Javadoc) + * @see org.apache.hadoop.hbase.HInternalScannerInterface#isMultipleMatchScanner() + */ + public boolean isMultipleMatchScanner() { + return multipleMatchers; + } + /** * Grab the next row's worth of values. The HScanner will return the most * recent data value for each row that is not newer than the target time. */ - public boolean next(HStoreKey key, TreeMap results) throws IOException { + public boolean next(HStoreKey key, TreeMap results) throws IOException { // Find the lowest-possible key. @@ -1195,11 +1245,34 @@ public class HRegion implements HConstants { for(int i = 0; i < scanners.length; i++) { while((scanners[i] != null) - && (keys[i].getRow().compareTo(chosenRow) == 0) - && (keys[i].getTimestamp() == chosenTimestamp)) { + && (keys[i].getRow().compareTo(chosenRow) == 0)) { - results.putAll(resultSets[i]); - insertedItem = true; + + // If we are doing a wild card match or there are multiple matchers + // per column, we need to scan all the older versions of this row + // to pick up the rest of the family members + + if(!wildcardMatch + && !multipleMatchers + && (keys[i].getTimestamp() != chosenTimestamp)) { + break; + } + + // NOTE: We used to do results.putAll(resultSets[i]); + // but this had the effect of overwriting newer + // values with older ones. So now we only insert + // a result if the map does not contain the key. + + for(Iterator> it + = resultSets[i].entrySet().iterator(); + it.hasNext(); ) { + + Map.Entry e = it.next(); + if(!results.containsKey(e.getKey())) { + results.put(e.getKey(), e.getValue()); + insertedItem = true; + } + } resultSets[i].clear(); if(! scanners[i].next(keys[i], resultSets[i])) { diff --git a/src/java/org/apache/hadoop/hbase/HRegionInfo.java b/src/java/org/apache/hadoop/hbase/HRegionInfo.java index bc0a22c17f6..797b6ad7190 100644 --- a/src/java/org/apache/hadoop/hbase/HRegionInfo.java +++ b/src/java/org/apache/hadoop/hbase/HRegionInfo.java @@ -15,13 +15,20 @@ */ package org.apache.hadoop.hbase; +import java.io.ByteArrayInputStream; import java.io.DataInput; +import java.io.DataInputStream; import java.io.DataOutput; import java.io.IOException; import org.apache.hadoop.io.Text; import org.apache.hadoop.io.Writable; +/** + * HRegion information. + * Contains HRegion id, start and end keys, a reference to this + * HRegions' table descriptor, etc. + */ public class HRegionInfo implements Writable { public long regionId; public HTableDescriptor tableDesc; @@ -36,9 +43,20 @@ public class HRegionInfo implements Writable { this.endKey = new Text(); this.regionName = new Text(); } + + public HRegionInfo(final byte [] serializedBytes) { + this(); + try { + readFields(new DataInputStream( + new ByteArrayInputStream(serializedBytes))); + } catch (IOException e) { + throw new RuntimeException(e); + } + } - public HRegionInfo(long regionId, HTableDescriptor tableDesc, Text startKey, - Text endKey) throws IllegalArgumentException { + public HRegionInfo(long regionId, HTableDescriptor tableDesc, + Text startKey, Text endKey) + throws IllegalArgumentException { this.regionId = regionId; @@ -58,8 +76,16 @@ public class HRegionInfo implements Writable { this.endKey.set(endKey); } - this.regionName = new Text(tableDesc.getName() + "_" - + (startKey == null ? "" : startKey.toString()) + "_" + regionId); + this.regionName = new Text(tableDesc.getName() + "_" + + (startKey == null ? "" : startKey.toString()) + "_" + + regionId); + } + + @Override + public String toString() { + return "regionname: " + this.regionName.toString() + ", startKey: <" + + this.startKey.toString() + ">, tableDesc: {" + + this.tableDesc.toString() + "}"; } ////////////////////////////////////////////////////////////////////////////// @@ -81,4 +107,4 @@ public class HRegionInfo implements Writable { this.endKey.readFields(in); this.regionName.readFields(in); } -} +} \ No newline at end of file diff --git a/src/java/org/apache/hadoop/hbase/HRegionInterface.java b/src/java/org/apache/hadoop/hbase/HRegionInterface.java index 3a74903e6f2..81620c8558f 100644 --- a/src/java/org/apache/hadoop/hbase/HRegionInterface.java +++ b/src/java/org/apache/hadoop/hbase/HRegionInterface.java @@ -69,7 +69,7 @@ public interface HRegionInterface extends VersionedProtocol { * @param columns - columns to scan * @param startRow - starting row to scan * - * @param scannerId - scanner identifier used in other calls + * @return scannerId - scanner identifier used in other calls * @throws IOException */ public long openScanner(Text regionName, Text[] columns, Text startRow) throws IOException; diff --git a/src/java/org/apache/hadoop/hbase/HRegionServer.java b/src/java/org/apache/hadoop/hbase/HRegionServer.java index 245337b7995..4d645199011 100644 --- a/src/java/org/apache/hadoop/hbase/HRegionServer.java +++ b/src/java/org/apache/hadoop/hbase/HRegionServer.java @@ -20,6 +20,7 @@ import org.apache.commons.logging.LogFactory; import org.apache.hadoop.io.*; import org.apache.hadoop.fs.*; import org.apache.hadoop.ipc.*; +import org.apache.hadoop.util.StringUtils; import org.apache.hadoop.conf.*; import java.io.*; @@ -45,7 +46,7 @@ public class HRegionServer private static final Log LOG = LogFactory.getLog(HRegionServer.class); - private boolean stopRequested; + private volatile boolean stopRequested; private Path regionDir; private HServerAddress address; private Configuration conf; @@ -82,12 +83,10 @@ public class HRegionServer long startTime = System.currentTimeMillis(); // Grab a list of regions to check - Vector checkSplit = new Vector(); locker.readLock().lock(); try { checkSplit.addAll(regions.values()); - } finally { locker.readLock().unlock(); } @@ -132,13 +131,15 @@ public class HRegionServer = (oldRegion.find(META_TABLE_NAME.toString()) == 0) ? ROOT_TABLE_NAME : META_TABLE_NAME; - LOG.debug("region split complete. updating meta"); + if(LOG.isDebugEnabled()) { + LOG.debug("region split complete. updating meta"); + } client.openTable(tableToUpdate); long lockid = client.startUpdate(oldRegion); - client.delete(lockid, META_COL_REGIONINFO); - client.delete(lockid, META_COL_SERVER); - client.delete(lockid, META_COL_STARTCODE); + client.delete(lockid, COL_REGIONINFO); + client.delete(lockid, COL_SERVER); + client.delete(lockid, COL_STARTCODE); client.commit(lockid); for(int i = 0; i < newRegions.length; i++) { @@ -147,13 +148,15 @@ public class HRegionServer newRegions[i].getRegionInfo().write(out); lockid = client.startUpdate(newRegions[i].getRegionName()); - client.put(lockid, META_COL_REGIONINFO, bytes.toByteArray()); + client.put(lockid, COL_REGIONINFO, bytes.toByteArray()); client.commit(lockid); } // Now tell the master about the new regions - LOG.debug("reporting region split to master"); + if(LOG.isDebugEnabled()) { + LOG.debug("reporting region split to master"); + } reportSplit(newRegions[0].getRegionInfo(), newRegions[1].getRegionInfo()); @@ -172,18 +175,24 @@ public class HRegionServer } // Sleep - - long waitTime = - splitCheckFrequency - (System.currentTimeMillis() - startTime); - - if(waitTime > 0) { + long waitTime = stopRequested ? 0 + : splitCheckFrequency - (System.currentTimeMillis() - startTime); + if (waitTime > 0) { try { + if (LOG.isDebugEnabled()) { + LOG.debug("Sleep splitChecker"); + } Thread.sleep(waitTime); - + if (LOG.isDebugEnabled()) { + LOG.debug("Wake splitChecker"); + } } catch(InterruptedException iex) { } } } + if(LOG.isDebugEnabled()) { + LOG.debug("splitChecker exiting"); + } } } @@ -221,18 +230,25 @@ public class HRegionServer } // Sleep - - long waitTime = - threadWakeFrequency - (System.currentTimeMillis() - startTime); + long waitTime = stopRequested ? 0 + : threadWakeFrequency - (System.currentTimeMillis() - startTime); if(waitTime > 0) { + if (LOG.isDebugEnabled()) { + LOG.debug("Sleep cacheFlusher"); + } try { Thread.sleep(waitTime); - } catch(InterruptedException iex) { } + if (LOG.isDebugEnabled()) { + LOG.debug("Wake cacheFlusher"); + } } } + if(LOG.isDebugEnabled()) { + LOG.debug("cacheFlusher exiting"); + } } } @@ -249,24 +265,31 @@ public class HRegionServer private class LogRoller implements Runnable { public void run() { while(! stopRequested) { - // If the number of log entries is high enough, roll the log. This is a // very fast operation, but should not be done too frequently. - if(log.getNumEntries() > maxLogEntries) { try { log.rollWriter(); - } catch(IOException iex) { } } - - try { - Thread.sleep(threadWakeFrequency); - - } catch(InterruptedException iex) { + + if(!stopRequested) { + if (LOG.isDebugEnabled()) { + LOG.debug("Sleep logRoller"); + } + try { + Thread.sleep(threadWakeFrequency); + } catch(InterruptedException iex) { + } + if (LOG.isDebugEnabled()) { + LOG.debug("Wake logRoller"); + } } } + if(LOG.isDebugEnabled()) { + LOG.debug("logRoller exiting"); + } } } @@ -290,11 +313,11 @@ public class HRegionServer } /** Start a HRegionServer at an indicated location */ - public HRegionServer(Path regionDir, HServerAddress address, Configuration conf) - throws IOException { + public HRegionServer(Path regionDir, HServerAddress address, + Configuration conf) + throws IOException { // Basic setup - this.stopRequested = false; this.regionDir = regionDir; this.conf = conf; @@ -302,69 +325,70 @@ public class HRegionServer this.regions = new TreeMap(); this.locker = new ReentrantReadWriteLock(); this.outboundMsgs = new Vector(); - this.scanners = Collections.synchronizedMap(new TreeMap()); + this.scanners = + Collections.synchronizedMap(new TreeMap()); // Config'ed params - - this.threadWakeFrequency = conf.getLong("hbase.hregionserver.thread.wakefrequency", 10 * 1000); - this.maxLogEntries = conf.getInt("hbase.hregionserver.maxlogentries", 30 * 1000); - this.msgInterval = conf.getLong("hbase.hregionserver.msginterval", 15 * 1000); - this.splitCheckFrequency = conf.getLong("hbase.hregionserver.thread.splitcheckfrequency", 60 * 1000); + this.threadWakeFrequency = conf.getLong(THREAD_WAKE_FREQUENCY, 10 * 1000); + this.maxLogEntries = conf.getInt("hbase.regionserver.maxlogentries", 30 * 1000); + this.msgInterval = conf.getLong("hbase.regionserver.msginterval", + 15 * 1000); + this.splitCheckFrequency = + conf.getLong("hbase.regionserver.thread.splitcheckfrequency", 60 * 1000); // Cache flushing - this.cacheFlusher = new Flusher(); - this.cacheFlusherThread = new Thread(cacheFlusher, "HRegionServer.cacheFlusher"); + this.cacheFlusherThread = + new Thread(cacheFlusher, "HRegionServer.cacheFlusher"); // Check regions to see if they need to be split - this.splitChecker = new SplitChecker(); - this.splitCheckerThread = new Thread(splitChecker, "HRegionServer.splitChecker"); + this.splitCheckerThread = + new Thread(splitChecker, "HRegionServer.splitChecker"); // Process requests from Master - this.toDo = new Vector(); this.worker = new Worker(); this.workerThread = new Thread(worker, "HRegionServer.worker"); try { - // Server to handle client requests - this.server = RPC.getServer(this, address.getBindAddress().toString(), - address.getPort(), conf.getInt("hbase.hregionserver.handler.count", 10), false, conf); + address.getPort(), conf.getInt("hbase.regionserver.handler.count", 10), + false, conf); this.address = new HServerAddress(server.getListenerAddress()); // Local file paths - - String serverName = this.address.getBindAddress() + "_" + this.address.getPort(); + String serverName = + this.address.getBindAddress() + "_" + this.address.getPort(); Path newlogdir = new Path(regionDir, "log" + "_" + serverName); this.oldlogfile = new Path(regionDir, "oldlogfile" + "_" + serverName); // Logging - this.fs = FileSystem.get(conf); HLog.consolidateOldLog(newlogdir, oldlogfile, fs, conf); + // TODO: Now we have a consolidated log for all regions, sort and + // then split result by region passing the splits as reconstruction + // logs to HRegions on start. Or, rather than consolidate, split logs + // into per region files. this.log = new HLog(fs, newlogdir, conf); this.logRoller = new LogRoller(); this.logRollerThread = new Thread(logRoller, "HRegionServer.logRoller"); // Remote HMaster - - this.hbaseMaster = (HMasterRegionInterface) - RPC.waitForProxy(HMasterRegionInterface.class, - HMasterRegionInterface.versionID, - new HServerAddress(conf.get(MASTER_ADDRESS)).getInetSocketAddress(), - conf); + this.hbaseMaster = (HMasterRegionInterface)RPC. + waitForProxy(HMasterRegionInterface.class, + HMasterRegionInterface.versionID, + new HServerAddress(conf.get(MASTER_ADDRESS)).getInetSocketAddress(), + conf); // Threads - this.workerThread.start(); this.cacheFlusherThread.start(); this.splitCheckerThread.start(); this.logRollerThread.start(); - this.leases = new Leases(conf.getLong("hbase.hregionserver.lease.period", + this.leases = new Leases(conf.getLong("hbase.regionserver.lease.period", 3 * 60 * 1000), threadWakeFrequency); // Server @@ -380,56 +404,44 @@ public class HRegionServer } /** - * Stop all the HRegionServer threads and close everything down. All ongoing - * transactions will be aborted all threads will be shut down. This method - * will return immediately. The caller should call join to wait for all - * processing to cease. + * Set a flag that will cause all the HRegionServer threads to shut down + * in an orderly fashion. */ - public void stop() throws IOException { - if(! stopRequested) { - stopRequested = true; - - closeAllRegions(); - log.close(); - fs.close(); - server.stop(); - } - LOG.info("stopping server at: " + address.toString()); + public synchronized void stop() throws IOException { + stopRequested = true; + notifyAll(); // Wakes run() if it is sleeping } - /** Call join to wait for all the threads to finish */ + /** Wait on all threads to finish. + * Presumption is that all closes and stops have already been called. + */ public void join() { try { this.workerThread.join(); - } catch(InterruptedException iex) { } - try { this.logRollerThread.join(); - } catch(InterruptedException iex) { } - try { this.cacheFlusherThread.join(); - } catch(InterruptedException iex) { } - - this.leases.close(); - + try { + this.splitCheckerThread.join(); + } catch(InterruptedException iex) { + } try { this.server.join(); - } catch(InterruptedException iex) { } LOG.info("server stopped at: " + address.toString()); } /** - * The HRegionServer sticks in this loop until close. It repeatedly checks in - * with the HMaster, sending heartbeats & reports, and receiving HRegion + * The HRegionServer sticks in this loop until closed. It repeatedly checks + * in with the HMaster, sending heartbeats & reports, and receiving HRegion * load/unload instructions. */ public void run() { @@ -439,27 +451,40 @@ public class HRegionServer long waitTime; // Let the master know we're here - try { + if (LOG.isDebugEnabled()) { + LOG.debug("Telling master we are up"); + } + hbaseMaster.regionServerStartup(info); + if (LOG.isDebugEnabled()) { + LOG.debug("Done telling master we are up"); + } } catch(IOException e) { - waitTime = msgInterval - (System.currentTimeMillis() - lastMsg); - + waitTime = stopRequested ? 0 + : msgInterval - (System.currentTimeMillis() - lastMsg); + if(waitTime > 0) { - try { - Thread.sleep(waitTime); - - } catch(InterruptedException iex) { + if (LOG.isDebugEnabled()) { + LOG.debug("Sleep"); + } + synchronized(this) { + try { + Thread.sleep(waitTime); + } catch(InterruptedException iex) { + } + } + if (LOG.isDebugEnabled()) { + LOG.debug("Wake"); } } continue; } - // Now ask the master what it wants us to do and tell it what we have done. - - while(! stopRequested) { - if((System.currentTimeMillis() - lastMsg) >= msgInterval) { + // Now ask master what it wants us to do and tell it what we have done. + while (!stopRequested) { + if ((System.currentTimeMillis() - lastMsg) >= msgInterval) { HMsg outboundArray[] = null; synchronized(outboundMsgs) { @@ -474,28 +499,43 @@ public class HRegionServer // Queue up the HMaster's instruction stream for processing synchronized(toDo) { - boolean restartOrStop = false; - for(int i = 0; i < msgs.length; i++) { + boolean restart = false; + for(int i = 0; i < msgs.length && !stopRequested && !restart; i++) { switch(msgs[i].getMsg()) { case HMsg.MSG_CALL_SERVER_STARTUP: + if (LOG.isDebugEnabled()) { + LOG.debug("Got call server startup message"); + } + toDo.clear(); closeAllRegions(); - restartOrStop = true; + restart = true; break; - case HMsg.MSG_REGIONSERVER_ALREADY_RUNNING: - stop(); - restartOrStop = true; + case HMsg.MSG_REGIONSERVER_STOP: + if (LOG.isDebugEnabled()) { + LOG.debug("Got regionserver stop message"); + } + toDo.clear(); + closeAllRegions(); + stopRequested = true; break; default: + if (LOG.isDebugEnabled()) { + LOG.debug("Got default message"); + } toDo.add(msgs[i]); } } + if(toDo.size() > 0) { + if (LOG.isDebugEnabled()) { + LOG.debug("notify on todo"); + } toDo.notifyAll(); } - if(restartOrStop) { + if(restart || stopRequested) { break; } } @@ -505,16 +545,47 @@ public class HRegionServer } } - waitTime = msgInterval - (System.currentTimeMillis() - lastMsg); - - if(waitTime > 0) { - try { - Thread.sleep(waitTime); - } catch(InterruptedException iex) { + waitTime = stopRequested ? 0 + : msgInterval - (System.currentTimeMillis() - lastMsg); + if (waitTime > 0) { + if (LOG.isDebugEnabled()) { + LOG.debug("Sleep"); + } + synchronized(this) { + try { + Thread.sleep(waitTime); + } catch(InterruptedException iex) { + } + } + if (LOG.isDebugEnabled()) { + LOG.debug("Wake"); } } } } + try { + LOG.info("stopping server at: " + address.toString()); + + // Send interrupts to wake up threads if sleeping so they notice shutdown. + + this.logRollerThread.interrupt(); + this.cacheFlusherThread.interrupt(); + this.splitCheckerThread.interrupt(); + + this.worker.stop(); + this.server.stop(); + + closeAllRegions(); + log.close(); + leases.close(); + join(); + + } catch(IOException e) { + e.printStackTrace(); + } + if(LOG.isDebugEnabled()) { + LOG.debug("main thread exiting"); + } } /** Add to the outbound message buffer */ @@ -556,48 +627,83 @@ public class HRegionServer private Worker worker; private Thread workerThread; private class Worker implements Runnable { + public void stop() { + synchronized(toDo) { + toDo.notifyAll(); + } + } + public void run() { - while(!stopRequested) { - HMsg msg = null; + for(HMsg msg = null; !stopRequested; ) { synchronized(toDo) { - while(toDo.size() == 0) { + while(toDo.size() == 0 && !stopRequested) { try { + if (LOG.isDebugEnabled()) { + LOG.debug("Wait on todo"); + } toDo.wait(); - + if (LOG.isDebugEnabled()) { + LOG.debug("Wake on todo"); + } } catch(InterruptedException e) { } } + if(stopRequested) { + continue; + } msg = toDo.remove(0); } + try { switch(msg.getMsg()) { case HMsg.MSG_REGION_OPEN: // Open a region + if (LOG.isDebugEnabled()) { + LOG.debug("MSG_REGION_OPEN"); + } openRegion(msg.getRegionInfo()); break; case HMsg.MSG_REGION_CLOSE: // Close a region + if (LOG.isDebugEnabled()) { + LOG.debug("MSG_REGION_CLOSE"); + } closeRegion(msg.getRegionInfo(), true); break; case HMsg.MSG_REGION_MERGE: // Merge two regions + if (LOG.isDebugEnabled()) { + LOG.debug("MSG_REGION_MERGE"); + } //TODO ??? throw new IOException("TODO: need to figure out merge"); //break; case HMsg.MSG_CALL_SERVER_STARTUP: // Close regions, restart + if (LOG.isDebugEnabled()) { + LOG.debug("MSG_CALL_SERVER_STARTUP"); + } closeAllRegions(); continue; - case HMsg.MSG_REGIONSERVER_ALREADY_RUNNING: // Go away - stop(); + case HMsg.MSG_REGIONSERVER_STOP: // Go away + if (LOG.isDebugEnabled()) { + LOG.debug("MSG_REGIONSERVER_STOP"); + } + stopRequested = true; continue; case HMsg.MSG_REGION_CLOSE_WITHOUT_REPORT: // Close a region, don't reply + if (LOG.isDebugEnabled()) { + LOG.debug("MSG_REGION_CLOSE_WITHOUT_REPORT"); + } closeRegion(msg.getRegionInfo(), false); break; case HMsg.MSG_REGION_CLOSE_AND_DELETE: + if (LOG.isDebugEnabled()) { + LOG.debug("MSG_REGION_CLOSE_AND_DELETE"); + } closeAndDeleteRegion(msg.getRegionInfo()); break; @@ -608,15 +714,17 @@ public class HRegionServer e.printStackTrace(); } } + if(LOG.isDebugEnabled()) { + LOG.debug("worker thread exiting"); + } } } private void openRegion(HRegionInfo regionInfo) throws IOException { - this.locker.writeLock().lock(); try { - HRegion region = new HRegion(regionDir, log, fs, conf, regionInfo, null, oldlogfile); - + HRegion region = + new HRegion(regionDir, log, fs, conf, regionInfo, null, oldlogfile); regions.put(region.getRegionName(), region); reportOpen(region); @@ -661,12 +769,20 @@ public class HRegionServer } /** Called either when the master tells us to restart or from stop() */ - private void closeAllRegions() throws IOException { + private void closeAllRegions() { this.locker.writeLock().lock(); try { for(Iterator it = regions.values().iterator(); it.hasNext(); ) { HRegion region = it.next(); - region.close(); + if (LOG.isDebugEnabled()) { + LOG.debug("closing region " + region.getRegionName()); + } + try { + region.close(); + + } catch(IOException e) { + e.printStackTrace(); + } } regions.clear(); @@ -722,9 +838,12 @@ public class HRegionServer throw new IOException("Not serving region " + regionName); } - byte results[] = region.get(row, column); + if (LOG.isDebugEnabled()) { + LOG.debug("get " + row.toString() + ", " + column.toString()); + } + BytesWritable results = region.get(row, column); if(results != null) { - return new BytesWritable(results); + return results; } return null; } @@ -738,15 +857,9 @@ public class HRegionServer throw new IOException("Not serving region " + regionName); } - byte results[][] = region.get(row, column, numVersions); + BytesWritable[] results = region.get(row, column, numVersions); if(results != null) { - BytesWritable realResults[] = new BytesWritable[results.length]; - for(int i = 0; i < realResults.length; i++) { - if(results[i] != null) { - realResults[i] = new BytesWritable(results[i]); - } - } - return realResults; + return results; } return null; } @@ -760,15 +873,9 @@ public class HRegionServer throw new IOException("Not serving region " + regionName); } - byte results[][] = region.get(row, column, timestamp, numVersions); + BytesWritable[] results = region.get(row, column, timestamp, numVersions); if(results != null) { - BytesWritable realResults[] = new BytesWritable[results.length]; - for(int i = 0; i < realResults.length; i++) { - if(results[i] != null) { - realResults[i] = new BytesWritable(results[i]); - } - } - return realResults; + return results; } return null; } @@ -780,12 +887,12 @@ public class HRegionServer throw new IOException("Not serving region " + regionName); } - TreeMap map = region.getFull(row); + TreeMap map = region.getFull(row); LabelledData result[] = new LabelledData[map.size()]; int counter = 0; for(Iterator it = map.keySet().iterator(); it.hasNext(); ) { Text colname = it.next(); - byte val[] = map.get(colname); + BytesWritable val = map.get(colname); result[counter++] = new LabelledData(colname, val); } return result; @@ -842,7 +949,7 @@ public class HRegionServer leases.renewLease(new Text(String.valueOf(clientid)), new Text(String.valueOf(lockid))); - region.put(lockid, column, val.get()); + region.put(lockid, column, val); } /** Remove a cell from the HBase. */ @@ -911,7 +1018,7 @@ public class HRegionServer // remote scanner interface ////////////////////////////////////////////////////////////////////////////// - private Map scanners; + private Map scanners; private class ScannerListener extends LeaseListener { private Text scannerName; @@ -920,7 +1027,7 @@ public class HRegionServer } public void leaseExpired() { - HScannerInterface s = scanners.remove(scannerName); + HInternalScannerInterface s = scanners.remove(scannerName); if(s != null) { try { s.close(); @@ -943,7 +1050,7 @@ public class HRegionServer long scannerId = -1L; try { - HScannerInterface s = r.getScanner(cols, firstRow); + HInternalScannerInterface s = r.getScanner(cols, firstRow); scannerId = rand.nextLong(); Text scannerName = new Text(String.valueOf(scannerId)); scanners.put(scannerName, s); @@ -959,18 +1066,30 @@ public class HRegionServer public LabelledData[] next(long scannerId, HStoreKey key) throws IOException { Text scannerName = new Text(String.valueOf(scannerId)); - HScannerInterface s = scanners.get(scannerName); + HInternalScannerInterface s = scanners.get(scannerName); if(s == null) { throw new IOException("unknown scanner"); } leases.renewLease(scannerName, scannerName); - TreeMap results = new TreeMap(); + TreeMap results = new TreeMap(); ArrayList values = new ArrayList(); if(s.next(key, results)) { - for(Iterator> it = results.entrySet().iterator(); + for(Iterator> it + = results.entrySet().iterator(); it.hasNext(); ) { - Map.Entry e = it.next(); - values.add(new LabelledData(e.getKey(), e.getValue())); + + Map.Entry e = it.next(); + BytesWritable val = e.getValue(); + if(val.getSize() == DELETE_BYTES.getSize() + && val.compareTo(DELETE_BYTES) == 0) { + + // Value is deleted. Don't return a value + + continue; + + } else { + values.add(new LabelledData(e.getKey(), val)); + } } } return values.toArray(new LabelledData[values.size()]); @@ -978,7 +1097,7 @@ public class HRegionServer public void close(long scannerId) throws IOException { Text scannerName = new Text(String.valueOf(scannerId)); - HScannerInterface s = scanners.remove(scannerName); + HInternalScannerInterface s = scanners.remove(scannerName); if(s == null) { throw new IOException("unknown scanner"); } @@ -991,33 +1110,53 @@ public class HRegionServer leases.cancelLease(scannerName, scannerName); } - ////////////////////////////////////////////////////////////////////////////// - // Main program - ////////////////////////////////////////////////////////////////////////////// - - private static void printUsage() { + private static void printUsageAndExit() { + printUsageAndExit(null); + } + + private static void printUsageAndExit(final String message) { + if (message != null) { + System.err.println(message); + } System.err.println("Usage: java " + - "org.apache.hbase.HRegionServer [--bind=hostname:port]"); + "org.apache.hbase.HRegionServer [--bind=hostname:port] start"); + System.exit(0); } public static void main(String [] args) throws IOException { + if (args.length < 1) { + printUsageAndExit(); + } + Configuration conf = new HBaseConfiguration(); // Process command-line args. TODO: Better cmd-line processing // (but hopefully something not as painful as cli options). + final String addressArgKey = "--bind="; for (String cmd: args) { - if (cmd.equals("-h") || cmd.startsWith("--h")) { - printUsage(); - return; + if (cmd.startsWith(addressArgKey)) { + conf.set(REGIONSERVER_ADDRESS, cmd.substring(addressArgKey.length())); + continue; } - final String addressArgKey = "--bind="; - if (cmd.startsWith(addressArgKey)) { - conf.set(REGIONSERVER_ADDRESS, - cmd.substring(addressArgKey.length())); + if (cmd.equals("start")) { + try { + (new Thread(new HRegionServer(conf))).start(); + } catch (Throwable t) { + LOG.error( "Can not start master because "+ + StringUtils.stringifyException(t) ); + System.exit(-1); + } + break; } + + if (cmd.equals("stop")) { + printUsageAndExit("There is no regionserver stop mechanism. To stop " + + "regionservers, shutdown the hbase master"); + } + + // Print out usage if we get to here. + printUsageAndExit(); } - - new HRegionServer(conf); } -} +} \ No newline at end of file diff --git a/src/java/org/apache/hadoop/hbase/HRegiondirReader.java b/src/java/org/apache/hadoop/hbase/HRegiondirReader.java new file mode 100644 index 00000000000..a006207e11b --- /dev/null +++ b/src/java/org/apache/hadoop/hbase/HRegiondirReader.java @@ -0,0 +1,219 @@ +/** + * Copyright 2007 The Apache Software Foundation + * + * Licensed 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.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.TreeMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.fs.PathFilter; +import org.apache.hadoop.io.BytesWritable; +import org.apache.hadoop.io.Text; + +/** + * A standalone HRegion directory reader. Currently reads content on + * file system only. + * TODO: Add dumping of HStoreFile content and HLog. + */ +public class HRegiondirReader { + private final Configuration conf; + private final Path parentdir; + + private static final Pattern REGION_NAME_PARSER = + Pattern.compile(HGlobals.HREGIONDIR_PREFIX + + "([^_]+)_([^_]*)_([^_]*)"); + + private static final String USAGE = "Usage: " + + "java org.apache.hadoop.hbase.HRegionDirReader " + + "[]"; + + private final List infos; + + public HRegiondirReader(final HBaseConfiguration conf, + final String parentdirName) + throws IOException { + this.conf = conf; + FileSystem fs = FileSystem.get(conf); + this.parentdir = new Path(parentdirName); + if (!fs.exists(parentdir)) { + throw new FileNotFoundException(parentdirName); + } + if (!fs.isDirectory(parentdir)) { + throw new IOException(parentdirName + " not a directory"); + } + // Look for regions in parentdir. + Path [] regiondirs = + fs.listPaths(parentdir, new PathFilter() { + public boolean accept(Path path) { + Matcher m = REGION_NAME_PARSER.matcher(path.getName()); + return m != null && m.matches(); + } + }); + // Create list of HRegionInfos for all regions found in + // parentdir. + this.infos = new ArrayList(); + for (Path d: regiondirs) { + Matcher m = REGION_NAME_PARSER.matcher(d.getName()); + if (m == null || !m.matches()) { + throw new IOException("Unparseable region dir name"); + } + String tableName = m.group(1); + String endKey = m.group(2); + long regionid = Long.parseLong(m.group(3)); + HTableDescriptor desc = getTableDescriptor(fs, d, tableName); + HRegionInfo info = new HRegionInfo(regionid, desc, + new Text(), (endKey == null || endKey.length() == 0)? + new Text(): new Text(endKey)); + infos.add(info); + } + } + + /** + * Returns a populated table descriptor. + * @param fs Current filesystem. + * @param d The regiondir for tableName + * @param tableName Name of this table. + * @return A HTableDescriptor populated with all known column + * families. + * @throws IOException + */ + private HTableDescriptor getTableDescriptor(final FileSystem fs, + final Path d, final String tableName) + throws IOException { + HTableDescriptor desc = new HTableDescriptor(tableName, 1); + Text [] families = getFamilies(fs, d); + for (Text f: families) { + desc.addFamily(f); + } + return desc; + } + + /** + * Get column families for this region by looking at + * directory names under this region. + * This is a hack. HRegions only know what columns they have + * because they are told by passed-in metadata. + * @param regiondir + * @return Array of family names. + * @throws IOException + */ + private Text [] getFamilies(final FileSystem fs, + final Path regiondir) + throws IOException { + Path [] subdirs = fs.listPaths(regiondir, new PathFilter() { + public boolean accept(Path path) { + return !path.getName().equals("log"); + } + }); + List families = new ArrayList(); + for (Path d: subdirs) { + // Convert names of subdirectories into column family names + // by adding the colon. + Text family = new Text(d.getName() + ":"); + families.add(family); + } + return families.toArray(new Text [] {}); + } + + public List getRegions() { + return this.infos; + } + + public HRegionInfo getRegionInfo(final String tableName) + throws IOException { + HRegionInfo result = null; + for(HRegionInfo i: getRegions()) { + if(i.tableDesc.getName().equals(tableName)) { + result = i; + break; + } + } + if (result == null) { + throw new NullPointerException("No such table: " + + tableName); + } + return result; + } + + private void dump(final String tableName) throws IOException { + dump(getRegionInfo(tableName)); + } + + private void dump(final HRegionInfo info) throws IOException { + HRegion r = new HRegion(this.parentdir, null, + FileSystem.get(this.conf), conf, info, null, null); + Text [] families = info.tableDesc.families().toArray(new Text [] {}); + HInternalScannerInterface scanner = r.getScanner(families, new Text()); + HStoreKey key = new HStoreKey(); + TreeMap results = new TreeMap(); + // Print out table header line. + String s = info.startKey.toString(); + String startKey = (s == null || s.length() <= 0)? "<>": s; + s = info.endKey.toString(); + String endKey = (s == null || s.length() <= 0)? "<>": s; + String tableName = info.tableDesc.getName().toString(); + System.out.println("table: " + tableName + + ", regionid: " + info.regionId + + ", startkey: " + startKey + + ", endkey: " + endKey); + // Now print rows. Offset by a space to distingush rows from + // table headers. TODO: Add in better formatting of output. + // Every line starts with row name followed by column name + // followed by cell content. + while(scanner.next(key, results)) { + for (Text colname: results.keySet()) { + Object value = null; + byte[] bytes = new byte[results.get(colname).getSize()]; + if (colname.toString().equals("info:regioninfo")) { + // Then bytes are instance of an HRegionInfo. + System.arraycopy(results.get(colname).get(), 0, bytes, 0, bytes.length); + value = new HRegionInfo(bytes); + } else { + value = new String(bytes, HConstants.UTF8_ENCODING); + } + + System.out.println(" " + key + ", " + colname.toString() + ": \"" + + value.toString() + "\""); + } + } + } + + public static void main(String[] args) throws IOException { + if (args.length < 1) { + System.err.println(USAGE); + System.exit(-1); + } + HBaseConfiguration c = new HBaseConfiguration(); + HRegiondirReader reader = new HRegiondirReader(c, args[0]); + if (args.length == 1) { + // Do all regions. + for(HRegionInfo info: reader.getRegions()) { + reader.dump(info); + } + } else { + for (int i = 1; i < args.length; i++) { + reader.dump(args[i]); + } + } + } +} \ No newline at end of file diff --git a/src/java/org/apache/hadoop/hbase/HServerAddress.java b/src/java/org/apache/hadoop/hbase/HServerAddress.java index 4b44d756a11..028b91b0614 100644 --- a/src/java/org/apache/hadoop/hbase/HServerAddress.java +++ b/src/java/org/apache/hadoop/hbase/HServerAddress.java @@ -92,7 +92,7 @@ public class HServerAddress implements Writable { } else { address = new InetSocketAddress(bindAddress, port); - stringValue = bindAddress + "_" + port; + stringValue = bindAddress + ":" + port; } } diff --git a/src/java/org/apache/hadoop/hbase/HServerInfo.java b/src/java/org/apache/hadoop/hbase/HServerInfo.java index 12be4d6e2ff..562f582ae22 100644 --- a/src/java/org/apache/hadoop/hbase/HServerInfo.java +++ b/src/java/org/apache/hadoop/hbase/HServerInfo.java @@ -49,6 +49,11 @@ public class HServerInfo implements Writable { public long getStartCode() { return startCode; } + + @Override + public String toString() { + return "address: " + this.serverAddress + ", startcode: " + this.startCode; + } ////////////////////////////////////////////////////////////////////////////// // Writable @@ -63,4 +68,4 @@ public class HServerInfo implements Writable { this.serverAddress.write(out); out.writeLong(this.startCode); } -} +} \ No newline at end of file diff --git a/src/java/org/apache/hadoop/hbase/HStore.java b/src/java/org/apache/hadoop/hbase/HStore.java index 65a21ad03f8..ce7c41039bc 100644 --- a/src/java/org/apache/hadoop/hbase/HStore.java +++ b/src/java/org/apache/hadoop/hbase/HStore.java @@ -78,30 +78,31 @@ public class HStore { /** * An HStore is a set of zero or more MapFiles, which stretch backwards over - * time. A given HStore is responsible for a certain set of columns for a row - * in the HRegion. + * time. A given HStore is responsible for a certain set of columns for a + * row in the HRegion. * - * The HRegion starts writing to its set of HStores when the HRegion's + *

The HRegion starts writing to its set of HStores when the HRegion's * memcache is flushed. This results in a round of new MapFiles, one for * each HStore. * - * There's no reason to consider append-logging at this level; all logging and - * locking is handled at the HRegion level. HStore just provides services to - * manage sets of MapFiles. One of the most important of those services is - * MapFile-compaction services. + *

There's no reason to consider append-logging at this level; all logging + * and locking is handled at the HRegion level. HStore just provides + * services to manage sets of MapFiles. One of the most important of those + * services is MapFile-compaction services. * - * The only thing having to do with logs that HStore needs to deal with is + *

The only thing having to do with logs that HStore needs to deal with is * the reconstructionLog. This is a segment of an HRegion's log that might - * be present upon startup. If the param is NULL, there's nothing to do. + * NOT be present upon startup. If the param is NULL, there's nothing to do. * If the param is non-NULL, we need to process the log to reconstruct - * a TreeMap that might not have been written to disk before the process died. + * a TreeMap that might not have been written to disk before the process + * died. * - * It's assumed that after this constructor returns, the reconstructionLog file - * will be deleted (by whoever has instantiated the HStore). + *

It's assumed that after this constructor returns, the reconstructionLog + * file will be deleted (by whoever has instantiated the HStore). */ public HStore(Path dir, Text regionName, Text colFamily, int maxVersions, - FileSystem fs, Path reconstructionLog, Configuration conf) throws IOException { - + FileSystem fs, Path reconstructionLog, Configuration conf) + throws IOException { this.dir = dir; this.regionName = regionName; this.colFamily = colFamily; @@ -114,14 +115,17 @@ public class HStore { this.loginfodir = HStoreFile.getInfoDir(dir, regionName, colFamily); fs.mkdirs(loginfodir); - LOG.debug("starting HStore for " + regionName + "/"+ colFamily); + if(LOG.isDebugEnabled()) { + LOG.debug("starting HStore for " + regionName + "/"+ colFamily); + } // Either restart or get rid of any leftover compaction work. Either way, // by the time processReadyCompaction() returns, we can get rid of the // existing compaction-dir. this.compactdir = new Path(dir, COMPACTION_DIR); - Path curCompactStore = HStoreFile.getHStoreDir(compactdir, regionName, colFamily); + Path curCompactStore = + HStoreFile.getHStoreDir(compactdir, regionName, colFamily); if(fs.exists(curCompactStore)) { processReadyCompaction(); fs.delete(curCompactStore); @@ -129,28 +133,25 @@ public class HStore { // Go through the 'mapdir' and 'loginfodir' together, make sure that all // MapFiles are in a reliable state. Every entry in 'mapdir' must have a - // corresponding one in 'loginfodir'. Without a corresponding log info file, - // the entry in 'mapdir'must be deleted. - + // corresponding one in 'loginfodir'. Without a corresponding log info + // file, the entry in 'mapdir' must be deleted. Vector hstoreFiles = HStoreFile.loadHStoreFiles(conf, dir, regionName, colFamily, fs); - for(Iterator it = hstoreFiles.iterator(); it.hasNext(); ) { HStoreFile hsf = it.next(); mapFiles.put(hsf.loadInfo(fs), hsf); } - // Now go through all the HSTORE_LOGINFOFILEs and figure out the most-recent - // log-seq-ID that's present. The most-recent such ID means we can ignore - // all log messages up to and including that ID (because they're already - // reflected in the TreeMaps). + // Now go through all the HSTORE_LOGINFOFILEs and figure out the + // most-recent log-seq-ID that's present. The most-recent such ID means we + // can ignore all log messages up to and including that ID (because they're + // already reflected in the TreeMaps). // - // If the HSTORE_LOGINFOFILE doesn't contain a number, just ignore it. That + // If the HSTORE_LOGINFOFILE doesn't contain a number, just ignore it. That // means it was built prior to the previous run of HStore, and so it cannot // contain any updates also contained in the log. - long maxSeqID = -1; - for(Iterator it = hstoreFiles.iterator(); it.hasNext(); ) { + for (Iterator it = hstoreFiles.iterator(); it.hasNext(); ) { HStoreFile hsf = it.next(); long seqid = hsf.loadInfo(fs); if(seqid > 0) { @@ -166,63 +167,72 @@ public class HStore { // We can ignore any log message that has a sequence ID that's equal to or // lower than maxSeqID. (Because we know such log messages are already // reflected in the MapFiles.) - - LOG.debug("reading reconstructionLog"); - + if(LOG.isDebugEnabled()) { + LOG.debug("reading reconstructionLog"); + } if(reconstructionLog != null && fs.exists(reconstructionLog)) { long maxSeqIdInLog = -1; TreeMap reconstructedCache = new TreeMap(); - SequenceFile.Reader login = new SequenceFile.Reader(fs, reconstructionLog, conf); - try { HLogKey key = new HLogKey(); HLogEdit val = new HLogEdit(); while(login.next(key, val)) { maxSeqIdInLog = Math.max(maxSeqIdInLog, key.getLogSeqNum()); - if(key.getLogSeqNum() <= maxSeqID) { + if (key.getLogSeqNum() <= maxSeqID) { + continue; + } + // Check this edit is for me. Also, guard against writing + // METACOLUMN info such as HBASE::CACHEFLUSH entries + Text column = val.getColumn(); + if (!key.getRegionName().equals(this.regionName) || + column.equals(HLog.METACOLUMN) || + HStoreKey.extractFamily(column).equals(this.colFamily)) { + if (LOG.isDebugEnabled()) { + LOG.debug("Passing on edit " + key.getRegionName() + ", " + + key.getRegionName() + ", " + column.toString() + ": " + + new String(val.getVal().get())); + } continue; } reconstructedCache.put(new HStoreKey(key.getRow(), val.getColumn(), val.getTimestamp()), val.getVal()); } - } finally { login.close(); } if(reconstructedCache.size() > 0) { - // We create a "virtual flush" at maxSeqIdInLog+1. - - LOG.debug("flushing reconstructionCache"); - + if(LOG.isDebugEnabled()) { + LOG.debug("flushing reconstructionCache"); + } flushCacheHelper(reconstructedCache, maxSeqIdInLog+1, true); } } // Compact all the MapFiles into a single file. The resulting MapFile // should be "timeless"; that is, it should not have an associated seq-ID, - // because all log messages have been reflected in the TreeMaps at this point. - + // because all log messages have been reflected in the TreeMaps at this + // point. if(mapFiles.size() >= 1) { compactHelper(true); } // Finally, start up all the map readers! (There should be just one at this // point, as we've compacted them all.) - - LOG.debug("starting map readers"); - + if(LOG.isDebugEnabled()) { + LOG.debug("starting map readers"); + } for(Iterator it = mapFiles.keySet().iterator(); it.hasNext(); ) { Long key = it.next().longValue(); HStoreFile hsf = mapFiles.get(key); - //TODO - is this really necessary? Don't I do this inside compact()? - - maps.put(key, new MapFile.Reader(fs, hsf.getMapFilePath().toString(), conf)); + // TODO - is this really necessary? Don't I do this inside compact()? + maps.put(key, + new MapFile.Reader(fs, hsf.getMapFilePath().toString(), conf)); } LOG.info("HStore online for " + this.regionName + "/" + this.colFamily); @@ -273,7 +283,9 @@ public class HStore { long logCacheFlushId, boolean addToAvailableMaps) throws IOException { synchronized(flushLock) { - LOG.debug("flushing HStore " + this.regionName + "/" + this.colFamily); + if(LOG.isDebugEnabled()) { + LOG.debug("flushing HStore " + this.regionName + "/" + this.colFamily); + } // A. Write the TreeMap out to the disk @@ -281,18 +293,23 @@ public class HStore { = HStoreFile.obtainNewHStoreFile(conf, dir, regionName, colFamily, fs); Path mapfile = flushedFile.getMapFilePath(); + if(LOG.isDebugEnabled()) { + LOG.debug("map file is: " + mapfile.toString()); + } + MapFile.Writer out = new MapFile.Writer(conf, fs, mapfile.toString(), HStoreKey.class, BytesWritable.class); try { - for(Iterator it = inputCache.keySet().iterator(); it.hasNext(); ) { - HStoreKey curkey = it.next(); + for (HStoreKey curkey: inputCache.keySet()) { if(this.colFamily.equals(HStoreKey.extractFamily(curkey.getColumn()))) { BytesWritable val = inputCache.get(curkey); out.append(curkey, val); } } - LOG.debug("HStore " + this.regionName + "/" + this.colFamily + " flushed"); + if(LOG.isDebugEnabled()) { + LOG.debug("HStore " + this.regionName + "/" + this.colFamily + " flushed"); + } } finally { out.close(); @@ -301,7 +318,9 @@ public class HStore { // B. Write out the log sequence number that corresponds to this output // MapFile. The MapFile is current up to and including the log seq num. - LOG.debug("writing log cache flush id"); + if(LOG.isDebugEnabled()) { + LOG.debug("writing log cache flush id"); + } flushedFile.writeInfo(fs, logCacheFlushId); // C. Finally, make the new MapFile available. @@ -312,7 +331,10 @@ public class HStore { try { maps.put(logCacheFlushId, new MapFile.Reader(fs, mapfile.toString(), conf)); mapFiles.put(logCacheFlushId, flushedFile); - LOG.debug("HStore available for " + this.regionName + "/" + this.colFamily); + if(LOG.isDebugEnabled()) { + LOG.debug("HStore available for " + this.regionName + "/" + + this.colFamily + " flush id=" + logCacheFlushId); + } } finally { this.locker.writeLock().unlock(); @@ -357,7 +379,9 @@ public class HStore { void compactHelper(boolean deleteSequenceInfo) throws IOException { synchronized(compactLock) { - LOG.debug("started compaction of " + this.regionName + "/" + this.colFamily); + if(LOG.isDebugEnabled()) { + LOG.debug("started compaction of " + this.regionName + "/" + this.colFamily); + } Path curCompactStore = HStoreFile.getHStoreDir(compactdir, regionName, colFamily); fs.mkdirs(curCompactStore); @@ -387,13 +411,17 @@ public class HStore { } } } - LOG.debug("max sequence id =" + maxSeenSeqID); + if(LOG.isDebugEnabled()) { + LOG.debug("max sequence id =" + maxSeenSeqID); + } HStoreFile compactedOutputFile = new HStoreFile(conf, compactdir, regionName, colFamily, -1); if(toCompactFiles.size() == 1) { - LOG.debug("nothing to compact for " + this.regionName + "/" + this.colFamily); + if(LOG.isDebugEnabled()) { + LOG.debug("nothing to compact for " + this.regionName + "/" + this.colFamily); + } HStoreFile hsf = toCompactFiles.elementAt(0); if(hsf.loadInfo(fs) == -1) { @@ -438,7 +466,9 @@ public class HStore { // Now, advance through the readers in order. This will have the // effect of a run-time sort of the entire dataset. - LOG.debug("processing HStoreFile readers"); + if(LOG.isDebugEnabled()) { + LOG.debug("processing HStoreFile readers"); + } int numDone = 0; for(int i = 0; i < readers.length; i++) { @@ -518,13 +548,17 @@ public class HStore { } } - LOG.debug("all HStores processed"); + if(LOG.isDebugEnabled()) { + LOG.debug("all HStores processed"); + } } finally { compactedOut.close(); } - LOG.debug("writing new compacted HStore"); + if(LOG.isDebugEnabled()) { + LOG.debug("writing new compacted HStore"); + } // Now, write out an HSTORE_LOGINFOFILE for the brand-new TreeMap. @@ -564,7 +598,9 @@ public class HStore { processReadyCompaction(); - LOG.debug("compaction complete for " + this.regionName + "/" + this.colFamily); + if(LOG.isDebugEnabled()) { + LOG.debug("compaction complete for " + this.regionName + "/" + this.colFamily); + } } finally { fs.delete(compactdir); @@ -609,7 +645,9 @@ public class HStore { // OK, there's actually compaction work that needs to be put into place. - LOG.debug("compaction starting"); + if(LOG.isDebugEnabled()) { + LOG.debug("compaction starting"); + } // 2. Load in the files to be deleted. // (Figuring out what MapFiles are going to be replaced) @@ -629,7 +667,9 @@ public class HStore { in.close(); } - LOG.debug("loaded files to be deleted"); + if(LOG.isDebugEnabled()) { + LOG.debug("loaded files to be deleted"); + } // 3. Unload all the replaced MapFiles. @@ -650,7 +690,9 @@ public class HStore { } } - LOG.debug("unloaded existing MapFiles"); + if(LOG.isDebugEnabled()) { + LOG.debug("unloaded existing MapFiles"); + } // What if we crash at this point? No big deal; we will restart // processReadyCompaction(), and nothing has been lost. @@ -663,7 +705,9 @@ public class HStore { fs.delete(hsf.getInfoFilePath()); } - LOG.debug("old files deleted"); + if(LOG.isDebugEnabled()) { + LOG.debug("old files deleted"); + } // What if we fail now? The above deletes will fail silently. We'd better // make sure not to write out any new files with the same names as @@ -671,7 +715,9 @@ public class HStore { // 5. Moving the new MapFile into place - LOG.debug("moving new MapFile into place"); + if(LOG.isDebugEnabled()) { + LOG.debug("moving new MapFile into place"); + } HStoreFile compactedFile = new HStoreFile(conf, compactdir, regionName, colFamily, -1); @@ -691,7 +737,9 @@ public class HStore { // 6. Loading the new TreeMap. - LOG.debug("loading new TreeMap"); + if(LOG.isDebugEnabled()) { + LOG.debug("loading new TreeMap"); + } mapFiles.put(orderVal, finalCompactedFile); maps.put(orderVal, new MapFile.Reader(fs, @@ -716,7 +764,7 @@ public class HStore { * * The returned object should map column names to byte arrays (byte[]). */ - public void getFull(HStoreKey key, TreeMap results) throws IOException { + public void getFull(HStoreKey key, TreeMap results) throws IOException { this.locker.readLock().lock(); try { MapFile.Reader[] maparray @@ -734,7 +782,7 @@ public class HStore { Text readcol = readkey.getColumn(); if(results.get(readcol) == null && key.matchesWithoutColumn(readkey)) { - results.put(new Text(readcol), readval.get()); + results.put(new Text(readcol), readval); readval = new BytesWritable(); } else if(key.getRow().compareTo(readkey.getRow()) > 0) { @@ -756,12 +804,12 @@ public class HStore { * * If 'numVersions' is negative, the method returns all available versions. */ - public byte[][] get(HStoreKey key, int numVersions) throws IOException { - if(numVersions == 0) { - throw new IllegalArgumentException("Must request at least one value."); + public BytesWritable[] get(HStoreKey key, int numVersions) throws IOException { + if(numVersions <= 0) { + throw new IllegalArgumentException("Number of versions must be > 0"); } - Vector results = new Vector(); + Vector results = new Vector(); this.locker.readLock().lock(); try { MapFile.Reader[] maparray @@ -776,7 +824,7 @@ public class HStore { HStoreKey readkey = (HStoreKey)map.getClosest(key, readval); if(readkey.matchesRowCol(key)) { - results.add(readval.get()); + results.add(readval); readval = new BytesWritable(); while(map.next(readkey, readval) && readkey.matchesRowCol(key)) { @@ -784,7 +832,7 @@ public class HStore { break; } else { - results.add(readval.get()); + results.add(readval); readval = new BytesWritable(); } } @@ -799,14 +847,14 @@ public class HStore { return null; } else { - return (byte[][]) results.toArray(new byte[results.size()][]); + return results.toArray(new BytesWritable[results.size()]); } } finally { this.locker.readLock().unlock(); } } - + /** * Gets the size of the largest MapFile and its mid key. * @@ -866,31 +914,54 @@ public class HStore { * Return a set of MapFile.Readers, one for each HStore file. * These should be closed after the user is done with them. */ - public HScannerInterface getScanner(long timestamp, Text targetCols[], + public HInternalScannerInterface getScanner(long timestamp, Text targetCols[], Text firstRow) throws IOException { return new HStoreScanner(timestamp, targetCols, firstRow); } + /** For debuging purposes. Dumps the keys from all the MapFiles */ + void dumpMaps() throws IOException { + this.locker.readLock().lock(); + try { + for(Iterator i = maps.values().iterator(); i.hasNext(); ) { + MapFile.Reader r = i.next(); + synchronized(r) { + r.reset(); + HStoreKey key = new HStoreKey(); + BytesWritable val = new BytesWritable(); + while(r.next(key, val)) { + System.out.println(key); + } + } + } + + } finally { + this.locker.readLock().unlock(); + } + } + ////////////////////////////////////////////////////////////////////////////// // This class implements the HScannerInterface. // It lets the caller scan the contents of this HStore. ////////////////////////////////////////////////////////////////////////////// class HStoreScanner extends HAbstractScanner { - MapFile.Reader readers[]; - Text lastRow = null; + private MapFile.Reader[] readers; - public HStoreScanner(long timestamp, Text targetCols[], Text firstRow) throws IOException { + public HStoreScanner(long timestamp, Text[] targetCols, Text firstRow) throws IOException { super(timestamp, targetCols); locker.readLock().lock(); try { this.readers = new MapFile.Reader[mapFiles.size()]; - int i = 0; + + // Most recent map file should be first + + int i = readers.length - 1; for(Iterator it = mapFiles.values().iterator(); it.hasNext(); ) { HStoreFile curHSF = it.next(); - readers[i++] = new MapFile.Reader(fs, curHSF.getMapFilePath().toString(), conf); + readers[i--] = new MapFile.Reader(fs, curHSF.getMapFilePath().toString(), conf); } this.keys = new HStoreKey[readers.length]; @@ -952,6 +1023,7 @@ public class HStore { * @return - true if there is more data available */ boolean getNext(int i) throws IOException { + vals[i] = new BytesWritable(); if(! readers[i].next(keys[i], vals[i])) { closeSubScanner(i); return false; diff --git a/src/java/org/apache/hadoop/hbase/HStoreKey.java b/src/java/org/apache/hadoop/hbase/HStoreKey.java index ce8d3e3c35d..87b610e48a9 100644 --- a/src/java/org/apache/hadoop/hbase/HStoreKey.java +++ b/src/java/org/apache/hadoop/hbase/HStoreKey.java @@ -23,9 +23,6 @@ import java.io.*; * A Key for a stored row ******************************************************************************/ public class HStoreKey implements WritableComparable { - public static final byte[] DELETE_BYTES = "HSTOREKEY::DELETEVAL".getBytes(); - public static final byte[] COMPLETE_CACHEFLUSH = "HSTOREKEY::CACHEFLUSH".getBytes(); - public static Text extractFamily(Text col) throws IOException { String column = col.toString(); int colpos = column.indexOf(":"); @@ -97,32 +94,42 @@ public class HStoreKey implements WritableComparable { * @param other Key to compare against. Compares row and column. * @return True if same row and column. * @see {@link #matchesWithoutColumn(HStoreKey)} + * @see {@link #matchesRowFamily(HStoreKey)} */ public boolean matchesRowCol(HStoreKey other) { - if(this.row.compareTo(other.row) == 0 && - this.column.compareTo(other.column) == 0) { - return true; - - } else { - return false; - } + return this.row.compareTo(other.row) == 0 + && this.column.compareTo(other.column) == 0; } /** - * @param other Key to copmare against. Compares row and - * timestamp. - * @return True if same row and timestamp is greater than - * other + * @param other Key to copmare against. Compares row and timestamp. + * + * @return True if same row and timestamp is greater than other * @see {@link #matchesRowCol(HStoreKey)} + * @see {@link #matchesRowFamily(HStoreKey)} */ public boolean matchesWithoutColumn(HStoreKey other) { - if((this.row.compareTo(other.row) == 0) && - (this.timestamp >= other.getTimestamp())) { - return true; + return this.row.compareTo(other.row) == 0 + && this.timestamp >= other.getTimestamp(); + } + + /** + * @param other Key to compare against. Compares row and column family + * + * @return true if same row and column family + * @see {@link #matchesRowCol(HStoreKey)} + * @see {@link #matchesWithoutColumn(HStoreKey)} + */ + public boolean matchesRowFamily(HStoreKey other) { + boolean status = false; + try { + status = this.row.compareTo(other.row) == 0 + && extractFamily(this.column).compareTo( + extractFamily(other.getColumn())) == 0; - } else { - return false; + } catch(IOException e) { } + return status; } public String toString() { diff --git a/src/java/org/apache/hadoop/hbase/HTableDescriptor.java b/src/java/org/apache/hadoop/hbase/HTableDescriptor.java index 1b9f14bd8b8..ea671c55157 100644 --- a/src/java/org/apache/hadoop/hbase/HTableDescriptor.java +++ b/src/java/org/apache/hadoop/hbase/HTableDescriptor.java @@ -15,26 +15,68 @@ */ package org.apache.hadoop.hbase; -import org.apache.hadoop.io.*; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.Iterator; +import java.util.TreeSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; -import java.io.*; -import java.util.*; +import org.apache.hadoop.io.Text; +import org.apache.hadoop.io.WritableComparable; -/******************************************************************************* - * HTableDescriptor contains various facts about an HTable, like its columns, - * column families, etc. - ******************************************************************************/ +/** + * HTableDescriptor contains various facts about an HTable, like + * column families, maximum number of column versions, etc. + */ public class HTableDescriptor implements WritableComparable { Text name; int maxVersions; TreeSet families = new TreeSet(); + + /** + * Legal table names can only contain 'word characters': + * i.e. [a-zA-Z_0-9]. + * + * Lets be restrictive until a reason to be otherwise. + */ + private static final Pattern LEGAL_TABLE_NAME = + Pattern.compile("[\\w-]+"); + + /** + * Legal family names can only contain 'word characters' and + * end in a colon. + */ + private static final Pattern LEGAL_FAMILY_NAME = + Pattern.compile("\\w+:"); public HTableDescriptor() { this.name = new Text(); this.families.clear(); } + /** + * Constructor. + * @param name Table name. + * @param maxVersions Number of versions of a column to keep. + * @throws IllegalArgumentException if passed a table name + * that is made of other than 'word' characters: i.e. + * [a-zA-Z_0-9] + */ public HTableDescriptor(String name, int maxVersions) { + Matcher m = LEGAL_TABLE_NAME.matcher(name); + if (m == null || !m.matches()) { + throw new IllegalArgumentException("Table names can only " + + "contain 'word characters': i.e. [a-zA-Z_0-9"); + } + if (maxVersions <= 0) { + // TODO: Allow maxVersion of 0 to be the way you say + // "Keep all versions". Until there is support, consider + // 0 -- or < 0 -- a configuration error. + throw new IllegalArgumentException("Maximum versions " + + "must be positive"); + } this.name = new Text(name); this.maxVersions = maxVersions; } @@ -47,19 +89,28 @@ public class HTableDescriptor implements WritableComparable { return maxVersions; } - /** Add a column */ + /** + * Add a column family. + * @param family Column family name to add. Column family names + * must end in a : + * @throws IllegalArgumentException if passed a table name + * that is made of other than 'word' characters: i.e. + * [a-zA-Z_0-9] + */ public void addFamily(Text family) { + String familyStr = family.toString(); + Matcher m = LEGAL_FAMILY_NAME.matcher(familyStr); + if (m == null || !m.matches()) { + throw new IllegalArgumentException("Family names can " + + "only contain 'word characters' and must end with a " + + "':'"); + } families.add(family); } /** Do we contain a given column? */ public boolean hasFamily(Text family) { - if(families.contains(family)) { - return true; - - } else { - return false; - } + return families.contains(family); } /** All the column families in this table. */ @@ -67,6 +118,12 @@ public class HTableDescriptor implements WritableComparable { return families; } + @Override + public String toString() { + return "name: " + this.name.toString() + + ", maxVersions: " + this.maxVersions + ", families: " + this.families; + } + ////////////////////////////////////////////////////////////////////////////// // Writable ////////////////////////////////////////////////////////////////////////////// @@ -120,4 +177,4 @@ public class HTableDescriptor implements WritableComparable { } return result; } -} +} \ No newline at end of file diff --git a/src/java/org/apache/hadoop/hbase/LabelledData.java b/src/java/org/apache/hadoop/hbase/LabelledData.java index 481dd1d0871..b5d96cde51b 100644 --- a/src/java/org/apache/hadoop/hbase/LabelledData.java +++ b/src/java/org/apache/hadoop/hbase/LabelledData.java @@ -31,9 +31,9 @@ public class LabelledData implements Writable { this.data = new BytesWritable(); } - public LabelledData(Text label, byte[] data) { + public LabelledData(Text label, BytesWritable data) { this.label = new Text(label); - this.data = new BytesWritable(data); + this.data = data; } public Text getLabel() { diff --git a/src/java/org/apache/hadoop/hbase/Leases.java b/src/java/org/apache/hadoop/hbase/Leases.java index 757fbbc39bf..58e39521b11 100644 --- a/src/java/org/apache/hadoop/hbase/Leases.java +++ b/src/java/org/apache/hadoop/hbase/Leases.java @@ -50,6 +50,7 @@ public class Leases { this.leaseMonitor = new LeaseMonitor(); this.leaseMonitorThread = new Thread(leaseMonitor); + this.leaseMonitorThread.setName("Lease.monitor"); leaseMonitorThread.start(); } @@ -60,6 +61,7 @@ public class Leases { public void close() { this.running = false; try { + this.leaseMonitorThread.interrupt(); this.leaseMonitorThread.join(); } catch (InterruptedException iex) { } diff --git a/src/java/org/apache/hadoop/hbase/package.html b/src/java/org/apache/hadoop/hbase/package.html new file mode 100644 index 00000000000..9866f5d6030 --- /dev/null +++ b/src/java/org/apache/hadoop/hbase/package.html @@ -0,0 +1,28 @@ + + + + +Provides HBase, the Hadoop simple database. + +

Requirements

+
    +
  • Hadoop It has its own set of requirements (Scroll down the page).
  • +
  • Java 1.5.x, preferably from Sun Set JAVA_HOME to the root of your Java installation
  • + +

    Getting Started

    +

    First, you need a working instance of Hadoop. Download releases at Hadoop downloads. +Unpack the release and connect to its top-level directory. Edit the file conf/hadoop-env.sh to define at least JAVA_HOME. Try the following command: +

    bin/hadoop
    +
    +This will display the documentation for the Hadoop command script. +

    +

    TODO

    + +

    Related Documentation

    + + + + + diff --git a/src/test/org/apache/hadoop/hbase/MiniHBaseCluster.java b/src/test/org/apache/hadoop/hbase/MiniHBaseCluster.java index d4b44c92bc5..9467cd65db8 100644 --- a/src/test/org/apache/hadoop/hbase/MiniHBaseCluster.java +++ b/src/test/org/apache/hadoop/hbase/MiniHBaseCluster.java @@ -22,22 +22,38 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.dfs.MiniDFSCluster; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; +import org.apache.log4j.Logger; /** * This class creates a single process HBase cluster for junit testing. * One thread is created for each server. */ public class MiniHBaseCluster implements HConstants { + private static final Logger LOG = + Logger.getLogger(MiniHBaseCluster.class.getName()); private Configuration conf; private MiniDFSCluster cluster; private FileSystem fs; private Path parentdir; - private HMasterRunner master; - private Thread masterThread; + private HMasterRunner masterRunner; + private Thread masterRunnerThread; private HRegionServerRunner[] regionServers; private Thread[] regionThreads; public MiniHBaseCluster(Configuration conf, int nRegionNodes) { + this(conf, nRegionNodes, true); + } + + /** + * Constructor. + * @param conf + * @param nRegionNodes + * @param miniHdfsFilesystem If true, set the hbase mini + * cluster atop a mini hdfs cluster. Otherwise, use the + * filesystem configured in conf. + */ + public MiniHBaseCluster(Configuration conf, int nRegionNodes, + final boolean miniHdfsFilesystem) { this.conf = conf; try { @@ -47,21 +63,20 @@ public class MiniHBaseCluster implements HConstants { "build/contrib/hbase/test"); String dir = testDir.getAbsolutePath(); - System.out.println(dir); + LOG.info("Setting test.build.data to " + dir); System.setProperty("test.build.data", dir); } - // To run using configured filesystem, comment out this - // line below that starts up the MiniDFSCluster. - this.cluster = new MiniDFSCluster(this.conf, 2, true, (String[])null); + if (miniHdfsFilesystem) { + this.cluster = + new MiniDFSCluster(this.conf, 2, true, (String[])null); + } this.fs = FileSystem.get(conf); - this.parentdir = - new Path(conf.get(HREGION_DIR, DEFAULT_HREGION_DIR)); + this.parentdir = new Path(conf.get(HREGION_DIR, DEFAULT_HREGION_DIR)); fs.mkdirs(parentdir); } catch(Throwable e) { - System.err.println("Mini DFS cluster failed to start"); - e.printStackTrace(); + LOG.error("Failed setup of FileSystem", e); throw e; } @@ -70,28 +85,27 @@ public class MiniHBaseCluster implements HConstants { } // Create the master - - this.master = new HMasterRunner(); - this.masterThread = new Thread(master, "HMaster"); + this.masterRunner = new HMasterRunner(); + this.masterRunnerThread = new Thread(masterRunner, "masterRunner"); // Start up the master - - masterThread.start(); - while(! master.isCrashed() && ! master.isInitialized()) { + LOG.info("Starting HMaster"); + masterRunnerThread.start(); + while(! masterRunner.isCrashed() && ! masterRunner.isInitialized()) { try { - System.err.println("Waiting for HMaster to initialize..."); + LOG.info("...waiting for HMaster to initialize..."); Thread.sleep(1000); - } catch(InterruptedException e) { } - if(master.isCrashed()) { + if(masterRunner.isCrashed()) { throw new RuntimeException("HMaster crashed"); } } - + LOG.info("HMaster started."); + // Set the master's port for the HRegionServers - - this.conf.set(MASTER_ADDRESS, master.getHMasterAddress().toString()); + String address = masterRunner.getHMasterAddress().toString(); + this.conf.set(MASTER_ADDRESS, address); // Start the HRegionServers @@ -99,28 +113,26 @@ public class MiniHBaseCluster implements HConstants { this.conf.set(REGIONSERVER_ADDRESS, "localhost:0"); } + LOG.info("Starting HRegionServers"); startRegionServers(this.conf, nRegionNodes); + LOG.info("HRegionServers running"); // Wait for things to get started - while(! master.isCrashed() && ! master.isUp()) { + while(! masterRunner.isCrashed() && ! masterRunner.isUp()) { try { - System.err.println("Waiting for Mini HBase cluster to start..."); + LOG.info("Waiting for Mini HBase cluster to start..."); Thread.sleep(1000); - } catch(InterruptedException e) { } - if(master.isCrashed()) { + if(masterRunner.isCrashed()) { throw new RuntimeException("HMaster crashed"); } } } catch(Throwable e) { - // Delete all DFS files - deleteFile(new File(System.getProperty("test.build.data"), "dfs")); - throw new RuntimeException("Mini HBase cluster did not start"); } } @@ -141,39 +153,35 @@ public class MiniHBaseCluster implements HConstants { * supplied port is not necessarily the actual port used. */ public HServerAddress getHMasterAddress() { - return master.getHMasterAddress(); + return masterRunner.getHMasterAddress(); } /** Shut down the HBase cluster */ public void shutdown() { - System.out.println("Shutting down the HBase Cluster"); + LOG.info("Shutting down the HBase Cluster"); for(int i = 0; i < regionServers.length; i++) { regionServers[i].shutdown(); } - master.shutdown(); - + masterRunner.shutdown(); for(int i = 0; i < regionServers.length; i++) { try { regionThreads[i].join(); - - } catch(InterruptedException e) { + } catch (InterruptedException e) { + e.printStackTrace(); } } try { - masterThread.join(); - - } catch(InterruptedException e) { + masterRunnerThread.join(); + } catch (InterruptedException e) { + e.printStackTrace(); } - - System.out.println("Shutting down Mini DFS cluster"); if (cluster != null) { + LOG.info("Shutting down Mini DFS cluster"); cluster.shutdown(); } // Delete all DFS files - deleteFile(new File(System.getProperty("test.build.data"), "dfs")); - } private void deleteFile(File f) { @@ -188,12 +196,14 @@ public class MiniHBaseCluster implements HConstants { private class HMasterRunner implements Runnable { private HMaster master = null; + private Thread masterThread = null; private volatile boolean isInitialized = false; private boolean isCrashed = false; private boolean isRunning = true; + private long threadSleepTime = conf.getLong(THREAD_WAKE_FREQUENCY, 10 * 1000); public HServerAddress getHMasterAddress() { - return master.getMasterAddress(); + return this.master.getMasterAddress(); } public synchronized boolean isInitialized() { @@ -218,33 +228,46 @@ public class MiniHBaseCluster implements HConstants { try { synchronized(this) { if(isRunning) { - master = new HMaster(conf); + this.master = new HMaster(conf); + masterThread = new Thread(this.master); + masterThread.start(); } isInitialized = true; } } catch(Throwable e) { shutdown(); - System.err.println("HMaster crashed:"); - e.printStackTrace(); + LOG.error("HMaster crashed:", e); synchronized(this) { isCrashed = true; } } + + while(this.master != null && this.master.isMasterRunning()) { + try { + Thread.sleep(threadSleepTime); + + } catch(InterruptedException e) { + } + } + synchronized(this) { + isCrashed = true; + } + shutdown(); } /** Shut down the HMaster and wait for it to finish */ public synchronized void shutdown() { isRunning = false; - if(master != null) { + if (this.master != null) { try { - master.stop(); - + this.master.shutdown(); } catch(IOException e) { - System.err.println("Master crashed during stop"); - e.printStackTrace(); - + LOG.error("Master crashed during stop", e); } finally { - master.join(); + try { + masterThread.join(); + } catch(InterruptedException e) { + } master = null; } } @@ -272,8 +295,7 @@ public class MiniHBaseCluster implements HConstants { } catch(Throwable e) { shutdown(); - System.err.println("HRegionServer crashed:"); - e.printStackTrace(); + LOG.error("HRegionServer crashed:", e); } } @@ -285,9 +307,7 @@ public class MiniHBaseCluster implements HConstants { server.stop(); } catch(IOException e) { - System.err.println("HRegionServer crashed during stop"); - e.printStackTrace(); - + LOG.error("HRegionServer crashed during stop", e); } finally { server.join(); server = null; diff --git a/src/test/org/apache/hadoop/hbase/TestGet.java b/src/test/org/apache/hadoop/hbase/TestGet.java new file mode 100644 index 00000000000..07ee39101be --- /dev/null +++ b/src/test/org/apache/hadoop/hbase/TestGet.java @@ -0,0 +1,231 @@ +/** + * Copyright 2006 The Apache Software Foundation + * + * Licensed 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.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.TreeMap; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.dfs.MiniDFSCluster; +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.io.BytesWritable; +import org.apache.hadoop.io.Text; + +import org.apache.log4j.Appender; +import org.apache.log4j.ConsoleAppender; +import org.apache.log4j.Layout; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.log4j.PatternLayout; + +import junit.framework.TestCase; + +public class TestGet extends TestCase { + private static final Text CONTENTS = new Text("contents:"); + private static final Text ROW_KEY = new Text(HGlobals.rootRegionInfo.regionName); + + + private void dumpRegion(HRegion r) throws IOException { + for(Iterator i = r.stores.values().iterator(); i.hasNext(); ) { + i.next().dumpMaps(); + } + } + + private void verifyGet(HRegion r) throws IOException { + // This should return a value because there is only one family member + + BytesWritable value = r.get(ROW_KEY, CONTENTS); + assertNotNull(value); + + // This should not return a value because there are multiple family members + + value = r.get(ROW_KEY, HConstants.COLUMN_FAMILY); + assertNull(value); + + // Find out what getFull returns + + TreeMap values = r.getFull(ROW_KEY); + //assertEquals(4, values.keySet().size()); + for(Iterator i = values.keySet().iterator(); i.hasNext(); ) { + Text column = i.next(); + System.out.println(column); + if(column.equals(HConstants.COL_SERVER)) { + BytesWritable val = values.get(column); + byte[] bytes = new byte[val.getSize()]; + System.arraycopy(val.get(), 0, bytes, 0, bytes.length); + System.out.println(" " + new String(bytes, HConstants.UTF8_ENCODING)); + } + } + } + + @SuppressWarnings("unchecked") + public void testGet() throws IOException { + MiniDFSCluster cluster = null; + + try { + + // Initialization + + if(System.getProperty("test.build.data") == null) { + String dir = new File(new File("").getAbsolutePath(), "build/contrib/hbase/test").getAbsolutePath(); + System.out.println(dir); + System.setProperty("test.build.data", dir); + } + Configuration conf = new HBaseConfiguration(); + + Environment.getenv(); + if(Environment.debugging) { + Logger rootLogger = Logger.getRootLogger(); + rootLogger.setLevel(Level.WARN); + + ConsoleAppender consoleAppender = null; + for(Enumeration e = (Enumeration)rootLogger.getAllAppenders(); + e.hasMoreElements();) { + + Appender a = e.nextElement(); + if(a instanceof ConsoleAppender) { + consoleAppender = (ConsoleAppender)a; + break; + } + } + if(consoleAppender != null) { + Layout layout = consoleAppender.getLayout(); + if(layout instanceof PatternLayout) { + PatternLayout consoleLayout = (PatternLayout)layout; + consoleLayout.setConversionPattern("%d %-5p [%t] %l: %m%n"); + } + } + Logger.getLogger("org.apache.hadoop.hbase").setLevel(Environment.logLevel); + } + cluster = new MiniDFSCluster(conf, 2, true, (String[])null); + FileSystem fs = cluster.getFileSystem(); + Path dir = new Path("/hbase"); + fs.mkdirs(dir); + + HTableDescriptor desc = new HTableDescriptor("test", 1); + desc.addFamily(CONTENTS); + desc.addFamily(HConstants.COLUMN_FAMILY); + + HRegionInfo info = new HRegionInfo(0L, desc, null, null); + Path regionDir = HStoreFile.getHRegionDir(dir, info.regionName); + fs.mkdirs(regionDir); + + HLog log = new HLog(fs, new Path(regionDir, "log"), conf); + + HRegion r = new HRegion(dir, log, fs, conf, info, null, null); + + // Write information to the table + + long lockid = r.startUpdate(ROW_KEY); + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + DataOutputStream s = new DataOutputStream(bytes); + CONTENTS.write(s); + r.put(lockid, CONTENTS, new BytesWritable(bytes.toByteArray())); + + bytes.reset(); + HGlobals.rootRegionInfo.write(s); + + r.put(lockid, HConstants.COL_REGIONINFO, new BytesWritable(bytes.toByteArray())); + + r.commit(lockid); + + lockid = r.startUpdate(ROW_KEY); + + r.put(lockid, HConstants.COL_SERVER, + new BytesWritable( + new HServerAddress("foo.bar.com:1234").toString().getBytes(HConstants.UTF8_ENCODING) + ) + ); + + r.put(lockid, HConstants.COL_STARTCODE, + new BytesWritable( + String.valueOf(lockid).getBytes(HConstants.UTF8_ENCODING) + ) + ); + + r.put(lockid, new Text(HConstants.COLUMN_FAMILY + "region"), + new BytesWritable("region".getBytes(HConstants.UTF8_ENCODING))); + + r.commit(lockid); + + // Verify that get works the same from memcache as when reading from disk + // NOTE dumpRegion won't work here because it only reads from disk. + + verifyGet(r); + + // Close and re-open region, forcing updates to disk + + r.close(); + log.rollWriter(); + r = new HRegion(dir, log, fs, conf, info, null, null); + + // Read it back + + dumpRegion(r); + verifyGet(r); + + // Update one family member and add a new one + + lockid = r.startUpdate(ROW_KEY); + + r.put(lockid, new Text(HConstants.COLUMN_FAMILY + "region"), + new BytesWritable("region2".getBytes())); + + r.put(lockid, HConstants.COL_SERVER, + new BytesWritable( + new HServerAddress("bar.foo.com:4321").toString().getBytes(HConstants.UTF8_ENCODING) + ) + ); + + r.put(lockid, new Text(HConstants.COLUMN_FAMILY + "junk"), + new BytesWritable("junk".getBytes())); + + r.commit(lockid); + + verifyGet(r); + + // Close region and re-open it + + r.close(); + log.rollWriter(); + r = new HRegion(dir, log, fs, conf, info, null, null); + + // Read it back + + dumpRegion(r); + verifyGet(r); + + // Close region once and for all + + r.close(); + + } catch(IOException e) { + e.printStackTrace(); + throw e; + + } finally { + if(cluster != null) { + cluster.shutdown(); + } + } + } +} diff --git a/src/test/org/apache/hadoop/hbase/TestHBaseCluster.java b/src/test/org/apache/hadoop/hbase/TestHBaseCluster.java new file mode 100644 index 00000000000..38fda0cdd87 --- /dev/null +++ b/src/test/org/apache/hadoop/hbase/TestHBaseCluster.java @@ -0,0 +1,289 @@ +/** + * Copyright 2006 The Apache Software Foundation + * + * Licensed 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.Enumeration; +import java.util.Iterator; +import java.util.TreeMap; +import java.util.TreeSet; + +import junit.framework.TestCase; +import junit.framework.Test; +import junit.framework.TestSuite; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.io.Text; + +import org.apache.log4j.Appender; +import org.apache.log4j.ConsoleAppender; +import org.apache.log4j.Layout; +import org.apache.log4j.Logger; +import org.apache.log4j.Level; +import org.apache.log4j.PatternLayout; + +/** + * Test HBase Master and Region servers, client API + */ +public class TestHBaseCluster extends TestCase { + + /** constructor */ + public TestHBaseCluster(String name) { + super(name); + } + + /** Test suite so that all tests get run */ + public static Test suite() { + TestSuite suite = new TestSuite(); + suite.addTest(new TestHBaseCluster("testSetup")); + suite.addTest(new TestHBaseCluster("testBasic")); + suite.addTest(new TestHBaseCluster("testScanner")); + suite.addTest(new TestHBaseCluster("testCleanup")); + return suite; + } + + private static final int FIRST_ROW = 1; + private static final int NUM_VALS = 1000; + private static final Text CONTENTS = new Text("contents:"); + private static final Text CONTENTS_BASIC = new Text("contents:basic"); + private static final String CONTENTSTR = "contentstr"; + private static final Text ANCHOR = new Text("anchor:"); + private static final String ANCHORNUM = "anchor:anchornum-"; + private static final String ANCHORSTR = "anchorstr"; + + private static Configuration conf = null; + private static boolean failures = false; + private static boolean initialized = false; + private static MiniHBaseCluster cluster = null; + private static HTableDescriptor desc = null; + private static HClient client = null; + + // Set up environment, start mini cluster, etc. + + @SuppressWarnings("unchecked") + public void testSetup() throws Exception { + try { + if(System.getProperty("test.build.data") == null) { + String dir = new File(new File("").getAbsolutePath(), "build/contrib/hbase/test").getAbsolutePath(); + System.out.println(dir); + System.setProperty("test.build.data", dir); + } + conf = new HBaseConfiguration(); + + Environment.getenv(); + if(Environment.debugging) { + Logger rootLogger = Logger.getRootLogger(); + rootLogger.setLevel(Level.WARN); + + ConsoleAppender consoleAppender = null; + for(Enumeration e = (Enumeration)rootLogger.getAllAppenders(); + e.hasMoreElements();) { + + Appender a = e.nextElement(); + if(a instanceof ConsoleAppender) { + consoleAppender = (ConsoleAppender)a; + break; + } + } + if(consoleAppender != null) { + Layout layout = consoleAppender.getLayout(); + if(layout instanceof PatternLayout) { + PatternLayout consoleLayout = (PatternLayout)layout; + consoleLayout.setConversionPattern("%d %-5p [%t] %l: %m%n"); + } + } + Logger.getLogger("org.apache.hadoop.hbase").setLevel(Environment.logLevel); + } + cluster = new MiniHBaseCluster(conf, 1); + client = new HClient(conf); + + desc = new HTableDescriptor("test", 3); + desc.addFamily(new Text(CONTENTS)); + desc.addFamily(new Text(ANCHOR)); + client.createTable(desc); + + } catch(Exception e) { + failures = true; + throw e; + } + initialized = true; + } + + // Test basic functionality. Writes to contents:basic and anchor:anchornum-* + + public void testBasic() throws IOException { + if(!initialized) { + throw new IllegalStateException(); + } + + try { + long startTime = System.currentTimeMillis(); + + client.openTable(desc.getName()); + + // Write out a bunch of values + + for (int k = FIRST_ROW; k <= NUM_VALS; k++) { + long writeid = client.startUpdate(new Text("row_" + k)); + client.put(writeid, CONTENTS_BASIC, (CONTENTSTR + k).getBytes()); + client.put(writeid, new Text(ANCHORNUM + k), (ANCHORSTR + k).getBytes()); + client.commit(writeid); + } + System.out.println("Write " + NUM_VALS + " rows. Elapsed time: " + + ((System.currentTimeMillis() - startTime) / 1000.0)); + + // Read them back in + + startTime = System.currentTimeMillis(); + + Text collabel = null; + for (int k = FIRST_ROW; k <= NUM_VALS; k++) { + Text rowlabel = new Text("row_" + k); + + byte bodydata[] = client.get(rowlabel, CONTENTS_BASIC); + assertNotNull(bodydata); + String bodystr = new String(bodydata).toString().trim(); + String teststr = CONTENTSTR + k; + assertEquals("Incorrect value for key: (" + rowlabel + "," + CONTENTS_BASIC + + "), expected: '" + teststr + "' got: '" + bodystr + "'", + bodystr, teststr); + collabel = new Text(ANCHORNUM + k); + bodydata = client.get(rowlabel, collabel); + bodystr = new String(bodydata).toString().trim(); + teststr = ANCHORSTR + k; + assertEquals("Incorrect value for key: (" + rowlabel + "," + collabel + + "), expected: '" + teststr + "' got: '" + bodystr + "'", + bodystr, teststr); + } + + System.out.println("Read " + NUM_VALS + " rows. Elapsed time: " + + ((System.currentTimeMillis() - startTime) / 1000.0)); + + } catch(IOException e) { + failures = true; + throw e; + } + } + + public void testScanner() throws IOException { + if(!initialized || failures) { + throw new IllegalStateException(); + } + + Text[] cols = new Text[] { + new Text(ANCHORNUM + "[0-9]+"), + new Text(CONTENTS_BASIC) + }; + + long startTime = System.currentTimeMillis(); + + HScannerInterface s = client.obtainScanner(cols, new Text()); + try { + + int contentsFetched = 0; + int anchorFetched = 0; + HStoreKey curKey = new HStoreKey(); + TreeMap curVals = new TreeMap(); + int k = 0; + while(s.next(curKey, curVals)) { + for(Iterator it = curVals.keySet().iterator(); it.hasNext(); ) { + Text col = it.next(); + byte val[] = curVals.get(col); + String curval = new String(val).trim(); + + if(col.compareTo(CONTENTS_BASIC) == 0) { + assertTrue("Error at:" + curKey.getRow() + "/" + curKey.getTimestamp() + + ", Value for " + col + " should start with: " + CONTENTSTR + + ", but was fetched as: " + curval, + curval.startsWith(CONTENTSTR)); + contentsFetched++; + + } else if(col.toString().startsWith(ANCHORNUM)) { + assertTrue("Error at:" + curKey.getRow() + "/" + curKey.getTimestamp() + + ", Value for " + col + " should start with: " + ANCHORSTR + + ", but was fetched as: " + curval, + curval.startsWith(ANCHORSTR)); + anchorFetched++; + + } else { + System.out.println(col); + } + } + curVals.clear(); + k++; + } + assertEquals("Expected " + NUM_VALS + " " + CONTENTS_BASIC + " values, but fetched " + contentsFetched, NUM_VALS, contentsFetched); + assertEquals("Expected " + NUM_VALS + " " + ANCHORNUM + " values, but fetched " + anchorFetched, NUM_VALS, anchorFetched); + + System.out.println("Scanned " + NUM_VALS + + " rows. Elapsed time: " + + ((System.currentTimeMillis() - startTime) / 1000.0)); + + } catch(IOException e) { + failures = true; + throw e; + + } finally { + s.close(); + } + } + + public void testListTables() throws IOException { + if(!initialized || failures) { + throw new IllegalStateException(); + } + + try { + HTableDescriptor[] tables = client.listTables(); + assertEquals(1, tables.length); + assertEquals(desc.getName(), tables[0].getName()); + TreeSet families = tables[0].families(); + assertEquals(2, families.size()); + assertTrue(families.contains(new Text(CONTENTS))); + assertTrue(families.contains(new Text(ANCHOR))); + + } catch(IOException e) { + failures = true; + throw e; + } + } + + public void testCleanup() throws IOException { + if(!initialized) { + throw new IllegalStateException(); + } + + try { + if(!failures) { + // Delete the table we created + + client.deleteTable(desc.getName()); + try { + Thread.sleep(60000); // Wait for table to be deleted + + } catch(InterruptedException e) { + } + } + + } finally { + // Shut down the cluster + + cluster.shutdown(); + client.close(); + } + } +} diff --git a/src/test/org/apache/hadoop/hbase/TestHMemcache.java b/src/test/org/apache/hadoop/hbase/TestHMemcache.java index 8ccb60b2785..1ba3185b9ff 100644 --- a/src/test/org/apache/hadoop/hbase/TestHMemcache.java +++ b/src/test/org/apache/hadoop/hbase/TestHMemcache.java @@ -16,6 +16,8 @@ package org.apache.hadoop.hbase; import java.io.IOException; +import java.util.Iterator; +import java.util.Map; import java.util.TreeMap; import junit.framework.TestCase; @@ -26,11 +28,8 @@ import org.apache.hadoop.fs.Path; import org.apache.hadoop.hbase.HMemcache.Snapshot; import org.apache.hadoop.io.BytesWritable; import org.apache.hadoop.io.Text; -import org.apache.log4j.Logger; public class TestHMemcache extends TestCase { - private final Logger LOG = - Logger.getLogger(this.getClass().getName()); private HMemcache hmemcache; @@ -77,10 +76,10 @@ public class TestHMemcache extends TestCase { */ private void addRows(final HMemcache hmc) { for (int i = 0; i < ROW_COUNT; i++) { - TreeMap columns = new TreeMap(); + TreeMap columns = new TreeMap(); for (int ii = 0; ii < COLUMNS_COUNT; ii++) { Text k = getColumnName(i, ii); - columns.put(k, k.toString().getBytes()); + columns.put(k, new BytesWritable(k.toString().getBytes())); } hmc.add(getRowName(i), columns, System.currentTimeMillis()); } @@ -139,7 +138,7 @@ public class TestHMemcache extends TestCase { } private void isExpectedRow(final int rowIndex, - TreeMap row) { + TreeMap row) { int i = 0; for (Text colname: row.keySet()) { String expectedColname = @@ -150,8 +149,10 @@ public class TestHMemcache extends TestCase { // 100 bytes in size at least. This is the default size // for BytesWriteable. For comparison, comvert bytes to // String and trim to remove trailing null bytes. - String colvalueStr = - new String(row.get(colname)).trim(); + BytesWritable value = row.get(colname); + byte[] bytes = new byte[value.getSize()]; + System.arraycopy(value.get(), 0, bytes, 0, bytes.length); + String colvalueStr = new String(bytes).trim(); assertEquals("Content", colnameStr, colvalueStr); } } @@ -160,7 +161,7 @@ public class TestHMemcache extends TestCase { addRows(this.hmemcache); for (int i = 0; i < ROW_COUNT; i++) { HStoreKey hsk = new HStoreKey(getRowName(i)); - TreeMap all = this.hmemcache.getFull(hsk); + TreeMap all = this.hmemcache.getFull(hsk); isExpectedRow(i, all); } } @@ -174,16 +175,22 @@ public class TestHMemcache extends TestCase { cols[(ii + (i * COLUMNS_COUNT))] = getColumnName(i, ii); } } - HScannerInterface scanner = + HInternalScannerInterface scanner = this.hmemcache.getScanner(timestamp, cols, new Text()); HStoreKey key = new HStoreKey(); - TreeMap results = new TreeMap(); + TreeMap results = new TreeMap(); for (int i = 0; scanner.next(key, results); i++) { assertTrue("Row name", key.toString().startsWith(getRowName(i).toString())); assertEquals("Count of columns", COLUMNS_COUNT, results.size()); - isExpectedRow(i, results); + TreeMap row = new TreeMap(); + for(Iterator> it = results.entrySet().iterator(); + it.hasNext(); ) { + Map.Entry e = it.next(); + row.put(e.getKey(), e.getValue()); + } + isExpectedRow(i, row); // Clear out set. Otherwise row results accumulate. results.clear(); } diff --git a/src/test/org/apache/hadoop/hbase/TestHRegion.java b/src/test/org/apache/hadoop/hbase/TestHRegion.java index 07ed3e1d955..dd88ffbb53a 100644 --- a/src/test/org/apache/hadoop/hbase/TestHRegion.java +++ b/src/test/org/apache/hadoop/hbase/TestHRegion.java @@ -31,6 +31,7 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.dfs.MiniDFSCluster; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; +import org.apache.hadoop.io.BytesWritable; import org.apache.hadoop.io.Text; import org.apache.log4j.Appender; @@ -167,8 +168,11 @@ public class TestHRegion extends TestCase { for (int k = FIRST_ROW; k <= NUM_VALS; k++) { long writeid = region.startUpdate(new Text("row_" + k)); - region.put(writeid, CONTENTS_BASIC, (CONTENTSTR + k).getBytes()); - region.put(writeid, new Text(ANCHORNUM + k), (ANCHORSTR + k).getBytes()); + region.put(writeid, CONTENTS_BASIC, + new BytesWritable((CONTENTSTR + k).getBytes())); + + region.put(writeid, new Text(ANCHORNUM + k), + new BytesWritable((ANCHORSTR + k).getBytes())); region.commit(writeid); } System.out.println("Write " + NUM_VALS + " rows. Elapsed time: " @@ -191,16 +195,20 @@ public class TestHRegion extends TestCase { for (int k = FIRST_ROW; k <= NUM_VALS; k++) { Text rowlabel = new Text("row_" + k); - byte bodydata[] = region.get(rowlabel, CONTENTS_BASIC); + BytesWritable bodydata = region.get(rowlabel, CONTENTS_BASIC); assertNotNull(bodydata); - String bodystr = new String(bodydata).toString().trim(); + byte[] bytes = new byte[bodydata.getSize()]; + System.arraycopy(bodydata.get(), 0, bytes, 0, bytes.length); + String bodystr = new String(bytes).toString().trim(); String teststr = CONTENTSTR + k; assertEquals("Incorrect value for key: (" + rowlabel + "," + CONTENTS_BASIC + "), expected: '" + teststr + "' got: '" + bodystr + "'", bodystr, teststr); collabel = new Text(ANCHORNUM + k); bodydata = region.get(rowlabel, collabel); - bodystr = new String(bodydata).toString().trim(); + bytes = new byte[bodydata.getSize()]; + System.arraycopy(bodydata.get(), 0, bytes, 0, bytes.length); + bodystr = new String(bytes).toString().trim(); teststr = ANCHORSTR + k; assertEquals("Incorrect value for key: (" + rowlabel + "," + collabel + "), expected: '" + teststr + "' got: '" + bodystr + "'", @@ -224,7 +232,7 @@ public class TestHRegion extends TestCase { // Try put with bad lockid. boolean exceptionThrown = false; try { - region.put(-1, CONTENTS_BASIC, "bad input".getBytes()); + region.put(-1, CONTENTS_BASIC, new BytesWritable("bad input".getBytes())); } catch (LockException e) { exceptionThrown = true; } @@ -237,7 +245,7 @@ public class TestHRegion extends TestCase { lockid = region.startUpdate(new Text("Some old key")); String unregisteredColName = "FamilyGroup:FamilyLabel"; region.put(lockid, new Text(unregisteredColName), - unregisteredColName.getBytes()); + new BytesWritable(unregisteredColName.getBytes())); } catch (IOException e) { exceptionThrown = true; } finally { @@ -333,8 +341,8 @@ public class TestHRegion extends TestCase { String kLabel = String.format("%1$03d", k); long lockid = region.startUpdate(new Text("row_vals1_" + kLabel)); - region.put(lockid, cols[0], vals1[k].getBytes()); - region.put(lockid, cols[1], vals1[k].getBytes()); + region.put(lockid, cols[0], new BytesWritable(vals1[k].getBytes())); + region.put(lockid, cols[1], new BytesWritable(vals1[k].getBytes())); region.commit(lockid); numInserted += 2; } @@ -346,17 +354,19 @@ public class TestHRegion extends TestCase { startTime = System.currentTimeMillis(); - HScannerInterface s = region.getScanner(cols, new Text()); + HInternalScannerInterface s = region.getScanner(cols, new Text()); int numFetched = 0; try { HStoreKey curKey = new HStoreKey(); - TreeMap curVals = new TreeMap(); + TreeMap curVals = new TreeMap(); int k = 0; while(s.next(curKey, curVals)) { for(Iterator it = curVals.keySet().iterator(); it.hasNext(); ) { Text col = it.next(); - byte val[] = curVals.get(col); - int curval = Integer.parseInt(new String(val).trim()); + BytesWritable val = curVals.get(col); + byte[] bytes = new byte[val.getSize()]; + System.arraycopy(val.get(), 0, bytes, 0, bytes.length); + int curval = Integer.parseInt(new String(bytes).trim()); for(int j = 0; j < cols.length; j++) { if(col.compareTo(cols[j]) == 0) { @@ -396,13 +406,15 @@ public class TestHRegion extends TestCase { numFetched = 0; try { HStoreKey curKey = new HStoreKey(); - TreeMap curVals = new TreeMap(); + TreeMap curVals = new TreeMap(); int k = 0; while(s.next(curKey, curVals)) { for(Iterator it = curVals.keySet().iterator(); it.hasNext(); ) { Text col = it.next(); - byte val[] = curVals.get(col); - int curval = Integer.parseInt(new String(val).trim()); + BytesWritable val = curVals.get(col); + byte[] bytes = new byte[val.getSize()]; + System.arraycopy(val.get(), 0, bytes, 0, bytes.length); + int curval = Integer.parseInt(new String(bytes).trim()); for(int j = 0; j < cols.length; j++) { if(col.compareTo(cols[j]) == 0) { @@ -433,8 +445,8 @@ public class TestHRegion extends TestCase { String kLabel = String.format("%1$03d", k); long lockid = region.startUpdate(new Text("row_vals1_" + kLabel)); - region.put(lockid, cols[0], vals1[k].getBytes()); - region.put(lockid, cols[1], vals1[k].getBytes()); + region.put(lockid, cols[0], new BytesWritable(vals1[k].getBytes())); + region.put(lockid, cols[1], new BytesWritable(vals1[k].getBytes())); region.commit(lockid); numInserted += 2; } @@ -450,13 +462,15 @@ public class TestHRegion extends TestCase { numFetched = 0; try { HStoreKey curKey = new HStoreKey(); - TreeMap curVals = new TreeMap(); + TreeMap curVals = new TreeMap(); int k = 0; while(s.next(curKey, curVals)) { for(Iterator it = curVals.keySet().iterator(); it.hasNext(); ) { Text col = it.next(); - byte val[] = curVals.get(col); - int curval = Integer.parseInt(new String(val).trim()); + BytesWritable val = curVals.get(col); + byte[] bytes = new byte[val.getSize()]; + System.arraycopy(val.get(), 0, bytes, 0, bytes.length); + int curval = Integer.parseInt(new String(bytes).trim()); for(int j = 0; j < cols.length; j++) { if(col.compareTo(cols[j]) == 0) { @@ -496,13 +510,15 @@ public class TestHRegion extends TestCase { numFetched = 0; try { HStoreKey curKey = new HStoreKey(); - TreeMap curVals = new TreeMap(); + TreeMap curVals = new TreeMap(); int k = 0; while(s.next(curKey, curVals)) { for(Iterator it = curVals.keySet().iterator(); it.hasNext(); ) { Text col = it.next(); - byte val[] = curVals.get(col); - int curval = Integer.parseInt(new String(val).trim()); + BytesWritable val = curVals.get(col); + byte[] bytes = new byte[val.getSize()]; + System.arraycopy(val.get(), 0, bytes, 0, bytes.length); + int curval = Integer.parseInt(new String(bytes).trim()); for (int j = 0; j < cols.length; j++) { if (col.compareTo(cols[j]) == 0) { @@ -532,13 +548,15 @@ public class TestHRegion extends TestCase { numFetched = 0; try { HStoreKey curKey = new HStoreKey(); - TreeMap curVals = new TreeMap(); + TreeMap curVals = new TreeMap(); int k = 500; while(s.next(curKey, curVals)) { for(Iterator it = curVals.keySet().iterator(); it.hasNext(); ) { Text col = it.next(); - byte val[] = curVals.get(col); - int curval = Integer.parseInt(new String(val).trim()); + BytesWritable val = curVals.get(col); + byte[] bytes = new byte[val.getSize()]; + System.arraycopy(val.get(), 0, bytes, 0, bytes.length); + int curval = Integer.parseInt(new String(bytes).trim()); for (int j = 0; j < cols.length; j++) { if (col.compareTo(cols[j]) == 0) { @@ -592,7 +610,7 @@ public class TestHRegion extends TestCase { // Write to the HRegion long writeid = region.startUpdate(new Text("row_" + k)); - region.put(writeid, CONTENTS_BODY, buf1.toString().getBytes()); + region.put(writeid, CONTENTS_BODY, new BytesWritable(buf1.toString().getBytes())); region.commit(writeid); if (k > 0 && k % (N_ROWS / 100) == 0) { System.out.println("Flushing write #" + k); @@ -707,20 +725,22 @@ public class TestHRegion extends TestCase { long startTime = System.currentTimeMillis(); - HScannerInterface s = region.getScanner(cols, new Text()); + HInternalScannerInterface s = region.getScanner(cols, new Text()); try { int contentsFetched = 0; int anchorFetched = 0; HStoreKey curKey = new HStoreKey(); - TreeMap curVals = new TreeMap(); + TreeMap curVals = new TreeMap(); int k = 0; while(s.next(curKey, curVals)) { for(Iterator it = curVals.keySet().iterator(); it.hasNext(); ) { Text col = it.next(); - byte val[] = curVals.get(col); - String curval = new String(val).trim(); + BytesWritable val = curVals.get(col); + byte[] bytes = new byte[val.getSize()]; + System.arraycopy(val.get(), 0, bytes, 0, bytes.length); + String curval = new String(bytes).trim(); if(col.compareTo(CONTENTS_BASIC) == 0) { assertTrue("Error at:" + curKey.getRow() + "/" + curKey.getTimestamp() @@ -767,13 +787,15 @@ public class TestHRegion extends TestCase { try { int numFetched = 0; HStoreKey curKey = new HStoreKey(); - TreeMap curVals = new TreeMap(); + TreeMap curVals = new TreeMap(); int k = 0; while(s.next(curKey, curVals)) { for(Iterator it = curVals.keySet().iterator(); it.hasNext(); ) { Text col = it.next(); - byte val[] = curVals.get(col); - int curval = Integer.parseInt(new String(val).trim()); + BytesWritable val = curVals.get(col); + byte[] bytes = new byte[val.getSize()]; + System.arraycopy(val.get(), 0, bytes, 0, bytes.length); + int curval = Integer.parseInt(new String(bytes).trim()); for (int j = 0; j < cols.length; j++) { if (col.compareTo(cols[j]) == 0) { @@ -805,12 +827,12 @@ public class TestHRegion extends TestCase { try { int numFetched = 0; HStoreKey curKey = new HStoreKey(); - TreeMap curVals = new TreeMap(); + TreeMap curVals = new TreeMap(); int k = 0; while(s.next(curKey, curVals)) { for(Iterator it = curVals.keySet().iterator(); it.hasNext(); ) { Text col = it.next(); - byte val[] = curVals.get(col); + BytesWritable val = curVals.get(col); assertTrue(col.compareTo(CONTENTS_BODY) == 0); assertNotNull(val); @@ -843,7 +865,7 @@ public class TestHRegion extends TestCase { try { int fetched = 0; HStoreKey curKey = new HStoreKey(); - TreeMap curVals = new TreeMap(); + TreeMap curVals = new TreeMap(); while(s.next(curKey, curVals)) { for(Iterator it = curVals.keySet().iterator(); it.hasNext(); ) { it.next(); diff --git a/src/test/org/apache/hadoop/hbase/TestScanner.java b/src/test/org/apache/hadoop/hbase/TestScanner.java new file mode 100644 index 00000000000..4563f438c36 --- /dev/null +++ b/src/test/org/apache/hadoop/hbase/TestScanner.java @@ -0,0 +1,319 @@ +/** + * Copyright 2006 The Apache Software Foundation + * + * Licensed 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.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.util.Enumeration; +import java.util.TreeMap; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.dfs.MiniDFSCluster; +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.io.BytesWritable; +import org.apache.hadoop.io.DataInputBuffer; +import org.apache.hadoop.io.Text; + +import org.apache.log4j.Appender; +import org.apache.log4j.ConsoleAppender; +import org.apache.log4j.Layout; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.log4j.PatternLayout; + +import junit.framework.TestCase; + +public class TestScanner extends TestCase { + private static final Text FIRST_ROW = new Text(); + private static final Text[] COLS = { + HConstants.COLUMN_FAMILY + }; + private static final Text[] EXPLICIT_COLS = { + HConstants.COL_REGIONINFO, + HConstants.COL_SERVER, + HConstants.COL_STARTCODE + }; + + private static final Text ROW_KEY = new Text(HGlobals.rootRegionInfo.regionName); + private static final HRegionInfo REGION_INFO = + new HRegionInfo(0L, HGlobals.rootTableDesc, null, null); + + private static final long START_CODE = Long.MAX_VALUE; + + private HRegion region; + private DataInputBuffer in = new DataInputBuffer(); + + /** Compare the HRegionInfo we read from HBase to what we stored */ + private void validateRegionInfo(BytesWritable regionBytes) throws IOException { + in.reset(regionBytes.get(), regionBytes.getSize()); + HRegionInfo info = new HRegionInfo(); + info.readFields(in); + + assertEquals(REGION_INFO.regionId, info.regionId); + assertEquals(0, info.startKey.getLength()); + assertEquals(0, info.endKey.getLength()); + assertEquals(0, info.regionName.compareTo(REGION_INFO.regionName)); + assertEquals(0, info.tableDesc.compareTo(REGION_INFO.tableDesc)); + } + + /** Use a scanner to get the region info and then validate the results */ + private void scan(boolean validateStartcode, String serverName) + throws IOException { + + HInternalScannerInterface scanner = null; + TreeMap results = new TreeMap(); + HStoreKey key = new HStoreKey(); + + Text[][] scanColumns = { + COLS, + EXPLICIT_COLS + }; + + for(int i = 0; i < scanColumns.length; i++) { + try { + scanner = region.getScanner(scanColumns[i], FIRST_ROW); + while(scanner.next(key, results)) { + assertTrue(results.containsKey(HConstants.COL_REGIONINFO)); + BytesWritable val = results.get(HConstants.COL_REGIONINFO); + byte[] bytes = new byte[val.getSize()]; + System.arraycopy(val.get(), 0, bytes, 0, bytes.length); + + validateRegionInfo(new BytesWritable(bytes)); + + if(validateStartcode) { + assertTrue(results.containsKey(HConstants.COL_STARTCODE)); + val = results.get(HConstants.COL_STARTCODE); + assertNotNull(val); + bytes = new byte[val.getSize()]; + System.arraycopy(val.get(), 0, bytes, 0, bytes.length); + assertFalse(bytes.length == 0); + long startCode = + Long.valueOf(new String(bytes, HConstants.UTF8_ENCODING)); + assertEquals(START_CODE, startCode); + } + + if(serverName != null) { + assertTrue(results.containsKey(HConstants.COL_SERVER)); + val = results.get(HConstants.COL_SERVER); + assertNotNull(val); + bytes = new byte[val.getSize()]; + System.arraycopy(val.get(), 0, bytes, 0, bytes.length); + assertFalse(bytes.length == 0); + String server = new String(bytes, HConstants.UTF8_ENCODING); + assertEquals(0, server.compareTo(serverName)); + } + results.clear(); + } + + } catch(IOException e) { + e.printStackTrace(); + throw e; + + } finally { + if(scanner != null) { + try { + scanner.close(); + + } catch(IOException e) { + e.printStackTrace(); + } + scanner = null; + } + } + } + } + + /** Use get to retrieve the HRegionInfo and validate it */ + private void getRegionInfo() throws IOException { + BytesWritable bytes = region.get(ROW_KEY, HConstants.COL_REGIONINFO); + validateRegionInfo(bytes); + } + + /** The test! */ + @SuppressWarnings("unchecked") + public void testScanner() throws IOException { + MiniDFSCluster cluster = null; + FileSystem fs = null; + + try { + + // Initialization + + if(System.getProperty("test.build.data") == null) { + String dir = new File(new File("").getAbsolutePath(), "build/contrib/hbase/test").getAbsolutePath(); + System.out.println(dir); + System.setProperty("test.build.data", dir); + } + Configuration conf = new HBaseConfiguration(); + + Environment.getenv(); + if(Environment.debugging) { + Logger rootLogger = Logger.getRootLogger(); + rootLogger.setLevel(Level.WARN); + + ConsoleAppender consoleAppender = null; + for(Enumeration e = (Enumeration)rootLogger.getAllAppenders(); + e.hasMoreElements();) { + + Appender a = e.nextElement(); + if(a instanceof ConsoleAppender) { + consoleAppender = (ConsoleAppender)a; + break; + } + } + if(consoleAppender != null) { + Layout layout = consoleAppender.getLayout(); + if(layout instanceof PatternLayout) { + PatternLayout consoleLayout = (PatternLayout)layout; + consoleLayout.setConversionPattern("%d %-5p [%t] %l: %m%n"); + } + } + Logger.getLogger("org.apache.hadoop.hbase").setLevel(Environment.logLevel); + } + cluster = new MiniDFSCluster(conf, 2, true, (String[])null); + fs = cluster.getFileSystem(); + Path dir = new Path("/hbase"); + fs.mkdirs(dir); + + Path regionDir = HStoreFile.getHRegionDir(dir, REGION_INFO.regionName); + fs.mkdirs(regionDir); + + HLog log = new HLog(fs, new Path(regionDir, "log"), conf); + + region = new HRegion(dir, log, fs, conf, REGION_INFO, null, null); + + // Write information to the meta table + + long lockid = region.startUpdate(ROW_KEY); + + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + DataOutputStream s = new DataOutputStream(byteStream); + HGlobals.rootRegionInfo.write(s); + region.put(lockid, HConstants.COL_REGIONINFO, + new BytesWritable(byteStream.toByteArray())); + region.commit(lockid); + + // What we just committed is in the memcache. Verify that we can get + // it back both with scanning and get + + scan(false, null); + getRegionInfo(); + + // Close and re-open + + region.close(); + log.rollWriter(); + region = new HRegion(dir, log, fs, conf, REGION_INFO, null, null); + + // Verify we can get the data back now that it is on disk. + + scan(false, null); + getRegionInfo(); + + // Store some new information + + HServerAddress address = new HServerAddress("foo.bar.com:1234"); + + lockid = region.startUpdate(ROW_KEY); + + region.put(lockid, HConstants.COL_SERVER, + new BytesWritable(address.toString().getBytes(HConstants.UTF8_ENCODING))); + + region.put(lockid, HConstants.COL_STARTCODE, + new BytesWritable( + String.valueOf(START_CODE).getBytes(HConstants.UTF8_ENCODING))); + + region.commit(lockid); + + // Validate that we can still get the HRegionInfo, even though it is in + // an older row on disk and there is a newer row in the memcache + + scan(true, address.toString()); + getRegionInfo(); + + // flush cache + + region.flushcache(false); + + // Validate again + + scan(true, address.toString()); + getRegionInfo(); + + // Close and reopen + + region.close(); + log.rollWriter(); + region = new HRegion(dir, log, fs, conf, REGION_INFO, null, null); + + // Validate again + + scan(true, address.toString()); + getRegionInfo(); + + // Now update the information again + + address = new HServerAddress("bar.foo.com:4321"); + + lockid = region.startUpdate(ROW_KEY); + + region.put(lockid, HConstants.COL_SERVER, + new BytesWritable(address.toString().getBytes(HConstants.UTF8_ENCODING))); + + region.commit(lockid); + + // Validate again + + scan(true, address.toString()); + getRegionInfo(); + + // flush cache + + region.flushcache(false); + + // Validate again + + scan(true, address.toString()); + getRegionInfo(); + + // Close and reopen + + region.close(); + log.rollWriter(); + region = new HRegion(dir, log, fs, conf, REGION_INFO, null, null); + + // Validate again + + scan(true, address.toString()); + getRegionInfo(); + + } catch(IOException e) { + e.printStackTrace(); + throw e; + + } finally { + if(fs != null) { + fs.close(); + } + if(cluster != null) { + cluster.shutdown(); + } + } + } +} diff --git a/src/test/org/apache/hadoop/hbase/TestToString.java b/src/test/org/apache/hadoop/hbase/TestToString.java new file mode 100644 index 00000000000..2940c35f52f --- /dev/null +++ b/src/test/org/apache/hadoop/hbase/TestToString.java @@ -0,0 +1,29 @@ +package org.apache.hadoop.hbase; + +import org.apache.hadoop.io.Text; + +import junit.framework.TestCase; + +public class TestToString extends TestCase { + public void testServerInfo() throws Exception { + final String hostport = "127.0.0.1:9999"; + HServerAddress address = new HServerAddress(hostport); + assertEquals("HServerAddress toString", address.toString(), hostport); + HServerInfo info = new HServerInfo(address, -1); + assertEquals("HServerInfo", info.toString(), + "address: " + hostport + ", startcode: " + -1); + } + + public void testHRegionInfo() throws Exception { + HTableDescriptor htd = new HTableDescriptor("hank", 10); + htd.addFamily(new Text("hankfamily:")); + htd.addFamily(new Text("hankotherfamily:")); + assertEquals("Table descriptor", htd.toString(), + "name: hank, maxVersions: 10, families: [hankfamily:, hankotherfamily:]"); + HRegionInfo hri = new HRegionInfo(-1, htd, new Text(), new Text("10")); + assertEquals("HRegionInfo", + "regionname: hank__-1, startKey: <>, tableDesc: {name: hank, " + + "maxVersions: 10, families: [hankfamily:, hankotherfamily:]}", + hri.toString()); + } +}