You are viewing a plain text version of this content. The canonical link for it is here.
Posted to common-commits@hadoop.apache.org by ji...@apache.org on 2007/08/01 22:10:20 UTC

svn commit: r561935 [1/2] - in /lucene/hadoop/trunk/src/contrib/hbase: ./ src/java/org/apache/hadoop/hbase/ src/test/org/apache/hadoop/hbase/

Author: jimk
Date: Wed Aug  1 13:10:11 2007
New Revision: 561935

URL: http://svn.apache.org/viewvc?view=rev&rev=561935
Log:
HADOOP-1528 HClient for multiple tables (phase 1)

Modified:

HConstants
static final Text[] COL_REGIONINFO_ARRAY = new Text [] {COL_REGIONINFO};
static final Text EMPTY_START_ROW = new Text();

HMaster
- don't process a region server exit message if the lease has timed
  out. Otherwise we end up with two pending server shutdown messages
  to process and chaos ensues.
- don't reassign the root region when the server's lease expires. The
  lease expiration handler will queue a PendingServerShutdown
  operation that must run before the root region is reassigned because
  the HLog of the dead server must be split before any regions served
  by the dead server are reassigned.
- added some additional debug level logging

HBaseClusterTestCase
- call HConnectionManager.deleteConnection(conf) in tearDown() so that
  multiple tests can be run from the same test class.

TestScanner2
- changes to make test compatible with the change from inner class
  HClient.RegionLocation to public class HRegionLocation

Leases
- cancelLease just returns if the lease is not found instead of
  throwing an IOException

New:

HConnection - an interface that describes the operations performed by
a connection implementation

HConnectionManager - manages connections for multiple HBase instances
and returns an object that implements HConnection from its static
method getConnection

HBaseAdmin - the HBase administrative methods refactored out of
HClient. Each HBaseAdmin object can control a single HBase
instance. To manipulate multiple instances, create multiple HBaseAdmin
objects. 

HTable - The data manipulation methods refactored out of HClient. Each
HTable object talks to a single table in a single HBase
instance. Create multiple HTable objects to use more than one table.

HRegionLocation - an inner class refactored out of HClient. Each
HRegionLocation has an HRegionInfo object and an HServerAddress
object.

HClient - totally re-implemented in terms of the new classes
above. HClient is now deprecated.


Added:
    lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HBaseAdmin.java
    lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HClient.java
    lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HConnection.java
    lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HConnectionManager.java
    lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HRegionLocation.java
    lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HTable.java
Modified:
    lucene/hadoop/trunk/src/contrib/hbase/CHANGES.txt
    lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HConstants.java
    lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HMaster.java
    lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/Leases.java
    lucene/hadoop/trunk/src/contrib/hbase/src/test/org/apache/hadoop/hbase/HBaseClusterTestCase.java
    lucene/hadoop/trunk/src/contrib/hbase/src/test/org/apache/hadoop/hbase/TestCleanRegionServerExit.java
    lucene/hadoop/trunk/src/contrib/hbase/src/test/org/apache/hadoop/hbase/TestRegionServerAbort.java
    lucene/hadoop/trunk/src/contrib/hbase/src/test/org/apache/hadoop/hbase/TestScanner2.java

Modified: lucene/hadoop/trunk/src/contrib/hbase/CHANGES.txt
URL: http://svn.apache.org/viewvc/lucene/hadoop/trunk/src/contrib/hbase/CHANGES.txt?view=diff&rev=561935&r1=561934&r2=561935
==============================================================================
--- lucene/hadoop/trunk/src/contrib/hbase/CHANGES.txt (original)
+++ lucene/hadoop/trunk/src/contrib/hbase/CHANGES.txt Wed Aug  1 13:10:11 2007
@@ -79,3 +79,5 @@
      10 concurrent clients
  50. HADOOP-1468 Add HBase batch update to reduce RPC overhead (restrict batches
      to a single row at a time)
+ 51. HADOOP-1528 HClient for multiple tables (phase 1)
+

