getOnlineRegions(byte[] tableName) {
@@ -3656,10 +4003,26 @@
}
return tableRegions;
}
+
+ /**
+ * Gets the count of online regions of the table in a region server.
+ * This method looks at the in-memory onlineRegions.
+ * @param regionName
+ * @return int regions count
+ * @throws IOException
+ */
+ public int getRegionsCount(byte[] regionName) throws IOException {
+ return getOnlineRegions(getRegionInfo(regionName).getTableName())
+ .size();
+ }
public SchemaChangeTracker getSchemaChangeTracker() {
return this.schemaChangeTracker;
}
+
+ public RegionServerDeleteRegionTracker getDeleteRegionTracker() {
+ return this.deleteRegionTracker;
+ }
// used by org/apache/hbase/tmpl/regionserver/RSStatusTmpl.jamon (HBASE-4070).
public String[] getCoprocessors() {
Index: src/main/java/org/apache/hadoop/hbase/regionserver/OnlineRegions.java
===================================================================
--- src/main/java/org/apache/hadoop/hbase/regionserver/OnlineRegions.java (revision 1236386)
+++ src/main/java/org/apache/hadoop/hbase/regionserver/OnlineRegions.java (working copy)
@@ -23,6 +23,7 @@
import java.io.IOException;
import java.util.List;
+import org.apache.zookeeper.KeeperException;
/**
* Interface to Map of online regions. In the Map, the key is the region's
@@ -65,5 +66,14 @@
* @param hRegion
*/
public void refreshRegion(HRegion hRegion) throws IOException;
+
+ /**
+ * Delete the specified region
+ *
+ * @param regionName
+ * @throws IOException
+ * @throws KeeperException
+ */
+ public void deleteRegion(String regionName) throws IOException, KeeperException;
}
Index: src/main/java/org/apache/hadoop/hbase/zookeeper/MasterDeleteRegionTracker.java
===================================================================
--- src/main/java/org/apache/hadoop/hbase/zookeeper/MasterDeleteRegionTracker.java (revision 0)
+++ src/main/java/org/apache/hadoop/hbase/zookeeper/MasterDeleteRegionTracker.java (revision 0)
@@ -0,0 +1,502 @@
+/**
+ * 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.zookeeper;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.hbase.Abortable;
+import org.apache.hadoop.hbase.ServerName;
+import org.apache.hadoop.hbase.master.MasterServices;
+import org.apache.hadoop.hbase.monitoring.MonitoredTask;
+import org.apache.hadoop.hbase.monitoring.TaskMonitor;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.hadoop.io.VersionedWritable;
+import org.apache.hadoop.hbase.util.Writables;
+import org.apache.hadoop.io.Writable;
+import org.apache.zookeeper.KeeperException;
+
+/**
+ * Tracks the list of regions to be deleted via ZK.
+ *
+ * This class is responsible for watching for changes to the
+ * region name list. It handles additions/deletions in the region name
+ * list and watches each node.
+ */
+public class MasterDeleteRegionTracker extends ZooKeeperNodeTracker {
+
+ public static final Log LOG =
+ LogFactory.getLog(MasterDeleteRegionTracker.class);
+ private final MasterServices masterServices;
+ // Used by tests only. Do not change this.
+ private volatile int sleepTimeMillis = 0;
+ // delete-region request pending more than this time will be timed out.
+ private long deleteRegionTimeoutMillis = 30000;
+
+ private Map monitoredTaskMap = null;
+
+ /**
+ * Constructs a new ZK node tracker.
+ *
+ * After construction, use {@link #start} to kick off tracking.
+ *
+ * @param watcher
+ * @param abortable
+ * @param masterServices
+ * @param deleteRegionTimeoutMillis
+ */
+ public MasterDeleteRegionTracker(ZooKeeperWatcher watcher,
+ Abortable abortable, MasterServices masterServices,
+ long deleteRegionTimeoutMillis) {
+ super(watcher, watcher.deleteRegionZNode, abortable);
+ this.masterServices = masterServices;
+ this.deleteRegionTimeoutMillis = deleteRegionTimeoutMillis;
+ this.monitoredTaskMap = new HashMap();
+ }
+
+ @Override
+ public void start() {
+ try {
+ watcher.registerListener(this);
+ List encodedRegionNames =
+ ZKUtil.listChildrenNoWatch(watcher, watcher.deleteRegionZNode);
+ processDeletedRegions(encodedRegionNames);
+ } catch (final KeeperException e) {
+ LOG.error("MasterDeleteRegionTracker startup failed.", e);
+ abortable.abort("MasterDeleteRegionTracker startup failed", e);
+ }
+ }
+
+ private List getCurrentRegions() throws KeeperException {
+ return
+ ZKUtil.listChildrenNoWatch(watcher, watcher.deleteRegionZNode);
+ }
+
+ /**
+ * When a primary master crashes and the secondary master takes over
+ * mid-flight during an delete-region process, the secondary should cleanup
+ * any completed delete-request not handled by the previous master.
+ * @param list of encodedRegionNames
+ * @throws KeeperException
+ */
+ private void processDeletedRegions(List encodedRegionNames)
+ throws KeeperException {
+ if (encodedRegionNames == null || encodedRegionNames.isEmpty()) {
+ String msg = "No current delete region(s) in progress. Skipping cleanup";
+ LOG.debug(msg);
+ return;
+ }
+ String msg = "Master seeing following regions undergoing delete " +
+ "process. Regions = " + encodedRegionNames;
+ setMonitoredTaskStatus("master", msg, false, false);
+ LOG.debug(msg);
+ for (String region : encodedRegionNames) {
+ LOG.debug("Processing region = "+ region);
+ setMonitoredTaskStatus(region, "Processing region = "+ region,
+ false, false);
+ try {
+ processRegionNode(region);
+ } catch (IOException e) {
+ String errmsg = "IOException while processing completed delete-region."
+ + " Cause = " + e.getCause();
+ LOG.error(errmsg, e);
+ setMonitoredTaskStatus(region, errmsg, false, false);
+ }
+ }
+ }
+
+ /**
+ * Get current delete-region request for a region.
+ * @param regionName
+ * @return DeleteRegionStatus
+ * @throws KeeperException
+ * @throws IOException
+ */
+ public RegionDeletionStatus getDeleteRegionStatus(String regionName)
+ throws KeeperException, IOException {
+ String path = getDeleteRegionNodePath(regionName);
+ byte[] state = ZKUtil.getData(watcher, path);
+ if (state == null || state.length <= 0) {
+ return null;
+ }
+ RegionDeletionStatus drs = new RegionDeletionStatus(regionName);
+ Writables.getWritable(state, drs);
+ return drs;
+ }
+
+
+ /**
+ * If delete-region request is handled for this region,
+ * then delete the ZK node created for this region.
+ * @param regionName
+ * @throws KeeperException
+ */
+ private void processRegionNode(final String regionName) throws KeeperException,
+ IOException {
+ LOG.debug("processRegionNode. RegionName = " + regionName);
+ RegionDeletionStatus drs = getDeleteRegionStatus(regionName);
+ if (drs == null) {
+ LOG.debug("DeleteRegionStatus is NULL. region = " + regionName);
+ return;
+ }
+ processDeleteRegionStatus(drs, regionName);
+ }
+
+ private void setMonitoredTaskStatus(String regionName, String msg,
+ boolean completed, boolean aborted) {
+ if (monitoredTaskMap.get(regionName) == null) {
+ monitoredTaskMap.put(regionName, TaskMonitor.get().createStatus(msg));
+ } else {
+ monitoredTaskMap.get(regionName).setStatus(msg);
+ }
+ if (completed) {
+ monitoredTaskMap.get(regionName).markComplete(msg);
+ } else if (aborted) {
+ monitoredTaskMap.get(regionName).abort(msg);
+ }
+ if (completed || aborted) {
+ monitoredTaskMap.remove(regionName);
+ }
+ }
+
+ /**
+ * Evaluate the master delete-region status and determine the current status.
+ * @param RegionDeletionStatus
+ * @param regionName
+ * @param servers
+ */
+ private void processDeleteRegionStatus(RegionDeletionStatus deleteStatus,
+ String regionName) throws KeeperException {
+
+ if (deleteStatus.getRegionDeletionStatus()
+ == RegionDeletionStatus.RegionDeletionState.SUCCESS ){
+ // delete region completed.
+ String msg = "Region server has successfully processed the " +
+ "delete-region request for region = " + regionName;
+ setMonitoredTaskStatus(regionName, msg, true, false);
+ LOG.debug(msg);
+ cleanupDeleteRegionNode(getDeleteRegionNodePath(regionName));
+ } else {
+ if (deleteStatus.getErrorCause() != null
+ && deleteStatus.getErrorCause().trim().length() > 0) {
+ String msg = "Delete-region request failed "
+ + "for region = " + regionName
+ + ", Error Cause = " + deleteStatus.getErrorCause();
+ setMonitoredTaskStatus(regionName, msg, false, true);
+ LOG.debug(msg);
+ } else {
+ String msg = "Delete-region request is in process "
+ + deleteStatus.getRegionDeletionStatus();
+ LOG.debug(msg);
+ }
+ }
+ }
+
+ /**
+ * Check whether a in-flight delete-region request has expired.
+ * @param regionName
+ * @return true if the delete-region request expired.
+ * @throws IOException
+ */
+ private boolean hasDeleteRegionExpired(String regionName)
+ throws IOException, KeeperException {
+ RegionDeletionStatus drs = getDeleteRegionStatus(regionName);
+ long createdTimeStamp = drs.getTimestamp();
+ long duration = System.currentTimeMillis() - createdTimeStamp;
+ LOG.debug("Created TimeStamp = " + createdTimeStamp
+ + " duration = " + duration + " Region = " + regionName
+ + " Master Delete Region Status = " + drs);
+ return (duration > deleteRegionTimeoutMillis);
+ }
+
+ /**
+ * Check whether there are any delete-region requests that are in progress now.
+ * We simply assume that a delete-region is in progress if we see
+ * a ZK delete-region for any region.
+ * @return true if it is in process
+ */
+ public boolean isDeleteRegionInProgress() {
+ try {
+ int deleteRegionCount =
+ ZKUtil.getNumberOfChildren(this.watcher, watcher.deleteRegionZNode);
+ return deleteRegionCount > 0;
+ } catch (final KeeperException ke) {
+ LOG.debug("KeeperException while getting current delete region progress.", ke);
+ }
+ return false;
+ }
+
+ /**
+ * Handle failed and expired delete-region requests. We simply delete all the
+ * expired/failed delete-region attempts. Why we should do this ?
+ * 1) Keeping the failed/expired delete-region nodes longer prohibits any
+ * future delete-region for the regions.
+ * 2) Any lingering expired/failed delete-region requests will prohibit the
+ * load balancer from running.
+ */
+ public void handleFailedOrExpiredDeleteRegionRequests() {
+ try {
+ List regionsList = getCurrentRegions();
+ for (String namedRegion : regionsList) {
+ String statmsg = "Cleaning failed or expired delete-region requests. " +
+ "Current regions undergoing " +
+ "delete-region process = " + regionsList;
+ setMonitoredTaskStatus(namedRegion, statmsg, false, false);
+ LOG.debug(statmsg);
+ if (hasDeleteRegionExpired(namedRegion)) {
+ // time out.. currently, we abandon the in-flight delete-region due to
+ // time out.
+ String msg = "Delete-region for region = " + namedRegion + " has expired."
+ + " Delete-region for this region has been in progress for " +
+ + deleteRegionTimeoutMillis +
+ ". Deleting the node now.";
+ LOG.debug(msg);
+ ZKUtil.deleteNode(this.watcher,
+ getDeleteRegionNodePath(namedRegion));
+ } else {
+ String msg = "Delete-region request is in progress for " +
+ "region = " + namedRegion;
+ LOG.debug(msg);
+ setMonitoredTaskStatus(namedRegion, msg, false, false);
+ }
+ }
+ } catch (final IOException e) {
+ String msg = "IOException during handleFailedOrExpiredDeleteRegionRequests. "
+ + e.getCause();
+ LOG.error(msg, e);
+ setMonitoredTaskStatus("master", msg, false, false);
+ } catch (final KeeperException ke) {
+ String msg = "KeeperException during handleFailedOrExpiredDeleteRegionRequests. "
+ + ke.getCause();
+ LOG.error(msg, ke);
+ setMonitoredTaskStatus("master", msg, false, false);
+ }
+ }
+
+ /**
+ * Clean the nodes of completed delete-region.
+ * @param path
+ * @throws KeeperException
+ */
+ private void cleanupDeleteRegionNode(String path)
+ throws KeeperException {
+ ZKUtil.deleteNode(this.watcher, path);
+ LOG.debug("Deleted a node for path " + path);
+ }
+
+ private int getZKNodeVersion(String nodePath) throws KeeperException {
+ return ZKUtil.checkExists(this.watcher, nodePath);
+ }
+
+ /**
+ * Create a new delete-region ZK node.
+ * @param regionName region name that is getting deleted
+ * @throws KeeperException
+ */
+ public void createDeleteRegionNode(String regionName)
+ throws KeeperException, IOException {
+ String msg = "Creating delete-region node for region = " + regionName;
+ setMonitoredTaskStatus(regionName, msg, false, false);
+ LOG.debug(msg + ". Path = "
+ + getDeleteRegionNodePath(regionName));
+ if (doesDeleteRegionNodeExist(regionName)) {
+ LOG.debug("Delete-region node already exists for region = " + regionName
+ + " Deleting the delete-region node.");
+ while(doesDeleteRegionNodeExist(regionName)) {
+ try {
+ Thread.sleep(50);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+ RegionDeletionStatus drs = new RegionDeletionStatus(regionName);
+ LOG.debug("Master creating the master delete-region status = " + drs);
+ ZKUtil.createSetData(this.watcher,
+ getDeleteRegionNodePath(regionName), Writables.getBytes(drs));
+ setMonitoredTaskStatus(regionName, "Created the ZK node for delete region."
+ + " Current delete region status = " + drs.toString(), true, false);
+ ZKUtil.watchAndCheckExists(this.watcher,
+ getDeleteRegionNodePath(regionName));
+ }
+
+ /**
+ * Check whether encodedRegionName node exist under
+ * /delete-region
+ * @param regionName
+ * @throws KeeperException
+ */
+ public boolean doesDeleteRegionNodeExist(String regionName)
+ throws KeeperException {
+ return ZKUtil.checkExists(watcher,
+ getDeleteRegionNodePath(regionName)) != -1;
+ }
+
+ /**
+ * We get notified as and when the RS cloud updates their ZK nodes with
+ * progress information. The path will be of the format
+ * /delete-region/
+ * @param path
+ */
+ @Override
+ public void nodeDataChanged(String path) {
+ String regionName = null;
+ if (path.startsWith(watcher.deleteRegionZNode) &&
+ !path.equals(watcher.deleteRegionZNode)) {
+ try {
+ LOG.debug("NodeDataChanged Path = " + path);
+ String[] paths = path.split("/");
+ regionName = paths[3];
+ processRegionNode(regionName);
+ } catch (final KeeperException e) {
+ setMonitoredTaskStatus(regionName,
+ "MasterDeleteRegionTracker: ZK exception while processing "
+ + " nodeDataChanged() event for region = " + regionName
+ + ". Cause = " + e.getCause(), false, false);
+ LOG.error("MasterDeleteRegionTracker: Unexpected zk exception getting"
+ + " delete-region change nodes", e);
+ } catch(final IOException ioe) {
+ setMonitoredTaskStatus(regionName,
+ "MasterDeleteRegionTracker: IOException while processing "
+ + "nodeDataChanged() event for region = " + regionName
+ + ". Cause = " + ioe.getCause(), false, false);
+ LOG.error("MasterDeleteRegionTracker: Unexpected IOException getting"
+ + " delete-region change nodes", ioe);
+ }
+ }
+ }
+
+ public String getDeleteRegionNodePath(String regionName) {
+ return ZKUtil.joinZNode(watcher.deleteRegionZNode, regionName);
+ }
+
+ public void setSleepTimeMillis(int sleepTimeMillis) {
+ this.sleepTimeMillis = sleepTimeMillis;
+ }
+
+ /**
+ * Holds the current state for a region to be deleted. DeleteRegionState
+ * includes the current delete status (INPROCESS, FAILURE or SUCCESS),
+ * timestamp of delete request.
+ *
+ * Master keeps track of delete regions requests using the delete-region status
+ * and periodically updates the region-delete status based on RS processing.
+ */
+ public static class RegionDeletionStatus extends VersionedWritable
+ implements Writable {
+
+ public enum RegionDeletionState {
+ INPROCESS, // delete-region request is in process
+ SUCCESS, // completed delete-region request
+ DEST_REGION_REMOVED_FROM_META, // current region is removed from META
+ DEST_REGION_REMOVE_FROM_META_FAILED,// failed to remove destination region from .META.
+ ADJ_REGION_REMOVED_FROM_META, // adjacent region is removed from META
+ ADJ_REGION_REMOVE_FROM_META_FAILED, // adjacent region remove request from META is failed
+ NEW_REGION_ADDED_IN_META, // new region is added in META
+ NEW_REGION_ADD_IN_META_FAILED, // new region add request is failed in META
+ DEST_REGION_DELETION_FROM_FS, // dest region is deleted from file-system
+ DEST_REGION_DELETION_FROM_FS_FAILED, // dest region delete request from file-system is failed
+ ADJ_REGION_DELETION_FROM_FS, // adjacent region is deleted from file-system
+ ADJ_REGION_DELETION_FROM_FS_FAILED, // adjacent region delete request from file-system is failed
+ FAILURE // failed request, master will do clean-up after the request
+ // exceeds the deleteRegionTimeoutMillis value
+ }
+
+ private static final byte VERSION = 0;
+ private RegionDeletionState regionDeletionStatus;
+ private long timestamp;
+ private String errorCause = "";
+ private String regionName = "";
+
+ public RegionDeletionStatus(String regionName) {
+ this.timestamp = System.currentTimeMillis();
+ this.regionDeletionStatus = RegionDeletionState.INPROCESS;
+ this.regionName = regionName;
+ }
+
+ public RegionDeletionState getRegionDeletionStatus() {
+ return regionDeletionStatus;
+ }
+
+ public void setRegionDeletionStatus(RegionDeletionState deleteRegionState) {
+ this.regionDeletionStatus = deleteRegionState;
+ }
+
+ public long getTimestamp() {
+ return timestamp;
+ }
+
+ public void setTimestamp(long stamp) {
+ this.timestamp = stamp;
+ }
+
+ public String getErrorCause() {
+ return errorCause;
+ }
+
+ public void setErrorCause(String errorCause) {
+ if (errorCause == null) {
+ return;
+ }
+ this.errorCause = errorCause;
+ }
+
+ /** @return the object version number */
+ @Override
+ public byte getVersion(){
+ return VERSION;
+ }
+
+ @Override
+ public void readFields(DataInput in) throws IOException {
+ byte version = in.readByte();
+ if (version > VERSION) {
+ throw new IOException("Version mismatch; " + version);
+ }
+ regionDeletionStatus = RegionDeletionState.valueOf(in.readUTF());
+ timestamp = in.readLong();
+ regionName = Bytes.toString(Bytes.readByteArray(in));
+ errorCause = Bytes.toString(Bytes.readByteArray(in));
+ }
+
+ @Override
+ public void write(DataOutput out) throws IOException {
+ super.write(out);
+ out.writeUTF(regionDeletionStatus.name());
+ out.writeLong(timestamp);
+ Bytes.writeByteArray(out, Bytes.toBytes(regionName));
+ Bytes.writeByteArray(out, Bytes.toBytes(errorCause));
+ }
+
+ @Override
+ public String toString() {
+ return
+ "regionName= " + regionName
+ + ", status= " + regionDeletionStatus
+ + ", ts= " + timestamp
+ + ", errorCause = " + errorCause;
+ }
+ }
+}
Index: src/main/java/org/apache/hadoop/hbase/zookeeper/RegionServerDeleteRegionTracker.java
===================================================================
--- src/main/java/org/apache/hadoop/hbase/zookeeper/RegionServerDeleteRegionTracker.java (revision 0)
+++ src/main/java/org/apache/hadoop/hbase/zookeeper/RegionServerDeleteRegionTracker.java (revision 0)
@@ -0,0 +1,402 @@
+/**
+ * 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.zookeeper;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.hbase.Abortable;
+import org.apache.hadoop.hbase.monitoring.MonitoredTask;
+import org.apache.hadoop.hbase.monitoring.TaskMonitor;
+import org.apache.hadoop.hbase.regionserver.HRegion;
+import org.apache.hadoop.hbase.regionserver.RegionServerServices;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.hadoop.io.VersionedWritable;
+import org.apache.hadoop.hbase.util.Writables;
+import org.apache.zookeeper.KeeperException;
+
+import org.apache.hadoop.io.Writable;
+
+import java.io.*;
+import java.util.List;
+
+/**
+ * Region server delete-region tracker. RS uses this tracker to keep track of
+ * delete-region requests from master and updates the status once
+ * the delete-region is complete.
+ */
+public class RegionServerDeleteRegionTracker extends ZooKeeperNodeTracker {
+
+ public static final Log LOG =
+ LogFactory.getLog(RegionServerDeleteRegionTracker.class);
+ private RegionServerServices regionServer = null;
+ private volatile int sleepTimeMillis = 0;
+
+ /**
+ * Constructs a new ZK node tracker.
+ *
+ * After construction, use {@link #start} to kick off tracking.
+ *
+ * @param watcher
+ * @param node
+ * @param abortable
+ */
+ public RegionServerDeleteRegionTracker(ZooKeeperWatcher watcher,
+ Abortable abortable,
+ RegionServerServices regionServer) {
+ super(watcher, watcher.deleteRegionZNode, abortable);
+ this.regionServer = regionServer;
+ }
+
+ @Override
+ public void start() {
+ try {
+ watcher.registerListener(this);
+ ZKUtil.listChildrenAndWatchThem(watcher, node);
+ } catch (final KeeperException e) {
+ LOG.error("RegionServerDeleteRegionTracker startup failed with " +
+ "KeeperException.", e);
+ }
+ }
+
+ /**
+ * Check whether there are any delete-region requests that are in progress now
+ * for the given region. We simply assume that a delete-region is in progress
+ * if we see a ZK delete-region node.
+ * @return true if it is progress otherwise false
+ */
+ public boolean isDeleteRegionInProgress(String regionName) {
+ try {
+ List regionList = ZKUtil.listChildrenAndWatchThem(this.watcher,
+ watcher.deleteRegionZNode);
+ if (regionList != null) {
+ for (String region : regionList) {
+ if (region.equals(regionName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ } catch (final KeeperException ke) {
+ LOG.debug("isDeleteRegionInProgress. " +
+ "KeeperException while getting current delete-region progress.");
+ return false;
+ }
+ return false;
+ }
+
+
+ /**
+ * This event will be triggered whenever new delete-region request
+ * is processed by the master. The path will be of the format
+ * /delete-region/region-name
+ * @param path full path of the node whose children have changed
+ */
+ @Override
+ public void nodeChildrenChanged(String path) {
+ LOG.debug("NodeChildrenChanged. Path = " + path);
+ if (path.equals(watcher.deleteRegionZNode)) {
+ try {
+ List regionsList =
+ ZKUtil.listChildrenAndWatchThem(watcher, watcher.deleteRegionZNode);
+ LOG.debug("RegionServerDeleteRegionTracker: " +
+ "Current list of regions with delete-region request= " + regionsList);
+ if (regionsList != null && regionsList.size() > 0) {
+ handleDeleteRegion(regionsList);
+ } else {
+ LOG.error("No region found for delete-region event." +
+ " Skipping delete-region");
+ }
+ } catch (final KeeperException ke) {
+ String errmsg = "KeeperException while handling nodeChildrenChanged for path = "
+ + path + " Cause = " + ke.getCause();
+ LOG.error(errmsg, ke);
+ TaskMonitor.get().createStatus(errmsg);
+ }
+ }
+ }
+
+ private void handleDeleteRegion(List regionsList) {
+ for (String regionName : regionsList) {
+ if (regionName != null) {
+ LOG.debug("Processing delete-region with status for region = "
+ + regionName);
+ handleDeleteRegion(regionName);
+ }
+ }
+ }
+
+ private void reportAndLogDeleteRegionError(String regionName,
+ Throwable exception, MonitoredTask status) {
+ try {
+ String errmsg = "Region Server "
+ + regionServer.getServerName().getServerName()
+ + " failed during delete-region process. Cause = "
+ + exception.getCause();
+ RegionDeletionStatus drs = getDeleteRegionStatus(regionName);
+ drs.update(RegionDeletionStatus.RegionDeletionState.FAILURE, errmsg);
+ String nodePath = getDeleteRegionNodePath(regionName);
+ ZKUtil
+ .updateExistingNodeData(this.watcher, nodePath,
+ Writables.getBytes(drs), getZKNodeVersion(nodePath));
+ LOG.debug("reportAndLogDeleteRegionError() "
+ + " Updated child ZKNode with DeleteRegionStatus = "
+ + drs + " for region = " + regionName);
+ if (status == null) {
+ status = TaskMonitor.get().createStatus(errmsg);
+ } else {
+ status.setStatus(errmsg);
+ }
+ } catch (final KeeperException e) {
+ String errmsg = "KeeperException while updating the delete-region node with "
+ + "error status for region=" + regionName + ", server="
+ + regionServer.getServerName().getServerName()
+ + ", Cause=" + e.getCause();
+ LOG.error(errmsg, e);
+ TaskMonitor.get().createStatus(errmsg);
+ } catch (final IOException ioe) {
+ String errmsg = "IOException while updating the delete-region node with "
+ + "server name for region=" + regionName + ", server="
+ + regionServer.getServerName().getServerName()
+ + ", Cause=" + ioe.getCause();
+ TaskMonitor.get().createStatus(errmsg);
+ LOG.error(errmsg, ioe);
+ }
+ }
+
+ private void handleDeleteRegion(final String regionName) {
+ MonitoredTask status = null;
+ try {
+ if (regionServer.getFromOnlineRegions(regionName) == null) {
+ LOG.debug("Region " + regionName + " does not exist");
+ return;
+ }
+ status = TaskMonitor.get().createStatus("Region server "
+ + regionServer.getServerName().getServerName()
+ + " handling delete region for region = " + regionName);
+ regionServer.deleteRegion(regionName);
+ RegionDeletionStatus drs = getDeleteRegionStatus(regionName);
+ drs.update(RegionDeletionStatus.RegionDeletionState.SUCCESS);
+ updateDeleteRegionStatus(regionName, drs);
+ String msg = "Delete-region request completed for region = "
+ + regionName + " on server = "
+ + regionServer.getServerName().getServerName();
+ LOG.info(msg);
+ status.setStatus(msg);
+ } catch (final IOException ioe) {
+ reportAndLogDeleteRegionError(regionName, ioe, status);
+ } catch (final KeeperException ke) {
+ reportAndLogDeleteRegionError(regionName, ke, status);
+ }
+ }
+
+ private int getZKNodeVersion(String nodePath) throws KeeperException {
+ return ZKUtil.checkExists(this.watcher, nodePath);
+ }
+
+ /**
+ * Get the data from /delete-region/
+ * znode and build RegionDeletionStatus object
+ * @param regionName
+ * @return RegionDeletionStatus
+ * @throws KeeperException
+ * @throws IOException
+ */
+ public RegionDeletionStatus getDeleteRegionStatus(String regionName)
+ throws KeeperException, IOException {
+ byte[] statusBytes = ZKUtil.getData(this.watcher,
+ getDeleteRegionNodePath(regionName.trim()));
+ if (statusBytes == null || statusBytes.length <= 0) {
+ LOG.info("Could not find the znode for region " + regionName);
+ return null;
+ }
+ RegionDeletionStatus drs = new RegionDeletionStatus();
+ Writables.getWritable(statusBytes, drs);
+ return drs;
+ }
+
+ public void updateDeleteRegionStatus(String regionName,
+ RegionDeletionStatus deleteRegionStatus)
+ throws KeeperException, IOException {
+ try {
+ if(sleepTimeMillis > 0) {
+ try {
+ LOG.debug("RegionServerDeleteRegionTracker sleeping for "
+ + sleepTimeMillis);
+ Thread.sleep(sleepTimeMillis);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ ZKUtil.updateExistingNodeData(this.watcher,
+ getDeleteRegionNodePath(regionName),
+ Writables.getBytes(deleteRegionStatus), -1);
+ String msg = "RegionServerDeleteRegionTracker completed for region="
+ + regionName + ", status=" + deleteRegionStatus;
+ LOG.debug(msg);
+ TaskMonitor.get().createStatus(msg);
+ } catch (final KeeperException.NoNodeException e) {
+ String errmsg = "KeeperException.NoNodeException while updating the"
+ + " delete-region node with server name for region=" + regionName
+ + ", server=" + regionServer.getServerName().getServerName()
+ + ", Cause=" + e.getCause();
+ TaskMonitor.get().createStatus(errmsg);
+ LOG.error(errmsg, e);
+ } catch (final KeeperException e) {
+ String errmsg = "KeeperException while updating the delete-region node"
+ + " with server name for region=" + regionName + ", server="
+ + regionServer.getServerName().getServerName()
+ + ", Cause=" + e.getCause();
+ LOG.error(errmsg, e);
+ TaskMonitor.get().createStatus(errmsg);
+ } catch (final IOException ioe) {
+ String errmsg = "IOException while updating the delete-region node with "
+ + "server name for region=" + regionName + ", server="
+ + regionServer.getServerName().getServerName()
+ + ", Cause=" + ioe.getCause();
+ LOG.error(errmsg, ioe);
+ TaskMonitor.get().createStatus(errmsg);
+ }
+ }
+
+ private String getDeleteRegionNodePath(String regionName) {
+ return ZKUtil.joinZNode(watcher.deleteRegionZNode, regionName);
+ }
+
+ public int getSleepTimeMillis() {
+ return sleepTimeMillis;
+ }
+
+ /**
+ * Set a sleep time in millis before this RS can update it's progress status.
+ * Used only for test cases to test complex test scenarios such as RS failures
+ * and RS exception handling.
+ * @param sleepTimeMillis
+ */
+ public void setSleepTimeMillis(int sleepTimeMillis) {
+ if (sleepTimeMillis > 0) {
+ this.sleepTimeMillis = sleepTimeMillis;
+ }
+ }
+
+
+ /**
+ * Holds the current delete-region state for a region. Delete region state
+ * includes the current delete state, timestamp of delete request,
+ * and an errorCause in case if the RS failed during the delete-region process.
+ */
+ public static class RegionDeletionStatus extends VersionedWritable
+ implements Writable {
+
+ public enum RegionDeletionState {
+ INPROCESS, // delete-region request is in process
+ SUCCESS, // completed delete-region request
+ DEST_REGION_REMOVED_FROM_META, // current region is removed from META
+ DEST_REGION_REMOVE_FROM_META_FAILED,// failed to remove destination region from .META.
+ ADJ_REGION_REMOVED_FROM_META, // adjacent region is removed from META
+ ADJ_REGION_REMOVE_FROM_META_FAILED, // adjacent region remove request from META is failed
+ NEW_REGION_ADDED_IN_META, // new region is added in META
+ NEW_REGION_ADD_IN_META_FAILED, // new region add request is failed in META
+ DEST_REGION_DELETION_FROM_FS, // dest region is deleted from file-system
+ DEST_REGION_DELETION_FROM_FS_FAILED, // dest region delete request from file-system is failed
+ ADJ_REGION_DELETION_FROM_FS, // adjacent region is deleted from file-system
+ ADJ_REGION_DELETION_FROM_FS_FAILED, // adjacent region delete request from file-system is failed
+ FAILURE // failed request, master will do clean-up after the request
+ // exceeds the deleteRegionTimeoutMillis value
+ }
+
+ private static final byte VERSION = 0;
+ private RegionDeletionState regionDeletionStatus;
+ private long timestamp;
+ private String errorCause = "";
+ private String regionName= "";
+
+ public RegionDeletionStatus() {
+ }
+
+ public RegionDeletionStatus(String regionName) {
+ this.timestamp = System.currentTimeMillis();
+ this.regionDeletionStatus = RegionDeletionState.INPROCESS;
+ this.regionName = regionName;
+ }
+
+ public RegionDeletionState getRegionDeletionStatus() {
+ return regionDeletionStatus;
+ }
+
+ public void setRegionDeletionStatus(RegionDeletionState drs) {
+ this.regionDeletionStatus = drs;
+ }
+
+ public String getErrorCause() {
+ return errorCause;
+ }
+
+ public void setErrorCause(String errorCause) {
+ if (errorCause == null) {
+ return;
+ }
+ this.errorCause = errorCause;
+ }
+
+ public void update(RegionDeletionState state, String errorCause) {
+ this.regionDeletionStatus = state;
+ this.errorCause = errorCause;
+ }
+
+ public void update(RegionDeletionState status) {
+ this.regionDeletionStatus = status;
+ }
+
+ /** @return the object version number */
+ @Override
+ public byte getVersion(){
+ return VERSION;
+ }
+
+ @Override
+ public void readFields(DataInput in) throws IOException {
+ byte version = in.readByte();
+ if (version > VERSION) {
+ throw new IOException("Version mismatch; " + version);
+ }
+ regionDeletionStatus = RegionDeletionState.valueOf(in.readUTF());
+ timestamp = in.readLong();
+ regionName = Bytes.toString(Bytes.readByteArray(in));
+ errorCause = Bytes.toString(Bytes.readByteArray(in));
+ }
+
+ @Override
+ public void write(DataOutput out) throws IOException {
+ super.write(out);
+ out.writeUTF(regionDeletionStatus.name());
+ out.writeLong(timestamp);
+ Bytes.writeByteArray(out, Bytes.toBytes(regionName));
+ Bytes.writeByteArray(out, Bytes.toBytes(errorCause));
+ }
+
+ @Override
+ public String toString() {
+ return
+ " regionName= " + regionName
+ + " status= " + regionDeletionStatus
+ + ", ts= " + timestamp
+ + ", errorCause = " + errorCause;
+ }
+ }
+}
Index: src/main/java/org/apache/hadoop/hbase/zookeeper/ZooKeeperWatcher.java
===================================================================
--- src/main/java/org/apache/hadoop/hbase/zookeeper/ZooKeeperWatcher.java (revision 1241350)
+++ src/main/java/org/apache/hadoop/hbase/zookeeper/ZooKeeperWatcher.java (working copy)
@@ -102,6 +102,8 @@
public String splitLogZNode;
// znode used to record table schema changes
public String schemaZNode;
+ //znode used to record delete region
+ public String deleteRegionZNode;
// Certain ZooKeeper nodes need to be world-readable
public static final ArrayList CREATOR_ALL_AND_WORLD_READABLE =
@@ -165,6 +167,7 @@
ZKUtil.createAndFailSilent(this, tableZNode);
ZKUtil.createAndFailSilent(this, splitLogZNode);
ZKUtil.createAndFailSilent(this, schemaZNode);
+ ZKUtil.createAndFailSilent(this, deleteRegionZNode);
} catch (KeeperException e) {
throw new ZooKeeperConnectionException(
prefix("Unexpected KeeperException creating base node"), e);
@@ -216,6 +219,8 @@
conf.get("zookeeper.znode.splitlog", HConstants.SPLIT_LOGDIR_NAME));
schemaZNode = ZKUtil.joinZNode(baseZNode,
conf.get("zookeeper.znode.schema", "schema"));
+ deleteRegionZNode = ZKUtil.joinZNode(baseZNode,
+ conf.get("zookeeper.znode.deleteRegion", "delete-region"));
}
/**
Index: src/main/ruby/hbase/admin.rb
===================================================================
--- src/main/ruby/hbase/admin.rb (revision 1236386)
+++ src/main/ruby/hbase/admin.rb (working copy)
@@ -62,6 +62,12 @@
end
#----------------------------------------------------------------------------------------------
+ # Requests a region delete
+ def delete_region(region_name)
+ @admin.deleteRegion(region_name)
+ end
+
+ #----------------------------------------------------------------------------------------------
# Requests a regionserver's HLog roll
def hlog_roll(server_name)
@admin.rollHLogWriter(server_name)
Index: src/main/ruby/shell.rb
===================================================================
--- src/main/ruby/shell.rb (revision 1236386)
+++ src/main/ruby/shell.rb (working copy)
@@ -270,6 +270,7 @@
unassign
zk_dump
hlog_roll
+ delete_region
]
)
Index: src/main/ruby/shell/commands/delete_region.rb
===================================================================
--- src/main/ruby/shell/commands/delete_region.rb (revision 0)
+++ src/main/ruby/shell/commands/delete_region.rb (revision 0)
@@ -0,0 +1,37 @@
+#
+# Copyright 2010 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.
+#
+
+module Shell
+ module Commands
+ class Compact < Command
+ def help
+ return <<-EOF
+Delete the named region
+EOF
+ end
+
+ def command(region_name)
+ format_simple_command do
+ admin.delete_region(region_name)
+ end
+ end
+ end
+ end
+end
Index: src/test/java/org/apache/hadoop/hbase/HBaseTestingUtility.java
===================================================================
--- src/test/java/org/apache/hadoop/hbase/HBaseTestingUtility.java (revision 1236386)
+++ src/test/java/org/apache/hadoop/hbase/HBaseTestingUtility.java (working copy)
@@ -1245,12 +1245,39 @@
return null;
}
LOG.debug("Found " + metaRows.size() + " rows for table " +
- Bytes.toString(tableName));
- byte [] firstrow = metaRows.get(0);
- LOG.debug("FirstRow=" + Bytes.toString(firstrow));
- int index = hbaseCluster.getServerWith(firstrow);
+ Bytes.toString(tableName));
+ return getRSForRegionInTable(metaRows, 0);
+ }
+
+ public HRegionServer getRSForSecondRegionInTable(byte[] tableName)
+ throws IOException {
+ List metaRows = getMetaTableRows(tableName);
+ if (metaRows == null || metaRows.size() < 2) {
+ return null;
+ }
+ LOG.debug("Found " + metaRows.size() + " rows for table " +
+ Bytes.toString(tableName));
+ return getRSForRegionInTable(metaRows, 1);
+ }
+
+ public HRegionServer getRSForLastRegionInTable(byte[] tableName)
+ throws IOException {
+ List metaRows = getMetaTableRows(tableName);
+ if (metaRows == null || metaRows.size() < 2) {
+ return null;
+ }
+ LOG.debug("Found " + metaRows.size() + " rows for table " +
+ Bytes.toString(tableName));
+ return getRSForRegionInTable(metaRows, metaRows.size()-1);
+ }
+
+ public HRegionServer getRSForRegionInTable(List metaRows, int regionIndex){
+ byte [] row = metaRows.get(regionIndex);
+ int index = hbaseCluster.getServerWith(row);
return hbaseCluster.getRegionServerThreads().get(index).getRegionServer();
}
+
+
/**
* Starts a MiniMRCluster with a default number of
Index: src/test/java/org/apache/hadoop/hbase/client/TestDeleteRegion.java
===================================================================
--- src/test/java/org/apache/hadoop/hbase/client/TestDeleteRegion.java (revision 0)
+++ src/test/java/org/apache/hadoop/hbase/client/TestDeleteRegion.java (revision 0)
@@ -0,0 +1,364 @@
+/**
+ * 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.hbase.HBaseTestingUtility;
+import org.apache.hadoop.hbase.HColumnDescriptor;
+import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.HRegionInfo;
+import org.apache.hadoop.hbase.HTableDescriptor;
+import org.apache.hadoop.hbase.LargeTests;
+import org.apache.hadoop.hbase.MiniHBaseCluster;
+import org.apache.hadoop.hbase.regionserver.HRegion;
+import org.apache.hadoop.hbase.regionserver.HRegionServer;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.hadoop.hbase.zookeeper.MasterDeleteRegionTracker;
+import org.apache.zookeeper.KeeperException;
+import org.junit.*;
+import org.junit.experimental.categories.Category;
+
+/**
+ * Class to test delete-region using HBaseAdmin.
+ * Spins up the minicluster once at test start and then takes it down afterward.
+ */
+@Category(LargeTests.class)
+public class TestDeleteRegion {
+
+ final Log LOG = LogFactory.getLog(getClass());
+ private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
+ private HBaseAdmin admin;
+ private static MiniHBaseCluster miniHBaseCluster = null;
+ private static MasterDeleteRegionTracker mdrt = null;
+
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ TEST_UTIL.getConfiguration().setInt("hbase.regionserver.msginterval", 100);
+ TEST_UTIL.getConfiguration().setInt("hbase.client.pause", 250);
+ TEST_UTIL.getConfiguration().setInt("hbase.client.retries.number", 6);
+ TEST_UTIL.getConfiguration().setBoolean("hbase.delete.region.enabled", true);
+ miniHBaseCluster = TEST_UTIL.startMiniCluster(2,5);
+ mdrt = TEST_UTIL.getHBaseCluster().getMaster().getDeleteRegionTracker();
+ }
+
+ @AfterClass
+ public static void tearDownAfterClass() throws Exception {
+ TEST_UTIL.shutdownMiniCluster();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ this.admin = TEST_UTIL.getHBaseAdmin();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ }
+
+ @Test
+ public void testDeleteFirstRegion() throws IOException,
+ KeeperException, InterruptedException {
+
+ String tableName = "TestHBADeleteRegionFirst";
+ createTable(tableName);
+ String regionName = getRegionName(tableName, 0);
+ admin.deleteRegion(regionName);
+ waitForDeleteRegionProcess(regionName);
+ assertFalse(mdrt.doesDeleteRegionNodeExist(regionName));
+ }
+
+ @Test
+ public void testDeleteMiddleRegion() throws IOException,
+ KeeperException, InterruptedException {
+ String tableName = "TestHBADeleteRegionMiddle";
+ createTable(tableName);
+ String regionName = getRegionName(tableName, 1);
+ admin.deleteRegion(regionName);
+ waitForDeleteRegionProcess(regionName);
+ assertFalse(mdrt.doesDeleteRegionNodeExist(regionName));
+ }
+
+ @Test
+ public void testDeleteLastRegion() throws IOException,
+ KeeperException, InterruptedException {
+ String tableName = "TestHBADeleteRegionLast";
+ createTable(tableName);
+ String regionName = getRegionName(tableName, 2);
+ admin.deleteRegion(regionName);
+ waitForDeleteRegionProcess(regionName);
+ assertFalse(mdrt.doesDeleteRegionNodeExist(regionName));
+ }
+
+ private String getRegionName(String tableName, int regionIndex){
+ String regionName = "";
+ HRegionServer rs = null;
+ try {
+ if (regionIndex == 0) {
+ rs = TEST_UTIL.getRSForFirstRegionInTable(Bytes.toBytes(tableName));
+ } else if (regionIndex == 1) {
+ rs = TEST_UTIL.getRSForSecondRegionInTable(Bytes.toBytes(tableName));
+ } else {
+ rs = TEST_UTIL.getRSForLastRegionInTable(Bytes.toBytes(tableName));
+ }
+
+ List onlineRegions = rs.getOnlineRegions();
+ for (HRegionInfo regionInfo : onlineRegions) {
+ if (!regionInfo.isMetaTable()) {
+ if (regionInfo.getRegionNameAsString().contains(tableName)) {
+ regionName = regionInfo.getEncodedName();
+ break;
+ }
+ }
+ }
+ } catch (Exception e) {
+ LOG.error(e.getMessage(), e);
+ }
+ return regionName;
+ }
+
+ private void createTable(String name) throws IOException{
+ byte[] TABLENAME = Bytes.toBytes(name);
+ HTableDescriptor htd = new HTableDescriptor(TABLENAME);
+ HColumnDescriptor hcd = new HColumnDescriptor("value");
+ htd.addFamily(hcd);
+ admin.createTable(htd, "00000000".getBytes(), "zzzzzzzz".getBytes(), 10);
+ }
+
+ private void waitForDeleteRegionProcess(final String regionName)
+ throws KeeperException, InterruptedException {
+ waitForDeleteRegionProcess(regionName, 90000);
+ }
+
+ private void waitForDeleteRegionProcess(final String regionName,
+ final long waitTimeMills) throws KeeperException, InterruptedException {
+ LOG.info("Waiting for ZK node creation for region = " + regionName);
+ final MasterDeleteRegionTracker mdrt =
+ TEST_UTIL.getHBaseCluster().getMaster().getDeleteRegionTracker();
+
+ final Runnable r = new Runnable() {
+ public void run() {
+ try {
+ while(!mdrt.doesDeleteRegionNodeExist(regionName)) {
+ try {
+ Thread.sleep(50);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ } catch (KeeperException ke) {
+ LOG.error(ke.getMessage(), ke);
+ ke.printStackTrace();
+ }
+ LOG.info("Waiting for ZK node deletion for region = " + regionName);
+ try {
+ while(mdrt.doesDeleteRegionNodeExist(regionName)) {
+ try {
+ Thread.sleep(50);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ } catch (KeeperException ke) {
+ LOG.error(ke.getMessage(), ke);
+ ke.printStackTrace();
+ }
+ }
+ };
+ Thread t = new Thread(r);
+ t.start();
+ if (waitTimeMills > 0) {
+ t.join(waitTimeMills);
+ } else {
+ t.join(10000);
+ }
+ }
+
+ /**
+ * The delete-region request blocks while a LB run is in progress. This
+ * test validates this behavior.
+ * @throws IOException
+ * @throws InterruptedException
+ * @throws KeeperException
+ */
+ @Test
+ public void testConcurrentDeleteRegionAndLoadBalancerRun() throws IOException,
+ InterruptedException, KeeperException {
+
+ final String tableName = "testDeleteRegionWithLoadBalancerRunning";
+ createTable(tableName);
+ LOG.info("Start testConcurrentDeleteRegionAndLoadBalancerRun()");
+ final MasterDeleteRegionTracker msct =
+ TEST_UTIL.getHBaseCluster().getMaster().getDeleteRegionTracker();
+
+ Runnable balancer = new Runnable() {
+ public void run() {
+ // run the balancer now.
+ miniHBaseCluster.getMaster().balance();
+ }
+ };
+
+ Runnable deleteRegion = new Runnable() {
+ public void run() {
+ try {
+ admin.deleteRegion(getRegionName(tableName, 0));
+ } catch (IOException e) {
+
+ }
+ }
+ };
+
+ balancer.run();
+ deleteRegion.run();
+ waitForDeleteRegionProcess(getRegionName(tableName, 0), 40000);
+ assertFalse(msct.doesDeleteRegionNodeExist(getRegionName(tableName, 0)));
+
+ LOG.info("End testConcurrentDeleteRegionAndLoadBalancerRun() ");
+ }
+
+
+ /**
+ * This test validates two things. One is that the LoadBalancer does not run
+ * when a delete-region process is in progress. The second thing is that
+ * it also checks that failed/expired delete-region(s) are expired
+ * to unblock the load balancer run.
+ * @throws IOException
+ * @throws InterruptedException
+ * @throws KeeperException
+ */
+ @Test (timeout=70000)
+ public void testLoadBalancerBlocksDuringDeleteRegionRequests()
+ throws KeeperException, IOException, InterruptedException {
+ LOG.info("Start testLoadBalancerBlocksDuringDeleteRegionRequests() ");
+ final MasterDeleteRegionTracker mdrt =
+ TEST_UTIL.getHBaseCluster().getMaster().getDeleteRegionTracker();
+ // Test that the load balancer does not run while an in-flight delete-region
+ // operation is in progress.Simulate a new delete-region request.
+ mdrt.createDeleteRegionNode("testLoadBalancerBlocks");
+ // The delete-region node is created.
+ assertTrue(mdrt.doesDeleteRegionNodeExist("testLoadBalancerBlocks"));
+ // Now, request an explicit LB run.
+
+ Runnable balancer1 = new Runnable() {
+ public void run() {
+ // run the balancer now.
+ miniHBaseCluster.getMaster().balance();
+ }
+ };
+ balancer1.run();
+
+ // Load balancer should not run now.
+ assertTrue(miniHBaseCluster.getMaster().isLoadBalancerRunning() == false);
+ LOG.info("End testLoadBalancerBlocksDuringDeleteRegionRequests() ");
+ }
+
+ /**
+ * Test that delete-region blocks while LB is running.
+ * @throws KeeperException
+ * @throws IOException
+ * @throws InterruptedException
+ */
+ @Test (timeout=10000)
+ public void testDeleteRegionBlocksDuringLoadBalancerRun()
+ throws KeeperException, IOException, InterruptedException {
+ final MasterDeleteRegionTracker mdrt =
+ TEST_UTIL.getHBaseCluster().getMaster().getDeleteRegionTracker();
+ final String tableName = "testDeleteRegionBlocksDuringLoadBalancerRun";
+ createTable(tableName);
+ // Test that the delete-region request does not run while an in-flight
+ // LB run is in progress. First, request an explicit LB run.
+ Runnable balancer1 = new Runnable() {
+ public void run() {
+ // run the balancer now.
+ miniHBaseCluster.getMaster().balance();
+ }
+ };
+
+ Runnable deleteRegion = new Runnable() {
+ public void run() {
+ try {
+ admin.deleteRegion(getRegionName(tableName, 0));
+ } catch (IOException e) {
+
+ }
+ }
+ };
+
+ Thread t1 = new Thread(balancer1);
+ Thread t2 = new Thread(deleteRegion);
+ t1.start();
+ t2.start();
+
+ // check that they both happen concurrently
+ Runnable balancerCheck = new Runnable() {
+ public void run() {
+ // check whether balancer is running.
+ while((miniHBaseCluster != null) &&
+ (miniHBaseCluster.getMaster() != null) &&
+ (!miniHBaseCluster.getMaster().isLoadBalancerRunning())) {
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ try {
+ assertFalse(
+ mdrt.doesDeleteRegionNodeExist(getRegionName(tableName, 0)));
+ } catch (KeeperException ke) {
+ LOG.error(ke.getMessage(), ke);
+ ke.printStackTrace();
+ }
+ LOG.debug("Load Balancer is now running or skipped");
+ while(miniHBaseCluster.getMaster().isLoadBalancerRunning()) {
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ assertTrue(
+ miniHBaseCluster.getMaster().isLoadBalancerRunning() == false);
+ try {
+ assertTrue(
+ mdrt.doesDeleteRegionNodeExist(getRegionName(tableName, 0)));
+ } catch (KeeperException ke) {
+ LOG.error(ke.getMessage(), ke);
+ }
+
+ }
+ };
+
+ Thread t = new Thread(balancerCheck);
+ t.start();
+ t.join(1000);
+ LOG.info("End testDeleteRegionBlocksDuringLoadBalancerRun()");
+ }
+
+ @org.junit.Rule
+ public org.apache.hadoop.hbase.ResourceCheckerJUnitRule cu =
+ new org.apache.hadoop.hbase.ResourceCheckerJUnitRule();
+}
Index: src/test/java/org/apache/hadoop/hbase/master/TestCatalogJanitor.java
===================================================================
--- src/test/java/org/apache/hadoop/hbase/master/TestCatalogJanitor.java (revision 1236386)
+++ src/test/java/org/apache/hadoop/hbase/master/TestCatalogJanitor.java (working copy)
@@ -48,6 +48,7 @@
import org.apache.hadoop.hbase.regionserver.Store;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.Writables;
+import org.apache.hadoop.hbase.zookeeper.MasterDeleteRegionTracker;
import org.apache.hadoop.hbase.zookeeper.MasterSchemaChangeTracker;
import org.apache.hadoop.hbase.zookeeper.RegionServerTracker;
import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
@@ -260,6 +261,11 @@
public RegionServerTracker getRegionServerTracker() {
return null;
}
+
+ @Override
+ public MasterDeleteRegionTracker getDeleteRegionTracker() {
+ return null;
+ }
}
@Test
Index: src/test/java/org/apache/hadoop/hbase/util/MockRegionServerServices.java
===================================================================
--- src/test/java/org/apache/hadoop/hbase/util/MockRegionServerServices.java (revision 1236386)
+++ src/test/java/org/apache/hadoop/hbase/util/MockRegionServerServices.java (working copy)
@@ -147,5 +147,10 @@
public boolean isAborted() {
return false;
}
+
+ @Override
+ public void deleteRegion(String regionName) throws IOException, KeeperException {
+ //no-op
+ }
}