/** * 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.client.crosssite; import java.io.Closeable; import java.io.IOException; import java.io.InterruptedIOException; import java.util.*; import java.util.Map.Entry; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import com.google.protobuf.Descriptors; import com.google.protobuf.Message; import com.google.protobuf.Service; import com.google.protobuf.ServiceException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.*; import org.apache.hadoop.hbase.client.*; import org.apache.hadoop.hbase.client.coprocessor.Batch; import org.apache.hadoop.hbase.client.coprocessor.Batch.Call; import org.apache.hadoop.hbase.client.coprocessor.Batch.Callback; import org.apache.hadoop.hbase.crosssite.ClusterInfo; import org.apache.hadoop.hbase.crosssite.CrossSiteConstants; import org.apache.hadoop.hbase.crosssite.CrossSiteDummyAbortable; import org.apache.hadoop.hbase.crosssite.CrossSiteUtil; import org.apache.hadoop.hbase.crosssite.CrossSiteZNodes; import org.apache.hadoop.hbase.crosssite.CrossSiteZNodes.TableState; import org.apache.hadoop.hbase.crosssite.TableAbnormalStateException; import org.apache.hadoop.hbase.crosssite.locator.ClusterLocator; import org.apache.hadoop.hbase.crosssite.locator.ClusterLocator.RowNotLocatableException; import org.apache.hadoop.hbase.crosssite.locator.PrefixClusterLocator; import org.apache.hadoop.hbase.ipc.CoprocessorRpcChannel; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.Pair; import org.apache.hadoop.hbase.util.Threads; import org.apache.hadoop.hbase.zookeeper.ZKUtil; import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher; import org.apache.zookeeper.KeeperException; /** * <p> * Used to communicate with a cross site table. * * <p> * This class is not thread safe for reads nor write. * * <p> * Note that this class implements the {@link Closeable} interface. When a CrossSiteHTable instance * is no longer required, it *should* be closed in order to ensure that the underlying resources are * promptly released. Please note that the close method can throw java.io.IOException that must be * handled. * * @see CrossSiteHBaseAdmin for create, drop, list, enable and disable of tables. */ public class CrossSiteHTable implements CrossSiteHTableInterface, HTableInterface{ private static final Log LOG = LogFactory.getLog(CrossSiteHTable.class); protected volatile Configuration configuration; protected final byte[] tableName; protected String tableNameAsString; protected boolean autoFlush; protected boolean clearBufferOnFail; protected int maxKeyValueSize; protected int scannerCaching; protected boolean closed = false; protected boolean cleanupPoolOnClose; protected long writeBufferSize; protected int operationTimeout = -1; // default value is -1. private ZooKeeperWatcher zkw; protected CrossSiteZNodes znodes; protected Map<String, HTableInterface> tablesCache; protected boolean failover; protected CachedZookeeperInfo cachedZKInfo; protected final ExecutorService pool; // used to dispatch the execution to each clusters. private final HTableInterfaceFactory hTableFactory; public CrossSiteHTable(Configuration conf, final String tableName) throws IOException { this(conf, Bytes.toBytes(tableName)); } public CrossSiteHTable(Configuration conf, final byte[] tableName) throws IOException { this(conf, tableName, null); } public CrossSiteHTable(Configuration conf, final byte[] tableName, final ExecutorService pool) throws IOException { this(conf, tableName, pool, new HTableFactory()); } public CrossSiteHTable(Configuration conf, final byte[] tableName, final ExecutorService pool, HTableInterfaceFactory hTableFactory) throws IOException { // super(); this.configuration = getCrossSiteConf(conf, conf.get(CrossSiteConstants.CROSS_SITE_ZOOKEEPER)); this.tableName = tableName; if (pool != null) { this.cleanupPoolOnClose = false; this.pool = pool; } else { this.cleanupPoolOnClose = true; this.pool = getDefaultExecutor(this.configuration); } this.hTableFactory = hTableFactory; try { finishSetup(); } catch (KeeperException e) { throw new IOException(e); } } /** * Gets the configuration with the given cluster key. * * @param conf * @param clusterKey * @return * @throws IOException */ private Configuration getCrossSiteConf(Configuration conf, String clusterKey) throws IOException { // create the connection to the global zk Configuration crossSiteZKConf = new Configuration(conf); ZKUtil.applyClusterKeyToConf(crossSiteZKConf, clusterKey); return crossSiteZKConf; } private void finishSetup() throws IOException, KeeperException { this.zkw = new ZooKeeperWatcher(configuration, "connection to global zookeeper", new CrossSiteDummyAbortable(), false); this.znodes = new CrossSiteZNodes(zkw); this.tableNameAsString = Bytes.toString(tableName); // check the state, only ENABLED/DISABLED are allowed. TableState state = znodes.getTableState(tableNameAsString); if (!TableState.ENABLED.equals(state) && !TableState.DISABLED.equals(state)) { throw new TableAbnormalStateException(tableNameAsString + ":" + state); } this.autoFlush = true; this.maxKeyValueSize = this.configuration.getInt("hbase.client.keyvalue.maxsize", -1); this.scannerCaching = this.configuration.getInt("hbase.client.scanner.caching", 1); this.writeBufferSize = this.configuration.getLong("hbase.client.write.buffer", 2097152); this.tablesCache = new TreeMap<String, HTableInterface>(); this.failover = this.configuration.getBoolean("hbase.crosssite.table.failover", false); loadZKInfo(); } private void loadZKInfo() throws KeeperException, IOException { CachedZookeeperInfo cachedZKInfo = new CachedZookeeperInfo(); cachedZKInfo.htd = znodes.getTableDesc(tableNameAsString); cachedZKInfo.clusterLocator = znodes.getClusterLocator(tableNameAsString); cachedZKInfo.clusterNames = getClusterNames(); cachedZKInfo.clusterInfos = getClusterInfos(cachedZKInfo.clusterNames); cachedZKInfo.hierarchyMap = znodes.getHierarchyMap(); this.cachedZKInfo = cachedZKInfo; } /** * For internal usage only. Refreshes the cached information of the CrossSiteHTable related with * the zookeeper, the cached tables won't be refreshed. This method could help to refresh the * cached zookeeper-related information without closing the underlying HTables. This will benefit * the usages of the internal components, for example the cross site thrift. * * @throws IOException */ public void refresh() throws IOException { try { loadZKInfo(); } catch (KeeperException e) { LOG.error("Fail to load the zk info", e); throw new IOException(e); } } public static ThreadPoolExecutor getDefaultExecutor(Configuration conf) { int maxThreads = conf.getInt("hbase.crosssite.table.threads.max", Integer.MAX_VALUE); if (maxThreads <= 0) { maxThreads = Integer.MAX_VALUE; } final SynchronousQueue<Runnable> blockingQueue = new SynchronousQueue<Runnable>(); RejectedExecutionHandler rejectHandler = new RejectedExecutionHandler() { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { try { blockingQueue.put(r); } catch (InterruptedException e) { throw new RejectedExecutionException(e); } } }; long keepAliveTime = conf.getLong("hbase.table.threads.keepalivetime", 60); ThreadPoolExecutor pool = new ThreadPoolExecutor(1, maxThreads, keepAliveTime, TimeUnit.SECONDS, blockingQueue, Threads.newDaemonThreadFactory("crosssite-hbase-table"), rejectHandler); ((ThreadPoolExecutor) pool).allowCoreThreadTimeOut(true); return pool; } /** * {@inheritDoc} */ @Override public byte[] getTableName() { return this.tableName; } @Override public TableName getName() { return TableName.valueOf(tableName); } /** * {@inheritDoc} */ @Override public Configuration getConfiguration() { return this.configuration; } public void setScannerCaching(int scannerCaching) { this.scannerCaching = scannerCaching; } /** * {@inheritDoc} */ @Override public HTableDescriptor getTableDescriptor() throws IOException { return this.cachedZKInfo.htd; } /** * {@inheritDoc} */ @Override public boolean exists(Get get) throws IOException { CachedZookeeperInfo cachedZKInfo = this.cachedZKInfo; String clusterName = cachedZKInfo.clusterLocator.getClusterName(get.getRow()); try { return getClusterHTable(clusterName).exists(get); } catch (IOException e) { // need clear the cached HTable if the connection is refused clearCachedTable(clusterName); LOG.warn("Fail to connect to the cluster " + clusterName, e); // Not do the failover for all IOException, only for the exceptions related with the connect // issue. if (failover && CrossSiteUtil.isFailoverException(e)) { LOG.warn("Failover: redirect the get request to the peers. Please notice, the data may be stale."); HTableInterface table = findAvailablePeer(cachedZKInfo.clusterInfos, clusterName); if (table == null) { LOG.error("Fail to find the peers", e); throw e; } else { try { return table.exists(get); } finally { try { table.close(); } catch (IOException e1) { LOG.warn("Fail to close the peer HTable", e1); } } } } else { throw e; } } } @Override public Boolean[] exists(final List<Get> gets) throws IOException { if (gets.isEmpty()) return new Boolean[]{}; if (gets.size() == 1) return new Boolean[] {exists(gets.get(0))}; Boolean[] results = new Boolean[gets.size()]; int i = 0; for(Get g: gets){ CachedZookeeperInfo cachedZKInfo = this.cachedZKInfo; String clusterName = cachedZKInfo.clusterLocator.getClusterName(g.getRow()); try { results[i] = getClusterHTable(clusterName).exists(g); } catch (IOException e) { // need clear the cached HTable if the connection is refused clearCachedTable(clusterName); LOG.warn("Fail to connect to the cluster " + clusterName, e); // Not do the failover for all IOException, only for the exceptions related with the connect // issue. if (failover && CrossSiteUtil.isFailoverException(e)) { LOG.warn("Failover: redirect the get request to the peers. Please notice, the data may be stale."); HTableInterface table = findAvailablePeer(cachedZKInfo.clusterInfos, clusterName); if (table == null) { LOG.error("Fail to find the peers", e); throw e; } else { try { results[i] = table.exists(g); } finally { try { table.close(); } catch (IOException e1) { LOG.warn("Fail to close the peer HTable", e1); } } } } else { throw e; } } i++; } return results; } /** * {@inheritDoc} */ @Override public void batch(List<? extends Row> actions, Object[] results) throws IOException, InterruptedException { batchCallback(actions, results, null); } /** * {@inheritDoc} */ @Override public Object[] batch(List<? extends Row> actions) throws IOException, InterruptedException { return batchCallback(actions, null); } @Override public <R> void batchCallback(final List<? extends Row> actions, final Object[] results, final Callback<R> callback) throws IOException, InterruptedException { if (results.length != actions.size()) { throw new IllegalArgumentException( "argument results must be the same size as argument actions"); } if (actions.isEmpty()) { return; } ClusterLocator clusterLocator = cachedZKInfo.clusterLocator; Map<String, Map<Integer, Row>> clusterMap = new TreeMap<String, Map<Integer,Row>>(); Map<Integer, Object> rmap = new TreeMap<Integer, Object>(); int index = 0; for (Row action : actions) { String clusterName = clusterLocator.getClusterName(action.getRow()); Map<Integer, Row> rows = clusterMap.get(clusterName); if (rows == null) { rows = new TreeMap<Integer, Row>(); clusterMap.put(clusterName, rows); } rows.put(Integer.valueOf(index++), action); } final AtomicBoolean hasError = new AtomicBoolean(false); Map<String, Future<Map<Integer, Object>>> futures = new HashMap<String, Future<Map<Integer, Object>>>(); for (final Entry<String, Map<Integer, Row>> entry : clusterMap.entrySet()) { futures.put(entry.getKey(), pool.submit(new Callable<Map<Integer, Object>>() { @Override public Map<Integer, Object> call() throws Exception { Map<Integer, Object> map = new TreeMap<Integer, Object>(); Map<Integer, Row> rowMap = entry.getValue(); Object[] rs = new Object[rowMap.size()]; List<Integer> indexes = new ArrayList<Integer>(rowMap.size()); List<Row> rows = new ArrayList<Row>(rowMap.size()); try { HTableInterface table = getClusterHTable(entry.getKey()); for (Entry<Integer, Row> rowEntry : rowMap.entrySet()) { indexes.add(rowEntry.getKey()); rows.add(rowEntry.getValue()); } table.batchCallback(rows, rs, callback); } catch (IOException e) { // need clear the cached HTable if the connection is refused clearCachedTable(entry.getKey()); hasError.set(true); LOG.error(e); } finally { int index = 0; for (Object r : rs) { map.put(indexes.get(index++), r); } } return map; } })); } try { for (Entry<String, Future<Map<Integer, Object>>> result : futures.entrySet()) { rmap.putAll(result.getValue().get()); } } catch (Exception e) { // do nothing } for (int i = 0; i < actions.size(); i++) { results[i] = rmap.get(Integer.valueOf(i)); } if (hasError.get()) { throw new IOException(); } } @Override public <R> Object[] batchCallback(List<? extends Row> actions, Callback<R> callback) throws IOException, InterruptedException { Object[] results = new Object[actions.size()]; batchCallback(actions, results, callback); return results; } /** * {@inheritDoc} */ @Override public Result get(Get get) throws IOException { CachedZookeeperInfo cachedZKInfo = this.cachedZKInfo; String clusterName = cachedZKInfo.clusterLocator.getClusterName(get.getRow()); try { return getClusterHTable(clusterName).get(get); } catch (IOException e) { // need clear the cached HTable if the connection is refused clearCachedTable(clusterName); LOG.warn("Fail to connect to the cluster " + clusterName, e); // Not do the failover for all IOException, only for the exceptions related with the connect // issue. if (failover && CrossSiteUtil.isFailoverException(e)) { LOG.warn("Failover: redirect the get request to the peers. Please notice, the data may be stale."); HTableInterface table = findAvailablePeer(cachedZKInfo.clusterInfos, clusterName); if (table == null) { LOG.error("Fail to find the peers", e); throw e; } else { try { return table.get(get); } catch(IOException e1) { LOG.error("Fail to get from peer.", e1); throw e1; } finally { try { table.close(); } catch (IOException e2) { LOG.warn("Fail to close the peer HTable", e2); } } } } else { throw e; } } } /** * {@inheritDoc} */ @Override public Result[] get(List<Get> gets) throws IOException { Map<String, Map<Integer, Get>> clusterMap = new TreeMap<String, Map<Integer, Get>>(); Map<Integer, Result> results = new TreeMap<Integer, Result>(); CachedZookeeperInfo cachedZKInfo = this.cachedZKInfo; ClusterLocator clusterLocator = cachedZKInfo.clusterLocator; int index = 0; for (Get get : gets) { String clusterName = clusterLocator.getClusterName(get.getRow()); Map<Integer, Get> getMap = clusterMap.get(clusterName); if(getMap == null) { getMap = new TreeMap<Integer, Get>(); clusterMap.put(clusterName, getMap); } getMap.put(Integer.valueOf(index++), get); } Map<String, Future<Map<Integer, Result>>> futures = new HashMap<String, Future<Map<Integer, Result>>>(); final Map<String, ClusterInfo> clusterInfos = cachedZKInfo.clusterInfos; for (final Entry<String, Map<Integer, Get>> entry : clusterMap.entrySet()) { futures.put(entry.getKey(), pool.submit(new Callable<Map<Integer, Result>>() { @Override public Map<Integer, Result> call() throws Exception { Map<Integer, Result> map = new TreeMap<Integer, Result>(); try { HTableInterface table = getClusterHTable(entry.getKey()); List<Get> gs = new ArrayList<Get>(entry.getValue().size()); List<Integer> indexes = new ArrayList<Integer>(entry.getValue().size()); for (Entry<Integer, Get> getEntry : entry.getValue().entrySet()) { indexes.add(getEntry.getKey()); gs.add(getEntry.getValue()); } Result[] rs = table.get(gs); int index = 0; for (Result r : rs) { map.put(indexes.get(index), r); index++; } } catch (IOException e) { // need clear the cached HTable if the connection is refused clearCachedTable(entry.getKey()); LOG.warn("Fail to connect to the cluster " + entry.getKey(), e); // Not do the failover for all IOException, // only for the exceptions related with the connect issue. if (failover && CrossSiteUtil.isFailoverException(e)) { LOG.warn("Failover: redirect the get request to the peers. Please notice, the data may be stale."); HTableInterface table = findAvailablePeer(clusterInfos, entry.getKey()); if (table == null) { LOG.error("Fail to find any peers", e); throw e; } else { try { List<Get> gs = new ArrayList<Get>(entry.getValue().size()); List<Integer> indexes = new ArrayList<Integer>(entry.getValue().size()); for (Entry<Integer, Get> getEntry : entry.getValue().entrySet()) { indexes.add(getEntry.getKey()); gs.add(getEntry.getValue()); } Result[] rs = table.get(gs); int index = 0; for (Result r : rs) { map.put(indexes.get(index), r); index++; } } finally { try { table.close(); } catch (IOException e1) { LOG.warn("Fail to close the peer HTable", e1); } } } } else { throw e; } } return map; } })); } try { for (Entry<String, Future<Map<Integer, Result>>> result : futures.entrySet()) { results.putAll(result.getValue().get()); } } catch (Exception e) { throw new IOException(e); } Result[] rs = new Result[gets.size()]; for (int i = 0; i < gets.size(); i++) { rs[i] = results.get(Integer.valueOf(i)); } return rs; } /** * {@inheritDoc} */ @Override public Result getRowOrBefore(byte[] row, byte[] family) throws IOException { CachedZookeeperInfo cachedZKInfo = this.cachedZKInfo; String clusterName = cachedZKInfo.clusterLocator.getClusterName(row); try { return getClusterHTable(clusterName).getRowOrBefore(row, family); } catch (IOException e) { // need clear the cached HTable if the connection is refused clearCachedTable(clusterName); LOG.warn("Fail to connect to the cluster " + clusterName, e); // Not do the failover for all IOException, only for the exceptions related with the connect // issue. if (failover && CrossSiteUtil.isFailoverException(e)) { LOG.warn("Failover: redirect the get request to the peers. Please notice, the data may be stale."); HTableInterface table = findAvailablePeer(cachedZKInfo.clusterInfos, clusterName); if (table == null) { LOG.error("Fail to find the peers", e); throw e; } else { try { return table.getRowOrBefore(row, family); } finally { try { table.close(); } catch (IOException e1) { LOG.warn("Fail to close the peer HTable", e1); } } } } else { throw e; } } } /** * {@inheritDoc} */ @Override public ResultScanner getScanner(Scan scan, String[] clusterNames) throws IOException { if (scan.getCaching() <= 0) { scan.setCaching(getScannerCaching()); } return new CrossSiteClientScanner(configuration, scan, tableName, getClusterInfoStartStopKeyPairs(scan.getStartRow(), scan.getStopRow(), clusterNames), failover, pool, znodes, hTableFactory); } /** * {@inheritDoc} */ @Override public ResultScanner getScanner(Scan scan) throws IOException { return getScanner(scan, null); } /** * {@inheritDoc} */ @Override public ResultScanner getScanner(byte[] family) throws IOException { Scan scan = new Scan(); scan.addFamily(family); return getScanner(scan); } /** * {@inheritDoc} */ @Override public ResultScanner getScanner(byte[] family, byte[] qualifier) throws IOException { Scan scan = new Scan(); scan.addColumn(family, qualifier); return getScanner(scan); } /** * {@inheritDoc} */ @Override public void put(Put put) throws InterruptedIOException, RetriesExhaustedWithDetailsException { validatePut(put); ClusterLocator clusterLocator = cachedZKInfo.clusterLocator; String clusterName = null; try { clusterName = clusterLocator.getClusterName(put.getRow()); } catch (IOException e) { LOG.error("Fail to get cluster name", e); } try { getClusterHTable(clusterName).put(put); } catch (IOException e) { // need clear the cached HTable if the connection is refused clearCachedTable(clusterName); throw (InterruptedIOException)e; } } /** * {@inheritDoc} */ @Override public void put(List<Put> puts) throws InterruptedIOException, RetriesExhaustedWithDetailsException { Map<String, List<Put>> tableMap = new HashMap<String, List<Put>>(); ClusterLocator clusterLocator = cachedZKInfo.clusterLocator; for (Put put : puts) { validatePut(put); String clusterName = null; try { clusterName = clusterLocator.getClusterName(put.getRow()); } catch (IOException e) { LOG.error("Fail to get cluster name", e); } List<Put> ps = tableMap.get(clusterName); if (ps == null) { ps = new ArrayList<Put>(); tableMap.put(clusterName, ps); } ps.add(put); } Map<String, Future<Void>> futures = new HashMap<String, Future<Void>>(); for (final Entry<String, List<Put>> entry : tableMap.entrySet()) { futures.put(entry.getKey(), pool.submit(new Callable<Void>() { @Override public Void call() throws Exception { try { getClusterHTable(entry.getKey()).put(entry.getValue()); } catch (IOException e) { // need clear the cached HTable if the connection is refused clearCachedTable(entry.getKey()); throw e; } return null; } })); } boolean hasError = false; for (Entry<String, Future<Void>> result : futures.entrySet()) { try { result.getValue().get(); } catch (Exception e) { hasError = true; LOG.error(e); } } if (hasError) { throw new InterruptedIOException(); } } /** * {@inheritDoc} */ @Override public boolean checkAndPut(byte[] row, byte[] family, byte[] qualifier, byte[] value, Put put) throws IOException { ClusterLocator clusterLocator = cachedZKInfo.clusterLocator; String clusterName = clusterLocator.getClusterName(row); try { return getClusterHTable(clusterName).checkAndPut(row, family, qualifier, value, put); } catch (IOException e) { // need clear the cached HTable if the connection is refused clearCachedTable(clusterName); throw e; } } /** * {@inheritDoc} */ @Override public void delete(Delete delete) throws IOException { ClusterLocator clusterLocator = cachedZKInfo.clusterLocator; String clusterName = clusterLocator.getClusterName(delete.getRow()); try { getClusterHTable(clusterName).delete(delete); } catch (IOException e) { // need clear the cached HTable if the connection is refused clearCachedTable(clusterName); throw e; } } /** * {@inheritDoc} */ @Override public void delete(List<Delete> deletes) throws IOException { Map<String, List<Delete>> tableMap = new HashMap<String, List<Delete>>(); ClusterLocator clusterLocator = cachedZKInfo.clusterLocator; for (Delete delete : deletes) { String clusterName = clusterLocator.getClusterName(delete.getRow()); List<Delete> ds = tableMap.get(clusterName); if (ds == null) { ds = new ArrayList<Delete>(); tableMap.put(clusterName, ds); } ds.add(delete); } Map<String, Future<Void>> futures = new HashMap<String, Future<Void>>(); for (final Entry<String, List<Delete>> entry : tableMap.entrySet()) { futures.put(entry.getKey(), pool.submit(new Callable<Void>() { @Override public Void call() throws Exception { try { getClusterHTable(entry.getKey()).delete(entry.getValue()); } catch (IOException e) { // need clear the cached HTable if the connection is refused clearCachedTable(entry.getKey()); throw e; } return null; } })); } boolean hasError = false; for (Entry<String, Future<Void>> result : futures.entrySet()) { try { result.getValue().get(); } catch (Exception e) { hasError = true; LOG.error(e); } } if (hasError) { throw new IOException(); } } /** * {@inheritDoc} */ @Override public boolean checkAndDelete(byte[] row, byte[] family, byte[] qualifier, byte[] value, Delete delete) throws IOException { ClusterLocator clusterLocator = cachedZKInfo.clusterLocator; String clusterName = clusterLocator.getClusterName(row); try { return getClusterHTable(clusterName).checkAndDelete(row, family, qualifier, value, delete); } catch (IOException e) { // need clear the cached HTable if the connection is refused clearCachedTable(clusterName); throw e; } } /** * {@inheritDoc} */ @Override public void mutateRow(RowMutations rm) throws IOException { ClusterLocator clusterLocator = cachedZKInfo.clusterLocator; String clusterName = clusterLocator.getClusterName(rm.getRow()); try { getClusterHTable(clusterName).mutateRow(rm); } catch (IOException e) { // need clear the cached HTable if the connection is refused clearCachedTable(clusterName); throw e; } } /** * {@inheritDoc} */ @Override public Result append(Append append) throws IOException { ClusterLocator clusterLocator = cachedZKInfo.clusterLocator; String clusterName = clusterLocator.getClusterName(append.getRow()); try { return getClusterHTable(clusterName).append(append); } catch (IOException e) { // need clear the cached HTable if the connection is refused clearCachedTable(clusterName); throw e; } } /** * {@inheritDoc} */ @Override public Result increment(Increment increment) throws IOException { ClusterLocator clusterLocator = cachedZKInfo.clusterLocator; String clusterName = clusterLocator.getClusterName(increment.getRow()); try { return getClusterHTable(clusterName).increment(increment); } catch (IOException e) { // need clear the cached HTable if the connection is refused clearCachedTable(clusterName); throw e; } } /** * {@inheritDoc} */ @Override public long incrementColumnValue(byte[] row, byte[] family, byte[] qualifier, long amount) throws IOException { return incrementColumnValue(row, family, qualifier, amount, true); } /** * {@inheritDoc} */ @Override public long incrementColumnValue(byte[] row, byte[] family, byte[] qualifier, long amount, boolean writeToWAL) throws IOException { NullPointerException npe = null; if (row == null) { npe = new NullPointerException("row is null"); } else if (family == null) { npe = new NullPointerException("column is null"); } else if (qualifier == null) { npe = new NullPointerException("qualifier is null"); } if (npe != null) { throw new IOException("Invalid arguments to incrementColumnValue", npe); } ClusterLocator clusterLocator = cachedZKInfo.clusterLocator; String clusterName = clusterLocator.getClusterName(row); try { return getClusterHTable(clusterName).incrementColumnValue(row, family, qualifier, amount, writeToWAL); } catch (IOException e) { // need clear the cached HTable if the connection is refused clearCachedTable(clusterName); throw e; } } @Override public long incrementColumnValue(final byte[] row, final byte[] family, final byte[] qualifier, final long amount, final Durability durability) throws IOException { NullPointerException npe = null; if (row == null) { npe = new NullPointerException("row is null"); } else if (family == null) { npe = new NullPointerException("column is null"); } else if (qualifier == null) { npe = new NullPointerException("qualifier is null"); } if (npe != null) { throw new IOException("Invalid arguments to incrementColumnValue", npe); } ClusterLocator clusterLocator = cachedZKInfo.clusterLocator; String clusterName = clusterLocator.getClusterName(row); try { return getClusterHTable(clusterName).incrementColumnValue(row, family, qualifier, amount, durability); } catch (IOException e) { // need clear the cached HTable if the connection is refused clearCachedTable(clusterName); throw e; } } /** * {@inheritDoc} */ @Override public boolean isAutoFlush() { return autoFlush; } /** * {@inheritDoc} */ @Override public void flushCommits() throws InterruptedIOException, RetriesExhaustedWithDetailsException { for (Entry<String, HTableInterface> entry : tablesCache.entrySet()) { if (entry.getValue() != null) { try { entry.getValue().flushCommits(); } catch (IOException e) { clearCachedTable(entry.getKey()); throw (InterruptedIOException)e; } } } } /** * {@inheritDoc} */ @Override public void close() throws IOException { if (this.closed) { return; } flushCommits(); if (cleanupPoolOnClose) { this.pool.shutdown(); } for (HTableInterface table : tablesCache.values()) { if (table != null) { try { table.close(); } catch (IOException e) { LOG.warn("Fail to close the HTable underneath the cross site table", e); } } } this.zkw.close(); this.tablesCache.clear(); this.cachedZKInfo.clear(); this.closed = true; } @Override public CoprocessorRpcChannel coprocessorService(byte[] row) { ClusterLocator clusterLocator = cachedZKInfo.clusterLocator; String clusterName = null; try { clusterName = clusterLocator.getClusterName(row); } catch (IOException e) { LOG.error("Fail to get cluster name", e); } try { return getClusterHTable(clusterName).coprocessorService(row); } catch (IOException e) { // need clear the cached HTable if the connection is refused clearCachedTable(clusterName); throw new RuntimeException(e); } } /** * {@inheritDoc} */ @Override public <T extends Service, R> Map<byte[], R> coprocessorService(Class<T> protocol, byte[] startKey, byte[] endKey, Call<T, R> callable) throws IOException, Throwable { final Map<byte[], R> results = Collections.synchronizedMap(new TreeMap<byte[], R>( Bytes.BYTES_COMPARATOR)); coprocessorService(protocol, startKey, endKey, callable, new Batch.Callback<R>() { public void update(byte[] region, byte[] row, R value) { results.put(region, value); } }); return results; } /** * {@inheritDoc} */ @Override public <T extends Service, R> Map<byte[], R> coprocessorService(Class<T> protocol, byte[] startKey, byte[] endKey, String[] clusterNames, Call<T, R> callable) throws IOException, Throwable { final Map<byte[], R> results = Collections.synchronizedMap(new TreeMap<byte[], R>( Bytes.BYTES_COMPARATOR)); coprocessorService(protocol, startKey, endKey, clusterNames, callable, new Batch.Callback<R>() { public void update(byte[] region, byte[] row, R value) { results.put(region, value); } }); return results; } /** * {@inheritDoc} */ @Override public <T extends Service, R> void coprocessorService(final Class<T> protocol, final byte[] startKey, final byte[] endKey, final Call<T, R> callable, final Callback<R> callback) throws IOException, Throwable { coprocessorService(protocol, startKey, endKey, null, callable, callback); } /** * {@inheritDoc} */ @Override public <T extends Service, R> void coprocessorService(final Class<T> protocol, final byte[] startKey, final byte[] endKey, final String[] clusterNames, final Call<T, R> callable, final Callback<R> callback) throws IOException, Throwable { List<Pair<ClusterInfo, Pair<byte[], byte[]>>> cis = getClusterInfoStartStopKeyPairs(startKey, endKey, clusterNames); Map<String, Future<Void>> futures = new HashMap<String, Future<Void>>(); for (final Pair<ClusterInfo, Pair<byte[], byte[]>> pair : cis) { futures.put(pair.getFirst().getName(), pool.submit(new Callable<Void>() { @Override public Void call() throws Exception { try { HTableInterface table = getClusterHTable(pair.getFirst().getName(), pair.getFirst() .getAddress()); table.coprocessorService(protocol, pair.getSecond().getFirst(), pair.getSecond() .getSecond(), callable, callback); } catch (Throwable e) { throw new Exception(e); } return null; } })); } boolean hasErrors = false; try { for (Entry<String, Future<Void>> result : futures.entrySet()) { result.getValue().get(); } } catch (Exception e) { // do nothing. Even the exception occurs, we regard the cross site // HTable has been deleted hasErrors = true; LOG.error("Fail to execute the coprocessor in the cross site table " + tableName, e); } if (hasErrors) { throw new IOException(); } } @Override public <R extends Message> Map<byte[], R> batchCoprocessorService( Descriptors.MethodDescriptor methodDescriptor, Message request, byte[] startKey, byte[] endKey, R responsePrototype) throws ServiceException, Throwable { final Map<byte[], R> results = Collections.synchronizedMap(new TreeMap<byte[], R>( Bytes.BYTES_COMPARATOR)); batchCoprocessorService(methodDescriptor, request, startKey, endKey, responsePrototype, new Callback<R>() { @Override public void update(byte[] region, byte[] row, R result) { if (region != null) { results.put(region, result); } } }); return results; } public <R extends Message> void batchCoprocessorService( final Descriptors.MethodDescriptor methodDescriptor, final Message request, byte[] startKey, byte[] endKey, String[] clusterNames, final R responsePrototype, final Callback<R> callback) throws ServiceException, Throwable { List<Pair<ClusterInfo, Pair<byte[], byte[]>>> cis = getClusterInfoStartStopKeyPairs(startKey, endKey, clusterNames); Map<String, Future<Void>> futures = new HashMap<String, Future<Void>>(); for (final Pair<ClusterInfo, Pair<byte[], byte[]>> pair : cis) { futures.put(pair.getFirst().getName(), pool.submit(new Callable<Void>() { @Override public Void call() throws Exception { try { HTableInterface table = getClusterHTable(pair.getFirst().getName(), pair.getFirst() .getAddress()); table.batchCoprocessorService(methodDescriptor, request, pair.getSecond().getFirst(), pair.getSecond() .getSecond(), responsePrototype, callback); } catch (Throwable e) { throw new Exception(e); } return null; } })); } boolean hasErrors = false; try { for (Entry<String, Future<Void>> result : futures.entrySet()) { result.getValue().get(); } } catch (Exception e) { // do nothing. Even the exception occurs, we regard the cross site // HTable has been deleted hasErrors = true; LOG.error("Fail to execute the coprocessor in the cross site table " + tableName, e); } if (hasErrors) { throw new IOException(); } } @Override public <R extends Message> void batchCoprocessorService( Descriptors.MethodDescriptor methodDescriptor, Message request, byte[] startKey, byte[] endKey, R responsePrototype, Callback<R> callback) throws ServiceException, Throwable { this.batchCoprocessorService(methodDescriptor, request, startKey, endKey, null, responsePrototype, callback); } /** * {@inheritDoc} */ @Override public void setAutoFlush(boolean autoFlush) { this.autoFlush = autoFlush; } @Override public void setAutoFlushTo(boolean autoFlush) { setAutoFlush(autoFlush, clearBufferOnFail); } /** * {@inheritDoc} */ @Override public void setAutoFlush(boolean autoFlush, boolean clearBufferOnFail) { this.autoFlush = autoFlush; this.clearBufferOnFail = autoFlush || clearBufferOnFail; } /** * {@inheritDoc} */ @Override public long getWriteBufferSize() { return writeBufferSize; } /** * {@inheritDoc} */ @Override public void setWriteBufferSize(long writeBufferSize) throws IOException { this.writeBufferSize = writeBufferSize; for (HTableInterface table : tablesCache.values()) { try { table.setWriteBufferSize(this.writeBufferSize); } catch (IOException e) { LOG.warn("Fail to set write buffer size to the table", e); } } } public int getScannerCaching() { return scannerCaching; } public void setOperationTimeout(int operationTimeout) { this.operationTimeout = operationTimeout; for (HTableInterface table : tablesCache.values()) { if (table instanceof HTable) { ((HTable) table).setOperationTimeout(this.operationTimeout); } } } public int getOperationTimeout() { return this.operationTimeout; } public HRegionLocation getRegionLocation(String row) throws IOException { return this.getRegionLocation(Bytes.toBytes(row)); } public HRegionLocation getRegionLocation(byte[] row) throws IOException { return this.getRegionLocation(row, false); } public HRegionLocation getRegionLocation(byte[] row, boolean reload) throws IOException { ClusterLocator clusterLocator = cachedZKInfo.clusterLocator; String clusterName = clusterLocator.getClusterName(row); try { HTableInterface table = getClusterHTable(clusterName); if (table instanceof HTable) { return ((HTable) table).getRegionLocation(row, reload); } throw new UnsupportedOperationException(); } catch (IOException e) { // need clear the cached HTable if the connection is refused clearCachedTable(clusterName); throw e; } } /** * Not supported. */ public HConnection getConnection() { throw new UnsupportedOperationException(); } /** * Not supported. */ public byte[][] getStartKeys() throws IOException { throw new UnsupportedOperationException(); } /** * Not supported. */ public byte[][] getEndKeys() throws IOException { throw new UnsupportedOperationException(); } /** * Not supported. */ public Pair<byte[][], byte[][]> getStartEndKeys() throws IOException { throw new UnsupportedOperationException(); } public NavigableMap<HRegionInfo, ServerName> getRegionLocations() throws IOException { List<ClusterInfo> cis = getClusterInfos(null, null); List<Future<NavigableMap<HRegionInfo, ServerName>>> futures = new ArrayList<Future<NavigableMap<HRegionInfo, ServerName>>>(); for (final ClusterInfo ci : cis) { futures.add(pool.submit(new Callable<NavigableMap<HRegionInfo, ServerName>>() { @Override public NavigableMap<HRegionInfo, ServerName> call() throws Exception { boolean closeTable = false; HTableInterface table = tablesCache.get(ci.getName()); if (table == null) { // Not cached.Let us create one. Do not cache this created one and make sure to close // this when the operation is done table = createHTable(ci.getName(), ci.getAddress()); closeTable = true; } try { if (table instanceof HTable) { return ((HTable) table).getRegionLocations(); } throw new UnsupportedOperationException(); } finally { if (closeTable) { try { table.close(); } catch (IOException e) { LOG.warn("Fail to close the HBaseAdmin", e); } } } } })); } try { NavigableMap<HRegionInfo, ServerName> regionLocations = new TreeMap<HRegionInfo, ServerName>(); for (Future<NavigableMap<HRegionInfo, ServerName>> result : futures) { if (result != null) { NavigableMap<HRegionInfo, ServerName> regions = result.get(); if (regions != null) { regionLocations.putAll(regions); } } } return regionLocations; } catch (Exception e) { // do nothing. Even the exception occurs, we regard the cross site // HTable has been deleted LOG.error("Fail to get region locations of the cross site table " + tableName, e); throw new IOException(e); } } public List<HRegionLocation> getRegionsInRange(byte[] startKey, byte[] endKey) throws IOException { return this.getRegionsInRange(startKey, endKey, false); } public List<HRegionLocation> getRegionsInRange(final byte[] startKey, final byte[] endKey, final boolean reload) throws IOException { List<ClusterInfo> cis = getClusterInfos(startKey, endKey); List<Future<List<HRegionLocation>>> futures = new ArrayList<Future<List<HRegionLocation>>>(); for (final ClusterInfo ci : cis) { futures.add(pool.submit(new Callable<List<HRegionLocation>>() { @Override public List<HRegionLocation> call() throws Exception { boolean closeTable = false; HTableInterface table = tablesCache.get(ci.getName()); if (table == null) { // Not cached.Let us create one. Do not cache this created one and make sure to close // this when the operation is done table = createHTable(ci.getName(), ci.getAddress()); closeTable = true; } try { if (table instanceof HTable) { return ((HTable) table).getRegionsInRange(startKey != null ? startKey : HConstants.EMPTY_START_ROW, endKey != null ? endKey : HConstants.EMPTY_END_ROW, reload); } throw new UnsupportedOperationException(); } finally { if (closeTable) { try { table.close(); } catch (IOException e) { LOG.warn("Fail to close the HBaseAdmin", e); } } } } })); } try { List<HRegionLocation> regionLocations = new ArrayList<HRegionLocation>(); for (Future<List<HRegionLocation>> result : futures) { if (result != null) { List<HRegionLocation> regions = result.get(); if (regions != null) { regionLocations.addAll(regions); } } } return regionLocations; } catch (Exception e) { // do nothing. Even the exception occurs, we regard the cross site // HTable has been deleted LOG.error("Fail to get region locations in a range of the cross site table " + tableName, e); throw new IOException(e); } } /** * Not supported. */ public List<Row> getWriteBuffer() { throw new UnsupportedOperationException(); } /** * Not supported. */ public void clearRegionCache() { throw new UnsupportedOperationException(); } /** * Gets all the cluster names. * * @return * @throws KeeperException */ private Set<String> getClusterNames() throws KeeperException { List<String> clusterNames = znodes.listClusters(); TreeSet<String> cns = new TreeSet<String>(); if (clusterNames != null) { cns.addAll(clusterNames); } return cns; } /** * Gets the HTable for the cluster. The HTable is cached in the cross site table until it's * closed, or the HTable is not reachable. * * @param clusterName * @return * @throws IOException */ public HTableInterface getClusterHTable(String clusterName) throws IOException { // find the cluster address HTableInterface table = tablesCache.get(clusterName); if (table == null) { String clusterAddress = null; try { clusterAddress = znodes.getClusterAddress(clusterName); } catch (KeeperException e) { throw new IOException(e); } table = createHTable(clusterName, clusterAddress); tablesCache.put(clusterName, table); } return table; } private HTableInterface createHTable(String clusterName, String clusterAddress) throws IOException { Configuration clusterConf = getCrossSiteConf(this.configuration, clusterAddress); HTableInterface table = null; try { table = this.hTableFactory.createHTableInterface(clusterConf, Bytes.toBytes(CrossSiteUtil.getClusterTableName(tableNameAsString, clusterName))); } catch (RuntimeException e) { if (e.getCause() instanceof IOException) { throw (IOException) e.getCause(); } else { throw new IOException(e); } } table.setWriteBufferSize(this.writeBufferSize); table.setAutoFlush(autoFlush, clearBufferOnFail); if (table instanceof HTable && operationTimeout != 1) { ((HTable) table).setOperationTimeout(operationTimeout); } return table; } private HTableInterface getClusterHTable(String clusterName, String clusterAddress) throws IOException { HTableInterface table = tablesCache.get(clusterName); if (table == null) { table = createHTable(clusterName, clusterAddress); tablesCache.put(clusterName, table); } return table; } public void validatePut(final Put put) throws IllegalArgumentException { if (put.isEmpty()) { throw new IllegalArgumentException("No columns to insert"); } if (maxKeyValueSize > 0) { for (List<Cell> list : put.getFamilyCellMap().values()) { for (Cell cell : list) { if (cell.getValueLength() > maxKeyValueSize) { throw new IllegalArgumentException("KeyValue size too large"); } } } } } /** * Finds the available peer. This peer is not cached. * * @param clusterInfos * @param clusterName * @return the available peer, returns null if no available ones. */ private HTableInterface findAvailablePeer(Map<String, ClusterInfo> clusterInfos, String clusterName) { ClusterInfo ci = clusterInfos.get(clusterName); if (ci != null) { List<ClusterInfo> allClusters = new ArrayList<ClusterInfo>(); allClusters.addAll(ci.getPeers()); allClusters.add(ci); for (ClusterInfo peer : allClusters) { try { Configuration clusterConf = getCrossSiteConf(this.configuration, peer.getAddress()); try { HBaseAdmin.checkHBaseAvailable(clusterConf); } catch (Exception e) { continue; } HTableInterface table = null; try { table = this.hTableFactory.createHTableInterface(clusterConf, Bytes .toBytes(CrossSiteUtil.getPeerClusterTableName(tableNameAsString, clusterName, peer.getName()))); } catch (RuntimeException e) { if (e.getCause() instanceof IOException) { throw (IOException) e.getCause(); } else { throw new IOException(e); } } if (operationTimeout != 1) { if (table instanceof HTable) { ((HTable) table).setOperationTimeout(operationTimeout); } } LOG.info("Found a peer " + peer + " for " + clusterName); return table; } catch (Exception e) { LOG.info("Fail to contact the peer " + peer, e); } } } return null; } /** * Gets the map of the <code>ClusterInfo</code>. The key is the cluster name. * * @param clusterNames * @return * @throws KeeperException */ private Map<String, ClusterInfo> getClusterInfos(Set<String> clusterNames) throws KeeperException { Map<String, ClusterInfo> clusterInfos = new TreeMap<String, ClusterInfo>(); for (String clusterName : clusterNames) { String address = znodes.getClusterAddress(clusterName); List<ClusterInfo> peerList = znodes.getPeerClusters(clusterName); Set<ClusterInfo> peers = null; if (peerList != null) { peers = new TreeSet<ClusterInfo>(); peers.addAll(peerList); } ClusterInfo ci = new ClusterInfo(clusterName, address, peers); clusterInfos.put(clusterName, ci); } return clusterInfos; } /** * Gets all the physical clusters by the start/stop rows. * * @param start * @param stop * @return the list of the <code>ClusterInfo</code>, returns an empty one if there're not * clusters between the start/stop rows. * @throws IOException */ private List<ClusterInfo> getClusterInfos(byte[] start, byte[] stop) throws IOException { CachedZookeeperInfo cachedZKInfo = this.cachedZKInfo; // find the clusters. Set<String> clusterNames = cachedZKInfo.clusterNames; ClusterLocator clusterLocator = cachedZKInfo.clusterLocator; // TODO when the Locator is CompositeSubstringClusterLocator or SubstringClusterLocator and // index is 0, we can extract the cluster names? if (clusterLocator instanceof PrefixClusterLocator) { // if the locator is a prefix locator, do the optimization. // find the start row and stop row in the scan, and find the cluster // names. clusterNames = filterClustersForPrefixClusterLocator((PrefixClusterLocator) clusterLocator, clusterNames, start, stop, false); } // clusters indicated in the parameter. List<ClusterInfo> results = new ArrayList<ClusterInfo>(); for (String clusterName : clusterNames) { ClusterInfo ci = cachedZKInfo.clusterInfos.get(clusterName); if (ci != null) { results.add(ci); } } return results; } /** * Gets the list of the pair of the <code>ClusterInfo</code> and start/stop keys. * <ul> * <li>If the clusters are specified, the start/stop key for the clusterA would be changed to * ClusterA + delimiter + start/stop key.</li> * <li>If the clusters are not specified, the start/stop key won't be changed.</li> * </ul> * * @param start * @param stop * @param clusters * @return * @throws IOException */ private List<Pair<ClusterInfo, Pair<byte[], byte[]>>> getClusterInfoStartStopKeyPairs( byte[] start, byte[] stop, String[] clusters) throws IOException { CachedZookeeperInfo cachedZKInfo = this.cachedZKInfo; // find the clusters. Set<String> clusterNames = null; boolean shouldAppendClusterName = false; if (clusters == null || clusters.length == 0) { clusterNames = cachedZKInfo.clusterNames; } else { // Hierarchy should be got in any case clusterNames = getPhysicalClusters(cachedZKInfo, clusters); shouldAppendClusterName = true; } ClusterLocator clusterLocator = cachedZKInfo.clusterLocator; String delimiter = null; // TODO when the Locator is CompositeSubstringClusterLocator or SubstringClusterLocator and // index is 0, we can extract the cluster names? if (clusterLocator instanceof PrefixClusterLocator) { // if the locator is a prefix locator, do the optimization. // find the start row and stop row in the scan, and find the cluster // names. PrefixClusterLocator prefixClusterLocator = (PrefixClusterLocator) clusterLocator; delimiter = prefixClusterLocator.getDelimiter(); if (shouldAppendClusterName) { clusterNames = filterClustersForPrefixClusterLocator(prefixClusterLocator, clusterNames, start, stop, true); } else { clusterNames = filterClustersForPrefixClusterLocator(prefixClusterLocator, clusterNames, start, stop, false); } } else { shouldAppendClusterName = false; } // clusters indicated in the parameter. List<Pair<ClusterInfo, Pair<byte[], byte[]>>> results = new ArrayList<Pair<ClusterInfo, Pair<byte[], byte[]>>>(); for (String clusterName : clusterNames) { ClusterInfo ci = cachedZKInfo.clusterInfos.get(clusterName); if (ci != null) { Pair<byte[], byte[]> keyPair = new Pair<byte[], byte[]>(getStartKey( shouldAppendClusterName, clusterName, delimiter, start), getStopKey( shouldAppendClusterName, clusterName, delimiter, stop)); results.add(new Pair<ClusterInfo, Pair<byte[], byte[]>>(ci, keyPair)); } } return results; } private byte[] getStartKey(boolean shouldAppendClusterName, String clusterName, String delimiter, byte[] startKey) { if (shouldAppendClusterName && startKey != null && !Bytes.equals(startKey, HConstants.EMPTY_START_ROW)) { String sk = Bytes.toString(startKey); return Bytes.toBytes(clusterName + delimiter + sk); } else { return startKey; } } private byte[] getStopKey(boolean shouldAppendClusterName, String clusterName, String delimiter, byte[] stopKey) { if (shouldAppendClusterName && stopKey != null && !Bytes.equals(stopKey, HConstants.EMPTY_END_ROW)) { String sk = Bytes.toString(stopKey); return Bytes.toBytes(clusterName + delimiter + sk); } else { return stopKey; } } /** * Gets all the physical clusters. * * @param cachedZKInfo * @param clusters * @return */ private Set<String> getPhysicalClusters(CachedZookeeperInfo cachedZKInfo, String[] clusters) { Set<String> results = new TreeSet<String>(); if (clusters != null) { for (String cluster : clusters) { results.add(cluster); results.addAll(CrossSiteUtil.getDescendantClusters(cachedZKInfo.hierarchyMap, cluster)); } } for (Iterator<String> ir = results.iterator(); ir.hasNext();) { if (!cachedZKInfo.clusterNames.contains(ir.next())) { ir.remove(); } } return results; } /** * Clears the cached table. * * @param clusterName */ private void clearCachedTable(String clusterName) { try { HTableInterface t = this.tablesCache.get(clusterName); if (t != null) { t.close(); } } catch (IOException e1) { LOG.warn("Fail to close the table of " + clusterName, e1); } tablesCache.remove(clusterName); } /** * Gets all the clusters by the start/stop rows when the cluster locator is PrefixClusterLocator. * * @param prefixClusterLocator * @param clusters * @param start * @param stop * @param skipFilter * @return */ private Set<String> filterClustersForPrefixClusterLocator( PrefixClusterLocator prefixClusterLocator, Set<String> clusters, byte[] start, byte[] stop, boolean skipFilter) { if (skipFilter) { return clusters; } String startCluster = null; String stopCluster = null; try { if (start != null && !Bytes.equals(start, HConstants.EMPTY_START_ROW)) { startCluster = Bytes.toString(start); if (startCluster.indexOf(prefixClusterLocator.getDelimiter()) >= 0) { startCluster = prefixClusterLocator.getClusterName(start); } } } catch (RowNotLocatableException e) { // do nothing } try { if (stop != null && !Bytes.equals(stop, HConstants.EMPTY_END_ROW)) { stopCluster = Bytes.toString(stop); if (stopCluster.indexOf(prefixClusterLocator.getDelimiter()) >= 0) { stopCluster = prefixClusterLocator.getClusterName(stop); } } } catch (RowNotLocatableException e) { // do nothing } // find the start row and stop row in the scan, and find the cluster name if (startCluster == null && stopCluster == null) { return clusters; } else { Set<String> results = new TreeSet<String>(); if (startCluster == null) { for (String cluster : clusters) { if (cluster.compareTo(stopCluster) <= 0) { results.add(cluster); } else { break; } } } else if (stopCluster == null) { boolean skip = true; for (String cluster : clusters) { if (!skip) { results.add(cluster); } else { if (cluster.compareTo(startCluster) >= 0) { results.add(cluster); skip = false; } } } } else { boolean more = false; for (String cluster : clusters) { if (!more) { if (cluster.compareTo(startCluster) >= 0) { more = true; if (cluster.compareTo(stopCluster) <= 0) { results.add(cluster); } else { break; } } } else { if (cluster.compareTo(stopCluster) <= 0) { results.add(cluster); } else { break; } } } } return results; } } /** * A cached zookeeper information. */ private class CachedZookeeperInfo { public HTableDescriptor htd; public ClusterLocator clusterLocator; public Set<String> clusterNames; public Map<String, ClusterInfo> clusterInfos; public Map<String, Set<String>> hierarchyMap; public void clear() { if (clusterNames != null) { clusterNames.clear(); } if (clusterInfos != null) { clusterInfos.clear(); } if (hierarchyMap != null) { hierarchyMap.clear(); } } } }