Added: lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HBaseAdmin.java
URL: http://svn.apache.org/viewvc/lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HBaseAdmin.java?view=auto&rev=561935
==============================================================================
--- lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HBaseAdmin.java (added)
+++ lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HBaseAdmin.java Wed Aug  1 13:10:11 2007
@@ -0,0 +1,481 @@
+/**
+ * Copyright 2007 The Apache Software Foundation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.hbase;
+
+import java.io.IOException;
+import java.util.NoSuchElementException;
+import java.util.SortedMap;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.hbase.io.KeyedData;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.io.DataInputBuffer;
+import org.apache.hadoop.io.Text;
+import org.apache.hadoop.ipc.RemoteException;
+
+/**
+ * Provides administrative functions for HBase
+ */
+public class HBaseAdmin implements HConstants {
+  protected final Log LOG = LogFactory.getLog(this.getClass().getName());
+
+  protected final HConnection connection;
+  protected final long pause;
+  protected final int numRetries;
+  protected volatile HMasterInterface master;
+  
+  /**
+   * Constructor
+   * 
+   * @param conf Configuration object
+   * @throws MasterNotRunningException
+   */
+  public HBaseAdmin(Configuration conf) throws MasterNotRunningException {
+    this.connection = HConnectionManager.getConnection(conf);
+    this.pause = conf.getLong("hbase.client.pause", 30 * 1000);
+    this.numRetries = conf.getInt("hbase.client.retries.number", 5);
+    this.master = connection.getMaster();
+  }
+
+  /**
+   * Creates a new table
+   * 
+   * @param desc table descriptor for table
+   * 
+   * @throws IllegalArgumentException if the table name is reserved
+   * @throws MasterNotRunningException if master is not running
+   * @throws NoServerForRegionException if root region is not being served
+   * @throws TableExistsException if table already exists (If concurrent
+   * threads, the table may have been created between test-for-existence
+   * and attempt-at-creation).
+   * @throws IOException
+   */
+  public void createTable(HTableDescriptor desc)
+  throws IOException {
+    
+    createTableAsync(desc);
+
+    // Wait for new table to come on-line
+    connection.getTableServers(desc.getName());
+  }
+  
+  /**
+   * Creates a new table but does not block and wait for it to come online.
+   * 
+   * @param desc table descriptor for table
+   * 
+   * @throws IllegalArgumentException if the table name is reserved
+   * @throws MasterNotRunningException if master is not running
+   * @throws NoServerForRegionException if root region is not being served
+   * @throws TableExistsException if table already exists (If concurrent
+   * threads, the table may have been created between test-for-existence
+   * and attempt-at-creation).
+   * @throws IOException
+   */
+  public void createTableAsync(HTableDescriptor desc)
+  throws IOException {
+    
+    if (this.master == null) {
+      throw new MasterNotRunningException("master has been shut down");
+    }
+    
+    checkReservedTableName(desc.getName());
+    try {
+      this.master.createTable(desc);
+
+    } catch (RemoteException e) {
+      throw RemoteExceptionHandler.decodeRemoteException(e);
+    }
+  }
+
+  /**
+   * Deletes a table
+   * 
+   * @param tableName name of table to delete
+   * @throws IOException
+   */
+  public void deleteTable(Text tableName) throws IOException {
+    if (this.master == null) {
+      throw new MasterNotRunningException("master has been shut down");
+    }
+    
+    checkReservedTableName(tableName);
+    HRegionLocation firstMetaServer = getFirstMetaServerForTable(tableName);
+
+    try {
+      this.master.deleteTable(tableName);
+    } catch (RemoteException e) {
+      throw RemoteExceptionHandler.decodeRemoteException(e);
+    }
+
+    // Wait until first region is deleted
+    HRegionInterface server =
+      connection.getHRegionConnection(firstMetaServer.getServerAddress());
+    DataInputBuffer inbuf = new DataInputBuffer();
+    HRegionInfo info = new HRegionInfo();
+    for (int tries = 0; tries < numRetries; tries++) {
+      long scannerId = -1L;
+      try {
+        scannerId =
+          server.openScanner(firstMetaServer.getRegionInfo().getRegionName(),
+            COL_REGIONINFO_ARRAY, tableName, System.currentTimeMillis(), null);
+        KeyedData[] values = server.next(scannerId);
+        if (values == null || values.length == 0) {
+          break;
+        }
+        boolean found = false;
+        for (int j = 0; j < values.length; j++) {
+          if (values[j].getKey().getColumn().equals(COL_REGIONINFO)) {
+            inbuf.reset(values[j].getData(), values[j].getData().length);
+            info.readFields(inbuf);
+            if (info.tableDesc.getName().equals(tableName)) {
+              found = true;
+            }
+          }
+        }
+        if (!found) {
+          break;
+        }
+
+      } catch (IOException ex) {
+        if(tries == numRetries - 1) {           // no more tries left
+          if (ex instanceof RemoteException) {
+            ex = RemoteExceptionHandler.decodeRemoteException((RemoteException) ex);
+          }
+          throw ex;
+        }
+
+      } finally {
+        if (scannerId != -1L) {
+          try {
+            server.close(scannerId);
+          } catch (Exception ex) {
+            LOG.warn(ex);
+          }
+        }
+      }
+
+      try {
+        Thread.sleep(pause);
+      } catch (InterruptedException e) {
+        // continue
+      }
+    }
+    LOG.info("table " + tableName + " deleted");
+  }
+
+  /**
+   * Brings a table on-line (enables it)
+   * 
+   * @param tableName name of the table
+   * @throws IOException
+   */
+  public void enableTable(Text tableName) throws IOException {
+    if (this.master == null) {
+      throw new MasterNotRunningException("master has been shut down");
+    }
+    
+    checkReservedTableName(tableName);
+    HRegionLocation firstMetaServer = getFirstMetaServerForTable(tableName);
+    
+    try {
+      this.master.enableTable(tableName);
+      
+    } catch (RemoteException e) {
+      throw RemoteExceptionHandler.decodeRemoteException(e);
+    }
+
+    // Wait until first region is enabled
+    
+    HRegionInterface server =
+      connection.getHRegionConnection(firstMetaServer.getServerAddress());
+
+    DataInputBuffer inbuf = new DataInputBuffer();
+    HRegionInfo info = new HRegionInfo();
+    for (int tries = 0; tries < numRetries; tries++) {
+      int valuesfound = 0;
+      long scannerId = -1L;
+      try {
+        scannerId =
+          server.openScanner(firstMetaServer.getRegionInfo().getRegionName(),
+            COL_REGIONINFO_ARRAY, tableName, System.currentTimeMillis(), null);
+        boolean isenabled = false;
+        
+        while (true) {
+          KeyedData[] values = server.next(scannerId);
+          if (values == null || values.length == 0) {
+            if (valuesfound == 0) {
+              throw new NoSuchElementException(
+                  "table " + tableName + " not found");
+            }
+            break;
+          }
+          valuesfound += 1;
+          for (int j = 0; j < values.length; j++) {
+            if (values[j].getKey().getColumn().equals(COL_REGIONINFO)) {
+              inbuf.reset(values[j].getData(), values[j].getData().length);
+              info.readFields(inbuf);
+              isenabled = !info.offLine;
+              break;
+            }
+          }
+          if (isenabled) {
+            break;
+          }
+        }
+        if (isenabled) {
+          break;
+        }
+        
+      } catch (IOException e) {
+        if (tries == numRetries - 1) {                  // no more retries
+          if (e instanceof RemoteException) {
+            e = RemoteExceptionHandler.decodeRemoteException((RemoteException) e);
+          }
+          throw e;
+        }
+        
+      } finally {
+        if (scannerId != -1L) {
+          try {
+            server.close(scannerId);
+            
+          } catch (Exception e) {
+            LOG.warn(e);
+          }
+        }
+      }
+      if (LOG.isDebugEnabled()) {
+        LOG.debug("Sleep. Waiting for first region to be enabled from " +
+            tableName);
+      }
+      try {
+        Thread.sleep(pause);
+        
+      } catch (InterruptedException e) {
+        // continue
+      }
+      if (LOG.isDebugEnabled()) {
+        LOG.debug("Wake. Waiting for first region to be enabled from " +
+            tableName);
+      }
+    }
+    LOG.info("Enabled table " + tableName);
+  }
+
+  /**
+   * Disables a table (takes it off-line) If it is being served, the master
+   * will tell the servers to stop serving it.
+   * 
+   * @param tableName name of table
+   * @throws IOException
+   */
+  public void disableTable(Text tableName) throws IOException {
+    if (this.master == null) {
+      throw new MasterNotRunningException("master has been shut down");
+    }
+    
+    checkReservedTableName(tableName);
+    HRegionLocation firstMetaServer = getFirstMetaServerForTable(tableName);
+
+    try {
+      this.master.disableTable(tableName);
+      
+    } catch (RemoteException e) {
+      throw RemoteExceptionHandler.decodeRemoteException(e);
+    }
+
+    // Wait until first region is disabled
+    
+    HRegionInterface server =
+      connection.getHRegionConnection(firstMetaServer.getServerAddress());
+
+    DataInputBuffer inbuf = new DataInputBuffer();
+    HRegionInfo info = new HRegionInfo();
+    for(int tries = 0; tries < numRetries; tries++) {
+      int valuesfound = 0;
+      long scannerId = -1L;
+      try {
+        scannerId =
+          server.openScanner(firstMetaServer.getRegionInfo().getRegionName(),
+            COL_REGIONINFO_ARRAY, tableName, System.currentTimeMillis(), null);
+        
+        boolean disabled = false;
+        while (true) {
+          KeyedData[] values = server.next(scannerId);
+          if (values == null || values.length == 0) {
+            if (valuesfound == 0) {
+              throw new NoSuchElementException("table " + tableName + " not found");
+            }
+            break;
+          }
+          valuesfound += 1;
+          for (int j = 0; j < values.length; j++) {
+            if (values[j].getKey().getColumn().equals(COL_REGIONINFO)) {
+              inbuf.reset(values[j].getData(), values[j].getData().length);
+              info.readFields(inbuf);
+              disabled = info.offLine;
+              break;
+            }
+          }
+          if (disabled) {
+            break;
+          }
+        }
+        if (disabled) {
+          break;
+        }
+        
+      } catch (IOException e) {
+        if (tries == numRetries - 1) {                  // no more retries
+          if (e instanceof RemoteException) {
+            e = RemoteExceptionHandler.decodeRemoteException((RemoteException) e);
+          }
+          throw e;
+        }
+        
+      } finally {
+        if (scannerId != -1L) {
+          try {
+            server.close(scannerId);
+            
+          } catch (Exception e) {
+            LOG.warn(e);
+          }
+        }
+      }
+      if (LOG.isDebugEnabled()) {
+        LOG.debug("Sleep. Waiting for first region to be disabled from " +
+            tableName);
+      }
+      try {
+        Thread.sleep(pause);
+      } catch (InterruptedException e) {
+        // continue
+      }
+      if (LOG.isDebugEnabled()) {
+        LOG.debug("Wake. Waiting for first region to be disabled from " +
+            tableName);
+      }
+    }
+    LOG.info("Disabled table " + tableName);
+  }
+  
+  /**
+   * @param tableName Table to check.
+   * @return True if table exists already.
+   * @throws MasterNotRunningException
+   */
+  public boolean tableExists(final Text tableName) throws MasterNotRunningException {
+    if (this.master == null) {
+      throw new MasterNotRunningException("master has been shut down");
+    }
+    
+    return connection.tableExists(tableName);
+  }
+
+  /**
+   * Add a column to an existing table
+   * 
+   * @param tableName name of the table to add column to
+   * @param column column descriptor of column to be added
+   * @throws IOException
+   */
+  public void addColumn(Text tableName, HColumnDescriptor column)
+  throws IOException {
+    if (this.master == null) {
+      throw new MasterNotRunningException("master has been shut down");
+    }
+    
+    checkReservedTableName(tableName);
+    try {
+      this.master.addColumn(tableName, column);
+      
+    } catch (RemoteException e) {
+      throw RemoteExceptionHandler.decodeRemoteException(e);
+    }
+  }
+
+  /**
+   * Delete a column from a table
+   * 
+   * @param tableName name of table
+   * @param columnName name of column to be deleted
+   * @throws IOException
+   */
+  public void deleteColumn(Text tableName, Text columnName)
+  throws IOException {
+    if (this.master == null) {
+      throw new MasterNotRunningException("master has been shut down");
+    }
+    
+    checkReservedTableName(tableName);
+    try {
+      this.master.deleteColumn(tableName, columnName);
+      
+    } catch (RemoteException e) {
+      throw RemoteExceptionHandler.decodeRemoteException(e);
+    }
+  }
+  
+  /** 
+   * Shuts down the HBase instance 
+   * @throws IOException
+   */
+  public synchronized void shutdown() throws IOException {
+    if (this.master == null) {
+      throw new MasterNotRunningException("master has been shut down");
+    }
+    
+    try {
+      this.master.shutdown();
+    } catch (RemoteException e) {
+      throw RemoteExceptionHandler.decodeRemoteException(e);
+    } finally {
+      this.master = null;
+    }
+  }
+
+  /*
+   * Verifies that the specified table name is not a reserved name
+   * @param tableName - the table name to be checked
+   * @throws IllegalArgumentException - if the table name is reserved
+   */
+  protected void checkReservedTableName(Text tableName) {
+    if(tableName.equals(ROOT_TABLE_NAME)
+        || tableName.equals(META_TABLE_NAME)) {
+      
+      throw new IllegalArgumentException(tableName + " is a reserved table name");
+    }
+  }
+  
+  private HRegionLocation getFirstMetaServerForTable(Text tableName)
+  throws IOException {
+    SortedMap<Text, HRegionLocation> metaservers =
+      connection.getTableServers(META_TABLE_NAME);
+    
+    return metaservers.get((metaservers.containsKey(tableName)) ?
+        tableName : metaservers.headMap(tableName).lastKey());
+  }
+  
+
+}

