diff --git src/main/java/org/apache/hadoop/hbase/HConstants.java src/main/java/org/apache/hadoop/hbase/HConstants.java
index a9d80a0..9424a00 100644
--- src/main/java/org/apache/hadoop/hbase/HConstants.java
+++ src/main/java/org/apache/hadoop/hbase/HConstants.java
@@ -669,11 +669,15 @@ public final class HConstants {
public static final String ENABLE_WAL_COMPRESSION =
"hbase.regionserver.wal.enablecompression";
-/** Region in Transition metrics threshold time */
+ /** Region in Transition metrics threshold time */
public static final String METRICS_RIT_STUCK_WARNING_THRESHOLD="hbase.metrics.rit.stuck.warning.threshold";
public static final String LOAD_BALANCER_SLOP_KEY = "hbase.regions.slop";
+ /** Configuration key for the directory to backup HFiles for a table */
+ public static final String HFILE_ARCHIVE_DIRECTORY = "hbase.table.archive.directory";
+ public static final String HFILE_ARCHIVE_ZNODE_PARENT = "hfilearchive";
+
private HConstants() {
// Can't be instantiated with this ctor.
}
diff --git src/main/java/org/apache/hadoop/hbase/backup/HFileArchiveMonitor.java src/main/java/org/apache/hadoop/hbase/backup/HFileArchiveMonitor.java
new file mode 100644
index 0000000..2736c89
--- /dev/null
+++ src/main/java/org/apache/hadoop/hbase/backup/HFileArchiveMonitor.java
@@ -0,0 +1,35 @@
+/**
+ * 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.backup;
+
+
+/**
+ * Monitor which tables should be archived.
+ */
+public interface HFileArchiveMonitor {
+
+ /**
+ * Determine if the given table should or should not allow its hfiles to be
+ * deleted
+ *
+ * @param tableName name of the table to check
+ * @return true if its store files should be retained, false
+ * otherwise
+ */
+ public boolean keepHFiles(String tableName);
+}
diff --git src/main/java/org/apache/hadoop/hbase/backup/HFileArchiveTableTracker.java src/main/java/org/apache/hadoop/hbase/backup/HFileArchiveTableTracker.java
new file mode 100644
index 0000000..3193e89
--- /dev/null
+++ src/main/java/org/apache/hadoop/hbase/backup/HFileArchiveTableTracker.java
@@ -0,0 +1,79 @@
+package org.apache.hadoop.hbase.backup;
+
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.conf.Configured;
+import org.apache.hadoop.hbase.Server;
+import org.apache.hadoop.hbase.master.MasterServices;
+import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
+
+/**
+ * Management of the actual tables to be archived, etc
+ *
+ * It is internally synchronized to ensure consistent view of the table state
+ */
+public class HFileArchiveTableTracker extends Configured implements HFileArchiveMonitor {
+ private static final Log LOG = LogFactory.getLog(HFileArchiveTableTracker.class);
+ private final Set archivedTables = new TreeSet();
+
+ protected final ZooKeeperWatcher zkw;
+ protected final Server parent;
+
+ public HFileArchiveTableTracker(Server parent, ZooKeeperWatcher zkw) {
+ this.parent = parent;
+ this.zkw = zkw;
+ }
+
+ /**
+ * Set the tables to be archived. Internally adds each table and attempts to
+ * register it
+ * @param tables add each of the tables to be archived.
+ */
+ public synchronized void setArchiveTables(List tables) {
+ archivedTables.clear();
+ archivedTables.addAll(tables);
+ for (String table : tables) {
+ registerTable(table);
+ }
+ }
+
+ /**
+ * Add the named table to be those being archived. Attempts to register the
+ * table
+ * @param table name of the table to be registered
+ */
+ public synchronized void addTable(String table) {
+ if (this.keepHFiles(table)) {
+ LOG.debug("Already archiving table: " + table + ", ignoring it");
+ return;
+ }
+ archivedTables.add(table);
+ registerTable(table);
+ }
+
+ /**
+ * Subclass hook for registering a table with external sources. Currently does
+ * nothing, but can be used for things like updating ZK, etc.
+ * @param table name of the table that is being archived
+ */
+ protected void registerTable(String table) {
+ // NOOP - subclass hook
+ }
+
+ public synchronized void removeTable(String table) {
+ archivedTables.remove(table);
+ }
+
+ public synchronized void clearArchive() {
+ archivedTables.clear();
+ }
+
+ @Override
+ public synchronized boolean keepHFiles(String tableName) {
+ return archivedTables.contains(tableName);
+ }
+}
diff --git src/main/java/org/apache/hadoop/hbase/backup/HFileArchiveTracker.java src/main/java/org/apache/hadoop/hbase/backup/HFileArchiveTracker.java
new file mode 100644
index 0000000..81b3145
--- /dev/null
+++ src/main/java/org/apache/hadoop/hbase/backup/HFileArchiveTracker.java
@@ -0,0 +1,225 @@
+/**
+ * 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.backup;
+
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.hbase.zookeeper.ZKUtil;
+import org.apache.hadoop.hbase.zookeeper.ZooKeeperListener;
+import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
+import org.apache.zookeeper.KeeperException;
+
+/**
+ * Track HFile archiving state changes in ZooKeeper. Keeps track of the tables
+ * whose HFiles should be archived.
+ */
+public class HFileArchiveTracker extends ZooKeeperListener implements HFileArchiveMonitor {
+ private static final Log LOG = LogFactory.getLog(HFileArchiveTracker.class);
+ private volatile HFileArchiveTableTracker tracker;
+
+ public HFileArchiveTracker(ZooKeeperWatcher watcher, HFileArchiveTableTracker tracker) {
+ super(watcher);
+ watcher.registerListener(this);
+ this.tracker = tracker;
+ }
+
+ /**
+ * Start monitoring for archive updates
+ * @throws KeeperException on failure to find/create nodes
+ */
+ public void start() throws KeeperException {
+ // if archiving is enabled, then read in the list of tables to archive
+ LOG.debug("Starting hfile archive tracker...");
+ this.checkEnabledAndUpdate();
+ LOG.debug("Finished starting hfile archive tracker!");
+ }
+
+ @Override
+ public void nodeCreated(String path) {
+ // if it is the archive path
+ if (path.startsWith(watcher.archiveHFileZNode)) {
+ LOG.debug("Archive node: " + path + " created");
+ // since we are already enabled, just update a single table
+ String table = path.substring(watcher.archiveHFileZNode.length());
+
+ // if a new table has been added
+ if (table.length() != 0) {
+ try {
+ addAndReWatchTable(path);
+ } catch (KeeperException e) {
+ LOG.warn("Couldn't read zookeeper data for table, not archiving", e);
+ }
+ }
+ // the top level node has come up, so read in all the tables
+ else {
+ checkEnabledAndUpdate();
+ }
+ }
+ }
+
+ @Override
+ public void nodeChildrenChanged(String path) {
+ if (path.startsWith(watcher.archiveHFileZNode)) {
+ LOG.debug("Archive node: " + path + " children changed.");
+ // a table was added to the archive
+ try {
+ updateWatchedTables();
+ } catch (KeeperException e) {
+ LOG.error("Failed to update tables to archive", e);
+ }
+ }
+ }
+
+ /**
+ * Add this table to the tracker and then read a watch on that node.
+ *
+ * Handles situtation where table is deleted in the time between the update
+ * and resetting the watch by deleting the table via
+ * {@link #safeStopTrackingTable(String)}
+ * @param tableZnode full zookeeper path to the table to be added
+ * @throws KeeperException if an unexpected zk exception occurs
+ */
+ private void addAndReWatchTable(String tableZnode) throws KeeperException {
+ getTracker().addTable(ZKUtil.getNodeName(tableZnode));
+ // re-add a watch to the table created
+ // and check to make sure it wasn't deleted
+ if (!ZKUtil.watchAndCheckExists(watcher, tableZnode)) {
+ safeStopTrackingTable(tableZnode);
+ }
+ }
+
+ /**
+ * Stop tracking a table. Ensures that the table doesn't exist, but if it does
+ * attempts to add the table back via {@link #addAndReWatchTable(String)} (its
+ * a 'safe' removal)
+ * @param tableZnode full zookeeper path to the table to be added
+ * @throws KeeperException if an unexpected zk exception occurs
+ */
+ private void safeStopTrackingTable(String tableZnode) throws KeeperException {
+ getTracker().removeTable(ZKUtil.getNodeName(tableZnode));
+ // if the table exists, then add and rewatch it
+ if (ZKUtil.checkExists(watcher, tableZnode) >= 0) {
+ addAndReWatchTable(tableZnode);
+ }
+ }
+
+ @Override
+ public void nodeDeleted(String path) {
+ if (path.startsWith(watcher.archiveHFileZNode)) {
+ LOG.debug("Archive node: " + path + " deleted");
+ String table = path.substring(watcher.archiveHFileZNode.length());
+ // if we stop archiving all tables
+ if (table.length() == 0) {
+ // make sure we have the tracker before deleting the archive
+ // but if we don't, we don't care about delete
+ clearTables();
+ // watches are one-time events, so we need to renew our subscription to
+ // the archive node and might as well check to make sure archiving
+ // didn't come back on at the same time
+ checkEnabledAndUpdate();
+ return;
+ }
+ // just stop archiving one table
+ // note that we don't attempt to add another watch for that table into zk.
+ // We have no assurances that the table will be archived again (or even
+ // exists for that matter), so its better not to add unnecessary load to
+ // zk for watches. If the table is created again, then we will get the
+ // notification in childrenChanaged.
+ getTracker().removeTable(ZKUtil.getNodeName(path));
+ }
+ }
+
+ /**
+ * Sets the watch on the top-level archive znode, and then updates the montior
+ * with the current tables that should be archived (and ensures that those
+ * nodes are watched as well).
+ */
+ private void checkEnabledAndUpdate() {
+ try {
+ if (ZKUtil.watchAndCheckExists(watcher, watcher.archiveHFileZNode)) {
+ LOG.debug(watcher.archiveHFileZNode + " znode does exist, checking for tables to archive");
+
+ // update the tables we should backup, to get the most recent state.
+ // This is safer than also watching for children and then hoping we get
+ // all the updates as it makes sure we get and watch all the children
+ updateWatchedTables();
+ } else {
+ LOG.debug("Archiving not currently enabling, waiting");
+ }
+ } catch (KeeperException e) {
+ LOG.warn("Failed to watch for archiving znode", e);
+ }
+ }
+
+ /**
+ * Read the list of children under the archive znode as table names and then
+ * sets those tables to the list of tables that we should archive
+ * @throws KeeperException if there is an unexpected zk exception
+ */
+ private void updateWatchedTables() throws KeeperException {
+ // get the children and watch for new children
+ LOG.debug("Updating watches on tables to archive.");
+ // get the children and add watches for each of the children
+ List tables = ZKUtil.listChildrenAndWatchThem(watcher, watcher.archiveHFileZNode);
+ LOG.debug("Starting archive for tables:" + tables);
+ // if archiving is still enabled
+ if (tables != null && tables.size() > 0) {
+ getTracker().setArchiveTables(tables);
+ } else {
+ LOG.debug("No tables to archive.");
+ // only if we currently have a tracker, then clear the archive
+ clearTables();
+ }
+ }
+
+ /**
+ * Remove the currently archived tables.
+ *
+ * Does some intelligent checking to make sure we don't prematurely create an
+ * archive tracker.
+ */
+ private void clearTables() {
+ if (tracker != null) {
+ getTracker().clearArchive();
+ }
+ }
+
+ @Override
+ public boolean keepHFiles(String tableName) {
+ return getTracker().keepHFiles(tableName);
+ }
+
+ /**
+ * @return the tracker for which tables should be archived.
+ */
+ public final HFileArchiveTableTracker getTracker() {
+ return this.tracker;
+ }
+
+ /**
+ * Set the table tracker for the overall archive tracker.
+ *
+ * Exposed for TESTING!
+ * @param tracker tracker for which tables should be archived.
+ */
+ public void setTracker(HFileArchiveTableTracker tracker) {
+ this.tracker = tracker;
+ }
+}
diff --git src/main/java/org/apache/hadoop/hbase/backup/ServerHFileTableArchiveTracker.java src/main/java/org/apache/hadoop/hbase/backup/ServerHFileTableArchiveTracker.java
new file mode 100644
index 0000000..13452af
--- /dev/null
+++ src/main/java/org/apache/hadoop/hbase/backup/ServerHFileTableArchiveTracker.java
@@ -0,0 +1,54 @@
+/**
+ * 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.backup;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.hbase.Server;
+import org.apache.hadoop.hbase.util.HFileArchiveUtil;
+import org.apache.hadoop.hbase.zookeeper.ZKUtil;
+import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
+import org.apache.zookeeper.KeeperException;
+
+/**
+ * Tracker that also updates ZK that a server has received the update start
+ * archiving a table.
+ */
+public class ServerHFileTableArchiveTracker extends HFileArchiveTableTracker {
+
+ private static final Log LOG = LogFactory.getLog(ServerHFileTableArchiveTracker.class);
+
+ public ServerHFileTableArchiveTracker(Server parent, ZooKeeperWatcher watcher) {
+ super(parent, watcher);
+ }
+
+ @Override
+ public void registerTable(String table) {
+ LOG.debug("Adding table '" + table + "' to be archived.");
+
+ // notify that we are archiving the table for this server
+ try {
+ String tablenode = HFileArchiveUtil.getTableNode(zkw, table);
+ String serverNode = ZKUtil.joinZNode(tablenode, parent.getServerName().toString());
+ ZKUtil.createEphemeralNodeAndWatch(zkw, serverNode, new byte[0]);
+ } catch (KeeperException e) {
+ LOG.error("Failing to update that this server(" + parent.getServerName()
+ + " is joining archive of table:" + table);
+ }
+ }
+}
diff --git src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java
index e3912c2..f6cb88d 100644
--- src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java
+++ src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java
@@ -23,9 +23,13 @@ import java.io.Closeable;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.SocketTimeoutException;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;
@@ -73,8 +77,13 @@ import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.ScanResponse;
import org.apache.hadoop.hbase.regionserver.wal.FailedLogCloseException;
import org.apache.hadoop.hbase.util.Addressing;
import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.hadoop.hbase.util.Writables;
+import org.apache.hadoop.hbase.zookeeper.RootRegionTracker;
+import org.apache.hadoop.hbase.zookeeper.ZKTable;
+import org.apache.hadoop.hbase.zookeeper.ZKUtil;
+import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
import org.apache.hadoop.ipc.RemoteException;
import org.apache.hadoop.util.StringUtils;
import org.apache.zookeeper.KeeperException;
@@ -1788,6 +1797,225 @@ public class HBaseAdmin implements Abortable, Closeable {
}
/**
+ * @return A new {@link HFileArchiveManager} to manage which tables' hfiles
+ * should be archived rather than deleted.
+ * @throws ZooKeeperConnectionException
+ * @throws IOException
+ */
+ private synchronized HFileArchiveManager createHFileArchiveManager()
+ throws ZooKeeperConnectionException, IOException {
+ return new HFileArchiveManager(this.getConnection(), this.conf);
+ }
+
+ /**
+ * Turn on backups for all HFiles for the given table.
+ *
+ * All deleted hfiles are moved to the
+ * {@value HConstants#HFILE_ARCHIVE_DIRECTORY} directory under the table
+ * directory, rather than being deleted.
+ *
+ * If backups are already enabled for this table, does nothing.
+ * Synchronous operation.
+ *
+ * Assumes that offline/dead regionservers will get the update on start.
+ *
+ * WARNING: No guarantees are made if multiple clients simultaneously
+ * attempt to disable/enable hfile backup on the same table.
+ * @param table name of the table to start backing up
+ * @throws IOException
+ */
+ public void enableHFileBackup(String table) throws IOException {
+ enableHFileBackup(Bytes.toBytes(table));
+ }
+
+
+ /**
+ * Enable HFile backups synchronously. Ensures that all regionservers hosting
+ * the table have recived the notification to start archiving hfiles.
+ * Synchronous operation.
+ *
+ * Assumes that offline/dead regionservers will get the update on start.
+ *
+ * WARNING: No guarantees are made if multiple clients simultaneously
+ * attempt to disable/enable hfile backup on the same table.
+ * @param tableName name of the table on which to start backing up hfiles
+ * @throws IOException if the backup cannot be enabled
+ */
+ public void enableHFileBackup(final byte[] tableName) throws IOException {
+ // this is a little inefficient since we do a read of meta to see if it
+ // exists, but is a lot cleaner than catching a NPE below when looking for
+ // online regions and the table doesn't exist.
+ if (!this.tableExists(tableName)) {
+ throw new IOException("Table: " + Bytes.toString(tableName)
+ + " does not exist, cannot create a backup for a non-existant table.");
+ }
+ // do the asynchronous update
+ enableHFileBackupAsync(tableName);
+
+ // and then wait for it to propagate
+ // while all the regions have yet to receive zk update and we are not done
+ // retrying and backing off
+ CatalogTracker ct = getCatalogTracker();
+ HFileArchiveManager manager = createHFileArchiveManager();
+ int tries = 0;
+ try {
+ // just doing the normal amount of retries, as opposed to the multiplier
+ // since we are creating a new connection every time, rather than worrying
+ // about connection timeouts, unavailability. If ZK does go down, then we
+ // are pretty hosed in terms of backup and want to bail quickly.
+ while (!allServersArchivingTable(manager, ct, tableName) && tries < this.numRetries) {
+ // Sleep while we wait for the RS to get updated
+ try {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("try:" + tries + "/" + this.numRetries
+ + ", Not all regionservers for table '" + Bytes.toString(tableName)
+ + "' have joined the backup. Waiting...");
+ }
+ Thread.sleep(getPauseTime(tries++));
+ } catch (InterruptedException e) {
+ throw new InterruptedIOException("Interrupted when backing up table "
+ + Bytes.toString(tableName));
+ }
+ }
+
+ LOG.debug("Done waiting for table: " + Bytes.toString(tableName) + " to join archive.");
+ // if we couldn't get all regions we expect, bail out
+ if (tries >= this.numRetries) {
+ // disable backups
+ manager.disableHFileBackup(tableName);
+ throw new IOException("Failed to get all regions to join backup in " + tries
+ + " tries, for table:" + Bytes.toString(tableName));
+ }
+ } finally {
+ cleanupCatalogTracker(ct);
+ manager.stop();
+ }
+ }
+
+ /**
+ * Turn on backups for all HFiles for the given table.
+ *
+ * All deleted hfiles are moved to the archive directory under the table
+ * directory, rather than being deleted.
+ *
+ * If backups are already enabled for this table, does nothing.
+ * @param table name of the table to start backing up
+ * @throws IOException if an unexpected exception occurs
+ */
+ public void enableHFileBackupAsync(final byte[] table)
+ throws IOException {
+ createHFileArchiveManager().enableHFileBackup(table).stop();
+ }
+
+ /**
+ * Disable hfile backups for the given table.
+ *
+ * Previously backed up files are still retained (if present).
+ * @param table name of the table stop backing up
+ * @throws IOException if an unexpected exception occurs
+ */
+ public void disableHFileBackup(String table) throws IOException {
+ disableHFileBackup(Bytes.toBytes(table));
+ }
+
+ /**
+ * Disable hfile backups for the given table.
+ *
+ * Previously backed up files are still retained (if present).
+ * @param table name of the table stop backing up
+ * @throws IOException if an unexpected exception occurs
+ */
+ public void disableHFileBackup(final byte[] table) throws IOException {
+ createHFileArchiveManager().disableHFileBackup(table).stop();
+ }
+
+ /**
+ * Disable hfile backups for all tables.
+ *
+ * Previously backed up files are still retained (if present).
+ * @throws IOException if an unexpected exception occurs
+ */
+ public void disableHFileBackup() throws IOException {
+ createHFileArchiveManager().disableHFileBackup().stop();
+ }
+
+ /**
+ * Determine if archiving is enabled (but not necessarily fully propagated)
+ * for a table
+ * @param table name of the table to check
+ * @return true if it is, false otherwise
+ * @throws IOException if a connection to ZooKeeper cannot be established
+ */
+ public boolean getArchivingEnabled(byte[] table) throws IOException {
+ HFileArchiveManager manager = createHFileArchiveManager();
+ try {
+ return manager.isArchivingEnabled(table);
+ } catch (IOException e) {
+ return false;
+ } finally {
+ manager.stop();
+ }
+ }
+
+ /**
+ * Determine if archiving is enabled (but not necessarily fully propagated)
+ * for a table
+ * @param table name of the table to check
+ * @return true if it is, false otherwise
+ * @throws IOException if a connection to ZooKeeper cannot be established
+ */
+ public boolean getArchivingEnabled(String table) throws IOException {
+ return getArchivingEnabled(Bytes.toBytes(table));
+ }
+
+ private boolean allServersArchivingTable(HFileArchiveManager manager, CatalogTracker ct,
+ byte[] tableName) throws IOException {
+
+ // then get the list of RS that have confirmed archiving table
+ List serverNames = manager.serversArchiving(tableName);
+ // add the master as a server to check for
+
+ Collections.sort(serverNames);
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Expecting archiving from at least servers:" + serverNames);
+ }
+
+ // get the regions and their servers associated with the table
+ List> regionAndLocations;
+ try {
+ regionAndLocations = MetaReader.getTableRegionsAndLocations(ct, Bytes.toString(tableName));
+ } catch (InterruptedException e) {
+ throw new InterruptedIOException(
+ "Interrupted when getting expected regions and servers to backup table: "
+ + Bytes.toString(tableName));
+ }
+ // now get the RS that should be archiving the table
+ Set expected = new TreeSet();
+ for (Pair rl : regionAndLocations) {
+ // if the region is assigned
+ if (rl.getSecond() != null) {
+ expected.add(rl.getSecond().toString());
+ }
+ }
+ // and add the master server as an expected server too, since that has the
+ // catalog janitor
+ expected.add(this.getClusterStatus().getMaster().toString());
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Expecting archiving from at least servers:" + serverNames);
+ }
+
+ // now compare the list of expected vs. those currently checked in
+ // now build list of the current RS
+ for (String expectedServer : expected) {
+ // if the expected RS is not in the list, then they haven't all joined
+ if (Collections.binarySearch(serverNames, expectedServer) < 0) return false;
+ }
+ return true;
+ }
+
+ /**
* @see {@link #execute}
*/
private abstract static class MasterCallable implements Callable{
diff --git src/main/java/org/apache/hadoop/hbase/client/HFileArchiveManager.java src/main/java/org/apache/hadoop/hbase/client/HFileArchiveManager.java
new file mode 100644
index 0000000..729258d
--- /dev/null
+++ src/main/java/org/apache/hadoop/hbase/client/HFileArchiveManager.java
@@ -0,0 +1,193 @@
+/**
+ * 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.apache.hadoop.hbase.util.HFileArchiveUtil.getTableNode;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hbase.ZooKeeperConnectionException;
+import org.apache.hadoop.hbase.util.HFileArchiveUtil;
+import org.apache.hadoop.hbase.zookeeper.ZKUtil;
+import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
+import org.apache.zookeeper.KeeperException;
+
+/**
+ * Client-side manager for which tables to archive
+ */
+class HFileArchiveManager {
+
+ private static final Log LOG = LogFactory.getLog(HFileArchiveManager.class);
+ private final ZooKeeperWatcher zooKeeper;
+ private volatile boolean stopped = false;
+
+ public HFileArchiveManager(HConnection connection, Configuration conf)
+ throws ZooKeeperConnectionException, IOException {
+ this(new ZooKeeperWatcher(conf, "hfileArchiveManger-on-"
+ + connection.toString(), connection));
+ }
+
+ public HFileArchiveManager(ZooKeeperWatcher watcher) {
+ this.zooKeeper = watcher;
+ }
+
+ /**
+ * Turn on auto-backups of HFiles on the specified table.
+ *
+ * When HFiles would be deleted, they are instead interned to the backup
+ * directory specified or HConstants#DEFAULT_HFILE_ARCHIVE_DIRECTORY
+ * @param table name of the table to enable backups on
+ * @return this for chaining.
+ * @throws IOException
+ */
+ public HFileArchiveManager enableHFileBackup(byte[] table)
+ throws IOException {
+ try {
+ enable(this.zooKeeper, table);
+ return this;
+ } catch (KeeperException e) {
+ throw new IOException(e);
+ }
+ }
+
+ /**
+ * Table backups on HFiles for the given table
+ * @param table name of the table to disable backups on
+ * @return this for chaining.
+ * @throws IOException
+ */
+ public HFileArchiveManager disableHFileBackup(byte[] table) throws IOException {
+ try {
+ disable(this.zooKeeper, table);
+ return this;
+ } catch (KeeperException e) {
+ throw new IOException(e);
+ }
+ }
+
+ /**
+ * Disable backups on all tables in the cluster
+ * @return this for chaining.
+ * @throws IOException if the number of attempts is exceeded
+ */
+ public HFileArchiveManager disableHFileBackup() throws IOException {
+ LOG.debug("Disabling backups on all tables.");
+ try {
+ ZKUtil.deleteNodeRecursively(this.zooKeeper, this.zooKeeper.archiveHFileZNode);
+ return this;
+ } catch (KeeperException e) {
+ throw new IOException("Unexpected ZK exception!", e);
+ }
+ }
+
+ /**
+ * Get the current list of all the regionservers that are currently involved
+ * in archiving the table
+ * @param table name of table under which to check for regions that are
+ * archiving.
+ * @return the currently online regions that are archiving the table
+ * @throws IOException if an unexpected connection issues occurs
+ */
+ @SuppressWarnings("unchecked")
+ public List serversArchiving(byte[] table) throws IOException {
+ try {
+ // build the table znode
+ String tableNode = getTableNode(zooKeeper, table);
+ List regions = ZKUtil.listChildrenNoWatch(zooKeeper, tableNode);
+ return (List) (regions == null ? Collections.emptyList() : regions);
+ } catch (KeeperException e) {
+ throw new IOException(e);
+ }
+ }
+
+ /**
+ * Best effort enable of table backups. If a region serving a table is
+ * offline, it will be notified on startup.
+ *
+ * No attempt is made to make sure that backups are successfully created - it
+ * is inherently an asynchronous operation.
+ * @param zooKeeper watcher connection to zk cluster
+ * @param table table name on which to enable archiving
+ * @throws KeeperException
+ */
+ private void enable(ZooKeeperWatcher zooKeeper, byte[] table)
+ throws KeeperException {
+ LOG.debug("Ensuring archiving znode exists");
+ ZKUtil.createAndFailSilent(zooKeeper, zooKeeper.archiveHFileZNode);
+
+ // then add the table to the list of znodes to archive
+ String tableNode = getTableNode(zooKeeper, table);
+ LOG.debug("Creating: " + tableNode + ", data: []");
+ ZKUtil.createSetData(zooKeeper, tableNode, new byte[0]);
+ }
+
+ /**
+ * Disable all archiving of files for a given table
+ *
+ * Note: Asynchronous
+ * @param zooKeeper watcher for the ZK cluster
+ * @param table name of the table to disable
+ * @throws KeeperException if an unexpected ZK connection issues occurs
+ */
+ private void disable(ZooKeeperWatcher zooKeeper, byte[] table) throws KeeperException {
+ // ensure the latest state of the archive node is found
+ zooKeeper.sync(zooKeeper.archiveHFileZNode);
+
+ // if the top-level archive node is gone, the we are done
+ if (ZKUtil.checkExists(zooKeeper, zooKeeper.archiveHFileZNode) < 0) {
+ return;
+ }
+ // delete the table node, from the archive - will be noticed by
+ // regionservers
+ String tableNode = getTableNode(zooKeeper, table);
+ // make sure the table is the latest version so the delete takes
+ zooKeeper.sync(tableNode);
+
+ LOG.debug("Attempting to delete table node:" + tableNode);
+ ZKUtil.deleteNodeRecursively(zooKeeper, tableNode);
+ }
+
+ public void stop() {
+ if (!this.stopped) {
+ this.stopped = true;
+ LOG.debug("Stopping HFileArchiveManager...");
+ this.zooKeeper.close();
+ }
+ }
+
+ /**
+ * Check to see if the table is currently marked for archiving
+ * @param table name of the table to check
+ * @return true if the archive znode for that table exists,
+ * false if not
+ * @throws IOException if an unexpected zookeeper error occurs
+ */
+ public boolean isArchivingEnabled(byte[] table) throws IOException {
+ String tableNode = HFileArchiveUtil.getTableNode(zooKeeper, table);
+ try {
+ return ZKUtil.checkExists(zooKeeper, tableNode) >= 0;
+ } catch (KeeperException e) {
+ throw new IOException("Failed to get the table znode:" + table, e);
+ }
+ }
+}
diff --git src/main/java/org/apache/hadoop/hbase/master/CatalogJanitor.java src/main/java/org/apache/hadoop/hbase/master/CatalogJanitor.java
index 79d5fdd..54ab07e 100644
--- src/main/java/org/apache/hadoop/hbase/master/CatalogJanitor.java
+++ src/main/java/org/apache/hadoop/hbase/master/CatalogJanitor.java
@@ -39,7 +39,8 @@ import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.HRegionInfo;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.Server;
-import org.apache.hadoop.hbase.TableExistsException;
+import org.apache.hadoop.hbase.backup.HFileArchiveMonitor;
+import org.apache.hadoop.hbase.backup.HFileArchiveTableTracker;
import org.apache.hadoop.hbase.catalog.MetaEditor;
import org.apache.hadoop.hbase.catalog.MetaReader;
import org.apache.hadoop.hbase.client.Result;
@@ -48,8 +49,12 @@ import org.apache.hadoop.hbase.regionserver.Store;
import org.apache.hadoop.hbase.regionserver.StoreFile;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.FSUtils;
+import org.apache.hadoop.hbase.util.HFileArchiveUtil;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.hadoop.hbase.util.Writables;
+import org.apache.hadoop.hbase.zookeeper.ZKUtil;
+import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
+import org.apache.zookeeper.KeeperException;
/**
@@ -61,14 +66,17 @@ class CatalogJanitor extends Chore {
private static final Log LOG = LogFactory.getLog(CatalogJanitor.class.getName());
private final Server server;
private final MasterServices services;
+ private final HFileArchiveMonitor hfileManager;
private boolean enabled = true;
- CatalogJanitor(final Server server, final MasterServices services) {
+ CatalogJanitor(final Server server, final MasterServices services,
+ HFileArchiveMonitor hfileArchiveManager) {
super(server.getServerName() + "-CatalogJanitor",
server.getConfiguration().getInt("hbase.catalogjanitor.interval", 300000),
server);
this.server = server;
this.services = services;
+ this.hfileManager = hfileArchiveManager;
}
@Override
@@ -215,7 +223,7 @@ class CatalogJanitor extends Chore {
if (hasNoReferences(a) && hasNoReferences(b)) {
LOG.debug("Deleting region " + parent.getRegionNameAsString() +
" because daughter splits no longer hold references");
- // wipe out daughter references from parent region
+ // wipe out daughter references from parent region in meta
removeDaughtersFromParent(parent);
// This latter regionOffline should not be necessary but is done for now
@@ -226,8 +234,7 @@ class CatalogJanitor extends Chore {
this.services.getAssignmentManager().regionOffline(parent);
}
FileSystem fs = this.services.getMasterFileSystem().getFileSystem();
- Path rootdir = this.services.getMasterFileSystem().getRootDir();
- HRegion.deleteRegion(fs, rootdir, parent);
+ HRegion.deleteRegion(fs, hfileManager, parent);
MetaEditor.deleteRegion(this.server.getCatalogTracker(), parent);
result = true;
}
@@ -326,4 +333,12 @@ class CatalogJanitor extends Chore {
throws FileNotFoundException, IOException {
return this.services.getTableDescriptors().get(Bytes.toString(tableName));
}
+
+ /**
+ * Exposed for TESTING
+ * @return the current monitor for the files being managed
+ */
+ public HFileArchiveMonitor getHfileTracker() {
+ return hfileManager;
+ }
}
diff --git src/main/java/org/apache/hadoop/hbase/master/HMaster.java src/main/java/org/apache/hadoop/hbase/master/HMaster.java
index 81e9023..7478b81 100644
--- src/main/java/org/apache/hadoop/hbase/master/HMaster.java
+++ src/main/java/org/apache/hadoop/hbase/master/HMaster.java
@@ -64,6 +64,9 @@ import org.apache.hadoop.hbase.TableNotDisabledException;
import org.apache.hadoop.hbase.TableNotFoundException;
import org.apache.hadoop.hbase.UnknownRegionException;
import org.apache.hadoop.hbase.ZooKeeperConnectionException;
+import org.apache.hadoop.hbase.backup.HFileArchiveTableTracker;
+import org.apache.hadoop.hbase.backup.HFileArchiveTracker;
+import org.apache.hadoop.hbase.backup.ServerHFileTableArchiveTracker;
import org.apache.hadoop.hbase.catalog.CatalogTracker;
import org.apache.hadoop.hbase.catalog.MetaReader;
import org.apache.hadoop.hbase.client.HConnectionManager;
@@ -90,7 +93,6 @@ import org.apache.hadoop.hbase.master.handler.TableDeleteFamilyHandler;
import org.apache.hadoop.hbase.master.handler.TableEventHandler;
import org.apache.hadoop.hbase.master.handler.TableModifyFamilyHandler;
import org.apache.hadoop.hbase.master.metrics.MasterMetrics;
-import org.apache.hadoop.hbase.master.RegionPlan;
import org.apache.hadoop.hbase.monitoring.MemoryBoundedLogMessageBuffer;
import org.apache.hadoop.hbase.monitoring.MonitoredTask;
import org.apache.hadoop.hbase.monitoring.TaskMonitor;
@@ -229,6 +231,8 @@ Server {
*/
private ObjectName mxBean = null;
+ private HFileArchiveTracker hfileArchiveTracker;
+
/**
* Initializes the HMaster. The steps are as follows:
*
@@ -444,6 +448,10 @@ Server {
boolean wasUp = this.clusterStatusTracker.isClusterUp();
if (!wasUp) this.clusterStatusTracker.setClusterUp();
+ this.hfileArchiveTracker = new HFileArchiveTracker(zooKeeper,
+ new ServerHFileTableArchiveTracker(HMaster.this, zooKeeper));
+ this.hfileArchiveTracker.start();
+
LOG.info("Server active/primary master; " + this.serverName +
", sessionid=0x" +
Long.toHexString(this.zooKeeper.getRecoverableZooKeeper().getSessionId()) +
@@ -601,7 +609,8 @@ Server {
// been assigned.
status.setStatus("Starting balancer and catalog janitor");
this.balancerChore = getAndStartBalancerChore(this);
- this.catalogJanitorChore = new CatalogJanitor(this, this);
+ this.catalogJanitorChore = new CatalogJanitor(this, this,
+ this.hfileArchiveTracker);
Threads.setDaemonThreadRunning(catalogJanitorChore.getThread());
registerMBean();
@@ -1831,6 +1840,14 @@ Server {
}
/**
+ * Exposed for testing only!
+ * @return the internal tracker for archiving hfiles
+ */
+ public HFileArchiveTracker getHFileArchiveMonitor() {
+ return this.hfileArchiveTracker;
+ }
+
+ /**
* Special method, only used by hbck.
*/
@Override
@@ -1884,4 +1901,12 @@ Server {
MBeanUtil.registerMBean("Master", "Master", mxBeanInfo);
LOG.info("Registered HMaster MXBean");
}
+
+ /**
+ * Exposed for TESTING
+ * @return the current catalog janitor
+ */
+ public CatalogJanitor getCatalogJanitor() {
+ return this.catalogJanitorChore;
+ }
}
diff --git src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java
index 0756a11..fc312c2 100644
--- src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java
+++ src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java
@@ -82,6 +82,7 @@ import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.NotServingRegionException;
import org.apache.hadoop.hbase.UnknownScannerException;
+import org.apache.hadoop.hbase.backup.HFileArchiveMonitor;
import org.apache.hadoop.hbase.client.Append;
import org.apache.hadoop.hbase.client.Delete;
import org.apache.hadoop.hbase.client.Get;
@@ -121,6 +122,7 @@ import org.apache.hadoop.hbase.util.ClassSize;
import org.apache.hadoop.hbase.util.CompressionTest;
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
import org.apache.hadoop.hbase.util.FSUtils;
+import org.apache.hadoop.hbase.util.HFileArchiveUtil;
import org.apache.hadoop.hbase.util.HashedBytes;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.hadoop.hbase.util.Threads;
@@ -904,16 +906,7 @@ public class HRegion implements HeapSize { // , Writable{
writestate.writesEnabled = false;
wasFlushing = writestate.flushing;
LOG.debug("Closing " + this + ": disabling compactions & flushes");
- while (writestate.compacting > 0 || writestate.flushing) {
- LOG.debug("waiting for " + writestate.compacting + " compactions" +
- (writestate.flushing ? " & cache flush" : "") +
- " to complete for region " + this);
- try {
- writestate.wait();
- } catch (InterruptedException iex) {
- // continue
- }
- }
+ waitForFlushesAndCompactions();
}
// If we were not just flushing, is it worth doing a preflush...one
// that will clear out of the bulk of the memstore before we put up
@@ -988,6 +981,25 @@ public class HRegion implements HeapSize { // , Writable{
}
}
+ /**
+ * Wait for all current flushes and compactions of the region to complete.
+ *
+ * VISBILE FOR TESTING
+ */
+ public void waitForFlushesAndCompactions() {
+ synchronized (writestate) {
+ while (writestate.compacting > 0 || writestate.flushing) {
+ LOG.debug("waiting for " + writestate.compacting + " compactions"
+ + (writestate.flushing ? " & cache flush" : "") + " to complete for region " + this);
+ try {
+ writestate.wait();
+ } catch (InterruptedException iex) {
+ // continue
+ }
+ }
+ }
+ }
+
protected ThreadPoolExecutor getStoreOpenAndCloseThreadPool(
final String threadNamePrefix) {
int numStores = Math.max(1, this.htableDescriptor.getFamilies().size());
@@ -3798,29 +3810,155 @@ public class HRegion implements HeapSize { // , Writable{
}
/**
- * Deletes all the files for a HRegion
+ * Removes all the files for a HRegion
*
* @param fs the file system object
- * @param rootdir qualified path of HBase root directory
+ * @param manager manager for if the region should be archived or deleted
* @param info HRegionInfo for region to be deleted
* @throws IOException
*/
- public static void deleteRegion(FileSystem fs, Path rootdir, HRegionInfo info)
- throws IOException {
- deleteRegion(fs, HRegion.getRegionDir(rootdir, info));
+ public static void deleteRegion(FileSystem fs, HFileArchiveMonitor manager,
+ HRegionInfo info) throws IOException {
+ Path rootDir = FSUtils.getRootDir(fs.getConf());
+ deleteRegion(fs, manager, rootDir,
+ HTableDescriptor.getTableDir(rootDir, info.getTableName()),
+ HRegion.getRegionDir(rootDir, info));
}
- private static void deleteRegion(FileSystem fs, Path regiondir)
- throws IOException {
+ private static void deleteRegion(FileSystem fs, RegionServerServices rss,
+ Path rootdir, Path table, Path regiondir) throws IOException {
+ HFileArchiveMonitor manager = rss == null ? null : rss
+ .getHFileArchiveMonitor();
+ deleteRegion(fs, manager, rootdir, table, regiondir);
+ }
+
+
+ /**
+ * Remove an entire region from the table directory.
+ *
+ * Either archives the region or outright deletes it, depending on if
+ * archiving is enabled.
+ * @param fs {@link FileSystem} from which to remove the region
+ * @param monitor Monitor for which tables should be archived or deleted
+ * @param rootdir {@link Path} to the root directory where hbase files are
+ * stored (for building the archive path)
+ * @param table {@link Path} to where the table is being stored (for building
+ * the archive path)
+ * @param regionDir {@link Path} to where a region is being stored (for
+ * building the archive path)
+ * @return true if the region was sucessfully deleted. false
+ * if the filesystem operations could not complete.
+ * @throws IOException if the request cannot be completed
+ */
+ private static boolean deleteRegion(FileSystem fs,
+ HFileArchiveMonitor monitor, Path rootdir, Path tableDir, Path regionDir)
+ throws IOException {
if (LOG.isDebugEnabled()) {
- LOG.debug("DELETING region " + regiondir.toString());
+ LOG.debug("REMOVING region " + regionDir.toString());
}
- if (!fs.delete(regiondir, true)) {
- LOG.warn("Failed delete of " + regiondir);
+ // check to make sure we don't keep files, in which case just delete them
+ String table = tableDir.getName();
+ if (monitor == null || !monitor.keepHFiles(table)) {
+ LOG.debug("Doing raw delete of hregion directory (" + regionDir + ") - no backup");
+ return deleteRegionWithoutArchiving(fs, regionDir);
}
+
+ // otherwise, we archive the files
+ // make sure the regiondir lives under the tabledir
+ Preconditions.checkArgument(regionDir.toString().startsWith(
+ tableDir.toString()));
+
+ // get the directory to archive region files
+ Path regionArchiveDir = HFileArchiveUtil.getRegionArchiveDir(fs.getConf(), tableDir, regionDir);
+ if (regionArchiveDir == null) {
+ LOG.warn("No archive directory could be found for the region:"
+ + regionDir + ", deleting instead");
+ return deleteRegionWithoutArchiving(fs, regionDir);
+ }
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("ARCHIVING HFiles for region in table: " + table + " to "
+ + regionArchiveDir);
+ }
+
+ // get the path to each of the store directories
+ FileStatus[] stores = fs.listStatus(regionDir);
+ // if there are no stores, just remove the region
+ if (stores == null || stores.length == 0) {
+ LOG.debug("No stores present in region:" + regionDir.getName()
+ + " for table" + table + ", done archiving.");
+ return deleteRegionWithoutArchiving(fs, regionDir);
+ }
+
+ // otherwise, we attempt to archive the store files
+ boolean failure = false;
+ for (FileStatus storeDir : stores) {
+ Path storeArchiveDir = new Path(regionArchiveDir, storeDir.getPath()
+ .getName());
+ if (!resolveAndArchive(fs, storeArchiveDir,
+ storeDir.getPath())) {
+ LOG.warn("Failed to archive all files in store directory: "
+ + storeDir.getPath());
+ failure = true;
+ }
+ }
+ return failure;
+ }
+
+ /**
+ * Resolve all the copies of files. Ensures that no two files will collide by
+ * moving existing archived files to a timestampted directory and the curent
+ * archive files to their place. Otherwise, just moves the files into the
+ * archive directory
+ * @param fs filesystem on which all the files live
+ * @param storeArchiveDirectory path to the archive of the store directory
+ * (already exists)
+ * @param store path to the store directory
+ * @return true if all files are moved successfully, false
+ * otherwise.
+ */
+ private static boolean resolveAndArchive(FileSystem fs,
+ Path storeArchiveDirectory, Path store) throws IOException {
+ FileStatus[] storeFiles = fs.listStatus(store);
+ // if there are no store files to move, we are done
+ if (storeFiles == null || storeFiles.length == 0) return true;
+
+ String archiveStartTime = Long.toString(EnvironmentEdgeManager
+ .currentTimeMillis());
+ boolean result = true;
+ for (FileStatus stat : storeFiles) {
+ Path file = stat.getPath();
+ // resolve copy over each of the files to be archived.
+ try {
+ if (!HFileArchiveUtil.resolveAndArchiveFile(fs, storeArchiveDirectory,
+ file, archiveStartTime)) {
+ result = false;
+ LOG.warn("Failed to archive file: " + file);
+ }
+ } catch (IOException e) {
+ result = false;
+ LOG.warn("Failed to archive file: " + file, e);
+ }
+ }
+ return result;
}
/**
+ * Without regard for backup, delete a region. Should be used with caution.
+ * @param regionDir {@link Path} to the region to be deleted.
+ * @throws IOException on filesystem operation failure
+ */
+ private static boolean deleteRegionWithoutArchiving(FileSystem fs, Path regionDir)
+ throws IOException {
+ if (fs.delete(regionDir, true)) {
+ LOG.debug("Deleted all region files in: " + regionDir);
+ return true;
+ }
+ LOG.debug("Failed to delete region directory:" + regionDir);
+ return false;
+ }
+
+
+ /**
* Computes the Path of the HRegion
*
* @param rootdir qualified path of HBase root directory
@@ -4019,8 +4157,13 @@ public class HRegion implements HeapSize { // , Writable{
LOG.debug("Files for new region");
listPaths(fs, dstRegion.getRegionDir());
}
- deleteRegion(fs, a.getRegionDir());
- deleteRegion(fs, b.getRegionDir());
+
+ // delete out the 'A' region
+ deleteRegion(fs, a.getRegionServerServices(), FSUtils.getRootDir(a.getBaseConf()),
+ a.getTableDir(), a.getRegionDir());
+ // delete out the 'B' region
+ deleteRegion(fs, b.getRegionServerServices(), FSUtils.getRootDir(b.getBaseConf()),
+ b.getTableDir(), b.getRegionDir());
LOG.info("merge completed. New region is " + dstRegion);
diff --git src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java
index ebffad6..25a5012 100644
--- src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java
+++ src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java
@@ -77,6 +77,10 @@ import org.apache.hadoop.hbase.Stoppable;
import org.apache.hadoop.hbase.UnknownRowLockException;
import org.apache.hadoop.hbase.UnknownScannerException;
import org.apache.hadoop.hbase.YouAreDeadException;
+import org.apache.hadoop.hbase.backup.HFileArchiveMonitor;
+import org.apache.hadoop.hbase.backup.HFileArchiveTableTracker;
+import org.apache.hadoop.hbase.backup.HFileArchiveTracker;
+import org.apache.hadoop.hbase.backup.ServerHFileTableArchiveTracker;
import org.apache.hadoop.hbase.catalog.CatalogTracker;
import org.apache.hadoop.hbase.catalog.MetaEditor;
import org.apache.hadoop.hbase.catalog.MetaReader;
@@ -166,7 +170,7 @@ import com.google.common.collect.Lists;
*/
@InterfaceAudience.Private
public class HRegionServer extends RegionServer
- implements HRegionInterface, HBaseRPCErrorHandler {
+implements HRegionInterface, HBaseRPCErrorHandler {
public static final Log LOG = LogFactory.getLog(HRegionServer.class);
@@ -278,6 +282,9 @@ public class HRegionServer extends RegionServer
*/
private ObjectName mxBean = null;
+ /** Store file archiving management */
+ HFileArchiveTracker hfileArchiveTracker;
+
/**
* Starts a HRegionServer at the default location
*
@@ -286,7 +293,7 @@ public class HRegionServer extends RegionServer
* @throws InterruptedException
*/
public HRegionServer(Configuration conf)
- throws IOException, InterruptedException {
+ throws IOException, InterruptedException {
this.fsOk = true;
this.conf = conf;
// Set how many times to retry talking to another server over HConnection.
@@ -365,11 +372,13 @@ public class HRegionServer extends RegionServer
private static void checkCodecs(final Configuration c) throws IOException {
// check to see if the codec list is available:
String [] codecs = c.getStrings("hbase.regionserver.codecs", (String[])null);
- if (codecs == null) return;
+ if (codecs == null) {
+ return;
+ }
for (String codec : codecs) {
if (!CompressionTest.testCompression(codec)) {
throw new IOException("Compression codec " + codec +
- " not supported, aborting RS construction");
+ " not supported, aborting RS construction");
}
}
}
@@ -410,7 +419,9 @@ public class HRegionServer extends RegionServer
@Override
public Integer apply(Writable from) {
- if (!(from instanceof Invocation)) return NORMAL_QOS;
+ if (!(from instanceof Invocation)) {
+ return NORMAL_QOS;
+ }
Invocation inv = (Invocation) from;
String methodName = inv.getMethodName();
@@ -437,7 +448,7 @@ public class HRegionServer extends RegionServer
return HIGH_QOS;
}
} else if (inv.getParameterClasses().length == 0) {
- // Just let it through. This is getOnlineRegions, etc.
+ // Just let it through. This is getOnlineRegions, etc.
} else if (inv.getParameterClasses()[0] == byte[].class) {
// first arg is byte array, so assume this is a regionname:
if (isMetaRegion((byte[]) inv.getParameters()[0])) {
@@ -499,10 +510,11 @@ public class HRegionServer extends RegionServer
* @throws IOException
* @throws InterruptedException
*/
- private void initializeZooKeeper() throws IOException, InterruptedException {
+ private void initializeZooKeeper() throws IOException, InterruptedException,
+ KeeperException {
// Open connection to zookeeper and set primary watcher
this.zooKeeper = new ZooKeeperWatcher(conf, REGIONSERVER + ":" +
- this.isa.getPort(), this);
+ this.isa.getPort(), this);
// Create the master address manager, register with zk, and start it. Then
// block until a master is available. No point in starting up if no master
@@ -521,6 +533,10 @@ public class HRegionServer extends RegionServer
this.catalogTracker = new CatalogTracker(this.zooKeeper, this.conf,
this, this.conf.getInt("hbase.regionserver.catalog.timeout", Integer.MAX_VALUE));
catalogTracker.start();
+
+ this.hfileArchiveTracker = new HFileArchiveTracker(zooKeeper,
+ new ServerHFileTableArchiveTracker(HRegionServer.this, zooKeeper));
+ this.hfileArchiveTracker.start();
}
/**
@@ -561,13 +577,13 @@ public class HRegionServer extends RegionServer
this.threadWakeFrequency * multiplier, this);
this.leases = new Leases((int) conf.getLong(
- HConstants.HBASE_REGIONSERVER_LEASE_PERIOD_KEY,
- HConstants.DEFAULT_HBASE_REGIONSERVER_LEASE_PERIOD),
- this.threadWakeFrequency);
+ HConstants.HBASE_REGIONSERVER_LEASE_PERIOD_KEY,
+ HConstants.DEFAULT_HBASE_REGIONSERVER_LEASE_PERIOD),
+ this.threadWakeFrequency);
// Create the thread for the ThriftServer.
if (conf.getBoolean("hbase.regionserver.export.thrift", false)) {
- thriftServer = new HRegionThriftServer((RegionServer)this, conf);
+ thriftServer = new HRegionThriftServer(this, conf);
thriftServer.start();
LOG.info("Started Thrift API from Region Server.");
}
@@ -576,6 +592,7 @@ public class HRegionServer extends RegionServer
/**
* The HRegionServer sticks in this loop until closed.
*/
+ @Override
@SuppressWarnings("deprecation")
public void run() {
try {
@@ -637,7 +654,9 @@ public class HRegionServer extends RegionServer
tryRegionServerReport();
lastMsg = System.currentTimeMillis();
}
- if (!this.stopped) this.sleeper.sleep();
+ if (!this.stopped) {
+ this.sleeper.sleep();
+ }
} // for
} catch (Throwable t) {
if (!checkOOME(t)) {
@@ -649,7 +668,9 @@ public class HRegionServer extends RegionServer
MBeanUtil.unregisterMBean(mxBean);
mxBean = null;
}
- if (this.thriftServer != null) this.thriftServer.shutdown();
+ if (this.thriftServer != null) {
+ this.thriftServer.shutdown();
+ }
this.leases.closeAfterLeasesExpire();
this.rpcServer.stop();
if (this.splitLogWorker != null) {
@@ -670,11 +691,18 @@ public class HRegionServer extends RegionServer
// Send interrupts to wake up threads if sleeping so they notice shutdown.
// TODO: Should we check they are alive? If OOME could have exited already
- if (this.cacheFlusher != null) this.cacheFlusher.interruptIfNecessary();
- if (this.compactSplitThread != null) this.compactSplitThread.interruptIfNecessary();
- if (this.hlogRoller != null) this.hlogRoller.interruptIfNecessary();
- if (this.compactionChecker != null)
+ if (this.cacheFlusher != null) {
+ this.cacheFlusher.interruptIfNecessary();
+ }
+ if (this.compactSplitThread != null) {
+ this.compactSplitThread.interruptIfNecessary();
+ }
+ if (this.hlogRoller != null) {
+ this.hlogRoller.interruptIfNecessary();
+ }
+ if (this.compactionChecker != null) {
this.compactionChecker.interrupt();
+ }
if (this.killed) {
// Just skip out w/o closing regions. Used when testing.
@@ -690,11 +718,13 @@ public class HRegionServer extends RegionServer
}
// Interrupt catalog tracker here in case any regions being opened out in
// handlers are stuck waiting on meta or root.
- if (this.catalogTracker != null) this.catalogTracker.stop();
+ if (this.catalogTracker != null) {
+ this.catalogTracker.stop();
+ }
if (!this.killed && this.fsOk) {
waitOnAllRegionsToClose(abortRequested);
LOG.info("stopping server " + this.serverNameFromMasterPOV +
- "; all regions closed.");
+ "; all regions closed.");
}
//fsOk flag may be changed when closing regions throws exception.
@@ -715,7 +745,7 @@ public class HRegionServer extends RegionServer
}
this.zooKeeper.close();
LOG.info("stopping server " + this.serverNameFromMasterPOV +
- "; zookeeper connection closed.");
+ "; zookeeper connection closed.");
if (!killed) {
join();
@@ -724,7 +754,9 @@ public class HRegionServer extends RegionServer
}
private boolean areAllUserRegionsOffline() {
- if (getNumberOfOnlineRegions() > 2) return false;
+ if (getNumberOfOnlineRegions() > 2) {
+ return false;
+ }
boolean allUserRegionsOffline = true;
for (Map.Entry e: this.onlineRegions.entrySet()) {
if (!e.getValue().getRegionInfo().isMetaRegion()) {
@@ -736,7 +768,7 @@ public class HRegionServer extends RegionServer
}
void tryRegionServerReport()
- throws IOException {
+ throws IOException {
HServerLoad hsl = buildServerLoad();
// Why we do this?
this.requestCount.set(0);
@@ -759,12 +791,12 @@ public class HRegionServer extends RegionServer
HServerLoad buildServerLoad() {
Collection regions = getOnlineRegionsLocalContext();
TreeMap regionLoads =
- new TreeMap(Bytes.BYTES_COMPARATOR);
+ new TreeMap(Bytes.BYTES_COMPARATOR);
for (HRegion region: regions) {
regionLoads.put(region.getRegionName(), createRegionLoad(region));
}
MemoryUsage memory =
- ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
+ ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
return new HServerLoad(requestCount.get(),(int)metrics.getRequests(),
(int)(memory.getUsed() / 1024 / 1024),
(int) (memory.getMax() / 1024 / 1024), regionLoads,
@@ -774,7 +806,9 @@ public class HRegionServer extends RegionServer
String getOnlineRegionsAsPrintableString() {
StringBuilder sb = new StringBuilder();
for (HRegion r: this.onlineRegions.values()) {
- if (sb.length() > 0) sb.append(", ");
+ if (sb.length() > 0) {
+ sb.append(", ");
+ }
sb.append(r.getRegionInfo().getEncodedName());
}
return sb.toString();
@@ -859,7 +893,7 @@ public class HRegionServer extends RegionServer
* @param c Extra configuration.
*/
protected void handleReportForDutyResponse(final MapWritable c)
- throws IOException {
+ throws IOException {
try {
for (Map.Entry e :c.entrySet()) {
String key = e.getKey().toString();
@@ -869,8 +903,8 @@ public class HRegionServer extends RegionServer
this.serverNameFromMasterPOV = new ServerName(hostnameFromMasterPOV,
this.isa.getPort(), this.startcode);
LOG.info("Master passed us hostname to use. Was=" +
- this.isa.getHostName() + ", Now=" +
- this.serverNameFromMasterPOV.getHostname());
+ this.isa.getHostName() + ", Now=" +
+ this.serverNameFromMasterPOV.getHostname());
continue;
}
String value = e.getValue().toString();
@@ -884,7 +918,7 @@ public class HRegionServer extends RegionServer
// config param for task trackers, but we can piggyback off of it.
if (this.conf.get("mapred.task.id") == null) {
this.conf.set("mapred.task.id", "hb_rs_" +
- this.serverNameFromMasterPOV.toString());
+ this.serverNameFromMasterPOV.toString());
}
// Set our ephemeral znode up in zookeeper now we have a name.
createMyEphemeralNode();
@@ -932,6 +966,7 @@ public class HRegionServer extends RegionServer
ZKUtil.deleteNode(this.zooKeeper, getMyEphemeralNodePath());
}
+ @Override
public RegionServerAccounting getRegionServerAccounting() {
return regionServerAccounting;
}
@@ -974,19 +1009,19 @@ public class HRegionServer extends RegionServer
(int) (store.getStorefilesIndexSize() / 1024);
totalStaticIndexSizeKB +=
- (int) (store.getTotalStaticIndexSize() / 1024);
+ (int) (store.getTotalStaticIndexSize() / 1024);
totalStaticBloomSizeKB +=
- (int) (store.getTotalStaticBloomSize() / 1024);
+ (int) (store.getTotalStaticBloomSize() / 1024);
}
}
return new HServerLoad.RegionLoad(name, stores, storefiles,
- storeUncompressedSizeMB,
- storefileSizeMB, memstoreSizeMB, storefileIndexSizeMB, rootIndexSizeKB,
- totalStaticIndexSizeKB, totalStaticBloomSizeKB,
- (int) r.readRequestsCount.get(), (int) r.writeRequestsCount.get(),
- totalCompactingKVs, currentCompactedKVs,
- r.getCoprocessorHost().getCoprocessors());
+ storeUncompressedSizeMB,
+ storefileSizeMB, memstoreSizeMB, storefileIndexSizeMB, rootIndexSizeKB,
+ totalStaticIndexSizeKB, totalStaticBloomSizeKB,
+ (int) r.readRequestsCount.get(), (int) r.writeRequestsCount.get(),
+ totalCompactingKVs, currentCompactedKVs,
+ r.getCoprocessorHost().getCoprocessors());
}
/**
@@ -1008,7 +1043,7 @@ public class HRegionServer extends RegionServer
private final static int DEFAULT_PRIORITY = Integer.MAX_VALUE;
CompactionChecker(final HRegionServer h, final int sleepTime,
- final Stoppable stopper) {
+ final Stoppable stopper) {
super("CompactionChecker", sleepTime, h);
this.instance = h;
LOG.info("Runs every " + StringUtils.formatTime(sleepTime));
@@ -1017,15 +1052,16 @@ public class HRegionServer extends RegionServer
* If not set, the compaction will use default priority.
*/
this.majorCompactPriority = this.instance.conf.
- getInt("hbase.regionserver.compactionChecker.majorCompactPriority",
- DEFAULT_PRIORITY);
+ getInt("hbase.regionserver.compactionChecker.majorCompactPriority",
+ DEFAULT_PRIORITY);
}
@Override
protected void chore() {
for (HRegion r : this.instance.onlineRegions.values()) {
- if (r == null)
+ if (r == null) {
continue;
+ }
for (Store s : r.getStores().values()) {
try {
if (s.needsCompaction()) {
@@ -1036,9 +1072,9 @@ public class HRegionServer extends RegionServer
if (majorCompactPriority == DEFAULT_PRIORITY ||
majorCompactPriority > r.getCompactPriority()) {
this.instance.compactSplitThread.requestCompaction(r, s,
- getName() + " requests major compaction; use default priority");
+ getName() + " requests major compaction; use default priority");
} else {
- this.instance.compactSplitThread.requestCompaction(r, s,
+ this.instance.compactSplitThread.requestCompaction(r, s,
getName() + " requests major compaction; use configured priority",
this.majorCompactPriority);
}
@@ -1072,10 +1108,12 @@ public class HRegionServer extends RegionServer
final Path oldLogDir = new Path(rootDir, HConstants.HREGION_OLDLOGDIR_NAME);
Path logdir = new Path(rootDir,
HLog.getHLogDirectoryName(this.serverNameFromMasterPOV.toString()));
- if (LOG.isDebugEnabled()) LOG.debug("logdir=" + logdir);
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("logdir=" + logdir);
+ }
if (this.fs.exists(logdir)) {
throw new RegionServerRunningException("Region server has already " +
- "created directory at " + this.serverNameFromMasterPOV.toString());
+ "created directory at " + this.serverNameFromMasterPOV.toString());
}
// Instantiate replication manager if replication enabled. Pass it the
@@ -1144,7 +1182,7 @@ public class HRegionServer extends RegionServer
int writeRequestsCount = 0;
long storefileIndexSize = 0;
HDFSBlocksDistribution hdfsBlocksDistribution =
- new HDFSBlocksDistribution();
+ new HDFSBlocksDistribution();
long totalStaticIndexSize = 0;
long totalStaticBloomSize = 0;
@@ -1164,51 +1202,51 @@ public class HRegionServer extends RegionServer
synchronized (r.stores) {
stores += r.stores.size();
for (Map.Entry ee : r.stores.entrySet()) {
- final Store store = ee.getValue();
- final SchemaMetrics schemaMetrics = store.getSchemaMetrics();
-
- {
- long tmpStorefiles = store.getStorefilesCount();
- schemaMetrics.accumulateStoreMetric(tempVals,
- StoreMetricType.STORE_FILE_COUNT, tmpStorefiles);
- storefiles += tmpStorefiles;
- }
+ final Store store = ee.getValue();
+ final SchemaMetrics schemaMetrics = store.getSchemaMetrics();
+ {
+ long tmpStorefiles = store.getStorefilesCount();
+ schemaMetrics.accumulateStoreMetric(tempVals,
+ StoreMetricType.STORE_FILE_COUNT, tmpStorefiles);
+ storefiles += tmpStorefiles;
+ }
- {
- long tmpStorefileIndexSize = store.getStorefilesIndexSize();
- schemaMetrics.accumulateStoreMetric(tempVals,
- StoreMetricType.STORE_FILE_INDEX_SIZE,
- (long) (tmpStorefileIndexSize / (1024.0 * 1024)));
- storefileIndexSize += tmpStorefileIndexSize;
- }
- {
- long tmpStorefilesSize = store.getStorefilesSize();
- schemaMetrics.accumulateStoreMetric(tempVals,
- StoreMetricType.STORE_FILE_SIZE_MB,
- (long) (tmpStorefilesSize / (1024.0 * 1024)));
- }
+ {
+ long tmpStorefileIndexSize = store.getStorefilesIndexSize();
+ schemaMetrics.accumulateStoreMetric(tempVals,
+ StoreMetricType.STORE_FILE_INDEX_SIZE,
+ (long) (tmpStorefileIndexSize / (1024.0 * 1024)));
+ storefileIndexSize += tmpStorefileIndexSize;
+ }
- {
- long tmpStaticBloomSize = store.getTotalStaticBloomSize();
- schemaMetrics.accumulateStoreMetric(tempVals,
- StoreMetricType.STATIC_BLOOM_SIZE_KB,
- (long) (tmpStaticBloomSize / 1024.0));
- totalStaticBloomSize += tmpStaticBloomSize;
- }
+ {
+ long tmpStorefilesSize = store.getStorefilesSize();
+ schemaMetrics.accumulateStoreMetric(tempVals,
+ StoreMetricType.STORE_FILE_SIZE_MB,
+ (long) (tmpStorefilesSize / (1024.0 * 1024)));
+ }
- {
- long tmpStaticIndexSize = store.getTotalStaticIndexSize();
- schemaMetrics.accumulateStoreMetric(tempVals,
- StoreMetricType.STATIC_INDEX_SIZE_KB,
- (long) (tmpStaticIndexSize / 1024.0));
- totalStaticIndexSize += tmpStaticIndexSize;
- }
+ {
+ long tmpStaticBloomSize = store.getTotalStaticBloomSize();
+ schemaMetrics.accumulateStoreMetric(tempVals,
+ StoreMetricType.STATIC_BLOOM_SIZE_KB,
+ (long) (tmpStaticBloomSize / 1024.0));
+ totalStaticBloomSize += tmpStaticBloomSize;
+ }
+ {
+ long tmpStaticIndexSize = store.getTotalStaticIndexSize();
schemaMetrics.accumulateStoreMetric(tempVals,
- StoreMetricType.MEMSTORE_SIZE_MB,
- (long) (store.getMemStoreSize() / (1024.0 * 1024)));
+ StoreMetricType.STATIC_INDEX_SIZE_KB,
+ (long) (tmpStaticIndexSize / 1024.0));
+ totalStaticIndexSize += tmpStaticIndexSize;
+ }
+
+ schemaMetrics.accumulateStoreMetric(tempVals,
+ StoreMetricType.MEMSTORE_SIZE_MB,
+ (long) (store.getMemStoreSize() / (1024.0 * 1024)));
}
}
@@ -1223,19 +1261,19 @@ public class HRegionServer extends RegionServer
this.metrics.storefiles.set(storefiles);
this.metrics.memstoreSizeMB.set((int) (memstoreSize / (1024 * 1024)));
this.metrics.storefileIndexSizeMB.set(
- (int) (storefileIndexSize / (1024 * 1024)));
+ (int) (storefileIndexSize / (1024 * 1024)));
this.metrics.rootIndexSizeKB.set(
- (int) (storefileIndexSize / 1024));
+ (int) (storefileIndexSize / 1024));
this.metrics.totalStaticIndexSizeKB.set(
- (int) (totalStaticIndexSize / 1024));
+ (int) (totalStaticIndexSize / 1024));
this.metrics.totalStaticBloomSizeKB.set(
- (int) (totalStaticBloomSize / 1024));
+ (int) (totalStaticBloomSize / 1024));
this.metrics.readRequestsCount.set(readRequestsCount);
this.metrics.writeRequestsCount.set(writeRequestsCount);
this.metrics.compactionQueueSize.set(compactSplitThread
- .getCompactionQueueSize());
+ .getCompactionQueueSize());
this.metrics.flushQueueSize.set(cacheFlusher
- .getFlushQueueSize());
+ .getFlushQueueSize());
BlockCache blockCache = cacheConfig.getBlockCache();
if (blockCache != null) {
@@ -1297,6 +1335,7 @@ public class HRegionServer extends RegionServer
private void startServiceThreads() throws IOException {
String n = Thread.currentThread().getName();
UncaughtExceptionHandler handler = new UncaughtExceptionHandler() {
+ @Override
public void uncaughtException(Thread t, Throwable e) {
abort("Uncaught exception in service thread " + t.getName(), e);
}
@@ -1347,7 +1386,7 @@ public class HRegionServer extends RegionServer
// Create the log splitting worker and start it
this.splitLogWorker = new SplitLogWorker(this.zooKeeper,
- this.getConfiguration(), this.getServerName().toString());
+ this.getConfiguration(), this.getServerName().toString());
splitLogWorker.start();
}
@@ -1359,11 +1398,13 @@ public class HRegionServer extends RegionServer
private int putUpWebUI() throws IOException {
int port = this.conf.getInt(HConstants.REGIONSERVER_INFO_PORT, 60030);
// -1 is for disabling info server
- if (port < 0) return port;
+ if (port < 0) {
+ return port;
+ }
String addr = this.conf.get("hbase.regionserver.info.bindAddress", "0.0.0.0");
// check if auto port bind enabled
boolean auto = this.conf.getBoolean(HConstants.REGIONSERVER_INFO_PORT_AUTO,
- false);
+ false);
while (true) {
try {
this.infoServer = new InfoServer("regionserver", addr, port, false, this.conf);
@@ -1423,14 +1464,14 @@ public class HRegionServer extends RegionServer
public void waitForServerOnline(){
while (!isOnline() && !isStopped()){
- sleeper.sleep();
+ sleeper.sleep();
}
}
@Override
public void postOpenDeployTasks(final HRegion r, final CatalogTracker ct,
final boolean daughter)
- throws KeeperException, IOException {
+ throws KeeperException, IOException {
LOG.info("Post open deploy tasks for region=" + r.getRegionNameAsString() +
", daughter=" + daughter);
// Do checks to see if we need to compact (references or too many files)
@@ -1442,7 +1483,7 @@ public class HRegionServer extends RegionServer
// Update ZK, ROOT or META
if (r.getRegionInfo().isRootRegion()) {
RootRegionTracker.setRootLocation(getZooKeeper(),
- this.serverNameFromMasterPOV);
+ this.serverNameFromMasterPOV);
} else if (r.getRegionInfo().isMetaRegion()) {
MetaEditor.updateMetaLocation(ct, r.getRegionInfo(),
this.serverNameFromMasterPOV);
@@ -1457,7 +1498,7 @@ public class HRegionServer extends RegionServer
}
}
LOG.info("Done with post open deploy task for region=" +
- r.getRegionNameAsString() + ", daughter=" + daughter);
+ r.getRegionNameAsString() + ", daughter=" + daughter);
}
@@ -1484,6 +1525,7 @@ public class HRegionServer extends RegionServer
* @param cause
* the exception that caused the abort, or null
*/
+ @Override
public void abort(String reason, Throwable cause) {
String msg = "ABORTING region server " + this + ": " + reason;
if (cause != null) {
@@ -1508,7 +1550,7 @@ public class HRegionServer extends RegionServer
}
if (hbaseMaster != null) {
hbaseMaster.reportRSFatalError(
- this.serverNameFromMasterPOV.getVersionedBytes(), msg);
+ this.serverNameFromMasterPOV.getVersionedBytes(), msg);
}
} catch (Throwable t) {
LOG.warn("Unable to report fatal error to master", t);
@@ -1523,6 +1565,7 @@ public class HRegionServer extends RegionServer
abort(reason, null);
}
+ @Override
public boolean isAborted() {
return this.abortRequested;
}
@@ -1550,7 +1593,9 @@ public class HRegionServer extends RegionServer
if (this.compactSplitThread != null) {
this.compactSplitThread.join();
}
- if (this.service != null) this.service.shutdown();
+ if (this.service != null) {
+ this.service.shutdown();
+ }
if (this.replicationSourceHandler != null &&
this.replicationSourceHandler == this.replicationSinkHandler) {
this.replicationSourceHandler.stopReplicationService();
@@ -1607,35 +1652,35 @@ public class HRegionServer extends RegionServer
}
InetSocketAddress isa =
- new InetSocketAddress(masterServerName.getHostname(), masterServerName.getPort());
+ new InetSocketAddress(masterServerName.getHostname(), masterServerName.getPort());
LOG.info("Attempting connect to Master server at " +
- this.masterAddressManager.getMasterAddress());
+ this.masterAddressManager.getMasterAddress());
try {
// Do initial RPC setup. The final argument indicates that the RPC
// should retry indefinitely.
master = (HMasterRegionInterface) HBaseRPC.waitForProxy(
- HMasterRegionInterface.class, HMasterRegionInterface.VERSION,
- isa, this.conf, -1,
- this.rpcTimeout, this.rpcTimeout);
+ HMasterRegionInterface.class, HMasterRegionInterface.VERSION,
+ isa, this.conf, -1,
+ this.rpcTimeout, this.rpcTimeout);
} catch (IOException e) {
e = e instanceof RemoteException ?
((RemoteException)e).unwrapRemoteException() : e;
- if (e instanceof ServerNotRunningYetException) {
- if (System.currentTimeMillis() > (previousLogTime+1000)){
- LOG.info("Master isn't available yet, retrying");
- previousLogTime = System.currentTimeMillis();
- }
- } else {
- if (System.currentTimeMillis() > (previousLogTime + 1000)) {
- LOG.warn("Unable to connect to master. Retrying. Error was:", e);
- previousLogTime = System.currentTimeMillis();
- }
- }
- try {
- Thread.sleep(200);
- } catch (InterruptedException ignored) {
- }
+ if (e instanceof ServerNotRunningYetException) {
+ if (System.currentTimeMillis() > (previousLogTime+1000)){
+ LOG.info("Master isn't available yet, retrying");
+ previousLogTime = System.currentTimeMillis();
+ }
+ } else {
+ if (System.currentTimeMillis() > (previousLogTime + 1000)) {
+ LOG.warn("Unable to connect to master. Retrying. Error was:", e);
+ previousLogTime = System.currentTimeMillis();
+ }
+ }
+ try {
+ Thread.sleep(200);
+ } catch (InterruptedException ignored) {
+ }
}
}
LOG.info("Connected to master at " + isa);
@@ -1661,11 +1706,13 @@ public class HRegionServer extends RegionServer
private MapWritable reportForDuty() throws IOException {
MapWritable result = null;
ServerName masterServerName = getMaster();
- if (masterServerName == null) return result;
+ if (masterServerName == null) {
+ return result;
+ }
try {
this.requestCount.set(0);
LOG.info("Telling master at " + masterServerName + " that we are up " +
- "with port=" + this.isa.getPort() + ", startcode=" + this.startcode);
+ "with port=" + this.isa.getPort() + ", startcode=" + this.startcode);
long now = EnvironmentEdgeManager.currentTimeMillis();
int port = this.isa.getPort();
result = this.hbaseMaster.regionServerStartup(port, this.startcode, now);
@@ -1703,13 +1750,19 @@ public class HRegionServer extends RegionServer
} else if (hri.isMetaRegion()) {
meta = e.getValue();
}
- if (meta != null && root != null) break;
+ if (meta != null && root != null) {
+ break;
+ }
}
} finally {
this.lock.writeLock().unlock();
}
- if (meta != null) closeRegion(meta.getRegionInfo(), abort, false);
- if (root != null) closeRegion(root.getRegionInfo(), abort, false);
+ if (meta != null) {
+ closeRegion(meta.getRegionInfo(), abort, false);
+ }
+ if (root != null) {
+ closeRegion(root.getRegionInfo(), abort, false);
+ }
}
/**
@@ -1736,12 +1789,13 @@ public class HRegionServer extends RegionServer
@Override
@QosPriority(priority=HIGH_QOS)
public HRegionInfo getRegionInfo(final byte[] regionName)
- throws NotServingRegionException, IOException {
+ throws NotServingRegionException, IOException {
checkOpen();
requestCount.incrementAndGet();
return getRegion(regionName).getRegionInfo();
}
+ @Override
public Result getClosestRowBefore(final byte[] regionName, final byte[] row,
final byte[] family) throws IOException {
checkOpen();
@@ -1759,6 +1813,7 @@ public class HRegionServer extends RegionServer
}
/** {@inheritDoc} */
+ @Override
public Result get(byte[] regionName, Get get) throws IOException {
checkOpen();
final long startTime = System.nanoTime();
@@ -1773,6 +1828,7 @@ public class HRegionServer extends RegionServer
}
}
+ @Override
public boolean exists(byte[] regionName, Get get) throws IOException {
checkOpen();
requestCount.incrementAndGet();
@@ -1796,6 +1852,7 @@ public class HRegionServer extends RegionServer
}
}
+ @Override
public void put(final byte[] regionName, final Put put) throws IOException {
if (put.getRow() == null) {
throw new IllegalArgumentException("update has null row");
@@ -1818,6 +1875,7 @@ public class HRegionServer extends RegionServer
}
}
+ @Override
public int put(final byte[] regionName, final List puts)
throws IOException {
checkOpen();
@@ -1890,6 +1948,7 @@ public class HRegionServer extends RegionServer
* @throws IOException
* @return true if the new put was execute, false otherwise
*/
+ @Override
public boolean checkAndPut(final byte[] regionName, final byte[] row,
final byte[] family, final byte[] qualifier, final byte[] value,
final Put put) throws IOException {
@@ -1903,8 +1962,8 @@ public class HRegionServer extends RegionServer
WritableByteArrayComparable comparator = new BinaryComparator(value);
if (region.getCoprocessorHost() != null) {
Boolean result = region.getCoprocessorHost()
- .preCheckAndPut(row, family, qualifier, CompareOp.EQUAL, comparator,
- put);
+ .preCheckAndPut(row, family, qualifier, CompareOp.EQUAL, comparator,
+ put);
if (result != null) {
return result.booleanValue();
}
@@ -1931,10 +1990,11 @@ public class HRegionServer extends RegionServer
* @throws IOException
* @return true if the new put was execute, false otherwise
*/
+ @Override
public boolean checkAndPut(final byte[] regionName, final byte[] row,
final byte[] family, final byte[] qualifier, final CompareOp compareOp,
final WritableByteArrayComparable comparator, final Put put)
- throws IOException {
+ throws IOException {
checkOpen();
if (regionName == null) {
throw new IOException("Invalid arguments to checkAndPut "
@@ -1944,7 +2004,7 @@ public class HRegionServer extends RegionServer
Integer lock = getLockFromId(put.getLockId());
if (region.getCoprocessorHost() != null) {
Boolean result = region.getCoprocessorHost()
- .preCheckAndPut(row, family, qualifier, compareOp, comparator, put);
+ .preCheckAndPut(row, family, qualifier, compareOp, comparator, put);
if (result != null) {
return result.booleanValue();
}
@@ -1970,6 +2030,7 @@ public class HRegionServer extends RegionServer
* @throws IOException
* @return true if the new put was execute, false otherwise
*/
+ @Override
public boolean checkAndDelete(final byte[] regionName, final byte[] row,
final byte[] family, final byte[] qualifier, final byte[] value,
final Delete delete) throws IOException {
@@ -2000,38 +2061,40 @@ public class HRegionServer extends RegionServer
@Override
public List getStoreFileList(byte[] regionName, byte[] columnFamily)
- throws IllegalArgumentException {
+ throws IllegalArgumentException {
return getStoreFileList(regionName, new byte[][]{columnFamily});
}
@Override
public List getStoreFileList(byte[] regionName, byte[][] columnFamilies)
- throws IllegalArgumentException {
+ throws IllegalArgumentException {
HRegion region = getOnlineRegion(regionName);
if (region == null) {
throw new IllegalArgumentException("No region: " + new String(regionName)
- + " available");
+ + " available");
}
return region.getStoreFileList(columnFamilies);
}
+ @Override
public List getStoreFileList(byte[] regionName)
- throws IllegalArgumentException {
+ throws IllegalArgumentException {
HRegion region = getOnlineRegion(regionName);
if (region == null) {
throw new IllegalArgumentException("No region: " + new String(regionName)
- + " available");
+ + " available");
}
Set columnFamilies = region.getStores().keySet();
int nCF = columnFamilies.size();
return region.getStoreFileList(columnFamilies.toArray(new byte[nCF][]));
}
-
- /**
- * Flushes the given region
- */
+
+ /**
+ * Flushes the given region
+ */
+ @Override
public void flushRegion(byte[] regionName)
- throws IllegalArgumentException, IOException {
+ throws IllegalArgumentException, IOException {
HRegion region = getOnlineRegion(regionName);
if (region == null) {
throw new IllegalArgumentException("No region : " + new String(regionName)
@@ -2040,23 +2103,27 @@ public class HRegionServer extends RegionServer
region.flushcache();
}
- /**
+ /**
* Flushes the given region if lastFlushTime < ifOlderThanTS
*/
- public void flushRegion(byte[] regionName, long ifOlderThanTS)
- throws IllegalArgumentException, IOException {
- HRegion region = getOnlineRegion(regionName);
- if (region == null) {
- throw new IllegalArgumentException("No region : " + new String(regionName)
- + " available");
- }
- if (region.getLastFlushTime() < ifOlderThanTS) region.flushcache();
- }
+ @Override
+ public void flushRegion(byte[] regionName, long ifOlderThanTS)
+ throws IllegalArgumentException, IOException {
+ HRegion region = getOnlineRegion(regionName);
+ if (region == null) {
+ throw new IllegalArgumentException("No region : " + new String(regionName)
+ + " available");
+ }
+ if (region.getLastFlushTime() < ifOlderThanTS) {
+ region.flushcache();
+ }
+ }
/**
* Gets last flush time for the given region
* @return the last flush time for a region
*/
+ @Override
public long getLastFlushTime(byte[] regionName) {
HRegion region = getOnlineRegion(regionName);
if (region == null) {
@@ -2065,7 +2132,7 @@ public class HRegionServer extends RegionServer
}
return region.getLastFlushTime();
}
-
+
/**
*
* @param regionName
@@ -2078,38 +2145,40 @@ public class HRegionServer extends RegionServer
* @throws IOException
* @return true if the new put was execute, false otherwise
*/
+ @Override
public boolean checkAndDelete(final byte[] regionName, final byte[] row,
final byte[] family, final byte[] qualifier, final CompareOp compareOp,
final WritableByteArrayComparable comparator, final Delete delete)
- throws IOException {
+ throws IOException {
checkOpen();
if (regionName == null) {
throw new IOException("Invalid arguments to checkAndDelete "
- + "regionName is null");
+ + "regionName is null");
}
HRegion region = getRegion(regionName);
Integer lock = getLockFromId(delete.getLockId());
if (region.getCoprocessorHost() != null) {
Boolean result = region.getCoprocessorHost().preCheckAndDelete(row,
family, qualifier, compareOp, comparator, delete);
- if (result != null) {
- return result.booleanValue();
- }
+ if (result != null) {
+ return result.booleanValue();
+ }
}
boolean result = checkAndMutate(regionName, row, family, qualifier,
compareOp, comparator, delete, lock);
- if (region.getCoprocessorHost() != null) {
- result = region.getCoprocessorHost().postCheckAndDelete(row, family,
- qualifier, compareOp, comparator, delete, result);
- }
- return result;
- }
+ if (region.getCoprocessorHost() != null) {
+ result = region.getCoprocessorHost().postCheckAndDelete(row, family,
+ qualifier, compareOp, comparator, delete, result);
+ }
+ return result;
+ }
//
// remote scanner interface
//
+ @Override
public long openScanner(byte[] regionName, Scan scan) throws IOException {
checkOpen();
NullPointerException npe = null;
@@ -2135,7 +2204,7 @@ public class HRegionServer extends RegionServer
}
if (r.getCoprocessorHost() != null) {
RegionScanner savedScanner = r.getCoprocessorHost().postScannerOpen(
- scan, s);
+ scan, s);
if (savedScanner == null) {
LOG.warn("PostScannerOpen impl returning null. "
+ "Check the RegionObserver implementation.");
@@ -2149,6 +2218,7 @@ public class HRegionServer extends RegionServer
}
}
+ @Override
public Result next(final long scannerId) throws IOException {
Result[] res = next(scannerId, 1);
if (res == null || res.length == 0) {
@@ -2157,10 +2227,13 @@ public class HRegionServer extends RegionServer
return res[0];
}
+ @Override
public Result[] next(final long scannerId, int nbRows) throws IOException {
String scannerName = String.valueOf(scannerId);
RegionScanner s = this.scanners.get(scannerName);
- if (s == null) throw new UnknownScannerException("Name: " + scannerName);
+ if (s == null) {
+ throw new UnknownScannerException("Name: " + scannerName);
+ }
try {
checkOpen();
} catch (IOException e) {
@@ -2170,7 +2243,7 @@ public class HRegionServer extends RegionServer
this.leases.cancelLease(scannerName);
} catch (LeaseException le) {
LOG.info("Server shutting down and client tried to access missing scanner " +
- scannerName);
+ scannerName);
}
throw e;
}
@@ -2187,7 +2260,7 @@ public class HRegionServer extends RegionServer
HRegion region = getRegion(s.getRegionInfo().getRegionName());
if (region != null && region.getCoprocessorHost() != null) {
Boolean bypass = region.getCoprocessorHost().preScannerNext(s,
- results, nbRows);
+ results, nbRows);
if (!results.isEmpty()) {
for (Result r : results) {
for (KeyValue kv : r.raw()) {
@@ -2237,11 +2310,14 @@ public class HRegionServer extends RegionServer
// We're done. On way out readd the above removed lease. Adding resets
// expiration time on lease.
if (this.scanners.containsKey(scannerName)) {
- if (lease != null) this.leases.addLease(lease);
+ if (lease != null) {
+ this.leases.addLease(lease);
+ }
}
}
}
+ @Override
public void close(final long scannerId) throws IOException {
try {
checkOpen();
@@ -2277,6 +2353,7 @@ public class HRegionServer extends RegionServer
//
// Methods that do the actual work for the remote API
//
+ @Override
public void delete(final byte[] regionName, final Delete delete)
throws IOException {
checkOpen();
@@ -2297,6 +2374,7 @@ public class HRegionServer extends RegionServer
}
}
+ @Override
public int delete(final byte[] regionName, final List deletes)
throws IOException {
checkOpen();
@@ -2329,6 +2407,7 @@ public class HRegionServer extends RegionServer
return -1;
}
+ @Override
public long lockRow(byte[] regionName, byte[] row) throws IOException {
checkOpen();
NullPointerException npe = null;
@@ -2381,7 +2460,7 @@ public class HRegionServer extends RegionServer
region.releaseRowLock(r);
this.leases.cancelLease(lockName);
LOG.debug("Row lock " + lockId
- + " has been explicitly released by client");
+ + " has been explicitly released by client");
} catch (Throwable t) {
throw convertThrowableToIOE(cleanup(t));
}
@@ -2405,7 +2484,7 @@ public class HRegionServer extends RegionServer
@Override
@QosPriority(priority=HIGH_QOS)
public RegionOpeningState openRegion(HRegionInfo region)
- throws IOException {
+ throws IOException {
return openRegion(region, -1);
}
@Override
@@ -2419,14 +2498,14 @@ public class HRegionServer extends RegionServer
// See HBASE-5094. Cross check with META if still this RS is owning the
// region.
Pair p = MetaReader.getRegion(
- this.catalogTracker, region.getRegionName());
+ this.catalogTracker, region.getRegionName());
if (this.getServerName().equals(p.getSecond())) {
LOG.warn("Attempted open of " + region.getEncodedName()
- + " but already online on this server");
+ + " but already online on this server");
return RegionOpeningState.ALREADY_OPENED;
} else {
LOG.warn("The region " + region.getEncodedName()
- + " is online on this server but META does not have this server.");
+ + " is online on this server but META does not have this server.");
this.removeFromOnlineRegions(region.getEncodedName());
}
}
@@ -2438,13 +2517,13 @@ public class HRegionServer extends RegionServer
// Need to pass the expected version in the constructor.
if (region.isRootRegion()) {
this.service.submit(new OpenRootHandler(this, this, region, htd,
- versionOfOfflineNode));
+ versionOfOfflineNode));
} else if (region.isMetaRegion()) {
this.service.submit(new OpenMetaHandler(this, this, region, htd,
- versionOfOfflineNode));
+ versionOfOfflineNode));
} else {
this.service.submit(new OpenRegionHandler(this, this, region, htd,
- versionOfOfflineNode));
+ versionOfOfflineNode));
}
return RegionOpeningState.OPENED;
}
@@ -2452,47 +2531,49 @@ public class HRegionServer extends RegionServer
@Override
@QosPriority(priority=HIGH_QOS)
public void openRegions(List regions)
- throws IOException {
+ throws IOException {
checkOpen();
LOG.info("Received request to open " + regions.size() + " region(s)");
- for (HRegionInfo region: regions) openRegion(region);
+ for (HRegionInfo region: regions) {
+ openRegion(region);
+ }
}
@Override
@QosPriority(priority=HIGH_QOS)
public boolean closeRegion(HRegionInfo region)
- throws IOException {
+ throws IOException {
return closeRegion(region, true, -1);
}
@Override
@QosPriority(priority=HIGH_QOS)
public boolean closeRegion(final HRegionInfo region,
- final int versionOfClosingNode)
- throws IOException {
+ final int versionOfClosingNode)
+ throws IOException {
return closeRegion(region, true, versionOfClosingNode);
}
@Override
@QosPriority(priority=HIGH_QOS)
public boolean closeRegion(HRegionInfo region, final boolean zk)
- throws IOException {
+ throws IOException {
return closeRegion(region, zk, -1);
}
@QosPriority(priority=HIGH_QOS)
protected boolean closeRegion(HRegionInfo region, final boolean zk,
- final int versionOfClosingNode)
- throws IOException {
+ final int versionOfClosingNode)
+ throws IOException {
checkOpen();
LOG.info("Received close region: " + region.getRegionNameAsString() +
". Version of ZK closing node:" + versionOfClosingNode);
boolean hasit = this.onlineRegions.containsKey(region.getEncodedName());
if (!hasit) {
LOG.warn("Received close for region we are not serving; " +
- region.getEncodedName());
+ region.getEncodedName());
throw new NotServingRegionException("Received close for "
- + region.getRegionNameAsString() + " but we are not serving it");
+ + region.getRegionNameAsString() + " but we are not serving it");
}
checkIfRegionInTransition(region, CLOSE);
return closeRegion(region, false, zk, versionOfClosingNode);
@@ -2501,7 +2582,7 @@ public class HRegionServer extends RegionServer
@Override
@QosPriority(priority=HIGH_QOS)
public boolean closeRegion(byte[] encodedRegionName, boolean zk)
- throws IOException {
+ throws IOException {
return closeRegion(encodedRegionName, false, zk);
}
@@ -2524,7 +2605,7 @@ public class HRegionServer extends RegionServer
return closeRegion(region.getRegionInfo(), abort, zk);
}
LOG.error("The specified region name" + encodedRegionNameStr
- + " does not exist to close the region.");
+ + " does not exist to close the region.");
return false;
}
@@ -2579,6 +2660,7 @@ public class HRegionServer extends RegionServer
/**
* @return true if a stop has been requested.
*/
+ @Override
public boolean isStopped() {
return this.stopped;
}
@@ -2592,6 +2674,7 @@ public class HRegionServer extends RegionServer
*
* @return the configuration
*/
+ @Override
public Configuration getConfiguration() {
return conf;
}
@@ -2627,12 +2710,14 @@ public class HRegionServer extends RegionServer
* @throws IOException
*/
public byte [] getRegionStats(final String encodedRegionName)
- throws IOException {
+ throws IOException {
HRegion r = null;
synchronized (this.onlineRegions) {
r = this.onlineRegions.get(encodedRegionName);
}
- if (r == null) return null;
+ if (r == null) {
+ return null;
+ }
ObjectMapper mapper = new ObjectMapper();
int stores = 0;
int storefiles = 0;
@@ -2699,6 +2784,7 @@ public class HRegionServer extends RegionServer
// we'll sort the regions in reverse
SortedMap sortedRegions = new TreeMap(
new Comparator() {
+ @Override
public int compare(Long a, Long b) {
return -1 * a.compareTo(b);
}
@@ -2723,6 +2809,7 @@ public class HRegionServer extends RegionServer
}
/** @return reference to FlushRequester */
+ @Override
public FlushRequester getFlushRequester() {
return this.cacheFlusher;
}
@@ -2752,7 +2839,7 @@ public class HRegionServer extends RegionServer
@QosPriority(priority=HIGH_QOS)
public ProtocolSignature getProtocolSignature(
String protocol, long version, int clientMethodsHashCode)
- throws IOException {
+ throws IOException {
if (protocol.equals(HRegionInterface.class.getName())) {
return new ProtocolSignature(HRegionInterface.VERSION, null);
} else if (protocol.equals(ClientProtocol.class.getName())) {
@@ -2766,7 +2853,7 @@ public class HRegionServer extends RegionServer
@Override
@QosPriority(priority=HIGH_QOS)
public long getProtocolVersion(final String protocol, final long clientVersion)
- throws IOException {
+ throws IOException {
if (protocol.equals(HRegionInterface.class.getName())) {
return HRegionInterface.VERSION;
} else if (protocol.equals(ClientProtocol.class.getName())) {
@@ -2794,6 +2881,7 @@ public class HRegionServer extends RegionServer
/**
* @return Return the fs.
*/
+ @Override
public FileSystem getFileSystem() {
return fs;
}
@@ -2817,7 +2905,7 @@ public class HRegionServer extends RegionServer
checkOpen();
if (regionName == null) {
throw new IOException("Invalid arguments to mutateRow " +
- "regionName is null");
+ "regionName is null");
}
requestCount.incrementAndGet();
try {
@@ -2834,11 +2922,11 @@ public class HRegionServer extends RegionServer
@Override
public Result append(byte[] regionName, Append append)
- throws IOException {
+ throws IOException {
checkOpen();
if (regionName == null) {
throw new IOException("Invalid arguments to increment " +
- "regionName is null");
+ "regionName is null");
}
requestCount.incrementAndGet();
try {
@@ -2865,11 +2953,11 @@ public class HRegionServer extends RegionServer
@Override
public Result increment(byte[] regionName, Increment increment)
- throws IOException {
+ throws IOException {
checkOpen();
if (regionName == null) {
throw new IOException("Invalid arguments to increment " +
- "regionName is null");
+ "regionName is null");
}
requestCount.incrementAndGet();
try {
@@ -2884,7 +2972,7 @@ public class HRegionServer extends RegionServer
}
}
resVal = region.increment(incVal, lock,
- increment.getWriteToWAL());
+ increment.getWriteToWAL());
if (region.getCoprocessorHost() != null) {
resVal = region.getCoprocessorHost().postIncrement(incVal, resVal);
}
@@ -2896,9 +2984,10 @@ public class HRegionServer extends RegionServer
}
/** {@inheritDoc} */
+ @Override
public long incrementColumnValue(byte[] regionName, byte[] row,
byte[] family, byte[] qualifier, long amount, boolean writeToWAL)
- throws IOException {
+ throws IOException {
checkOpen();
if (regionName == null) {
@@ -2931,6 +3020,7 @@ public class HRegionServer extends RegionServer
/** {@inheritDoc}
* @deprecated Use {@link #getServerName()} instead.
*/
+ @Deprecated
@Override
@QosPriority(priority=HIGH_QOS)
public HServerInfo getHServerInfo() throws IOException {
@@ -2963,20 +3053,20 @@ public class HRegionServer extends RegionServer
response.add(regionName, originalIndex, new Result());
} else if (action instanceof Get) {
response.add(regionName, originalIndex,
- get(regionName, (Get)action));
+ get(regionName, (Get)action));
} else if (action instanceof Put) {
puts.add(a); // wont throw.
} else if (action instanceof Exec) {
ExecResult result = execCoprocessor(regionName, (Exec)action);
response.add(regionName, new Pair(
a.getOriginalIndex(), result.getValue()
- ));
+ ));
} else if (action instanceof Increment) {
response.add(regionName, originalIndex,
- increment(regionName, (Increment)action));
+ increment(regionName, (Increment)action));
} else if (action instanceof Append) {
response.add(regionName, originalIndex,
- append(regionName, (Append)action));
+ append(regionName, (Append)action));
} else if (action instanceof RowMutations) {
mutateRow(regionName, (RowMutations)action);
response.add(regionName, originalIndex, new Result());
@@ -3079,6 +3169,7 @@ public class HRegionServer extends RegionServer
}
}
+ @Override
public String toString() {
return getServerName().toString();
}
@@ -3101,8 +3192,8 @@ public class HRegionServer extends RegionServer
public ServerName getServerName() {
// Our servername could change after we talk to the master.
return this.serverNameFromMasterPOV == null?
- new ServerName(this.isa.getHostName(), this.isa.getPort(), this.startcode):
- this.serverNameFromMasterPOV;
+ new ServerName(this.isa.getHostName(), this.isa.getPort(), this.startcode):
+ this.serverNameFromMasterPOV;
}
@Override
@@ -3115,6 +3206,7 @@ public class HRegionServer extends RegionServer
}
+ @Override
public ConcurrentSkipListMap getRegionsInTransitionInRS() {
return this.regionsInTransitionInRS;
}
@@ -3123,6 +3215,11 @@ public class HRegionServer extends RegionServer
return service;
}
+ @Override
+ public HFileArchiveMonitor getHFileArchiveMonitor() {
+ return this.hfileArchiveTracker;
+ }
+
//
// Main program and support routines
//
@@ -3131,7 +3228,7 @@ public class HRegionServer extends RegionServer
* Load the replication service objects, if any
*/
static private void createNewReplicationInstance(Configuration conf,
- HRegionServer server, FileSystem fs, Path logDir, Path oldLogDir) throws IOException{
+ HRegionServer server, FileSystem fs, Path logDir, Path oldLogDir) throws IOException{
// If replication is not enabled, then return immediately.
if (!conf.getBoolean(HConstants.REPLICATION_ENABLE_KEY, false)) {
@@ -3140,34 +3237,34 @@ public class HRegionServer extends RegionServer
// read in the name of the source replication class from the config file.
String sourceClassname = conf.get(HConstants.REPLICATION_SOURCE_SERVICE_CLASSNAME,
- HConstants.REPLICATION_SERVICE_CLASSNAME_DEFAULT);
+ HConstants.REPLICATION_SERVICE_CLASSNAME_DEFAULT);
// read in the name of the sink replication class from the config file.
String sinkClassname = conf.get(HConstants.REPLICATION_SINK_SERVICE_CLASSNAME,
- HConstants.REPLICATION_SERVICE_CLASSNAME_DEFAULT);
+ HConstants.REPLICATION_SERVICE_CLASSNAME_DEFAULT);
// If both the sink and the source class names are the same, then instantiate
// only one object.
if (sourceClassname.equals(sinkClassname)) {
server.replicationSourceHandler = (ReplicationSourceService)
- newReplicationInstance(sourceClassname,
- conf, server, fs, logDir, oldLogDir);
+ newReplicationInstance(sourceClassname,
+ conf, server, fs, logDir, oldLogDir);
server.replicationSinkHandler = (ReplicationSinkService)
- server.replicationSourceHandler;
+ server.replicationSourceHandler;
}
else {
server.replicationSourceHandler = (ReplicationSourceService)
- newReplicationInstance(sourceClassname,
- conf, server, fs, logDir, oldLogDir);
+ newReplicationInstance(sourceClassname,
+ conf, server, fs, logDir, oldLogDir);
server.replicationSinkHandler = (ReplicationSinkService)
- newReplicationInstance(sinkClassname,
- conf, server, fs, logDir, oldLogDir);
+ newReplicationInstance(sinkClassname,
+ conf, server, fs, logDir, oldLogDir);
}
}
static private ReplicationService newReplicationInstance(String classname,
- Configuration conf, HRegionServer server, FileSystem fs, Path logDir,
- Path oldLogDir) throws IOException{
+ Configuration conf, HRegionServer server, FileSystem fs, Path logDir,
+ Path oldLogDir) throws IOException{
Class> clazz = null;
try {
@@ -3179,7 +3276,7 @@ public class HRegionServer extends RegionServer
// create an instance of the replication object.
ReplicationService service = (ReplicationService)
- ReflectionUtils.newInstance(clazz, conf);
+ ReflectionUtils.newInstance(clazz, conf);
service.initialize(server, fs, logDir, oldLogDir);
return service;
}
@@ -3208,7 +3305,7 @@ public class HRegionServer extends RegionServer
// Install shutdown hook that will catch signals and run an orderly shutdown
// of the hrs.
ShutdownHook.install(hrs.getConfiguration(), FileSystem.get(hrs
- .getConfiguration()), hrs, t);
+ .getConfiguration()), hrs, t);
return t;
}
@@ -3235,9 +3332,11 @@ public class HRegionServer extends RegionServer
@Override
@QosPriority(priority=HIGH_QOS)
public void replicateLogEntries(final HLog.Entry[] entries)
- throws IOException {
+ throws IOException {
checkOpen();
- if (this.replicationSinkHandler == null) return;
+ if (this.replicationSinkHandler == null) {
+ return;
+ }
this.replicationSinkHandler.replicateLogEntries(entries);
}
@@ -3245,11 +3344,11 @@ public class HRegionServer extends RegionServer
* @see org.apache.hadoop.hbase.regionserver.HRegionServerCommandLine
*/
public static void main(String[] args) throws Exception {
- VersionInfo.logVersion();
+ VersionInfo.logVersion();
Configuration conf = HBaseConfiguration.create();
@SuppressWarnings("unchecked")
Class extends HRegionServer> regionServerClass = (Class extends HRegionServer>) conf
- .getClass(HConstants.REGION_SERVER_IMPL, HRegionServer.class);
+ .getClass(HConstants.REGION_SERVER_IMPL, HRegionServer.class);
new HRegionServerCommandLine(regionServerClass).doMain(args);
}
@@ -3276,18 +3375,19 @@ public class HRegionServer extends RegionServer
* @param tableName
* @return Online regions from tableName
*/
- public List getOnlineRegions(byte[] tableName) {
- List tableRegions = new ArrayList();
- synchronized (this.onlineRegions) {
- for (HRegion region: this.onlineRegions.values()) {
- HRegionInfo regionInfo = region.getRegionInfo();
- if(Bytes.equals(regionInfo.getTableName(), tableName)) {
- tableRegions.add(region);
- }
- }
- }
- return tableRegions;
- }
+ @Override
+ public List getOnlineRegions(byte[] tableName) {
+ List tableRegions = new ArrayList();
+ synchronized (this.onlineRegions) {
+ for (HRegion region: this.onlineRegions.values()) {
+ HRegionInfo regionInfo = region.getRegionInfo();
+ if(Bytes.equals(regionInfo.getTableName(), tableName)) {
+ tableRegions.add(region);
+ }
+ }
+ }
+ return tableRegions;
+ }
// used by org/apache/hbase/tmpl/regionserver/RSStatusTmpl.jamon (HBASE-4070).
public String[] getCoprocessors() {
@@ -3302,7 +3402,7 @@ public class HRegionServer extends RegionServer
void registerMBean() {
MXBeanImpl mxBeanInfo = MXBeanImpl.init(this);
mxBean = MBeanUtil.registerMBean("RegionServer", "RegionServer",
- mxBeanInfo);
+ mxBeanInfo);
LOG.info("Registered RegionServer MXBean");
}
}
diff --git src/main/java/org/apache/hadoop/hbase/regionserver/RegionServerServices.java src/main/java/org/apache/hadoop/hbase/regionserver/RegionServerServices.java
index 6884d53..20cba55 100644
--- src/main/java/org/apache/hadoop/hbase/regionserver/RegionServerServices.java
+++ src/main/java/org/apache/hadoop/hbase/regionserver/RegionServerServices.java
@@ -24,6 +24,7 @@ import java.util.Map;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.hbase.backup.HFileArchiveMonitor;
import org.apache.hadoop.hbase.catalog.CatalogTracker;
import org.apache.hadoop.hbase.ipc.RpcServer;
import org.apache.hadoop.hbase.regionserver.wal.HLog;
@@ -86,4 +87,10 @@ public interface RegionServerServices extends OnlineRegions {
* @return Return the FileSystem object used by the regionserver
*/
public FileSystem getFileSystem();
+
+ /**
+ * @return the manager that keeps track of which tables should be archived and
+ * where they should be archived
+ */
+ public HFileArchiveMonitor getHFileArchiveMonitor();
}
diff --git src/main/java/org/apache/hadoop/hbase/regionserver/SplitTransaction.java src/main/java/org/apache/hadoop/hbase/regionserver/SplitTransaction.java
index ea12da4..2ffec94 100644
--- src/main/java/org/apache/hadoop/hbase/regionserver/SplitTransaction.java
+++ src/main/java/org/apache/hadoop/hbase/regionserver/SplitTransaction.java
@@ -233,7 +233,7 @@ public class SplitTransaction {
this.fileSplitTimeout = testing ? this.fileSplitTimeout :
server.getConfiguration().getLong("hbase.regionserver.fileSplitTimeout",
this.fileSplitTimeout);
-
+ LOG.debug("Done with pre-split");
// Set ephemeral SPLITTING znode up in zk. Mocked servers sometimes don't
// have zookeeper so don't do zk stuff if server or zookeeper is null
if (server != null && server.getZooKeeper() != null) {
@@ -253,6 +253,7 @@ public class SplitTransaction {
List hstoreFilesToSplit = null;
Exception exceptionToThrow = null;
try{
+ LOG.debug("Finishing closing parent in split");
hstoreFilesToSplit = this.parent.close(false);
} catch (Exception e) {
exceptionToThrow = e;
@@ -284,19 +285,20 @@ public class SplitTransaction {
// splitStoreFiles creates daughter region dirs under the parent splits dir
// Nothing to unroll here if failure -- clean up of CREATE_SPLIT_DIR will
// clean this up.
+ LOG.debug("Splitting store files");
splitStoreFiles(this.splitdir, hstoreFilesToSplit);
-
+ LOG.debug("Finishing splitting store files");
// Log to the journal that we are creating region A, the first daughter
// region. We could fail halfway through. If we do, we could have left
// stuff in fs that needs cleanup -- a storefile or two. Thats why we
// add entry to journal BEFORE rather than AFTER the change.
this.journal.add(JournalEntry.STARTED_REGION_A_CREATION);
HRegion a = createDaughterRegion(this.hri_a, this.parent.rsServices);
-
+ LOG.debug("Created first daughter");
// Ditto
this.journal.add(JournalEntry.STARTED_REGION_B_CREATION);
HRegion b = createDaughterRegion(this.hri_b, this.parent.rsServices);
-
+ LOG.debug("Created second daughter");
// This is the point of no return. Adding subsequent edits to .META. as we
// do below when we do the daughter opens adding each to .META. can fail in
// various interesting ways the most interesting of which is a timeout
@@ -319,6 +321,7 @@ public class SplitTransaction {
MetaEditor.offlineParentInMeta(server.getCatalogTracker(),
this.parent.getRegionInfo(), a.getRegionInfo(), b.getRegionInfo());
}
+ LOG.debug("Finishing creating daughters in split");
return new PairOfSameType(a, b);
}
@@ -452,6 +455,7 @@ public class SplitTransaction {
PairOfSameType regions = createDaughters(server, services);
openDaughters(server, services, regions.getFirst(), regions.getSecond());
transitionZKNode(server, regions.getFirst(), regions.getSecond());
+ LOG.debug("Finishing executing split");
return regions;
}
@@ -541,6 +545,7 @@ public class SplitTransaction {
*/
private static void createSplitDir(final FileSystem fs, final Path splitdir)
throws IOException {
+ LOG.debug("Creating the splitdir: " + splitdir + " if doesn't exist");
if (fs.exists(splitdir)) {
LOG.info("The " + splitdir
+ " directory exists. Hence deleting it to recreate it");
diff --git src/main/java/org/apache/hadoop/hbase/regionserver/Store.java src/main/java/org/apache/hadoop/hbase/regionserver/Store.java
index bf1618e..4565156 100644
--- src/main/java/org/apache/hadoop/hbase/regionserver/Store.java
+++ src/main/java/org/apache/hadoop/hbase/regionserver/Store.java
@@ -51,6 +51,7 @@ import org.apache.hadoop.hbase.HRegionInfo;
import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.KeyValue.KVComparator;
import org.apache.hadoop.hbase.RemoteExceptionHandler;
+import org.apache.hadoop.hbase.backup.HFileArchiveMonitor;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.io.HeapSize;
import org.apache.hadoop.hbase.io.hfile.CacheConfig;
@@ -74,6 +75,7 @@ import org.apache.hadoop.hbase.util.ClassSize;
import org.apache.hadoop.hbase.util.CollectionBackedScanner;
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
import org.apache.hadoop.hbase.util.FSUtils;
+import org.apache.hadoop.hbase.util.HFileArchiveUtil;
import org.apache.hadoop.util.StringUtils;
import com.google.common.base.Preconditions;
@@ -129,7 +131,7 @@ public class Store extends SchemaConfigured implements HeapSize {
private final int blockingStoreFileCount;
private volatile long storeSize = 0L;
private volatile long totalUncompressedBytes = 0L;
- private final Object flushLock = new Object();
+ final Object flushLock = new Object();
final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final boolean verifyBulkLoads;
@@ -1014,6 +1016,7 @@ public class Store extends SchemaConfigured implements HeapSize {
this.compactor.compact(this, filesToCompact, cr.isMajor(), maxId);
// Move the compaction into place.
if (this.conf.getBoolean("hbase.hstore.compaction.complete", true)) {
+ LOG.debug("Completing compaction by moving files into place.");
sf = completeCompaction(filesToCompact, writer);
if (region.getCoprocessorHost() != null) {
region.getCoprocessorHost().postCompact(this, sf);
@@ -1567,10 +1570,14 @@ public class Store extends SchemaConfigured implements HeapSize {
// Tell observers that list of StoreFiles has changed.
notifyChangedReadersObservers();
- // Finally, delete old store files.
- for (StoreFile hsf: compactedFiles) {
- hsf.deleteReader();
- }
+
+ // let the archive util decide if we should archive or delete the files
+ RegionServerServices rss = this.region.getRegionServerServices();
+ HFileArchiveMonitor monitor = rss == null ? null : rss
+ .getHFileArchiveMonitor();
+
+ // remove the store files, either by backup or outright deletion
+ removeStoreFiles(monitor, compactedFiles);
} catch (IOException e) {
e = RemoteExceptionHandler.checkIOException(e);
LOG.error("Failed replacing compacted files in " + this +
@@ -1594,6 +1601,104 @@ public class Store extends SchemaConfigured implements HeapSize {
return result;
}
+ /**
+ * Remove the HFiles from the region under the given table.
+ *
+ * Either archives the HFiles if archiving is enabled or just deletes them
+ * from the FS.
+ * @param monitor monitor which tables should be archived or deleted
+ * @param compactedFiles the actual files to be deleted
+ * @throws IOException if the {@link FileSystem} level operations fail, but
+ * not if files cannot be removed/archive properly.
+ */
+ void removeStoreFiles(HFileArchiveMonitor monitor,
+ Collection compactedFiles) throws IOException {
+ LOG.debug("Removing store files after compaction...");
+ if(compactedFiles.size() == 0){
+ LOG.debug("No files in current compaction, done!");
+ return;
+ }
+ HRegionInfo info = this.region.getRegionInfo();
+ String table = info.getTableNameAsString();
+
+ // check to make sure we should keep the files
+ // if so, we just delete the old files
+ if (monitor == null || !monitor.keepHFiles(table)) {
+ deleteStoreFilesWithoutArchiving(compactedFiles);
+ LOG.debug("Deteled old (compacted) store files after compaction.");
+ return;
+ }
+
+ LOG.debug("Attempting to archive store files.");
+ // otherwise, we attempt to archive the store files
+ Path storeArchiveDir = HFileArchiveUtil.getStoreArchivePath(this.conf,
+ this.region.getTableDir(), info.getEncodedName(), this.family.getName());
+
+ // make sure we don't archive if we can't and that the archive dir exists
+ if (storeArchiveDir == null || !fs.mkdirs(storeArchiveDir)) {
+ LOG.warn("Could make archive directory (" + storeArchiveDir + ") for store:" + this
+ + ", deleting compacted files instead.");
+ deleteStoreFilesWithoutArchiving(compactedFiles);
+ return;
+ }
+
+ LOG.debug("moving store files to the archive directory: " + storeArchiveDir);
+ List failedStores = new ArrayList();
+ String archiveStartTime = Long.toString(EnvironmentEdgeManager
+ .currentTimeMillis());
+ for (StoreFile file : compactedFiles) {
+ try {
+ LOG.debug("Archiving store file:" + file.getPath());
+ if (!HFileArchiveUtil.resolveAndArchiveFile(fs, storeArchiveDir,
+ file.getPath(), archiveStartTime)) {
+ LOG.warn("Couldn't archive " + file + " into backup directory: "
+ + storeArchiveDir);
+ failedStores.add(file);
+ }
+ } catch (IOException e) {
+ LOG.warn("Couldn't archive " + file + " into backup directory: "
+ + storeArchiveDir, e);
+ failedStores.add(file);
+ }
+ }
+
+ // only fail after we have attempted to cleanup as many files as possible
+ if (failedStores.size() > 0) {
+ try {
+ LOG.warn("Failed to complete archive, so just deleting extra store files.");
+ deleteStoreFilesWithoutArchiving(failedStores);
+ } catch (IOException e) {
+ LOG.debug("Failed to delete store file(s) when archiving failed", e);
+ }
+ }
+ }
+
+ /**
+ * Just do a simple delete of the given store files
+ *
+ * A best effort is made to delete each of the files, rather than bailing on
+ * the first failure
+ * @param compactedFiles store files to remove from the file system.
+ * @throws IOException if not all the files can be removed
+ */
+ private void deleteStoreFilesWithoutArchiving(Collection compactedFiles)
+ throws IOException {
+ boolean failure = false;
+ for (StoreFile hsf : compactedFiles) {
+ try {
+ LOG.debug("Deleting store file:" + hsf.getPath());
+ hsf.deleteReader();
+ } catch (IOException e) {
+ LOG.error("Failed to delete store file:" + hsf.getPath());
+ failure = true;
+ }
+ }
+ if (failure) {
+ throw new IOException(
+ "Failed to delete all store files. See log for failures.");
+ }
+ }
+
public ImmutableList sortAndClone(List storeFiles) {
Collections.sort(storeFiles, StoreFile.Comparators.FLUSH_TIME);
ImmutableList newList = ImmutableList.copyOf(storeFiles);
diff --git src/main/java/org/apache/hadoop/hbase/util/HFileArchiveCleanup.java src/main/java/org/apache/hadoop/hbase/util/HFileArchiveCleanup.java
new file mode 100644
index 0000000..8b49261
--- /dev/null
+++ src/main/java/org/apache/hadoop/hbase/util/HFileArchiveCleanup.java
@@ -0,0 +1,198 @@
+package org.apache.hadoop.hbase.util;
+
+import java.io.IOException;
+import java.util.Stack;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.GnuParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.OptionBuilder;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+import org.apache.commons.cli.Parser;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileStatus;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.hbase.HBaseConfiguration;
+import org.apache.hadoop.hbase.HTableDescriptor;
+
+/**
+ * Utility to help cleanup extra archive files.
+ *
+ * Run with -h to see help.
+ */
+public class HFileArchiveCleanup {
+
+ private static final Log LOG = LogFactory.getLog(HFileArchiveCleanup.class);
+
+ private static Configuration conf;
+
+ // configuration
+ private long start = -1;
+ private long end = Long.MAX_VALUE;
+ private String table = null;
+ private boolean all = true;
+
+ public static void main(String[] args) throws Exception {
+ // setup
+ HFileArchiveCleanup cleaner = new HFileArchiveCleanup();
+ try {
+ if (!cleaner.parse(args)) {
+ System.exit(-1);
+ }
+ } catch (ParseException e) {
+ LOG.error("Parse Exception: " + e.getMessage());
+ cleaner.printHelp();
+ System.exit(-1);
+ }
+
+ // run the cleanup
+ Configuration conf = getConf();
+ FileSystem fs = FSUtils.getCurrentFileSystem(conf);
+
+ Path archiveDir;
+ // if we aren't cleaning a specific table, then get the table directory
+ if (!cleaner.all) {
+ Path tableDir = HTableDescriptor.getTableDir(FSUtils.getRootDir(conf),
+ Bytes.toBytes(cleaner.table));
+ archiveDir = HFileArchiveUtil.getTableArchivePath(conf, tableDir);
+ } else {
+ archiveDir = new Path(FSUtils.getRootDir(conf),
+ HFileArchiveUtil.getConfiguredArchiveDir(conf));
+ }
+
+ LOG.debug("Cleaning up: " + archiveDir);
+ // iterate through the archive directory and delete everything that falls
+ // outside the range
+ Stack directories = new Stack();
+ directories.add(archiveDir);
+ // while there are more directories to look at
+ while (!directories.isEmpty()) {
+ Path parent = directories.pop();
+ // if the parent exists
+ if (fs.exists(parent)) {
+ FileStatus[] children = fs.listStatus(parent);
+ for (FileStatus child : children) {
+ // push directory down to be cleaned
+ if (child.isDir()) {
+ directories.push(child.getPath());
+ continue;
+ }
+ // otherwise its a file and we can check deletion
+ // we have to check mod times against what the command line says
+ long mod = child.getModificationTime();
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Child file: " + child.getPath() + ", has mod time:" + mod);
+ }
+ // check to see if it meets the credentials
+ if (cleaner.start <= mod && mod < cleaner.end) {
+ try {
+ LOG.debug("Start:" + cleaner.start + " =< " + mod + " < " + cleaner.end + ": end");
+ fs.delete(child.getPath(), false);
+ } catch (IOException e) {
+ LOG.warn("Failed to delete child", e);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Set the configuration to use.
+ *
+ * Exposed for TESTING!
+ * @param conf
+ */
+ static void setConfiguration(Configuration conf) {
+ HFileArchiveCleanup.conf = conf;
+ }
+
+ private synchronized static Configuration getConf() {
+ if (HFileArchiveCleanup.conf == null) {
+ setConfiguration(HBaseConfiguration.create());
+ }
+ return HFileArchiveCleanup.conf;
+ }
+
+ // ---------------------------
+ // Setup and options parsing
+ // ---------------------------
+
+ private final Options opts;
+ private final Parser parser;
+
+ @SuppressWarnings("static-access")
+ public HFileArchiveCleanup() {
+ // create all the necessary options
+ opts = new Options();
+ // create the time option
+ OptionBuilder
+ .hasArg()
+ .withArgName("start time")
+ .withDescription(
+ "Start time from which to start removing backup files in seconds from the epoch (inclusive). If not set, all archive files are removed from the specified table up to the end time. Not setting start OR end time deletes all archived files.");
+ opts.addOption(OptionBuilder.create('s'));
+
+ // end time option
+ OptionBuilder
+ .hasArg()
+ .withArgName("end time")
+ .withDescription(
+ "End time from which to start removing backup files in seconds from the epoch (exclusive). If not set, all archive files forward from the start time are removed. Not setting start OR end time deletes all archived files.");
+ opts.addOption(OptionBuilder.create('e'));
+
+ // create the tablename option
+ OptionBuilder
+ .hasArg()
+ .withArgName("table")
+ .withDescription(
+ "Name of the table for which to remove backups. If not set, all archived files will be cleaned up.")
+ .withType(String.class);
+ opts.addOption(OptionBuilder.create('t'));
+
+ // add the help
+ opts.addOption(OptionBuilder.hasArg(false).withDescription("Show this help").create('h'));
+
+ // create the parser
+ parser = new GnuParser();
+ }
+
+ public boolean parse(String[] args) throws ParseException {
+ CommandLine cmd;
+ cmd = parser.parse(opts, args);
+
+ if (cmd.hasOption('h')) {
+ printHelp();
+ return false;
+ }
+ // get start/end times
+ if (cmd.hasOption('s')) {
+ this.start = Long.parseLong(cmd.getOptionValue('s'));
+ LOG.debug("Setting start time to:" + start);
+ }
+ if (cmd.hasOption('e')) {
+ this.end = Long.parseLong(cmd.getOptionValue('e'));
+ LOG.debug("Setting end time to:" + end);
+ }
+
+ if (start > end) {
+ throw new ParseException("Start time cannot exceed end time.");
+ }
+
+ // get the table to cleanup
+ if (cmd.hasOption('t')) {
+ this.table = cmd.getOptionValue('t');
+ this.all = false;
+ }
+ return true;
+ }
+
+ public void printHelp() {
+ HelpFormatter help = new HelpFormatter();
+ help.printHelp("java HFileArchiveCleanup", opts);
+ }
+}
diff --git src/main/java/org/apache/hadoop/hbase/util/HFileArchiveUtil.java src/main/java/org/apache/hadoop/hbase/util/HFileArchiveUtil.java
new file mode 100644
index 0000000..90b6572
--- /dev/null
+++ src/main/java/org/apache/hadoop/hbase/util/HFileArchiveUtil.java
@@ -0,0 +1,194 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.hbase.util;
+
+import java.io.IOException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.backup.HFileArchiveMonitor;
+import org.apache.hadoop.hbase.regionserver.HRegion;
+import org.apache.hadoop.hbase.regionserver.Store;
+import org.apache.hadoop.hbase.zookeeper.ZKUtil;
+import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * Helper class for all utilities related to archival/retrieval of HFiles
+ */
+public class HFileArchiveUtil {
+ private static final Log LOG = LogFactory.getLog(HFileArchiveUtil.class);
+ private static final String SEPARATOR = ".";
+ public static final String DEFAULT_HFILE_ARCHIVE_DIRECTORY = ".archive";
+
+ private HFileArchiveUtil() {
+ // non-external instantiation - util class
+ }
+
+ /**
+ * Attempt to archive the passed in file to the store archive directory.
+ *
+ * If the same file already exists in the archive, it is moved to a
+ * timestamped directory under the archive directory and the new file is put
+ * in its place.
+ * @param fs FileSystem on which to move files
+ * @param storeArchiveDirectory {@link Path} to the directory that stores the
+ * archives of the hfiles
+ * @param currentFile {@link Path} to the original HFile that will be archived
+ * @param archiveStartTime
+ * @return true if the file is successfully archived. false
+ * if there was a problem, but the operation still completed.
+ * @throws IOException on failure to complete {@link FileSystem} operations.
+ */
+ public static boolean resolveAndArchiveFile(FileSystem fs, Path storeArchiveDirectory,
+ Path currentFile, String archiveStartTime) throws IOException {
+ // build path as it should be in the archive
+ String filename = currentFile.getName();
+ Path archiveFile = new Path(storeArchiveDirectory, filename);
+
+ // if the file already exists in the archive, move that one to a
+ // timestamped backup
+ if (fs.exists(archiveFile)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("File:" + archiveFile
+ + " already exists in archive, moving to timestamped backup and overwriting current.");
+ }
+
+ Path backedupArchiveFile = new Path(storeArchiveDirectory, filename + SEPARATOR
+ + archiveStartTime);
+
+ // move the archive file to the stamped backup
+ if (!fs.rename(archiveFile, backedupArchiveFile)) {
+ LOG.warn("Could not rename archive file to backup: " + backedupArchiveFile
+ + ", deleting existing file in favor of newer.");
+ fs.delete(archiveFile, false);
+ } else if (LOG.isDebugEnabled()) {
+ LOG.debug("Backed up archive file from: " + archiveFile);
+ }
+ } else if (LOG.isDebugEnabled()) {
+ LOG.debug("No existing file in archive for:" + archiveFile
+ + ", free to archive original file.");
+ }
+
+ // at this point, we should have a free spot for the archive file
+ if (!fs.rename(currentFile, archiveFile)) {
+ LOG.error("Failed to archive file:" + currentFile);
+ return false;
+ } else if (LOG.isDebugEnabled()) {
+ LOG.debug("Finished archiving file from: " + currentFile + ", to: " + archiveFile);
+ }
+ return true;
+ }
+
+ /**
+ * Get the directory to archive a store directory
+ * @param monitor Monitor if a table should be archived
+ * @param tabledir the original table directory
+ * @param regionName encoded name of the region under which the store lives
+ * @param family name of the family in the store
+ * @return {@link Path} to the directory to archive the given store or
+ * null if it should not be archived
+ */
+ public static Path getStoreArchivePath(Configuration conf, Path tabledir,
+ String regionName, byte[] family) {
+ // get the archive directory for a table
+ Path archiveDir = getTableArchivePath(conf, tabledir);
+
+ // and then the per-store archive directory comes from that (final
+ // destination of archived hfiles)
+ return Store.getStoreHomedir(archiveDir, regionName, family);
+ }
+
+ /**
+ * Get the archive directory for a given region under the specified table
+ * @param conf {@link Configuration} to read the archive directory from
+ * @param tabledir the original table directory
+ * @param regiondir the path to the region directory
+ * @return {@link Path} to the directory to archive the given region, or
+ * null if it should not be archived
+ */
+ public static Path getRegionArchiveDir(Configuration conf, Path tabledir, Path regiondir) {
+ // get the archive directory for a table
+ Path archiveDir = getTableArchivePath(conf, tabledir);
+ // if the monitor isn't archiving that table, then we don't specify an
+ // archive directory
+ if (archiveDir == null) return null;
+
+ // then add on the region path under the archive
+ String encodedRegionName = regiondir.getName();
+ return HRegion.getRegionDir(archiveDir, encodedRegionName);
+ }
+
+ /**
+ * Get the path to the table archive directory based on the configured archive
+ * directory.
+ *
+ * Assumed that the table should already be archived.
+ * @param conf {@link Configuration} to read the archive property from
+ * @param tabledir directory of the table to be archived.
+ * @return {@link Path} to the archive directory for the table
+ */
+ public static Path getTableArchivePath(Configuration conf, Path tabledir) {
+ return getTableArchivePath(getConfiguredArchiveDir(conf), tabledir);
+ }
+
+ private static Path getTableArchivePath(String archivedir, Path tabledir) {
+ Path root = tabledir.getParent();
+ // now build the archive directory path
+ // first the top-level archive directory
+ // generally "/hbase/.archive/[table]
+ return new Path(new Path(root, archivedir), tabledir.getName());
+ }
+
+ /**
+ * Get the archive directory as per the configuration
+ * @param conf {@link Configuration} to read the archive directory from (can
+ * be null, in which case you get the default value).
+ * @return the configured archived directory or the default specified by
+ * {@value HFileArchiveUtil#DEFAULT_HFILE_ARCHIVE_DIRECTORY}
+ */
+ public static String getConfiguredArchiveDir(Configuration conf) {
+ return conf == null ? HFileArchiveUtil.DEFAULT_HFILE_ARCHIVE_DIRECTORY : conf.get(
+ HConstants.HFILE_ARCHIVE_DIRECTORY, HFileArchiveUtil.DEFAULT_HFILE_ARCHIVE_DIRECTORY);
+ }
+
+ /**
+ * Get the zookeeper node associated with archiving the given table
+ * @param zkw watcher for the zk cluster
+ * @param table name of the table to check
+ * @return znode for the table's archive status
+ */
+ public static String getTableNode(ZooKeeperWatcher zkw, byte[] table) {
+ return ZKUtil.joinZNode(zkw.archiveHFileZNode, Bytes.toString(table));
+ }
+
+ /**
+ * Get the zookeeper node associated with archiving the given table
+ * @param zkw watcher for the zk cluster
+ * @param table name of the table to check
+ * @return znode for the table's archive status
+ */
+ public static String getTableNode(ZooKeeperWatcher zkw, String table) {
+ return ZKUtil.joinZNode(zkw.archiveHFileZNode, table);
+ }
+}
diff --git src/main/java/org/apache/hadoop/hbase/zookeeper/ZooKeeperWatcher.java src/main/java/org/apache/hadoop/hbase/zookeeper/ZooKeeperWatcher.java
index 1ae1281..27c3230 100644
--- src/main/java/org/apache/hadoop/hbase/zookeeper/ZooKeeperWatcher.java
+++ src/main/java/org/apache/hadoop/hbase/zookeeper/ZooKeeperWatcher.java
@@ -103,6 +103,8 @@ public class ZooKeeperWatcher implements Watcher, Abortable, Closeable {
public String clusterIdZNode;
// znode used for log splitting work assignment
public String splitLogZNode;
+ // znode to track archiving hfiles
+ public String archiveHFileZNode;
// Certain ZooKeeper nodes need to be world-readable
public static final ArrayList CREATOR_ALL_AND_WORLD_READABLE =
@@ -213,6 +215,9 @@ public class ZooKeeperWatcher implements Watcher, Abortable, Closeable {
conf.get("zookeeper.znode.clusterId", "hbaseid"));
splitLogZNode = ZKUtil.joinZNode(baseZNode,
conf.get("zookeeper.znode.splitlog", HConstants.SPLIT_LOGDIR_NAME));
+ archiveHFileZNode = ZKUtil
+ .joinZNode(baseZNode, conf.get("zookeeper.znode.hfile.archive",
+ HConstants.HFILE_ARCHIVE_ZNODE_PARENT));
}
/**
diff --git src/main/resources/hbase-default.xml src/main/resources/hbase-default.xml
index f54b345..3150f18 100644
--- src/main/resources/hbase-default.xml
+++ src/main/resources/hbase-default.xml
@@ -859,7 +859,6 @@
files when hbase.data.umask.enable is true
-
hbase.metrics.showTableName
true
@@ -869,7 +868,6 @@
In both cases, the aggregated metric M across tables and cfs will be reported.
-
hbase.metrics.exposeOperationTimes
true
@@ -878,5 +876,12 @@
have their times exposed through Hadoop metrics per CF and per region.
-
+
+ hbase.table.archive.directory
+ .archive
+ Per-table directory name under which to backup files for a
+ table. Files are moved to the same directories as they would be under the
+ table directory, but instead are just one level lower (under
+ table/.archive/... rather than table/...). Currently only applies to HFiles.
+
diff --git src/test/java/org/apache/hadoop/hbase/master/MockRegionServer.java src/test/java/org/apache/hadoop/hbase/master/MockRegionServer.java
index f3168d1..c77a27c 100644
--- src/test/java/org/apache/hadoop/hbase/master/MockRegionServer.java
+++ src/test/java/org/apache/hadoop/hbase/master/MockRegionServer.java
@@ -29,6 +29,7 @@ import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.hbase.HRegionInfo;
import org.apache.hadoop.hbase.ServerName;
import org.apache.hadoop.hbase.ZooKeeperConnectionException;
+import org.apache.hadoop.hbase.backup.HFileArchiveMonitor;
import org.apache.hadoop.hbase.catalog.CatalogTracker;
import org.apache.hadoop.hbase.client.AdminProtocol;
import org.apache.hadoop.hbase.client.ClientProtocol;
@@ -508,4 +509,10 @@ class MockRegionServer implements AdminProtocol, ClientProtocol, RegionServerSer
// TODO Auto-generated method stub
return null;
}
+
+ @Override
+ public HFileArchiveMonitor getHFileArchiveMonitor() {
+ // TODO Auto-generated method stub
+ return null;
+ }
}
\ No newline at end of file
diff --git src/test/java/org/apache/hadoop/hbase/master/TestCatalogJanitor.java src/test/java/org/apache/hadoop/hbase/master/TestCatalogJanitor.java
index 1020374..25c2af3 100644
--- src/test/java/org/apache/hadoop/hbase/master/TestCatalogJanitor.java
+++ src/test/java/org/apache/hadoop/hbase/master/TestCatalogJanitor.java
@@ -19,10 +19,13 @@
*/
package org.apache.hadoop.hbase.master;
+import static org.apache.hadoop.hbase.util.HFileArchiveTestingUtil.compareArchiveToOriginal;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -33,6 +36,8 @@ import java.util.SortedMap;
import java.util.TreeMap;
import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FSDataOutputStream;
+import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.HBaseTestingUtility;
@@ -41,14 +46,19 @@ import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.HRegionInfo;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.KeyValue;
+import org.apache.hadoop.hbase.MediumTests;
import org.apache.hadoop.hbase.NotAllMetaRegionsOnlineException;
import org.apache.hadoop.hbase.Server;
import org.apache.hadoop.hbase.ServerName;
import org.apache.hadoop.hbase.SmallTests;
import org.apache.hadoop.hbase.TableDescriptors;
+import org.apache.hadoop.hbase.backup.HFileArchiveMonitor;
+import org.apache.hadoop.hbase.backup.HFileArchiveTableTracker;
+import org.apache.hadoop.hbase.backup.HFileArchiveTracker;
import org.apache.hadoop.hbase.catalog.CatalogTracker;
import org.apache.hadoop.hbase.client.AdminProtocol;
import org.apache.hadoop.hbase.client.ClientProtocol;
+import org.apache.hadoop.hbase.client.HBaseAdmin;
import org.apache.hadoop.hbase.client.HConnection;
import org.apache.hadoop.hbase.client.HConnectionManager;
import org.apache.hadoop.hbase.client.HConnectionTestingUtility;
@@ -60,10 +70,12 @@ import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.MutateRequest;
import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.MutateResponse;
import org.apache.hadoop.hbase.regionserver.Store;
import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.hadoop.hbase.util.HFileArchiveUtil;
import org.apache.hadoop.hbase.util.Writables;
import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
import org.junit.Test;
import org.junit.experimental.categories.Category;
+import org.mockito.Matchers;
import org.mockito.Mockito;
import com.google.protobuf.RpcController;
@@ -81,15 +93,15 @@ public class TestCatalogJanitor {
private final CatalogTracker ct;
MockServer(final HBaseTestingUtility htu)
- throws NotAllMetaRegionsOnlineException, IOException, InterruptedException {
+ throws NotAllMetaRegionsOnlineException, IOException, InterruptedException {
this.c = htu.getConfiguration();
ClientProtocol ri = Mockito.mock(ClientProtocol.class);
MutateResponse.Builder builder = MutateResponse.newBuilder();
builder.setProcessed(true);
try {
Mockito.when(ri.mutate(
- (RpcController)Mockito.any(), (MutateRequest)Mockito.any())).
- thenReturn(builder.build());
+ (RpcController)Matchers.any(), (MutateRequest)Matchers.any())).
+ thenReturn(builder.build());
} catch (ServiceException se) {
throw ProtobufUtil.getRemoteException(se);
}
@@ -135,7 +147,7 @@ public class TestCatalogJanitor {
public void abort(String why, Throwable e) {
//no-op
}
-
+
@Override
public boolean isAborted() {
return false;
@@ -166,6 +178,10 @@ public class TestCatalogJanitor {
MockMasterServices(final Server server) throws IOException {
this.mfs = new MasterFileSystem(server, this, null);
+ // this is funky, but ensures that the filesystem gets the right root
+ // directory when directly accessed
+ this.mfs.getFileSystem().getConf()
+ .set(HConstants.HBASE_DIR, mfs.getRootDir().toString());
this.asm = Mockito.mock(AssignmentManager.class);
}
@@ -224,7 +240,7 @@ public class TestCatalogJanitor {
public void abort(String why, Throwable e) {
//no-op
}
-
+
@Override
public boolean isAborted() {
return false;
@@ -250,29 +266,29 @@ public class TestCatalogJanitor {
// TODO Auto-generated method stub
return null;
}
-
+
@Override
public Map getAll() throws IOException {
// TODO Auto-generated method stub
return null;
}
-
+
@Override
public HTableDescriptor get(byte[] tablename)
- throws FileNotFoundException, IOException {
+ throws FileNotFoundException, IOException {
return get(Bytes.toString(tablename));
}
-
+
@Override
public HTableDescriptor get(String tablename)
- throws FileNotFoundException, IOException {
+ throws FileNotFoundException, IOException {
return createHTableDescriptor();
}
-
+
@Override
public void add(HTableDescriptor htd) throws IOException {
// TODO Auto-generated method stub
-
+
}
};
}
@@ -317,33 +333,36 @@ public class TestCatalogJanitor {
Server server = new MockServer(htu);
try {
MasterServices services = new MockMasterServices(server);
- CatalogJanitor janitor = new CatalogJanitor(server, services);
+ ZooKeeperWatcher watcher = Mockito.mock(ZooKeeperWatcher.class);
+ HFileArchiveMonitor monitor = new HFileArchiveTracker(watcher, new HFileArchiveTableTracker(
+ services, watcher));
+ CatalogJanitor janitor = new CatalogJanitor(server, services, monitor);
// Create regions.
HTableDescriptor htd = new HTableDescriptor("table");
htd.addFamily(new HColumnDescriptor("f"));
HRegionInfo parent =
- new HRegionInfo(htd.getName(), Bytes.toBytes("aaa"),
+ new HRegionInfo(htd.getName(), Bytes.toBytes("aaa"),
Bytes.toBytes("eee"));
HRegionInfo splita =
- new HRegionInfo(htd.getName(), Bytes.toBytes("aaa"),
+ new HRegionInfo(htd.getName(), Bytes.toBytes("aaa"),
Bytes.toBytes("ccc"));
HRegionInfo splitb =
- new HRegionInfo(htd.getName(), Bytes.toBytes("ccc"),
+ new HRegionInfo(htd.getName(), Bytes.toBytes("ccc"),
Bytes.toBytes("eee"));
// Test that when both daughter regions are in place, that we do not
// remove the parent.
List kvs = new ArrayList();
kvs.add(new KeyValue(parent.getRegionName(), HConstants.CATALOG_FAMILY,
- HConstants.SPLITA_QUALIFIER, Writables.getBytes(splita)));
+ HConstants.SPLITA_QUALIFIER, Writables.getBytes(splita)));
kvs.add(new KeyValue(parent.getRegionName(), HConstants.CATALOG_FAMILY,
- HConstants.SPLITB_QUALIFIER, Writables.getBytes(splitb)));
+ HConstants.SPLITB_QUALIFIER, Writables.getBytes(splitb)));
Result r = new Result(kvs);
// Add a reference under splitA directory so we don't clear out the parent.
Path rootdir = services.getMasterFileSystem().getRootDir();
Path tabledir =
- HTableDescriptor.getTableDir(rootdir, htd.getName());
+ HTableDescriptor.getTableDir(rootdir, htd.getName());
Path storedir = Store.getStoreHomedir(tabledir, splita.getEncodedName(),
- htd.getColumnFamilies()[0].getName());
+ htd.getColumnFamilies()[0].getName());
Reference ref = new Reference(Bytes.toBytes("ccc"), Reference.Range.top);
long now = System.currentTimeMillis();
// Reference name has this format: StoreFile#REF_NAME_PARSER
@@ -367,7 +386,7 @@ public class TestCatalogJanitor {
*/
@Test
public void testParentCleanedEvenIfDaughterGoneFirst()
- throws IOException, InterruptedException {
+ throws IOException, InterruptedException {
parentWithSpecifiedEndKeyCleanedEvenIfDaughterGoneFirst(
"testParentCleanedEvenIfDaughterGoneFirst", Bytes.toBytes("eee"));
}
@@ -379,11 +398,217 @@ public class TestCatalogJanitor {
*/
@Test
public void testLastParentCleanedEvenIfDaughterGoneFirst()
- throws IOException, InterruptedException {
+ throws IOException, InterruptedException {
parentWithSpecifiedEndKeyCleanedEvenIfDaughterGoneFirst(
"testLastParentCleanedEvenIfDaughterGoneFirst", new byte[0]);
}
+ @Test
+ public void testArchiveOldRegion() throws Exception {
+ String table = "table";
+ HBaseTestingUtility htu = new HBaseTestingUtility();
+ setRootDirAndCleanIt(htu, "testCleanParent");
+ Server server = new MockServer(htu);
+ try {
+ MasterServices services = new MockMasterServices(server);
+ // add the test table as the one to be archived
+ ZooKeeperWatcher watcher = Mockito.mock(ZooKeeperWatcher.class);
+ HFileArchiveTracker monitor = new HFileArchiveTracker(watcher, new HFileArchiveTableTracker(
+ services, watcher));
+
+ // create the janitor
+ CatalogJanitor janitor = new CatalogJanitor(server, services, monitor);
+
+ // Create regions.
+ HTableDescriptor htd = new HTableDescriptor(table);
+ htd.addFamily(new HColumnDescriptor("f"));
+ HRegionInfo parent = new HRegionInfo(htd.getName(), Bytes.toBytes("aaa"),
+ Bytes.toBytes("eee"));
+ HRegionInfo splita = new HRegionInfo(htd.getName(), Bytes.toBytes("aaa"),
+ Bytes.toBytes("ccc"));
+ HRegionInfo splitb = new HRegionInfo(htd.getName(), Bytes.toBytes("ccc"),
+ Bytes.toBytes("eee"));
+ // Test that when both daughter regions are in place, that we do not
+ // remove the parent.
+ List kvs = new ArrayList();
+ kvs.add(new KeyValue(parent.getRegionName(), HConstants.CATALOG_FAMILY,
+ HConstants.SPLITA_QUALIFIER, Writables.getBytes(splita)));
+ kvs.add(new KeyValue(parent.getRegionName(), HConstants.CATALOG_FAMILY,
+ HConstants.SPLITB_QUALIFIER, Writables.getBytes(splitb)));
+ Result r = new Result(kvs);
+
+ Path rootdir = services.getMasterFileSystem().getRootDir();
+ Path tabledir = HTableDescriptor.getTableDir(rootdir, htd.getName());
+ Path storedir = Store.getStoreHomedir(tabledir, parent.getEncodedName(),
+ htd.getColumnFamilies()[0].getName());
+
+ // first do a delete without archiving
+ addMockStoreFiles(2, services, storedir);
+ assertTrue(janitor.cleanParent(parent, r));
+
+ // and make sure that no files are archived
+ // add the table to the manager so we can get the archive dir (also
+ // enables future archiving)
+ monitor.getTracker().addTable(table);
+ FileSystem fs = services.getMasterFileSystem().getFileSystem();
+ Path storeArchive = HFileArchiveUtil.getStoreArchivePath(services.getConfiguration(),
+ tabledir, parent.getEncodedName(), htd.getColumnFamilies()[0].getName());
+ assertEquals(0, fs.listStatus(storeArchive).length);
+
+ // enable archiving, make sure that files get archived
+ monitor.getTracker().addTable(table);
+ addMockStoreFiles(2, services, storedir);
+ // get the current store files for comparison
+ FileStatus[] storeFiles = fs.listStatus(storedir);
+
+ // do the cleaning of the parent
+ assertTrue(janitor.cleanParent(parent, r));
+
+ // and now check to make sure that the files have actually been archived
+ FileStatus[] archivedStoreFiles = fs.listStatus(storeArchive);
+ compareArchiveToOriginal(storeFiles, archivedStoreFiles, fs);
+ } finally {
+ server.stop("shutdown");
+ }
+ }
+
+ /**
+ * Test that if a store file with the same name is present as those already
+ * backed up cause the already archived files to be timestamped backup
+ */
+ @Test
+ public void testDuplicateHFileResolution() throws Exception {
+ String table = "table";
+ HBaseTestingUtility htu = new HBaseTestingUtility();
+ setRootDirAndCleanIt(htu, "testCleanParent");
+ Server server = new MockServer(htu);
+ try {
+ MasterServices services = new MockMasterServices(server);
+ // add the test table as the one to be archived
+ ZooKeeperWatcher watcher = Mockito.mock(ZooKeeperWatcher.class);
+ HFileArchiveTracker manager = new HFileArchiveTracker(watcher, new HFileArchiveTableTracker(
+ services, watcher));
+ HFileArchiveTableTracker tracker = manager.getTracker();
+ tracker.addTable(table);
+
+ // create the janitor
+ CatalogJanitor janitor = new CatalogJanitor(server, services, manager);
+
+ // Create regions.
+ HTableDescriptor htd = new HTableDescriptor(table);
+ htd.addFamily(new HColumnDescriptor("f"));
+ HRegionInfo parent = new HRegionInfo(htd.getName(), Bytes.toBytes("aaa"),
+ Bytes.toBytes("eee"));
+ HRegionInfo splita = new HRegionInfo(htd.getName(), Bytes.toBytes("aaa"),
+ Bytes.toBytes("ccc"));
+ HRegionInfo splitb = new HRegionInfo(htd.getName(), Bytes.toBytes("ccc"),
+ Bytes.toBytes("eee"));
+ // Test that when both daughter regions are in place, that we do not
+ // remove the parent.
+ List kvs = new ArrayList();
+ kvs.add(new KeyValue(parent.getRegionName(), HConstants.CATALOG_FAMILY,
+ HConstants.SPLITA_QUALIFIER, Writables.getBytes(splita)));
+ kvs.add(new KeyValue(parent.getRegionName(), HConstants.CATALOG_FAMILY,
+ HConstants.SPLITB_QUALIFIER, Writables.getBytes(splitb)));
+ Result r = new Result(kvs);
+
+ Path rootdir = services.getMasterFileSystem().getRootDir();
+ Path tabledir = HTableDescriptor.getTableDir(rootdir, htd.getName());
+ Path storedir = Store.getStoreHomedir(tabledir, parent.getEncodedName(),
+ htd.getColumnFamilies()[0].getName());
+
+ FileSystem fs = services.getMasterFileSystem().getFileSystem();
+ Path storeArchive = HFileArchiveUtil.getStoreArchivePath(services.getConfiguration(),
+ tabledir, parent.getEncodedName(), htd.getColumnFamilies()[0].getName());
+
+ // enable archiving, make sure that files get archived
+ tracker.addTable(table);
+ addMockStoreFiles(2, services, storedir);
+ // get the current store files for comparison
+ FileStatus[] storeFiles = fs.listStatus(storedir);
+
+ // do the cleaning of the parent
+ assertTrue(janitor.cleanParent(parent, r));
+
+ // and now check to make sure that the files have actually been archived
+ FileStatus[] archivedStoreFiles = fs.listStatus(storeArchive);
+ compareArchiveToOriginal(storeFiles, archivedStoreFiles, fs);
+
+ // now add store files with the same names as before to check backup
+ // enable archiving, make sure that files get archived
+ tracker.addTable(table);
+ addMockStoreFiles(2, services, storedir);
+
+ // do the cleaning of the parent
+ assertTrue(janitor.cleanParent(parent, r));
+
+ // and now check to make sure that the files have actually been archived
+ archivedStoreFiles = fs.listStatus(storeArchive);
+ compareArchiveToOriginal(storeFiles, archivedStoreFiles, fs, true);
+ } finally {
+ server.stop("shutdown");
+ }
+ }
+
+ @Category(MediumTests.class)
+ @Test
+ public void testCatalogJanitorMonitoredInArchive() throws Exception {
+ HBaseTestingUtility util = new HBaseTestingUtility();
+ try {
+ // setup the minicluster
+ util.startMiniCluster();
+ byte[] TABLE_NAME = Bytes.toBytes("TABLE");
+ HBaseAdmin admin = util.getHBaseAdmin();
+ util.createTable(TABLE_NAME, Bytes.toBytes("cf"));
+ assertFalse(admin.getArchivingEnabled(TABLE_NAME));
+
+ // make sure a simple enable works
+ admin.enableHFileBackup(TABLE_NAME);
+ assertTrue(admin.getArchivingEnabled(TABLE_NAME));
+ admin.disableHFileBackup();
+ assertFalse(admin.getArchivingEnabled(TABLE_NAME));
+
+ // set the table tracker to fail on archiving
+ CatalogJanitor catalog = util.getMiniHBaseCluster().getMaster().getCatalogJanitor();
+ HFileArchiveTracker tracker = (HFileArchiveTracker) catalog.getHfileTracker();
+
+ // keep the original around
+ HFileArchiveTableTracker orig = tracker.getTracker();
+
+ tracker.setTracker(new HFileArchiveTableTracker(util.getMiniHBaseCluster().getMaster(), util
+ .getZooKeeperWatcher()));
+
+ try {
+ // try turning on archiving, but it should fail
+ admin.enableHFileBackup(TABLE_NAME);
+ fail("Shouldn't have been able to finish archiving with the catalog tracker not joining");
+ } catch (IOException e) {
+ // reset the tracker, if we don't get an exception, then everything
+ // breaks
+ tracker.setTracker(orig);
+ }
+ } finally {
+ util.shutdownMiniCluster();
+ }
+ }
+
+ private void addMockStoreFiles(int count, MasterServices services,
+ Path storedir) throws IOException {
+ // get the existing store files
+ FileSystem fs = services.getMasterFileSystem().getFileSystem();
+ fs.mkdirs(storedir);
+ // create the store files in the parent
+ for (int i = 0; i < count; i++) {
+ Path storeFile = new Path(storedir, "_store" + i);
+ FSDataOutputStream dos = fs.create(storeFile, true);
+ dos.writeBytes("Some data: " + i);
+ dos.close();
+ }
+ // make sure the mock store files are there
+ FileStatus[] storeFiles = fs.listStatus(storedir);
+ assertEquals(count, storeFiles.length);
+ }
+
/**
* Make sure parent with specified end key gets cleaned up even if daughter is cleaned up before it.
*
@@ -393,13 +618,16 @@ public class TestCatalogJanitor {
* @throws InterruptedException
*/
private void parentWithSpecifiedEndKeyCleanedEvenIfDaughterGoneFirst(
- final String rootDir, final byte[] lastEndKey)
- throws IOException, InterruptedException {
+ final String rootDir, final byte[] lastEndKey)
+ throws IOException, InterruptedException {
HBaseTestingUtility htu = new HBaseTestingUtility();
setRootDirAndCleanIt(htu, rootDir);
Server server = new MockServer(htu);
MasterServices services = new MockMasterServices(server);
- CatalogJanitor janitor = new CatalogJanitor(server, services);
+ ZooKeeperWatcher watcher = Mockito.mock(ZooKeeperWatcher.class);
+ HFileArchiveTracker monitor = new HFileArchiveTracker(watcher, new HFileArchiveTableTracker(
+ services, watcher));
+ CatalogJanitor janitor = new CatalogJanitor(server, services, monitor);
final HTableDescriptor htd = createHTableDescriptor();
// Create regions: aaa->{lastEndKey}, aaa->ccc, aaa->bbb, bbb->ccc, etc.
@@ -429,12 +657,12 @@ public class TestCatalogJanitor {
HRegionInfo splitba = new HRegionInfo(htd.getName(), Bytes.toBytes("ccc"),
Bytes.toBytes("ddd"));
HRegionInfo splitbb = new HRegionInfo(htd.getName(), Bytes.toBytes("ddd"),
- lastEndKey);
+ lastEndKey);
// First test that our Comparator works right up in CatalogJanitor.
// Just fo kicks.
SortedMap regions =
- new TreeMap(new CatalogJanitor.SplitParentFirstComparator());
+ new TreeMap(new CatalogJanitor.SplitParentFirstComparator());
// Now make sure that this regions map sorts as we expect it to.
regions.put(parent, createResult(parent, splita, splitb));
regions.put(splitb, createResult(splitb, splitba, splitbb));
@@ -455,7 +683,7 @@ public class TestCatalogJanitor {
// Now play around with the cleanParent function. Create a ref from splita
// up to the parent.
Path splitaRef =
- createReferences(services, htd, parent, splita, Bytes.toBytes("ccc"), false);
+ createReferences(services, htd, parent, splita, Bytes.toBytes("ccc"), false);
// Make sure actual super parent sticks around because splita has a ref.
assertFalse(janitor.cleanParent(parent, regions.get(parent)));
@@ -471,9 +699,9 @@ public class TestCatalogJanitor {
assertTrue(fs.delete(splitaRef, true));
// Create the refs from daughters of splita.
Path splitaaRef =
- createReferences(services, htd, splita, splitaa, Bytes.toBytes("bbb"), false);
+ createReferences(services, htd, splita, splitaa, Bytes.toBytes("bbb"), false);
Path splitabRef =
- createReferences(services, htd, splita, splitab, Bytes.toBytes("bbb"), true);
+ createReferences(services, htd, splita, splitab, Bytes.toBytes("bbb"), true);
// Test splita. It should stick around because references from splitab, etc.
assertFalse(janitor.cleanParent(splita, regions.get(splita)));
@@ -492,10 +720,12 @@ public class TestCatalogJanitor {
private String setRootDirAndCleanIt(final HBaseTestingUtility htu,
final String subdir)
- throws IOException {
+ throws IOException {
Path testdir = htu.getDataTestDir(subdir);
FileSystem fs = FileSystem.get(htu.getConfiguration());
- if (fs.exists(testdir)) assertTrue(fs.delete(testdir, true));
+ if (fs.exists(testdir)) {
+ assertTrue(fs.delete(testdir, true));
+ }
htu.getConfiguration().set(HConstants.HBASE_DIR, testdir.toString());
return htu.getConfiguration().get(HConstants.HBASE_DIR);
}
@@ -513,7 +743,7 @@ public class TestCatalogJanitor {
private Path createReferences(final MasterServices services,
final HTableDescriptor htd, final HRegionInfo parent,
final HRegionInfo daughter, final byte [] midkey, final boolean top)
- throws IOException {
+ throws IOException {
Path rootdir = services.getMasterFileSystem().getRootDir();
Path tabledir = HTableDescriptor.getTableDir(rootdir, parent.getTableName());
Path storedir = Store.getStoreHomedir(tabledir, daughter.getEncodedName(),
@@ -530,7 +760,7 @@ public class TestCatalogJanitor {
private Result createResult(final HRegionInfo parent, final HRegionInfo a,
final HRegionInfo b)
- throws IOException {
+ throws IOException {
List kvs = new ArrayList();
kvs.add(new KeyValue(parent.getRegionName(), HConstants.CATALOG_FAMILY,
HConstants.SPLITA_QUALIFIER, Writables.getBytes(a)));
@@ -547,6 +777,6 @@ public class TestCatalogJanitor {
@org.junit.Rule
public org.apache.hadoop.hbase.ResourceCheckerJUnitRule cu =
- new org.apache.hadoop.hbase.ResourceCheckerJUnitRule();
+ new org.apache.hadoop.hbase.ResourceCheckerJUnitRule();
}
diff --git src/test/java/org/apache/hadoop/hbase/regionserver/TestRegionHFileArchiving.java src/test/java/org/apache/hadoop/hbase/regionserver/TestRegionHFileArchiving.java
new file mode 100644
index 0000000..83a96ea
--- /dev/null
+++ src/test/java/org/apache/hadoop/hbase/regionserver/TestRegionHFileArchiving.java
@@ -0,0 +1,549 @@
+/**
+ * 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.regionserver;
+
+import static org.apache.hadoop.hbase.util.HFileArchiveTestingUtil.compareArchiveToOriginal;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FSDataInputStream;
+import org.apache.hadoop.fs.FSDataOutputStream;
+import org.apache.hadoop.fs.FileStatus;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.hbase.HBaseTestingUtility;
+import org.apache.hadoop.hbase.HRegionInfo;
+import org.apache.hadoop.hbase.MediumTests;
+import org.apache.hadoop.hbase.backup.HFileArchiveMonitor;
+import org.apache.hadoop.hbase.backup.HFileArchiveTableTracker;
+import org.apache.hadoop.hbase.backup.HFileArchiveTracker;
+import org.apache.hadoop.hbase.client.HBaseAdmin;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.hadoop.hbase.util.HFileArchiveTestingUtil;
+import org.apache.hadoop.hbase.util.HFileArchiveUtil;
+import org.apache.hadoop.hbase.util.JVMClusterUtil;
+import org.apache.hadoop.hbase.util.JVMClusterUtil.RegionServerThread;
+import org.apache.hadoop.hbase.zookeeper.ZKUtil;
+import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
+import org.apache.zookeeper.KeeperException;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
+
+/**
+ * Spin up a small cluster and check that a region properly archives its hfiles
+ * when enabled.
+ */
+@Category(MediumTests.class)
+public class TestRegionHFileArchiving {
+
+ private static final Log LOG = LogFactory.getLog(TestRegionHFileArchiving.class);
+ private static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
+ private static final String STRING_TABLE_NAME = "test";
+ private static final byte[] TEST_FAM = Bytes.toBytes("fam");
+ private static final byte[] TABLE_NAME = Bytes.toBytes(STRING_TABLE_NAME);
+ private static final int numRS = 2;
+ private static final int maxTries = 5;
+
+ /**
+ * Setup the config for the cluster
+ */
+ @BeforeClass
+ public static void setupCluster() throws Exception {
+ setupConf(UTIL.getConfiguration());
+ UTIL.startMiniCluster(numRS);
+ }
+
+ private static void setupConf(Configuration conf) {
+ // disable the ui
+ conf.setInt("hbase.regionsever.info.port", -1);
+ // change the flush size to a small amount, regulating number of store files
+ conf.setInt("hbase.hregion.memstore.flush.size", 25000);
+ // so make sure we get a compaction when doing a load, but keep around some
+ // files in the store
+ conf.setInt("hbase.hstore.compaction.min", 10);
+ conf.setInt("hbase.hstore.compactionThreshold", 10);
+ // block writes if we get to 12 store files
+ conf.setInt("hbase.hstore.blockingStoreFiles", 12);
+ // drop the number of attempts for the hbase admin
+ UTIL.getConfiguration().setInt("hbase.client.retries.number", 1);
+ }
+
+ @Before
+ public void setup() throws Exception {
+ UTIL.createTable(TABLE_NAME, TEST_FAM);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ UTIL.deleteTable(TABLE_NAME);
+ // and cleanup the archive directory
+ try {
+ UTIL.getTestFileSystem().delete(new Path(UTIL.getDefaultRootDirPath(), ".archive"), true);
+ } catch (IOException e) {
+ LOG.warn("Failure to delete archive directory", e);
+ }
+ // make sure that backups are off for all tables
+ UTIL.getHBaseAdmin().disableHFileBackup();
+ }
+
+ @AfterClass
+ public static void cleanupTest() throws Exception {
+ try {
+ UTIL.shutdownMiniCluster();
+ } catch (Exception e) {
+ // NOOP;
+ }
+ }
+
+ @Test
+ public void testEnableDisableArchiving() throws Exception {
+ // Make sure archiving is not enabled
+ ZooKeeperWatcher zk = UTIL.getZooKeeperWatcher();
+ assertFalse(UTIL.getHBaseAdmin().getArchivingEnabled(TABLE_NAME));
+
+ // get the RS and region serving our table
+ HBaseAdmin admin = UTIL.getHBaseAdmin();
+ List servingRegions = UTIL.getHBaseCluster().getRegions(TABLE_NAME);
+ // make sure we only have 1 region serving this table
+ assertEquals(1, servingRegions.size());
+ HRegion region = servingRegions.get(0);
+ HRegionServer hrs = UTIL.getRSForFirstRegionInTable(TABLE_NAME);
+ FileSystem fs = hrs.getFileSystem();
+
+ // enable archiving
+ admin.enableHFileBackup(TABLE_NAME);
+ assertTrue(UTIL.getHBaseAdmin().getArchivingEnabled(TABLE_NAME));
+
+ // put some load on the table and make sure that files do get archived
+ loadAndCompact(region);
+
+ // check that we actually have some store files
+ // make sure we don't have any extra archive files from random compactions
+ HFileArchiveMonitor monitor = hrs.getHFileArchiveMonitor();
+ Store store = region.getStore(TEST_FAM);
+ Path storeArchiveDir = HFileArchiveUtil.getStoreArchivePath(UTIL.getConfiguration(),
+ region.getTableDir(), region.getRegionInfo().getEncodedName(), store.getFamily().getName());
+ assertTrue(fs.exists(storeArchiveDir));
+ assertTrue(fs.listStatus(storeArchiveDir).length > 0);
+
+ // now test that we properly stop backing up
+ LOG.debug("Stopping backup and testing that we don't archive");
+ admin.disableHFileBackup(STRING_TABLE_NAME);
+ int counter = 0;
+ while (monitor.keepHFiles(STRING_TABLE_NAME)) {
+ LOG.debug("Waiting for archive change to propaate");
+ Thread.sleep(100);
+ // max tries to propagate - if not, something is probably horribly
+ // wrong with zk/notification
+ assertTrue("Exceeded max tries to propagate hfile backup changes", counter++ < maxTries);
+ }
+
+ // delete the existing archive files
+ fs.delete(storeArchiveDir, true);
+
+ // and then put some more data in the table and ensure it compacts
+ loadAndCompact(region);
+
+ // put data into the table again to make sure that we don't copy new files
+ // over into the archive directory
+
+ // ensure there are no archived files
+ assertFalse(fs.exists(storeArchiveDir));
+
+ // make sure that overall backup also disables per-table
+ admin.enableHFileBackup(TABLE_NAME);
+ admin.disableHFileBackup();
+ assertFalse(UTIL.getHBaseAdmin().getArchivingEnabled(TABLE_NAME));
+ }
+
+ /**
+ * Ensure that archiving won't be turned on but have the tracker miss the
+ * update to the table via incorrect ZK watches (especially because
+ * createWithParents is not transactional).
+ * @throws Exception
+ */
+ @Test
+ public void testFindsTablesAfterArchivingEnabled() throws Exception {
+ // 1. create a tracker to track the nodes
+ ZooKeeperWatcher zkw = UTIL.getZooKeeperWatcher();
+ HRegionServer hrs = UTIL.getRSForFirstRegionInTable(TABLE_NAME);
+ HFileArchiveTracker tracker = hrs.hfileArchiveTracker;
+
+ // 2. create the archiving enabled znode
+ ZKUtil.createAndFailSilent(zkw, zkw.archiveHFileZNode);
+
+ // 3. now turn on archiving for the test table
+ HBaseAdmin admin = UTIL.getHBaseAdmin();
+ admin.enableHFileBackup(TABLE_NAME);
+
+ // 4. make sure that archiving is enabled for that tracker
+ assertTrue(tracker.keepHFiles(STRING_TABLE_NAME));
+ }
+
+ /**
+ * Test that we do synchronously start archiving and not return until we are
+ * done
+ */
+ @Test
+ public void testSynchronousArchiving() throws Exception {
+ HBaseAdmin admin = UTIL.getHBaseAdmin();
+ ZooKeeperWatcher zk = UTIL.getZooKeeperWatcher();
+
+ // 1. turn on hfile backups
+ LOG.debug("----Starting archiving");
+ admin.enableHFileBackup(TABLE_NAME);
+ assertTrue(UTIL.getHBaseAdmin().getArchivingEnabled( TABLE_NAME));
+
+ // 2. ensure that backups are kept on each RS
+ // get all the monitors
+ for (int i = 0; i < numRS; i++) {
+ HRegionServer hrs = UTIL.getHBaseCluster().getRegionServer(i);
+ // make sure that at least regions hosting the table have received the
+ // update to start archiving
+ if (hrs.getOnlineRegions(TABLE_NAME).size() > 0) {
+ assertTrue(hrs.getHFileArchiveMonitor().keepHFiles(STRING_TABLE_NAME));
+ }
+ }
+
+ // 3. now attempt to archive some other table that doesn't exist
+ try {
+ admin.enableHFileBackup("other table");
+ fail("Should get an IOException if a table cannot be backed up.");
+ } catch (IOException e) {
+ // this should happen
+ }
+ assertFalse("Table 'other table' should not be archived - it doesn't exist!", UTIL
+ .getHBaseAdmin().getArchivingEnabled(Bytes.toBytes("other table")));
+
+
+
+ // 4. now prevent one of the regionservers from archiving, which should
+ // cause archiving to fail
+ //make sure all archiving is off
+ admin.disableHFileBackup();
+ assertFalse(admin.getArchivingEnabled(TABLE_NAME));
+
+ //then hack the RS to not do any registration
+ HRegionServer hrs = UTIL.getRSForFirstRegionInTable(TABLE_NAME);
+ HFileArchiveTableTracker orig = hrs.hfileArchiveTracker.getTracker();
+ hrs.hfileArchiveTracker.setTracker(new HFileArchiveTableTracker(hrs, UTIL.getZooKeeperWatcher()));
+
+ // try turning on archiving, but it should fail
+ try {
+ admin.enableHFileBackup(TABLE_NAME);
+ fail("Shouldn't have been able to finish archiving");
+ } catch (IOException e) {
+ // reset the tracker, if we don't get an exception, then everything breaks
+ hrs.hfileArchiveTracker.setTracker(orig);
+ }
+ }
+
+ /**
+ * Test the advanced case where we turn on archiving and the region propagates
+ * the change down to the store
+ */
+ // timeout = 200 sec - if it takes longer, something is seriously borked with
+ // the minicluster.
+ @Test(timeout = 200000)
+ public void testCompactAndArchive() throws Exception {
+ HBaseAdmin admin = UTIL.getHBaseAdmin();
+
+ // get the RS and region serving our table
+ List servingRegions = UTIL.getHBaseCluster().getRegions(TABLE_NAME);
+ // make sure we only have 1 region serving this table
+ assertEquals(1, servingRegions.size());
+ HRegion region = servingRegions.get(0);
+
+ // get the parent RS and monitor
+ HRegionServer hrs = UTIL.getRSForFirstRegionInTable(TABLE_NAME);
+ FileSystem fs = hrs.getFileSystem();
+ Store store = region.getStores().get(TEST_FAM);
+
+ // 1. put some data on the region
+ LOG.debug("-------Loading table");
+ UTIL.loadRegion(region, TEST_FAM);
+
+ // get the current store files for the region
+ // and that there is only one store in the region
+ assertEquals(1, region.getStores().size());
+
+ int fileCount = store.getStorefiles().size();
+ assertTrue("Need more than 1 store file to compact and test archiving", fileCount > 1);
+ LOG.debug("Currently have: " + fileCount + " store files.");
+ LOG.debug("Has store files:");
+ for (StoreFile sf : store.getStorefiles()) {
+ LOG.debug("\t" + sf.getPath());
+ }
+
+ // 2. make sure that table archiving is enabled
+ // first force a flush to make sure nothing weird happens
+ region.flushcache();
+
+ // turn on hfile backups into .archive
+ LOG.debug("-----Enabling backups");
+ admin.enableHFileBackup(TABLE_NAME);
+
+ // make sure we don't have any extra archive files from random compactions
+ Path storeArchiveDir = HFileArchiveUtil.getStoreArchivePath(UTIL.getConfiguration(),
+ region.getTableDir(), region.getRegionInfo().getEncodedName(), store.getFamily().getName());
+ LOG.debug("-----Initial files in store archive:");
+ if (fs.exists(storeArchiveDir)) {
+ for (FileStatus f : fs.listStatus(storeArchiveDir)) {
+ LOG.debug("Deleting archive file: " + f.getPath()
+ + ", so we have a consistent backup view.");
+ }
+ fs.delete(storeArchiveDir, true);
+ } else LOG.debug("[EMPTY]");
+
+ // make sure that archiving is in a 'clean' state
+ assertNull(fs.listStatus(storeArchiveDir));
+
+ // make sure we block the store from compacting/flushing files in the middle
+ // of our
+ // copy of the store files
+ List origFiles;
+ FileStatus[] originals;
+ List copiedStores;
+ FileStatus[] archivedFiles;
+ synchronized (store.filesCompacting) {
+ synchronized (store.flushLock) {
+ LOG.debug("Locked the store");
+ // get the original store files before compaction
+ LOG.debug("------Original store files:");
+ originals = fs.listStatus(store.getHomedir());
+ for (FileStatus f : originals) {
+ LOG.debug("\t" + f.getPath());
+ }
+ // copy the original store files so we can use them for testing
+ // overwriting
+ // store files with the same name below
+ origFiles = store.getStorefiles();
+ copiedStores = new ArrayList(origFiles.size());
+ Path temproot = new Path(hrs.getRootDir(), "store_copy");
+ for (StoreFile f : origFiles) {
+ if (!fs.exists(f.getPath())) continue;
+
+ Path tmpStore = new Path(temproot, f.getPath().getName());
+ FSDataOutputStream tmpOutput = fs.create(tmpStore);
+ FSDataInputStream storeInput = fs.open(f.getPath());
+ while (storeInput.available() > 0) {
+ byte[] remaining = new byte[1024];
+ storeInput.read(remaining);
+ tmpOutput.write(remaining);
+ }
+ tmpOutput.close();
+ storeInput.close();
+ copiedStores.add(tmpStore);
+ }
+ LOG.debug("---------- Triggering compaction");
+ compactRegion(region, TEST_FAM);
+
+ // then get the archived store files
+ LOG.debug("----------Archived store files after compaction:");
+ archivedFiles = fs.listStatus(storeArchiveDir);
+ }
+ }
+ LOG.debug("Unlocked the store.");
+ for (FileStatus f : archivedFiles) {
+ LOG.debug("\t" + f.getPath());
+ }
+
+ // ensure the archived files match the original store files (at least in
+ // naming)
+ compareArchiveToOriginal(originals, archivedFiles, fs);
+ LOG.debug("Archive matches originals.");
+
+ // 3. Now copy back in the store files and trigger another compaction
+
+ // first delete out the existing files
+ LOG.debug("Deleting out existing store files, and moving in our copies.");
+
+ // lock again so we don't interfere with a compaction/flush
+ synchronized (store.filesCompacting) {
+ synchronized (store.flushLock) {
+ LOG.debug("Locked the store");
+ // delete the store directory (just in case)
+ fs.delete(store.getHomedir(), true);
+
+ // and copy back in the original store files (from before)
+ fs.mkdirs(store.getHomedir());
+ for (int i = 0; i < copiedStores.size(); i++) {
+ fs.rename(copiedStores.get(i), origFiles.get(i).getPath());
+ }
+
+ // now archive the files again
+ LOG.debug("Removing the store files again.");
+ store.removeStoreFiles(hrs.getHFileArchiveMonitor(), origFiles);
+
+ // ensure the files match to originals, but with a backup directory
+ LOG.debug("Checking originals vs. backed up (from archived) versions");
+ archivedFiles = fs.listStatus(storeArchiveDir);
+ compareArchiveToOriginal(originals, archivedFiles, fs, true);
+ }
+ }
+ }
+
+ @Test
+ public void testRegionSplitAndArchive() throws Exception {
+ HBaseAdmin admin = UTIL.getHBaseAdmin();
+
+ // start archiving
+ admin.enableHFileBackup(TABLE_NAME);
+
+ // get the current store files for the region
+ final List servingRegions = UTIL.getHBaseCluster().getRegions(TABLE_NAME);
+ // make sure we only have 1 region serving this table
+ assertEquals(1, servingRegions.size());
+ HRegion region = servingRegions.get(0);
+
+ // and that there is only one store in the region
+ assertEquals(1, region.getStores().size());
+ Store store = region.getStores().get(TEST_FAM);
+
+ // prep the store files so we get some files
+ LOG.debug("Loading store files");
+ // prepStoreFiles(admin, store, 3);
+ UTIL.loadRegion(region, TEST_FAM);
+
+ // get the files before compaction
+ FileSystem fs = region.getRegionServerServices().getFileSystem();
+
+ // delete out the current archive files, just for ease of comparison
+ // and synchronize to make sure we don't clobber another compaction
+ Path storeArchiveDir = HFileArchiveUtil.getStoreArchivePath(UTIL.getConfiguration(),
+ region.getTableDir(), region.getRegionInfo().getEncodedName(), store.getFamily().getName());
+
+ synchronized (region.writestate) {
+ // wait for all the compactions/flushes to complete on the region
+ LOG.debug("Waiting on region " + region + "to complete compactions & flushes");
+ region.waitForFlushesAndCompactions();
+ LOG.debug("Removing archived files - general cleanup");
+ assertTrue(fs.delete(storeArchiveDir, true));
+ assertTrue(fs.mkdirs(storeArchiveDir));
+ }
+ LOG.debug("Starting split of region");
+ // now split our region
+ admin.split(TABLE_NAME);
+ while (UTIL.getHBaseCluster().getRegions(TABLE_NAME).size() < 2) {
+ LOG.debug("Waiting for regions to split.");
+ Thread.sleep(100);
+ }
+ LOG.debug("Regions finished splitting.");
+ // at this point the region should have split
+ servingRegions.clear();
+ servingRegions.addAll(UTIL.getHBaseCluster().getRegions(TABLE_NAME));
+ // make sure we now have 2 regions serving this table
+ assertEquals(2, servingRegions.size());
+
+ // now check to make sure that those regions will also archive
+ Collection regionservers = Collections2.filter(Collections2.transform(UTIL
+ .getMiniHBaseCluster().getRegionServerThreads(),
+ new Function() {
+
+ @Override
+ public HRegionServer apply(RegionServerThread input) {
+ return input.getRegionServer();
+ }
+ }), new Predicate() {
+
+ @Override
+ public boolean apply(HRegionServer input) {
+ // get the names of the regions hosted by the rs
+ Collection regions;
+ try {
+ regions = Collections2.transform(input.getOnlineRegions(),
+ new Function() {
+ @Override
+ public String apply(HRegionInfo input) {
+ return input.getEncodedName();
+ }
+ });
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ // then check to make sure this RS is serving one of the serving regions
+ boolean found = false;
+ for (HRegion region : servingRegions) {
+ if (regions.contains(region.getRegionInfo().getEncodedName())) {
+ found = true;
+ break;
+ }
+ }
+ return found;
+
+ }
+ });
+
+ assertTrue(regionservers.size() > 0);
+
+ // check each of the region servers to make sure it has got the update
+ for (HRegionServer serving : regionservers) {
+ assertTrue("RegionServer:" + serving + " hasn't been included in backup",
+ serving.hfileArchiveTracker.keepHFiles(STRING_TABLE_NAME));
+ }
+ }
+
+ /**
+ * Load the given region and then ensure that it compacts some files
+ */
+ private void loadAndCompact(HRegion region) throws Exception {
+ int tries = 0;
+ Exception last = null;
+ while (tries++ <= maxTries) {
+ try {
+ // load the region with data
+ UTIL.loadRegion(region, TEST_FAM);
+ // and then trigger a compaction to be sure we try to archive
+ compactRegion(region, TEST_FAM);
+ return;
+ } catch (Exception e) {
+ // keep this around for if we fail later
+ last = e;
+ }
+ }
+ throw last;
+ }
+
+ /**
+ * Compact all the store files in a given region.
+ */
+ private void compactRegion(HRegion region, byte[] family) throws IOException {
+ Store store = region.getStores().get(TEST_FAM);
+ store.compactRecentForTesting(store.getStorefiles().size());
+ }
+}
diff --git src/test/java/org/apache/hadoop/hbase/util/HFileArchiveTestingUtil.java src/test/java/org/apache/hadoop/hbase/util/HFileArchiveTestingUtil.java
new file mode 100644
index 0000000..2abe472
--- /dev/null
+++ src/test/java/org/apache/hadoop/hbase/util/HFileArchiveTestingUtil.java
@@ -0,0 +1,106 @@
+package org.apache.hadoop.hbase.util;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.hadoop.fs.FileStatus;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
+
+public class HFileArchiveTestingUtil {
+
+ private HFileArchiveTestingUtil() {
+ // NOOP private ctor since this is just a utility class
+ }
+
+ /**
+ * Compare the archived files to the files in the original directory
+ * @param previous original files that should have been archived
+ * @param archived files that were archived
+ * @param fs filessystem on which the archiving took place
+ * @throws IOException
+ */
+ public static void compareArchiveToOriginal(FileStatus[] previous, FileStatus[] archived,
+ FileSystem fs) throws IOException {
+ compareArchiveToOriginal(previous, archived, fs, false);
+ }
+
+ /**
+ * Compare the archived files to the files in the original directory
+ * @param previous original files that should have been archived
+ * @param archived files that were archived
+ * @param fs filessystem on which the archiving took place
+ * @param hasTimedBackup true if we expect to find an archive backup
+ * directory with a copy of the files in the archive directory (and
+ * the original files).
+ * @throws IOException
+ */
+ public static void compareArchiveToOriginal(FileStatus[] previous, FileStatus[] archived,
+ FileSystem fs, boolean hasTimedBackup) throws IOException {
+ boolean hasBackup = false;
+
+ List currentFiles = new ArrayList(previous.length);
+ List backedupFiles = new ArrayList(previous.length);
+ for (FileStatus f : archived) {
+ String name = f.getPath().getName();
+ // if the file has been backed up
+ if (name.contains(".")) {
+ Path parent = f.getPath().getParent();
+ String shortName = name.split("[.]")[0];
+ Path modPath = new Path(parent, shortName);
+ FileStatus file = new FileStatus(f.getLen(), f.isDir(), f.getReplication(),
+ f.getBlockSize(), f.getModificationTime(), modPath);
+ backedupFiles.add(file);
+ hasBackup = true;
+ } else {
+ // otherwise, add it to the list to compare to the original store files
+ currentFiles.add(name);
+ }
+ }
+ // check the backed up files versus the current (should match up, less the
+ // backup time in the name)
+ if (backedupFiles.size() > 0) {
+ compareArchiveToOriginal(previous, backedupFiles.toArray(new FileStatus[0]), fs);
+ }
+ // do the rest of the comparison
+ Collections.sort(currentFiles);
+
+ List originalFileNames = new ArrayList(previous.length);
+ for (FileStatus f : previous) {
+ originalFileNames.add(f.getPath().getName());
+ }
+ Collections.sort(originalFileNames);
+
+ assertEquals(
+ "Not the same numbe of current files\n" + compareFileLists(originalFileNames, currentFiles),
+ originalFileNames.size(), currentFiles.size());
+ assertEquals(
+ "Different backup files, but same amount\n"
+ + compareFileLists(originalFileNames, currentFiles), originalFileNames, currentFiles);
+ assertEquals("Expected a backup dir, but none was found", hasTimedBackup, hasBackup);
+ }
+
+ /* Get a pretty representation of the differences */
+ private static String compareFileLists(List expected, List gotten) {
+ StringBuilder sb = new StringBuilder("Expected (" + expected.size() + "): \t\t Gotten ("
+ + gotten.size() + "):\n");
+ List notFound = new ArrayList();
+ for (String s : expected) {
+ if (gotten.contains(s)) sb.append(s + "\t\t" + s + "\n");
+ else notFound.add(s);
+ }
+ sb.append("Not Found:\n");
+ for (String s : notFound) {
+ sb.append(s + "\n");
+ }
+ sb.append("\nExtra:\n");
+ for (String s : gotten) {
+ if (!expected.contains(s)) sb.append(s + "\n");
+ }
+ return sb.toString();
+ }
+}
diff --git src/test/java/org/apache/hadoop/hbase/util/MockRegionServerServices.java src/test/java/org/apache/hadoop/hbase/util/MockRegionServerServices.java
index 7d02759..1d7a6a4 100644
--- src/test/java/org/apache/hadoop/hbase/util/MockRegionServerServices.java
+++ src/test/java/org/apache/hadoop/hbase/util/MockRegionServerServices.java
@@ -26,6 +26,7 @@ import java.util.concurrent.ConcurrentSkipListMap;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.hbase.ServerName;
+import org.apache.hadoop.hbase.backup.HFileArchiveMonitor;
import org.apache.hadoop.hbase.catalog.CatalogTracker;
import org.apache.hadoop.hbase.fs.HFileSystem;
import org.apache.hadoop.hbase.ipc.RpcServer;
@@ -35,7 +36,6 @@ import org.apache.hadoop.hbase.regionserver.HRegion;
import org.apache.hadoop.hbase.regionserver.RegionServerAccounting;
import org.apache.hadoop.hbase.regionserver.RegionServerServices;
import org.apache.hadoop.hbase.regionserver.wal.HLog;
-import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
import org.apache.zookeeper.KeeperException;
@@ -156,4 +156,10 @@ public class MockRegionServerServices implements RegionServerServices {
public void setFileSystem(FileSystem hfs) {
this.hfs = (HFileSystem)hfs;
}
+
+ @Override
+ public HFileArchiveMonitor getHFileArchiveMonitor() {
+ // TODO Implement getHFileArchiveManager
+ return null;
+ }
}
diff --git src/test/java/org/apache/hadoop/hbase/util/TestHFileArchivingCleanup.java src/test/java/org/apache/hadoop/hbase/util/TestHFileArchivingCleanup.java
new file mode 100644
index 0000000..e5a4a37
--- /dev/null
+++ src/test/java/org/apache/hadoop/hbase/util/TestHFileArchivingCleanup.java
@@ -0,0 +1,162 @@
+package org.apache.hadoop.hbase.util;
+
+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.conf.Configuration;
+import org.apache.hadoop.fs.FSDataOutputStream;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.hbase.HBaseTestingUtility;
+import org.apache.hadoop.hbase.backup.HFileArchiveMonitor;
+import org.apache.hadoop.hbase.client.HBaseAdmin;
+import org.apache.hadoop.hbase.regionserver.HRegion;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class TestHFileArchivingCleanup {
+
+ private static final Log LOG = LogFactory.getLog(TestHFileArchivingCleanup.class);
+ private static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
+ private static final String STRING_TABLE_NAME = "test";
+ private static final byte[] TABLE_NAME = Bytes.toBytes(STRING_TABLE_NAME);
+ private static final byte[] TEST_FAM = Bytes.toBytes("fam");
+
+ /**
+ * Setup the config for the cluster
+ */
+ @BeforeClass
+ public static void setupCluster() throws Exception {
+ setupConf(UTIL.getConfiguration());
+ UTIL.startMiniCluster();
+ }
+
+ private static void setupConf(Configuration conf) {
+ // disable the ui
+ conf.setInt("hbase.regionsever.info.port", -1);
+ // change the flush size to a small amount, regulating number of store files
+ }
+
+ @Before
+ public void setup() throws Exception {
+ UTIL.createTable(TABLE_NAME, TEST_FAM);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ UTIL.deleteTable(TABLE_NAME);
+ // and cleanup the archive directory
+ try {
+ UTIL.getTestFileSystem().delete(new Path(UTIL.getDefaultRootDirPath(), ".archive"), true);
+ } catch (IOException e) {
+ LOG.warn("Failure to delete archive directory", e);
+ }
+ // make sure that backups are off for all tables
+ UTIL.getHBaseAdmin().disableHFileBackup();
+ }
+
+ @AfterClass
+ public static void cleanupTest() throws Exception {
+ try {
+ UTIL.shutdownMiniCluster();
+ } catch (Exception e) {
+ // NOOP;
+ }
+ }
+
+ /**
+ * Test that when an attempt to start archiving fails, the master deletes any
+ * archive fails newer than the attempt to start archiving. Ensures that we
+ * don't have unknown archive files lying around
+ * @throws Exception
+ */
+ // @Ignore
+ @Test
+ public void testMasterDeletesFailedArchive() throws Exception {
+ final HBaseAdmin admin = UTIL.getHBaseAdmin();
+
+ // get the current store files for the region
+ List servingRegions = UTIL.getHBaseCluster().getRegions(TABLE_NAME);
+ // make sure we only have 1 region serving this table
+ assertEquals(1, servingRegions.size());
+ final HRegion region = servingRegions.get(0);
+
+ // turn on archiving
+ admin.enableHFileBackup(TABLE_NAME);
+ LOG.debug("----Starting test of cleanup");
+ // so lets put some files in the archive that are newer than the start
+ FileSystem fs = UTIL.getTestFileSystem();
+ Path archiveDir = HFileArchiveUtil.getTableArchivePath(UTIL.getConfiguration(),
+ region.getTableDir());
+ // write a tmp file to the archive dir
+ Path tmpFile = new Path(archiveDir, "toDelete");
+ FSDataOutputStream out = fs.create(tmpFile);
+ out.write(1);
+ out.close();
+ LOG.debug("Created toDelete");
+
+ // now run the cleanup util
+ HFileArchiveCleanup.setConfiguration(UTIL.getConfiguration());
+ HFileArchiveCleanup.main(new String[0]);
+ // make sure the fake archived file has been cleaned up
+ assertFalse(fs.exists(tmpFile));
+
+ // now do the same create again, but make sure it still exists since we have
+ // an earlier end time
+ long end = EnvironmentEdgeManager.currentTimeMillis();
+
+ LOG.debug("re-created toDelete");
+ // write a tmp file to the archive dir
+ out = fs.create(tmpFile);
+ out.write(1);
+ out.close();
+ HFileArchiveCleanup.main(new String[] { "-e", Long.toString(end) });
+ assertTrue(fs.exists(tmpFile));
+
+ LOG.debug("Still not deleting the file");
+ // now bump the start time to match the end time - still should be there
+ HFileArchiveCleanup.main(new String[] { "-s", Long.toString(end), "-e", Long.toString(end) });
+ assertTrue(fs.exists(tmpFile));
+
+ // now move the start up to include the file, which should delete it
+ LOG.debug("Now should delete the file");
+ HFileArchiveCleanup.main(new String[] { "-s",
+ Long.toString(fs.getFileStatus(tmpFile).getModificationTime()) });
+ assertFalse(fs.exists(tmpFile));
+
+ // now create the files in multiple table directories, and check that we
+ // only delete the one in the specified directory
+ out = fs.create(tmpFile);
+ out.write(1);
+ out.close();
+
+ // create the table archive and put a file in there
+ Path tableArchive = new Path(archiveDir, STRING_TABLE_NAME);
+ fs.mkdirs(tableArchive);
+ Path tableFile = new Path(tableArchive, "table");
+ out = fs.create(tableFile);
+ out.write(1);
+ out.close();
+
+ // now delete just that table
+ LOG.debug("Just cleaning up table: table, files:" + tableFile);
+ HFileArchiveCleanup
+ .main(new String[] { "-s",
+ Long.toString(fs.getFileStatus(tableFile).getModificationTime()), "-t",
+ STRING_TABLE_NAME });
+ assertTrue(fs.exists(tmpFile));
+ assertFalse(fs.exists(tableFile));
+
+ }
+
+}