Added: lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HClient.java
URL: http://svn.apache.org/viewvc/lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HClient.java?view=auto&rev=561935
==============================================================================
--- lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HClient.java (added)
+++ lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HClient.java Wed Aug  1 13:10:11 2007
@@ -0,0 +1,708 @@
+/**
+ * Copyright 2007 The Apache Software Foundation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// temporary until I change all the classes that depend on HClient.
+package org.apache.hadoop.hbase;
+
+import java.io.IOException;
+import java.util.SortedMap;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hbase.filter.RowFilterInterface;
+import org.apache.hadoop.io.Text;
+
+/**
+ * The HClient class is deprecated and is now implemented entirely in terms of
+ * the classes that replace it:
+ * <ul>
+ * <li>HConnection which manages connections to a a single HBase instance</li>
+ * <li>HTable which accesses one table (to access multiple tables, you create
+ * multiple HTable instances</li>
+ * <li>HBaseAdmin which performs administrative functions for a single HBase
+ * instance</li>
+ * </ul>
+ * <p>
+ * HClient continues to be supported in the short term to give users a chance
+ * to migrate to the use of HConnection, HTable and HBaseAdmin. Any new API
+ * features which are added will be added to these three classes and will not
+ * be supported in HClient.
+ */
+@Deprecated
+public class HClient implements HConstants {
+  private final Configuration conf;
+  protected AtomicReference<HConnection> connection;
+  protected AtomicReference<HBaseAdmin> admin;
+  protected AtomicReference<HTable> table;
+
+  /** 
+   * Creates a new HClient
+   * @param conf - Configuration object
+   */
+  public HClient(Configuration conf) {
+    this.conf = conf;
+    this.connection = new AtomicReference<HConnection>();
+    this.admin = new AtomicReference<HBaseAdmin>();
+    this.table = new AtomicReference<HTable>();
+  }
+
+  /* Lazily creates a HConnection */
+  private synchronized HConnection getHConnection() {
+    HConnection conn = connection.get();
+    if (conn == null) {
+      conn = HConnectionManager.getConnection(conf);
+      connection.set(conn);
+    }
+    return conn;
+  }
+  
+  /* Lazily creates a HBaseAdmin */
+  private synchronized HBaseAdmin getHBaseAdmin() throws MasterNotRunningException {
+    getHConnection();                   // ensure we have a connection
+    HBaseAdmin adm = admin.get();
+    if (adm == null) {
+      adm = new HBaseAdmin(conf);
+      admin.set(adm);
+    }
+    return adm;
+  }
+
+  /**
+   * Find region location hosting passed row using cached info
+   * @param row Row to find.
+   * @return Location of row.
+   */
+  protected HRegionLocation getRegionLocation(Text row) {
+    if(this.table.get() == null) {
+      throw new IllegalStateException("Must open table first");
+    }
+    return table.get().getRegionLocation(row);
+  }
+  
+  /** 
+   * Establishes a connection to the region server at the specified address.
+   * @param regionServer - the server to connect to
+   * @throws IOException
+   */
+  protected HRegionInterface getHRegionConnection(
+      HServerAddress regionServer) throws IOException {
+    return getHConnection().getHRegionConnection(regionServer);
+  }
+  
+  /**
+   * @return - true if the master server is running
+   */
+  public boolean isMasterRunning() {
+    return getHConnection().isMasterRunning();
+  }
+
+  //
+  // Administrative methods
+  //
+
+  /**
+   * Creates a new table
+   * 
+   * @param desc table descriptor for table
+   * 
+   * @throws IllegalArgumentException if the table name is reserved
+   * @throws MasterNotRunningException if master is not running
+   * @throws NoServerForRegionException if root region is not being served
+   * @throws TableExistsException if table already exists (If concurrent
+   * threads, the table may have been created between test-for-existence
+   * and attempt-at-creation).
+   * @throws IOException
+   */
+  public void createTable(HTableDescriptor desc)
+  throws IOException {
+    
+    getHBaseAdmin().createTable(desc);
+  }
+  
+  /**
+   * Creates a new table but does not block and wait for it to come online.
+   * 
+   * @param desc table descriptor for table
+   * 
+   * @throws IllegalArgumentException if the table name is reserved
+   * @throws MasterNotRunningException if master is not running
+   * @throws NoServerForRegionException if root region is not being served
+   * @throws TableExistsException if table already exists (If concurrent
+   * threads, the table may have been created between test-for-existence
+   * and attempt-at-creation).
+   * @throws IOException
+   */
+  public void createTableAsync(HTableDescriptor desc)
+      throws IOException {
+
+    getHBaseAdmin().createTableAsync(desc);
+  }
+
+  /**
+   * Deletes a table
+   * 
+   * @param tableName name of table to delete
+   * @throws IOException
+   */
+  public void deleteTable(Text tableName) throws IOException {
+    getHBaseAdmin().deleteTable(tableName);
+  }
+
+  /**
+   * Brings a table on-line (enables it)
+   * 
+   * @param tableName name of the table
+   * @throws IOException
+   */
+  public void enableTable(Text tableName) throws IOException {
+    getHBaseAdmin().enableTable(tableName);
+  }
+
+  /**
+   * Disables a table (takes it off-line) If it is being served, the master
+   * will tell the servers to stop serving it.
+   * 
+   * @param tableName name of table
+   * @throws IOException
+   */
+  public void disableTable(Text tableName) throws IOException {
+    getHBaseAdmin().disableTable(tableName);
+  }
+  
+  /**
+   * @param tableName Table to check.
+   * @return True if table exists already.
+   * @throws MasterNotRunningException
+   */
+  public boolean tableExists(final Text tableName) throws MasterNotRunningException {
+    return getHBaseAdmin().tableExists(tableName);
+  }
+
+  /**
+   * Add a column to an existing table
+   * 
+   * @param tableName name of the table to add column to
+   * @param column column descriptor of column to be added
+   * @throws IOException
+   */
+  public void addColumn(Text tableName, HColumnDescriptor column)
+  throws IOException {
+    getHBaseAdmin().addColumn(tableName, column);
+  }
+
+  /**
+   * Delete a column from a table
+   * 
+   * @param tableName name of table
+   * @param columnName name of column to be deleted
+   * @throws IOException
+   */
+  public void deleteColumn(Text tableName, Text columnName)
+  throws IOException {
+    getHBaseAdmin().deleteColumn(tableName, columnName);
+  }
+  
+  /** 
+   * Shuts down the HBase instance 
+   * @throws IOException
+   */
+  public void shutdown() throws IOException {
+    getHBaseAdmin().shutdown();
+  }
+
+  //////////////////////////////////////////////////////////////////////////////
+  // Client API
+  //////////////////////////////////////////////////////////////////////////////
+  
+  /**
+   * Loads information so that a table can be manipulated.
+   * 
+   * @param tableName the table to be located
+   * @throws IOException if the table can not be located after retrying
+   */
+  public synchronized void openTable(Text tableName) throws IOException {
+    HTable table = this.table.get();
+    if (table != null) {
+      table.checkUpdateInProgress();
+    }
+    this.table.set(new HTable(conf, tableName));
+  }
+  
+  /**
+   * Gets the starting row key for every region in the currently open table
+   * @return Array of region starting row keys
+   */
+  public Text[] getStartKeys() {
+    if(this.table.get() == null) {
+      throw new IllegalStateException("Must open table first");
+    }
+    return table.get().getStartKeys();
+  }
+  
+  /**
+   * List all the userspace tables.  In other words, scan the META table.
+   *
+   * If we wanted this to be really fast, we could implement a special
+   * catalog table that just contains table names and their descriptors.
+   * Right now, it only exists as part of the META table's region info.
+   *
+   * @return - returns an array of HTableDescriptors 
+   * @throws IOException
+   */
+  public HTableDescriptor[] listTables() throws IOException {
+    return getHConnection().listTables();
+  }
+
+  /** 
+   * Get a single value for the specified row and column
+   *
+   * @param row row key
+   * @param column column name
+   * @return value for specified row/column
+   * @throws IOException
+   */
+  public byte[] get(Text row, Text column) throws IOException {
+    if(this.table.get() == null) {
+      throw new IllegalStateException("Must open table first");
+    }
+    return this.table.get().get(row, column);
+  }
+ 
+  /** 
+   * Get the specified number of versions of the specified row and column
+   * 
+   * @param row         - row key
+   * @param column      - column name
+   * @param numVersions - number of versions to retrieve
+   * @return            - array byte values
+   * @throws IOException
+   */
+  public byte[][] get(Text row, Text column, int numVersions)
+  throws IOException {
+    if(this.table.get() == null) {
+      throw new IllegalStateException("Must open table first");
+    }
+    return this.table.get().get(row, column, numVersions);
+  }
+  
+  /** 
+   * Get the specified number of versions of the specified row and column with
+   * the specified timestamp.
+   *
+   * @param row         - row key
+   * @param column      - column name
+   * @param timestamp   - timestamp
+   * @param numVersions - number of versions to retrieve
+   * @return            - array of values that match the above criteria
+   * @throws IOException
+   */
+  public byte[][] get(Text row, Text column, long timestamp, int numVersions)
+  throws IOException {
+    if(this.table.get() == null) {
+      throw new IllegalStateException("Must open table first");
+    }
+    return this.table.get().get(row, column, timestamp, numVersions);
+  }
+    
+  /** 
+   * Get all the data for the specified row
+   * 
+   * @param row         - row key
+   * @return            - map of colums to values
+   * @throws IOException
+   */
+  public SortedMap<Text, byte[]> getRow(Text row) throws IOException {
+    if(this.table.get() == null) {
+      throw new IllegalStateException("Must open table first");
+    }
+    return this.table.get().getRow(row);
+  }
+
+  /** 
+   * Get a scanner on the current table starting at the specified row.
+   * Return the specified columns.
+   *
+   * @param columns array of columns to return
+   * @param startRow starting row in table to scan
+   * @return scanner
+   * @throws IOException
+   */
+  public HScannerInterface obtainScanner(Text[] columns,
+      Text startRow) throws IOException {
+    return obtainScanner(columns, startRow, System.currentTimeMillis(), null);
+  }
+  
+  /** 
+   * Get a scanner on the current table starting at the specified row.
+   * Return the specified columns.
+   *
+   * @param columns array of columns to return
+   * @param startRow starting row in table to scan
+   * @param timestamp only return results whose timestamp <= this value
+   * @return scanner
+   * @throws IOException
+   */
+  public HScannerInterface obtainScanner(Text[] columns,
+      Text startRow, long timestamp) throws IOException {
+    return obtainScanner(columns, startRow, timestamp, null);
+  }
+  
+  /** 
+   * Get a scanner on the current table starting at the specified row.
+   * Return the specified columns.
+   *
+   * @param columns array of columns to return
+   * @param startRow starting row in table to scan
+   * @param filter a row filter using row-key regexp and/or column data filter.
+   * @return scanner
+   * @throws IOException
+   */
+  public HScannerInterface obtainScanner(Text[] columns,
+      Text startRow, RowFilterInterface filter) throws IOException { 
+    return obtainScanner(columns, startRow, System.currentTimeMillis(), filter);
+  }
+  
+  /** 
+   * Get a scanner on the current table starting at the specified row.
+   * Return the specified columns.
+   *
+   * @param columns array of columns to return
+   * @param startRow starting row in table to scan
+   * @param timestamp only return results whose timestamp <= this value
+   * @param filter a row filter using row-key regexp and/or column data filter.
+   * @return scanner
+   * @throws IOException
+   */
+  public HScannerInterface obtainScanner(Text[] columns,
+      Text startRow, long timestamp, RowFilterInterface filter)
+  throws IOException {
+    if(this.table.get() == null) {
+      throw new IllegalStateException("Must open table first");
+    }
+    return this.table.get().obtainScanner(columns, startRow, timestamp, filter);
+  }
+
+  /** 
+   * Start a batch of row insertions/updates.
+   * 
+   * No changes are committed until the call to commitBatchUpdate returns.
+   * A call to abortBatchUpdate will abandon the entire batch.
+   *
+   * @param row name of row to be updated
+   * @return lockid to be used in subsequent put, delete and commit calls
+   */
+  public long startBatchUpdate(final Text row) {
+    if(this.table.get() == null) {
+      throw new IllegalStateException("Must open table first");
+    }
+    return this.table.get().startBatchUpdate(row);
+  }
+  
+  /** 
+   * Abort a batch mutation
+   * @param lockid lock id returned by startBatchUpdate
+   */
+  public void abortBatch(final long lockid) {
+    if(this.table.get() == null) {
+      throw new IllegalStateException("Must open table first");
+    }
+    this.table.get().abortBatch(lockid);
+  }
+  
+  /** 
+   * Finalize a batch mutation
+   *
+   * @param lockid lock id returned by startBatchUpdate
+   * @throws IOException
+   */
+  public void commitBatch(final long lockid) throws IOException {
+    commitBatch(lockid, System.currentTimeMillis());
+  }
+
+  /** 
+   * Finalize a batch mutation
+   *
+   * @param lockid lock id returned by startBatchUpdate
+   * @param timestamp time to associate with all the changes
+   * @throws IOException
+   */
+  public void commitBatch(final long lockid, final long timestamp)
+  throws IOException {
+    if(this.table.get() == null) {
+      throw new IllegalStateException("Must open table first");
+    }
+    this.table.get().commitBatch(lockid, timestamp);
+  }
+  
+  /** 
+   * Start an atomic row insertion/update.  No changes are committed until the 
+   * call to commit() returns. A call to abort() will abandon any updates in progress.
+   *
+   * Callers to this method are given a lease for each unique lockid; before the
+   * lease expires, either abort() or commit() must be called. If it is not 
+   * called, the system will automatically call abort() on the client's behalf.
+   *
+   * The client can gain extra time with a call to renewLease().
+   * Start an atomic row insertion or update
+   * 
+   * @param row Name of row to start update against.
+   * @return Row lockid.
+   * @throws IOException
+   */
+  public long startUpdate(final Text row) throws IOException {
+    if(this.table.get() == null) {
+      throw new IllegalStateException("Must open table first");
+    }
+    return this.table.get().startUpdate(row);
+  }
+  
+  /** 
+   * Change a value for the specified column.
+   * Runs {@link #abort(long)} if exception thrown.
+   *
+   * @param lockid lock id returned from startUpdate
+   * @param column column whose value is being set
+   * @param val new value for column
+   * @throws IOException
+   */
+  public void put(long lockid, Text column, byte val[]) throws IOException {
+    if(this.table.get() == null) {
+      throw new IllegalStateException("Must open table first");
+    }
+    this.table.get().put(lockid, column, val);
+  }
+  
+  /** 
+   * Delete the value for a column
+   *
+   * @param lockid              - lock id returned from startUpdate
+   * @param column              - name of column whose value is to be deleted
+   * @throws IOException
+   */
+  public void delete(long lockid, Text column) throws IOException {
+    if(this.table.get() == null) {
+      throw new IllegalStateException("Must open table first");
+    }
+    this.table.get().delete(lockid, column);
+  }
+  
+  /** 
+   * Abort a row mutation
+   *
+   * @param lockid              - lock id returned from startUpdate
+   * @throws IOException
+   */
+  public void abort(long lockid) throws IOException {
+    if(this.table.get() == null) {
+      throw new IllegalStateException("Must open table first");
+    }
+    this.table.get().abort(lockid);
+  }
+  
+  /** 
+   * Finalize a row mutation
+   *
+   * @param lockid              - lock id returned from startUpdate
+   * @throws IOException
+   */
+  public void commit(long lockid) throws IOException {
+    commit(lockid, System.currentTimeMillis());
+  }
+
+  /** 
+   * Finalize a row mutation
+   *
+   * @param lockid              - lock id returned from startUpdate
+   * @param timestamp           - time to associate with the change
+   * @throws IOException
+   */
+  public void commit(long lockid, long timestamp) throws IOException {
+    if(this.table.get() == null) {
+      throw new IllegalStateException("Must open table first");
+    }
+    this.table.get().commit(lockid, timestamp);
+  }
+  
+  /**
+   * Renew lease on update
+   * 
+   * @param lockid              - lock id returned from startUpdate
+   * @throws IOException
+   */
+  public void renewLease(long lockid) throws IOException {
+    if(this.table.get() == null) {
+      throw new IllegalStateException("Must open table first");
+    }
+    this.table.get().renewLease(lockid);
+  }
+
+  private void printUsage() {
+    printUsage(null);
+  }
+  
+  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] <command> <args>");
+    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  Create named table.");
+    System.err.println(" deleteTable  Delete named table.");
+    System.err.println(" listTables   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");
+  }
+  
+  private void printCreateTableUsage(final String message) {
+    if (message != null && message.length() > 0) {
+      System.err.println(message);
+    }
+    System.err.println("Usage: java " + this.getClass().getName() +
+      " [options] createTable <name> <colfamily1> ... <max_versions>");
+    System.err.println("Example Usage:");
+    System.err.println(" % java " + this.getClass().getName() +
+      " createTable testtable column_x column_y column_z 3");
+  }
+  
+  private void printDeleteTableUsage(final String message) {
+    if (message != null && message.length() > 0) {
+      System.err.println(message);
+    }
+    System.err.println("Usage: java " + this.getClass().getName() +
+      " [options] deleteTable <name>");
+    System.err.println("Example Usage:");
+    System.err.println(" % java " + this.getClass().getName() +
+      " deleteTable testtable");
+  }
+  
+  /**
+   * Process command-line args.
+   * @param args - command arguments
+   * @return 0 if successful -1 otherwise
+   */
+  public int doCommandLine(final String 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 {
+      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 + 2 > args.length) {
+            printCreateTableUsage("Error: Supply a table name," +
+              " at least one column family, and maximum versions");
+            errCode = 1;
+            break;
+          }
+          HTableDescriptor desc = new HTableDescriptor(args[i + 1]);
+          boolean addedFamily = false;
+          for (int ii = i + 2; ii < (args.length - 1); ii++) {
+            desc.addFamily(new HColumnDescriptor(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) {
+            printDeleteTableUsage("Error: Must supply a table name");
+            errCode = 1;
+            break;
+          }
+          deleteTable(new Text(args[i + 1]));
+          errCode = 0;
+          break;
+        }
+        
+        printUsage();
+        break;
+      }
+    } catch (IOException e) {
+      e.printStackTrace();
+    } catch (RuntimeException e) {
+      e.printStackTrace();
+    }
+    
+    return errCode;
+  }    
+
+  /**
+   * @return the configuration for this client
+   */
+  protected Configuration getConf(){
+    return conf;
+  }
+  
+  /**
+   * Main program
+   * @param args
+   */
+  public static void main(final String args[]) {
+    Configuration c = new HBaseConfiguration();
+    int errCode = (new HClient(c)).doCommandLine(args);
+    System.exit(errCode);
+  }
+
+}

Added: lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HConnection.java
URL: http://svn.apache.org/viewvc/lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HConnection.java?view=auto&rev=561935
==============================================================================
--- lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HConnection.java (added)
+++ lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HConnection.java Wed Aug  1 13:10:11 2007
@@ -0,0 +1,92 @@
+/**
+ * Copyright 2007 The Apache Software Foundation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.hbase;
+
+import java.io.IOException;
+import java.util.SortedMap;
+
+import org.apache.hadoop.io.Text;
+
+/**
+ * 
+ */
+public interface HConnection {
+  /**
+   * @return proxy connection to master server for this instance
+   * @throws MasterNotRunningException
+   */
+  public HMasterInterface getMaster() throws MasterNotRunningException;
+
+  /** @return - true if the master server is running */
+  public boolean isMasterRunning();
+  
+  /**
+   * @param tableName Table to check.
+   * @return True if table exists already.
+   */
+  public boolean tableExists(final Text tableName);
+  
+  /**
+   * List all the userspace tables.  In other words, scan the META table.
+   *
+   * If we wanted this to be really fast, we could implement a special
+   * catalog table that just contains table names and their descriptors.
+   * Right now, it only exists as part of the META table's region info.
+   *
+   * @return - returns an array of HTableDescriptors 
+   * @throws IOException
+   */
+  public HTableDescriptor[] listTables() throws IOException;
+  
+  /**
+   * Gets the servers of the given table.
+   * 
+   * @param tableName - the table to be located
+   * @return map of startRow -> RegionLocation
+   * @throws IOException - if the table can not be located after retrying
+   */
+  public SortedMap<Text, HRegionLocation> getTableServers(Text tableName)
+  throws IOException;
+  
+  /**
+   * Reloads servers for the specified table.
+   * 
+   * @param tableName name of table whose servers are to be reloaded
+   * @return map of start key -> RegionLocation
+   * @throws IOException
+   */
+  public SortedMap<Text, HRegionLocation>
+  reloadTableServers(final Text tableName) throws IOException;
+  
+  /** 
+   * Establishes a connection to the region server at the specified address.
+   * @param regionServer - the server to connect to
+   * @return proxy for HRegionServer
+   * @throws IOException
+   */
+  public HRegionInterface getHRegionConnection(HServerAddress regionServer)
+  throws IOException;
+  
+  /**
+   * Discard all the information about this table
+   * @param tableName the name of the table to close
+   */
+  public void close(Text tableName);
+}

Added: lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HConnectionManager.java
URL: http://svn.apache.org/viewvc/lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HConnectionManager.java?view=auto&rev=561935
==============================================================================
--- lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HConnectionManager.java (added)
+++ lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HConnectionManager.java Wed Aug  1 13:10:11 2007
@@ -0,0 +1,761 @@
+/**
+ * Copyright 2007 The Apache Software Foundation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.hbase;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+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.ipc.RPC;
+import org.apache.hadoop.ipc.RemoteException;
+import org.apache.hadoop.io.DataInputBuffer;
+import org.apache.hadoop.io.Text;
+
+import org.apache.hadoop.hbase.io.KeyedData;
+
+/**
+ * A non-instantiable class that manages connections to multiple tables in
+ * multiple HBase instances
+ */
+public class HConnectionManager implements HConstants {
+  private HConnectionManager(){}                        // Not instantiable
+  
+  // A Map of master HServerAddress -> connection information for that instance
+  // Note that although the Map is synchronized, the objects it contains
+  // are mutable and hence require synchronized access to them
+  
+  private static final Map<String, HConnection> HBASE_INSTANCES =
+    Collections.synchronizedMap(new HashMap<String, HConnection>());
+
+  /**
+   * Get the connection object for the instance specified by the configuration
+   * If no current connection exists, create a new connection for that instance
+   * @param conf
+   * @return HConnection object for the instance specified by the configuration
+   */
+  public static HConnection getConnection(Configuration conf) {
+    HConnection connection;
+    synchronized (HBASE_INSTANCES) {
+      String instanceName = conf.get(HBASE_DIR, DEFAULT_HBASE_DIR);
+
+      connection = HBASE_INSTANCES.get(instanceName);
+
+      if (connection == null) {
+        connection = new TableServers(conf);
+        HBASE_INSTANCES.put(instanceName, connection);
+      }
+    }
+    return connection;
+  }
+  
+  /**
+   * Delete connection information for the instance specified by the configuration
+   * @param conf
+   */
+  public static void deleteConnection(Configuration conf) {
+    synchronized (HBASE_INSTANCES) {
+      HBASE_INSTANCES.remove(conf.get(HBASE_DIR, DEFAULT_HBASE_DIR));
+    }    
+  }
+  
+  /* encapsulates finding the servers for an HBase instance */
+  private static class TableServers implements HConnection, HConstants {
+    private final Log LOG = LogFactory.getLog(this.getClass().getName());
+    private final Class<? extends HRegionInterface> serverInterfaceClass;
+    private final long threadWakeFrequency;
+    private final long pause;
+    private final int numRetries;
+
+    private final Integer masterLock = new Integer(0);
+    private volatile HMasterInterface master;
+    private volatile boolean masterChecked;
+    
+    private final Integer rootRegionLock = new Integer(0);
+    private final Integer metaRegionLock = new Integer(0);
+    
+    private volatile Configuration conf;
+
+    // Map tableName -> (Map startRow -> (HRegionInfo, HServerAddress)
+    private Map<Text, SortedMap<Text, HRegionLocation>> tablesToServers;
+    
+    // Set of closed tables
+    private Set<Text> closedTables;
+    
+    // Set of tables currently being located
+    private HashSet<Text> tablesBeingLocated;
+
+    // Known region HServerAddress.toString() -> HRegionInterface 
+    private HashMap<String, HRegionInterface> servers;
+
+    /** constructor
+     * @param conf Configuration object
+     */
+    @SuppressWarnings("unchecked")
+    public TableServers(Configuration conf) {
+      this.conf = conf;
+      
+      String serverClassName =
+        conf.get(REGION_SERVER_CLASS, DEFAULT_REGION_SERVER_CLASS);
+
+      try {
+        this.serverInterfaceClass =
+          (Class<? extends HRegionInterface>) Class.forName(serverClassName);
+        
+      } catch (ClassNotFoundException e) {
+        throw new UnsupportedOperationException(
+            "Unable to find region server interface " + serverClassName, e);
+      }
+
+      this.threadWakeFrequency = conf.getLong(THREAD_WAKE_FREQUENCY, 10 * 1000);
+      this.pause = conf.getLong("hbase.client.pause", 30 * 1000);
+      this.numRetries = conf.getInt("hbase.client.retries.number", 5);
+      
+      this.master = null;
+      this.masterChecked = false;
+
+      this.tablesToServers = Collections.synchronizedMap(
+        new HashMap<Text, SortedMap<Text, HRegionLocation>>());
+      
+      this.closedTables = Collections.synchronizedSet(new HashSet<Text>());
+      this.tablesBeingLocated = new HashSet<Text>();
+      
+      this.servers = new HashMap<String, HRegionInterface>();
+    }
+    
+    /** {@inheritDoc} */
+    public HMasterInterface getMaster() throws MasterNotRunningException {
+      synchronized (this.masterLock) {
+        for (int tries = 0;
+        !this.masterChecked && this.master == null && tries < numRetries;
+        tries++) {
+          
+          HServerAddress masterLocation = new HServerAddress(this.conf.get(
+              MASTER_ADDRESS, DEFAULT_MASTER_ADDRESS));
+
+          try {
+            HMasterInterface tryMaster = (HMasterInterface)RPC.getProxy(
+                HMasterInterface.class, HMasterInterface.versionID, 
+                masterLocation.getInetSocketAddress(), this.conf);
+            
+            if (tryMaster.isMasterRunning()) {
+              this.master = tryMaster;
+              break;
+            }
+            
+          } catch (IOException e) {
+            if(tries == numRetries - 1) {
+              // This was our last chance - don't bother sleeping
+              break;
+            }
+            LOG.info("Attempt " + tries + " of " + this.numRetries +
+                " failed with <" + e + ">. Retrying after sleep of " + this.pause);
+          }
+
+          // We either cannot connect to master or it is not running. Sleep & retry
+          
+          try {
+            Thread.sleep(this.pause);
+          } catch (InterruptedException e) {
+            // continue
+          }
+        }
+        this.masterChecked = true;
+      }
+      if (this.master == null) {
+        throw new MasterNotRunningException();
+      }
+      return this.master;
+    }
+
+    /** {@inheritDoc} */
+    public boolean isMasterRunning() {
+      if (this.master == null) {
+        try {
+          getMaster();
+          
+        } catch (MasterNotRunningException e) {
+          return false;
+        }
+      }
+      return true;
+    }
+
+    /** {@inheritDoc} */
+    public boolean tableExists(final Text tableName) {
+      boolean exists = true;
+      try {
+        SortedMap<Text, HRegionLocation> servers = getTableServers(tableName);
+        if (servers == null || servers.size() == 0) {
+          exists = false;
+        }
+
+      } catch (IOException e) {
+        exists = false;
+      }
+      return exists;
+    }
+
+    /** {@inheritDoc} */
+    public HTableDescriptor[] listTables() throws IOException {
+      TreeSet<HTableDescriptor> uniqueTables = new TreeSet<HTableDescriptor>();
+
+      SortedMap<Text, HRegionLocation> metaTables =
+        getTableServers(META_TABLE_NAME);
+
+      for (HRegionLocation t: metaTables.values()) {
+        HRegionInterface server = getHRegionConnection(t.getServerAddress());
+        long scannerId = -1L;
+        try {
+          scannerId = server.openScanner(t.getRegionInfo().getRegionName(),
+              COLUMN_FAMILY_ARRAY, EMPTY_START_ROW, System.currentTimeMillis(),
+              null);
+
+          DataInputBuffer inbuf = new DataInputBuffer();
+          while (true) {
+            KeyedData[] values = server.next(scannerId);
+            if (values.length == 0) {
+              break;
+            }
+            for (int i = 0; i < values.length; i++) {
+              if (values[i].getKey().getColumn().equals(COL_REGIONINFO)) {
+                inbuf.reset(values[i].getData(), values[i].getData().length);
+                HRegionInfo info = new HRegionInfo();
+                info.readFields(inbuf);
+
+                // Only examine the rows where the startKey is zero length   
+                if (info.startKey.getLength() == 0) {
+                  uniqueTables.add(info.tableDesc);
+                }
+              }
+            }
+          }
+        } catch (RemoteException ex) {
+          throw RemoteExceptionHandler.decodeRemoteException(ex);
+
+        } finally {
+          if (scannerId != -1L) {
+            server.close(scannerId);
+          }
+        }
+      }
+      return uniqueTables.toArray(new HTableDescriptor[uniqueTables.size()]);
+    }
+
+    /** {@inheritDoc} */
+    public SortedMap<Text, HRegionLocation>
+    getTableServers(Text tableName) throws IOException {
+      
+      if (tableName == null || tableName.getLength() == 0) {
+        throw new IllegalArgumentException(
+            "table name cannot be null or zero length");
+      }
+
+      if (closedTables.contains(tableName)) {
+        throw new IllegalStateException("table closed: " + tableName);
+      }
+      
+      SortedMap<Text, HRegionLocation> tableServers  =
+        tablesToServers.get(tableName);
+      
+      if (tableServers == null ) {
+        if (LOG.isDebugEnabled()) {
+          LOG.debug("No servers for " + tableName + ". Doing a find...");
+        }
+        // We don't know where the table is.
+        // Load the information from meta.
+        tableServers = findServersForTable(tableName);
+      }
+      SortedMap<Text, HRegionLocation> servers =
+        new TreeMap<Text, HRegionLocation>();
+
+      servers.putAll(tableServers);
+      return servers;
+    }
+
+    /** {@inheritDoc} */
+    public SortedMap<Text, HRegionLocation>
+    reloadTableServers(final Text tableName) throws IOException {
+      
+      if (closedTables.contains(tableName)) {
+        throw new IllegalStateException("table closed: " + tableName);
+      }
+
+      SortedMap<Text, HRegionLocation> servers =
+        new TreeMap<Text, HRegionLocation>();
+      
+      // Reload information for the whole table
+
+      servers.putAll(findServersForTable(tableName));
+      if (LOG.isDebugEnabled()) {
+        LOG.debug("Result of findTable: " + servers.toString());
+      }
+      
+      return servers;
+    }
+
+    /** {@inheritDoc} */
+    public HRegionInterface getHRegionConnection(
+        HServerAddress regionServer) throws IOException {
+
+      HRegionInterface server;
+      synchronized (this.servers) {
+        // See if we already have a connection
+        server = this.servers.get(regionServer.toString());
+
+        if (server == null) { // Get a connection
+          long versionId = 0;
+          try {
+            versionId =
+              serverInterfaceClass.getDeclaredField("versionID").getLong(server);
+
+          } catch (IllegalAccessException e) {
+            // Should never happen unless visibility of versionID changes
+            throw new UnsupportedOperationException(
+                "Unable to open a connection to a " +
+                serverInterfaceClass.getName() + " server.", e);
+
+          } catch (NoSuchFieldException e) {
+            // Should never happen unless versionID field name changes in HRegionInterface
+            throw new UnsupportedOperationException(
+                "Unable to open a connection to a " +
+                serverInterfaceClass.getName() + " server.", e);
+          }
+
+          try {
+            server = (HRegionInterface) RPC.waitForProxy(serverInterfaceClass,
+                versionId, regionServer.getInetSocketAddress(), this.conf);
+
+          } catch (RemoteException e) {
+            throw RemoteExceptionHandler.decodeRemoteException(e);
+          }
+
+          this.servers.put(regionServer.toString(), server);
+        }
+      }
+      return server;
+    }
+
+    /** {@inheritDoc} */
+    public void close(Text tableName) {
+      if (tableName == null || tableName.getLength() == 0) {
+        throw new IllegalArgumentException(
+            "table name cannot be null or zero length");
+      }
+      
+      if (closedTables.contains(tableName)) {
+        throw new IllegalStateException("table closed: " + tableName);
+      }
+
+      SortedMap<Text, HRegionLocation> tableServers =
+        tablesToServers.remove(tableName);
+
+      if (tableServers == null) {
+        throw new IllegalArgumentException("table was not opened: " + tableName);
+      }
+      
+      closedTables.add(tableName);
+      
+      // Shut down connections to the HRegionServers
+
+      synchronized (this.servers) {
+        for (HRegionLocation r: tableServers.values()) {
+          this.servers.remove(r.getServerAddress().toString());
+        }
+      }
+    }
+    
+    /*
+     * Clears the cache of all known information about the specified table and
+     * locates a table by searching the META or ROOT region (as appropriate) or
+     * by querying the master for the location of the root region if that is the
+     * table requested.
+     * 
+     * @param tableName - name of table to find servers for
+     * @return - map of first row to table info for all regions in the table
+     * @throws IOException
+     */
+    private SortedMap<Text, HRegionLocation> findServersForTable(Text tableName)
+    throws IOException {
+      
+      // Wipe out everything we know about this table
+
+      if (this.tablesToServers.remove(tableName) != null) {
+        if (LOG.isDebugEnabled()) {
+          LOG.debug("Wiping out all we know of " + tableName);
+        }
+      }
+      
+      SortedMap<Text, HRegionLocation> servers =
+        new TreeMap<Text, HRegionLocation>();
+      
+      if (tableName.equals(ROOT_TABLE_NAME)) {
+        synchronized (rootRegionLock) {
+          // This block guards against two threads trying to find the root
+          // region at the same time. One will go do the find while the 
+          // second waits. The second thread will not do find.
+          
+          SortedMap<Text, HRegionLocation> tableServers =
+            this.tablesToServers.get(ROOT_TABLE_NAME);
+          
+          if (tableServers == null) {
+            tableServers = locateRootRegion();
+          }
+          servers.putAll(tableServers);
+        }
+        
+      } else if (tableName.equals(META_TABLE_NAME)) {
+        synchronized (metaRegionLock) {
+          // This block guards against two threads trying to load the meta 
+          // region at the same time. The first will load the meta region and
+          // the second will use the value that the first one found.
+          
+          if (tablesToServers.get(ROOT_TABLE_NAME) == null) {
+            findServersForTable(ROOT_TABLE_NAME);
+          }
+          
+          SortedMap<Text, HRegionLocation> tableServers =
+            this.tablesToServers.get(META_TABLE_NAME);
+          
+          if (tableServers == null) {
+            for (int tries = 0; tries < numRetries; tries++) {
+              try {
+                tableServers = loadMetaFromRoot();
+                break;
+
+              } catch (IOException e) {
+                if (tries < numRetries - 1) {
+                  findServersForTable(ROOT_TABLE_NAME);
+                  continue;
+                }
+                throw e;
+              }
+            }
+          }
+          servers.putAll(tableServers);
+        }
+      } else {
+        boolean waited = false;
+        synchronized (this.tablesBeingLocated) {
+          // This block ensures that only one thread will actually try to
+          // find a table. If a second thread comes along it will wait
+          // until the first thread finishes finding the table.
+          
+          while (this.tablesBeingLocated.contains(tableName)) {
+            waited = true;
+            try {
+              this.tablesBeingLocated.wait(threadWakeFrequency);
+            } catch (InterruptedException e) {
+            }
+          }
+          if (!waited) {
+            this.tablesBeingLocated.add(tableName);
+            
+          } else {
+            SortedMap<Text, HRegionLocation> tableServers =
+              this.tablesToServers.get(tableName);
+            
+            if (tableServers == null) {
+              throw new TableNotFoundException("table not found: " + tableName);
+            }
+            servers.putAll(tableServers);
+          }
+        }
+        if (!waited) {
+          try {
+            for (int tries = 0; tries < numRetries; tries++) {
+              boolean success = true;                         // assume this works
+
+              SortedMap<Text, HRegionLocation> metaServers =
+                this.tablesToServers.get(META_TABLE_NAME);
+              if (metaServers == null) {
+                metaServers = findServersForTable(META_TABLE_NAME);
+              }
+              Text firstMetaRegion = metaServers.headMap(tableName).lastKey();
+              metaServers = metaServers.tailMap(firstMetaRegion);
+
+              for (HRegionLocation t: metaServers.values()) {
+                try {
+                  servers.putAll(scanOneMetaRegion(t, tableName));
+
+                } catch (IOException e) {
+                  if (tries < numRetries - 1) {
+                    findServersForTable(META_TABLE_NAME);
+                    success = false;
+                    break;
+                  }
+                  throw e;
+                }
+              }
+              if (success) {
+                break;
+              }
+            }
+          } finally {
+            synchronized (this.tablesBeingLocated) {
+              // Wake up the threads waiting for us to find the table
+              this.tablesBeingLocated.remove(tableName);
+              this.tablesBeingLocated.notifyAll();
+            }
+          }
+        }
+      }
+      this.tablesToServers.put(tableName, servers);
+      if (LOG.isDebugEnabled()) {
+        for (Map.Entry<Text, HRegionLocation> e: servers.entrySet()) {
+          LOG.debug("Server " + e.getKey() + " is serving: " + e.getValue() +
+              " for table " + tableName);
+        }
+      }
+      return servers;
+    }
+
+    /*
+     * Load the meta table from the root table.
+     * 
+     * @return map of first row to TableInfo for all meta regions
+     * @throws IOException
+     */
+    private TreeMap<Text, HRegionLocation> loadMetaFromRoot()
+    throws IOException {
+      
+      SortedMap<Text, HRegionLocation> rootRegion =
+        this.tablesToServers.get(ROOT_TABLE_NAME);
+      
+      return scanOneMetaRegion(
+          rootRegion.get(rootRegion.firstKey()), META_TABLE_NAME);
+    }
+    
+    /*
+     * Repeatedly try to find the root region by asking the master for where it is
+     * @return TreeMap<Text, TableInfo> for root regin if found
+     * @throws NoServerForRegionException - if the root region can not be located
+     * after retrying
+     * @throws IOException 
+     */
+    private TreeMap<Text, HRegionLocation> locateRootRegion()
+    throws IOException {
+    
+      getMaster();
+      
+      HServerAddress rootRegionLocation = null;
+      for (int tries = 0; tries < numRetries; tries++) {
+        int localTimeouts = 0;
+        while (rootRegionLocation == null && localTimeouts < numRetries) {
+          rootRegionLocation = master.findRootRegion();
+          if (rootRegionLocation == null) {
+            try {
+              if (LOG.isDebugEnabled()) {
+                LOG.debug("Sleeping. Waiting for root region.");
+              }
+              Thread.sleep(pause);
+              if (LOG.isDebugEnabled()) {
+                LOG.debug("Wake. Retry finding root region.");
+              }
+            } catch (InterruptedException iex) {
+              // continue
+            }
+            localTimeouts++;
+          }
+        }
+        
+        if (rootRegionLocation == null) {
+          throw new NoServerForRegionException(
+              "Timed out trying to locate root region");
+        }
+        
+        HRegionInterface rootRegion = getHRegionConnection(rootRegionLocation);
+
+        try {
+          rootRegion.getRegionInfo(HGlobals.rootRegionInfo.regionName);
+          break;
+          
+        } catch (IOException e) {
+          if (tries == numRetries - 1) {
+            // Don't bother sleeping. We've run out of retries.
+            if (e instanceof RemoteException) {
+              e = RemoteExceptionHandler.decodeRemoteException(
+                  (RemoteException) e);
+            }
+            throw e;
+          }
+          
+          // Sleep and retry finding root region.
+          try {
+            if (LOG.isDebugEnabled()) {
+              LOG.debug("Root region location changed. Sleeping.");
+            }
+            Thread.sleep(pause);
+            if (LOG.isDebugEnabled()) {
+              LOG.debug("Wake. Retry finding root region.");
+            }
+          } catch (InterruptedException iex) {
+            // continue
+          }
+        }
+        rootRegionLocation = null;
+      }
+      
+      if (rootRegionLocation == null) {
+        throw new NoServerForRegionException(
+          "unable to locate root region server");
+      }
+      
+      TreeMap<Text, HRegionLocation> rootServer =
+        new TreeMap<Text, HRegionLocation>();
+      
+      rootServer.put(EMPTY_START_ROW,
+          new HRegionLocation(HGlobals.rootRegionInfo, rootRegionLocation));
+      
+      return rootServer;
+    }
+
+    /*
+     * Scans a single meta region
+     * @param t the meta region we're going to scan
+     * @param tableName the name of the table we're looking for
+     * @return returns a map of startingRow to TableInfo
+     * @throws TableNotFoundException - if table does not exist
+     * @throws IllegalStateException - if table is offline
+     * @throws NoServerForRegionException - if table can not be found after retrying
+     * @throws IOException 
+     */
+    private TreeMap<Text, HRegionLocation> scanOneMetaRegion(
+        final HRegionLocation t, final Text tableName) throws IOException {
+      
+      HRegionInterface server = getHRegionConnection(t.getServerAddress());
+      TreeMap<Text, HRegionLocation> servers =
+        new TreeMap<Text, HRegionLocation>();
+      
+      for (int tries = 0; servers.size() == 0 && tries < numRetries; tries++) {
+
+        long scannerId = -1L;
+        try {
+          scannerId =
+            server.openScanner(t.getRegionInfo().getRegionName(),
+                COLUMN_FAMILY_ARRAY, tableName, System.currentTimeMillis(), null);
+
+          DataInputBuffer inbuf = new DataInputBuffer();
+          while (true) {
+            HRegionInfo regionInfo = null;
+            String serverAddress = null;
+            KeyedData[] values = server.next(scannerId);
+            if (values.length == 0) {
+              if (servers.size() == 0) {
+                // If we didn't find any servers then the table does not exist
+                throw new TableNotFoundException("table '" + tableName +
+                    "' does not exist in " + t);
+              }
+
+              // We found at least one server for the table and now we're done.
+              if (LOG.isDebugEnabled()) {
+                LOG.debug("Found " + servers.size() + " server(s) for " +
+                    "location: " + t + " for tablename " + tableName);
+              }
+              break;
+            }
+
+            byte[] bytes = null;
+            TreeMap<Text, byte[]> results = new TreeMap<Text, byte[]>();
+            for (int i = 0; i < values.length; i++) {
+              results.put(values[i].getKey().getColumn(), values[i].getData());
+            }
+            regionInfo = new HRegionInfo();
+            bytes = results.get(COL_REGIONINFO);
+            inbuf.reset(bytes, bytes.length);
+            regionInfo.readFields(inbuf);
+
+            if (!regionInfo.tableDesc.getName().equals(tableName)) {
+              // We're done
+              if (LOG.isDebugEnabled()) {
+                LOG.debug("Found " + servers.size() + " servers for table " +
+                  tableName);
+              }
+              break;
+            }
+
+            if (regionInfo.offLine) {
+              throw new IllegalStateException("table offline: " + tableName);
+            }
+
+            bytes = results.get(COL_SERVER);
+            if (bytes == null || bytes.length == 0) {
+              // We need to rescan because the table we want is unassigned.
+              if (LOG.isDebugEnabled()) {
+                LOG.debug("no server address for " + regionInfo.toString());
+              }
+              servers.clear();
+              break;
+            }
+            serverAddress = new String(bytes, UTF8_ENCODING);
+            servers.put(regionInfo.startKey, new HRegionLocation(
+                regionInfo, new HServerAddress(serverAddress)));
+          }
+        } catch (IOException e) {
+          if (tries == numRetries - 1) {                 // no retries left
+            if (e instanceof RemoteException) {
+              e = RemoteExceptionHandler.decodeRemoteException((RemoteException) e);
+            }
+            throw e;
+          }
+          
+        } finally {
+          if (scannerId != -1L) {
+            try {
+              server.close(scannerId);
+            } catch (Exception ex) {
+              LOG.warn(ex);
+            }
+          }
+        }
+        
+        if (servers.size() == 0 && tries == numRetries - 1) {
+          throw new NoServerForRegionException("failed to find server for " +
+              tableName + " after " + numRetries + " retries");
+        }
+
+        if (servers.size() <= 0) {
+          // The table is not yet being served. Sleep and retry.
+          if (LOG.isDebugEnabled()) {
+            LOG.debug("Sleeping. Table " + tableName +
+              " not currently being served.");
+          }
+          try {
+            Thread.sleep(pause);
+          } catch (InterruptedException ie) {
+            // continue
+          }
+          if (LOG.isDebugEnabled()) {
+            LOG.debug("Wake. Retry finding table " + tableName);
+          }
+        }
+      }
+      return servers;
+    }
+  }
+}

Modified: lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HConstants.java
URL: http://svn.apache.org/viewvc/lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HConstants.java?view=diff&rev=561935&r1=561934&r2=561935
==============================================================================
--- lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HConstants.java (original)
+++ lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HConstants.java Wed Aug  1 13:10:11 2007
@@ -41,7 +41,8 @@
   
   /** Parameter name for master address */
   static final String MASTER_ADDRESS = "hbase.master";
-  
+
+  /** default host address */
   static final String DEFAULT_HOST = "0.0.0.0";
   
   /** Default master address */
@@ -100,11 +101,15 @@
   
   /** The ROOT and META column family */
   static final Text COLUMN_FAMILY = new Text("info:");
-  
+
+  /** Array of meta column names */
   static final Text [] COLUMN_FAMILY_ARRAY = new Text [] {COLUMN_FAMILY};
   
   /** ROOT/META column family member - contains HRegionInfo */
   static final Text COL_REGIONINFO = new Text(COLUMN_FAMILY + "regioninfo");
+
+  /** Array of column - contains HRegionInfo */
+  static final Text[] COL_REGIONINFO_ARRAY = new Text [] {COL_REGIONINFO};
   
   /** ROOT/META column family member - contains HServerAddress.toString() */
   static final Text COL_SERVER = new Text(COLUMN_FAMILY + "server");
@@ -113,6 +118,9 @@
   static final Text COL_STARTCODE = new Text(COLUMN_FAMILY + "serverstartcode");
 
   // Other constants
+
+  /** used by scanners, etc when they want to start at the beginning of a region */
+  static final Text EMPTY_START_ROW = new Text();
 
   /** When we encode strings, we always specify UTF8 encoding */
   static final String UTF8_ENCODING = "UTF-8";

Modified: lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HMaster.java
URL: http://svn.apache.org/viewvc/lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HMaster.java?view=diff&rev=561935&r1=561934&r2=561935
==============================================================================
--- lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HMaster.java (original)
+++ lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HMaster.java Wed Aug  1 13:10:11 2007
@@ -942,25 +942,29 @@
       
       // HRegionServer is shutting down. Cancel the server's lease.
       
-      LOG.info("Region server " + s + ": MSG_REPORT_EXITING");
-      cancelLease(s, serverLabel);
-      
-      // Get all the regions the server was serving reassigned
-      // (if we are not shutting down).
+      if (cancelLease(s, serverLabel)) {
+        // Only process the exit message if the server still has a lease.
+        // Otherwise we could end up processing the server exit twice.
+
+        LOG.info("Region server " + s + ": MSG_REPORT_EXITING");
       
-      if (!closed) {
-        for (int i = 1; i < msgs.length; i++) {
-          HRegionInfo info = msgs[i].getRegionInfo();
-          
-          if (info.tableDesc.getName().equals(ROOT_TABLE_NAME)) {
-            rootRegionLocation = null;
-          
-          } else if (info.tableDesc.getName().equals(META_TABLE_NAME)) {
-            onlineMetaRegions.remove(info.getStartKey());
+        // Get all the regions the server was serving reassigned
+        // (if we are not shutting down).
+
+        if (!closed) {
+          for (int i = 1; i < msgs.length; i++) {
+            HRegionInfo info = msgs[i].getRegionInfo();
+
+            if (info.tableDesc.getName().equals(ROOT_TABLE_NAME)) {
+              rootRegionLocation = null;
+
+            } else if (info.tableDesc.getName().equals(META_TABLE_NAME)) {
+              onlineMetaRegions.remove(info.getStartKey());
+            }
+
+            unassignedRegions.put(info.regionName, info);
+            assignAttempts.put(info.regionName, Long.valueOf(0L));
           }
-          
-          unassignedRegions.put(info.regionName, info);
-          assignAttempts.put(info.regionName, Long.valueOf(0L));
         }
       }
       
@@ -1021,14 +1025,16 @@
   }
 
   /** cancel a server's lease */
-  private void cancelLease(final String serverName, final long serverLabel)
-  throws IOException {
+  private boolean cancelLease(final String serverName, final long serverLabel) {
+    boolean leaseCancelled = false;
     if (serversToServerInfo.remove(serverName) != null) {
       // Only cancel lease once.
       // This method can be called a couple of times during shutdown.
       LOG.info("Cancelling lease for " + serverName);
       serverLeases.cancelLease(serverLabel, serverLabel);
+      leaseCancelled = true;
     }
+    return leaseCancelled;
   }
   
   /** Process all the incoming messages from a server that's contacted us. */
@@ -1721,6 +1727,10 @@
           if (rootRegionLocation == null || !rootScanned) {
             // We can't proceed until the root region is online and has been
             // scanned
+            if (LOG.isDebugEnabled()) {
+              LOG.debug("root region=" + rootRegionLocation.toString() +
+                  ", rootScanned=" + rootScanned);
+            }
             return false;
           }
           metaRegionName = HGlobals.rootRegionInfo.regionName;
@@ -1735,6 +1745,11 @@
             // online message from being processed. So return false to have this
             // operation requeued.
             
+            if (LOG.isDebugEnabled()) {
+              LOG.debug("rootScanned=" + rootScanned + ", numberOfMetaRegions=" +
+                  numberOfMetaRegions.get() + ", onlineMetaRegions.size()=" +
+                  onlineMetaRegions.size());
+            }
             return false;
           }
 
@@ -2474,16 +2489,15 @@
      */
     public void leaseExpired() {
       LOG.info(server + " lease expired");
+      
+      // Remove the server from the known servers list
+      
       HServerInfo storedInfo = serversToServerInfo.remove(server);
-      if(rootRegionLocation != null
-          && rootRegionLocation.toString().equals(
-              storedInfo.getServerAddress().toString())) {
-        
-        rootRegionLocation = null;
-        unassignedRegions.put(HGlobals.rootRegionInfo.regionName,
-            HGlobals.rootRegionInfo);
-        assignAttempts.put(HGlobals.rootRegionInfo.regionName, 0L);
-      }
+      
+      // NOTE: If the server was serving the root region, we cannot reassign it
+      // here because the new server will start serving the root region before
+      // the PendingServerShutdown operation has a chance to split the log file.
+      
       try {
         msgQueue.put(new PendingServerShutdown(storedInfo));
       } catch (InterruptedException e) {

Added: lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HRegionLocation.java
URL: http://svn.apache.org/viewvc/lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HRegionLocation.java?view=auto&rev=561935
==============================================================================
--- lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HRegionLocation.java (added)
+++ lucene/hadoop/trunk/src/contrib/hbase/src/java/org/apache/hadoop/hbase/HRegionLocation.java Wed Aug  1 13:10:11 2007
@@ -0,0 +1,94 @@
+/**
+ * Copyright 2007 The Apache Software Foundation
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.hbase;
+
+/**
+ * Contains the HRegionInfo for the region and the HServerAddress for the
+ * HRegionServer serving the region
+ */
+@SuppressWarnings("unchecked")
+public class HRegionLocation implements Comparable {
+  private HRegionInfo regionInfo;
+  private HServerAddress serverAddress;
+
+  /**
+   * Constructor
+   * 
+   * @param regionInfo the HRegionInfo for the region
+   * @param serverAddress the HServerAddress for the region server
+   */
+  public HRegionLocation(HRegionInfo regionInfo, HServerAddress serverAddress) {
+    this.regionInfo = regionInfo;
+    this.serverAddress = serverAddress;
+  }
+  
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString() {
+    return "address: " + this.serverAddress.toString() + ", regioninfo: " +
+      this.regionInfo;
+  }
+  
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean equals(Object o) {
+    return this.compareTo(o) == 0;
+  }
+  
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public int hashCode() {
+    int result = this.regionInfo.hashCode();
+    result ^= this.serverAddress.hashCode();
+    return result;
+  }
+  
+  /** @return HRegionInfo */
+  public HRegionInfo getRegionInfo(){
+    return regionInfo;
+  }
+
+  /** @return HServerAddress */
+  public HServerAddress getServerAddress(){
+    return serverAddress;
+  }
+
+  //
+  // Comparable
+  //
+  
+  /**
+   * {@inheritDoc}
+   */
+  public int compareTo(Object o) {
+    HRegionLocation other = (HRegionLocation) o;
+    int result = this.regionInfo.compareTo(other.regionInfo);
+    if(result == 0) {
+      result = this.serverAddress.compareTo(other.serverAddress);
+    }
+    return result;
+  }
+}