HBASE-14030 HBase Backup/Restore Phase 1 (Vladimir Rodionov)
This commit is contained in:
parent
5eefe13173
commit
de69f0df34
|
@ -99,6 +99,8 @@ if [ $# = 0 ]; then
|
||||||
echo " pe Run PerformanceEvaluation"
|
echo " pe Run PerformanceEvaluation"
|
||||||
echo " ltt Run LoadTestTool"
|
echo " ltt Run LoadTestTool"
|
||||||
echo " version Print the version"
|
echo " version Print the version"
|
||||||
|
echo " backup backup tables for recovery"
|
||||||
|
echo " restore restore tables from existing backup image"
|
||||||
echo " CLASSNAME Run the class named CLASSNAME"
|
echo " CLASSNAME Run the class named CLASSNAME"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
@ -303,6 +305,10 @@ elif [ "$COMMAND" = "hfile" ] ; then
|
||||||
CLASS='org.apache.hadoop.hbase.io.hfile.HFilePrettyPrinter'
|
CLASS='org.apache.hadoop.hbase.io.hfile.HFilePrettyPrinter'
|
||||||
elif [ "$COMMAND" = "zkcli" ] ; then
|
elif [ "$COMMAND" = "zkcli" ] ; then
|
||||||
CLASS="org.apache.hadoop.hbase.zookeeper.ZooKeeperMainServer"
|
CLASS="org.apache.hadoop.hbase.zookeeper.ZooKeeperMainServer"
|
||||||
|
elif [ "$COMMAND" = "backup" ] ; then
|
||||||
|
CLASS='org.apache.hadoop.hbase.backup.BackupClient'
|
||||||
|
elif [ "$COMMAND" = "restore" ] ; then
|
||||||
|
CLASS='org.apache.hadoop.hbase.backup.RestoreClient'
|
||||||
elif [ "$COMMAND" = "upgrade" ] ; then
|
elif [ "$COMMAND" = "upgrade" ] ; then
|
||||||
echo "This command was used to upgrade to HBase 0.96, it was removed in HBase 2.0.0."
|
echo "This command was used to upgrade to HBase 0.96, it was removed in HBase 2.0.0."
|
||||||
echo "Please follow the documentation at http://hbase.apache.org/book.html#upgrading."
|
echo "Please follow the documentation at http://hbase.apache.org/book.html#upgrading."
|
||||||
|
|
|
@ -40,11 +40,11 @@ import org.apache.hadoop.hbase.util.Bytes;
|
||||||
public final class HConstants {
|
public final class HConstants {
|
||||||
// NOTICE!!!! Please do not add a constants here, unless they are referenced by a lot of classes.
|
// NOTICE!!!! Please do not add a constants here, unless they are referenced by a lot of classes.
|
||||||
|
|
||||||
//Bytes.UTF8_ENCODING should be updated if this changed
|
// Bytes.UTF8_ENCODING should be updated if this changed
|
||||||
/** When we encode strings, we always specify UTF8 encoding */
|
/** When we encode strings, we always specify UTF8 encoding */
|
||||||
public static final String UTF8_ENCODING = "UTF-8";
|
public static final String UTF8_ENCODING = "UTF-8";
|
||||||
|
|
||||||
//Bytes.UTF8_CHARSET should be updated if this changed
|
// Bytes.UTF8_CHARSET should be updated if this changed
|
||||||
/** When we encode strings, we always specify UTF8 encoding */
|
/** When we encode strings, we always specify UTF8 encoding */
|
||||||
public static final Charset UTF8_CHARSET = Charset.forName(UTF8_ENCODING);
|
public static final Charset UTF8_CHARSET = Charset.forName(UTF8_ENCODING);
|
||||||
/**
|
/**
|
||||||
|
@ -55,9 +55,9 @@ public final class HConstants {
|
||||||
/** Used as a magic return value while optimized index key feature enabled(HBASE-7845) */
|
/** Used as a magic return value while optimized index key feature enabled(HBASE-7845) */
|
||||||
public final static int INDEX_KEY_MAGIC = -2;
|
public final static int INDEX_KEY_MAGIC = -2;
|
||||||
/*
|
/*
|
||||||
* Name of directory that holds recovered edits written by the wal log
|
* Name of directory that holds recovered edits written by the wal log splitting code, one per
|
||||||
* splitting code, one per region
|
* region
|
||||||
*/
|
*/
|
||||||
public static final String RECOVERED_EDITS_DIR = "recovered.edits";
|
public static final String RECOVERED_EDITS_DIR = "recovered.edits";
|
||||||
/**
|
/**
|
||||||
* The first four bytes of Hadoop RPC connections
|
* The first four bytes of Hadoop RPC connections
|
||||||
|
@ -70,27 +70,24 @@ public final class HConstants {
|
||||||
/** The size data structures with minor version is 0 */
|
/** The size data structures with minor version is 0 */
|
||||||
public static final int HFILEBLOCK_HEADER_SIZE_NO_CHECKSUM = MAGIC_LENGTH + 2 * Bytes.SIZEOF_INT
|
public static final int HFILEBLOCK_HEADER_SIZE_NO_CHECKSUM = MAGIC_LENGTH + 2 * Bytes.SIZEOF_INT
|
||||||
+ Bytes.SIZEOF_LONG;
|
+ Bytes.SIZEOF_LONG;
|
||||||
/** The size of a version 2 HFile block header, minor version 1.
|
/**
|
||||||
* There is a 1 byte checksum type, followed by a 4 byte bytesPerChecksum
|
* The size of a version 2 HFile block header, minor version 1. There is a 1 byte checksum type,
|
||||||
* followed by another 4 byte value to store sizeofDataOnDisk.
|
* followed by a 4 byte bytesPerChecksum followed by another 4 byte value to store
|
||||||
|
* sizeofDataOnDisk.
|
||||||
*/
|
*/
|
||||||
public static final int HFILEBLOCK_HEADER_SIZE = HFILEBLOCK_HEADER_SIZE_NO_CHECKSUM +
|
public static final int HFILEBLOCK_HEADER_SIZE = HFILEBLOCK_HEADER_SIZE_NO_CHECKSUM
|
||||||
Bytes.SIZEOF_BYTE + 2 * Bytes.SIZEOF_INT;
|
+ Bytes.SIZEOF_BYTE + 2 * Bytes.SIZEOF_INT;
|
||||||
/** Just an array of bytes of the right size. */
|
/** Just an array of bytes of the right size. */
|
||||||
public static final byte[] HFILEBLOCK_DUMMY_HEADER = new byte[HFILEBLOCK_HEADER_SIZE];
|
public static final byte[] HFILEBLOCK_DUMMY_HEADER = new byte[HFILEBLOCK_HEADER_SIZE];
|
||||||
|
|
||||||
//End HFileBlockConstants.
|
// End HFileBlockConstants.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Status codes used for return values of bulk operations.
|
* Status codes used for return values of bulk operations.
|
||||||
*/
|
*/
|
||||||
@InterfaceAudience.Private
|
@InterfaceAudience.Private
|
||||||
public enum OperationStatusCode {
|
public enum OperationStatusCode {
|
||||||
NOT_RUN,
|
NOT_RUN, SUCCESS, BAD_FAMILY, SANITY_CHECK_FAILURE, FAILURE;
|
||||||
SUCCESS,
|
|
||||||
BAD_FAMILY,
|
|
||||||
SANITY_CHECK_FAILURE,
|
|
||||||
FAILURE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** long constant for zero */
|
/** long constant for zero */
|
||||||
|
@ -104,19 +101,16 @@ public final class HConstants {
|
||||||
public static final String VERSION_FILE_NAME = "hbase.version";
|
public static final String VERSION_FILE_NAME = "hbase.version";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current version of file system.
|
* Current version of file system. Version 4 supports only one kind of bloom filter. Version 5
|
||||||
* Version 4 supports only one kind of bloom filter.
|
* changes versions in catalog table regions. Version 6 enables blockcaching on catalog tables.
|
||||||
* Version 5 changes versions in catalog table regions.
|
* Version 7 introduces hfile -- hbase 0.19 to 0.20.. Version 8 introduces namespace
|
||||||
* Version 6 enables blockcaching on catalog tables.
|
|
||||||
* Version 7 introduces hfile -- hbase 0.19 to 0.20..
|
|
||||||
* Version 8 introduces namespace
|
|
||||||
*/
|
*/
|
||||||
// public static final String FILE_SYSTEM_VERSION = "6";
|
// public static final String FILE_SYSTEM_VERSION = "6";
|
||||||
public static final String FILE_SYSTEM_VERSION = "8";
|
public static final String FILE_SYSTEM_VERSION = "8";
|
||||||
|
|
||||||
// Configuration parameters
|
// Configuration parameters
|
||||||
|
|
||||||
//TODO: Is having HBase homed on port 60k OK?
|
// TODO: Is having HBase homed on port 60k OK?
|
||||||
|
|
||||||
/** Cluster is in distributed mode or not */
|
/** Cluster is in distributed mode or not */
|
||||||
public static final String CLUSTER_DISTRIBUTED = "hbase.cluster.distributed";
|
public static final String CLUSTER_DISTRIBUTED = "hbase.cluster.distributed";
|
||||||
|
@ -131,12 +125,10 @@ public final class HConstants {
|
||||||
public static final String ENSEMBLE_TABLE_NAME = "hbase:ensemble";
|
public static final String ENSEMBLE_TABLE_NAME = "hbase:ensemble";
|
||||||
|
|
||||||
/** Config for pluggable region normalizer */
|
/** Config for pluggable region normalizer */
|
||||||
public static final String HBASE_MASTER_NORMALIZER_CLASS =
|
public static final String HBASE_MASTER_NORMALIZER_CLASS = "hbase.master.normalizer.class";
|
||||||
"hbase.master.normalizer.class";
|
|
||||||
|
|
||||||
/** Config for enabling/disabling pluggable region normalizer */
|
/** Config for enabling/disabling pluggable region normalizer */
|
||||||
public static final String HBASE_NORMALIZER_ENABLED =
|
public static final String HBASE_NORMALIZER_ENABLED = "hbase.normalizer.enabled";
|
||||||
"hbase.normalizer.enabled";
|
|
||||||
|
|
||||||
/** Cluster is standalone or pseudo-distributed */
|
/** Cluster is standalone or pseudo-distributed */
|
||||||
public static final boolean CLUSTER_IS_LOCAL = false;
|
public static final boolean CLUSTER_IS_LOCAL = false;
|
||||||
|
@ -174,21 +166,18 @@ public final class HConstants {
|
||||||
public static final String ZOOKEEPER_QUORUM = "hbase.zookeeper.quorum";
|
public static final String ZOOKEEPER_QUORUM = "hbase.zookeeper.quorum";
|
||||||
|
|
||||||
/** Common prefix of ZooKeeper configuration properties */
|
/** Common prefix of ZooKeeper configuration properties */
|
||||||
public static final String ZK_CFG_PROPERTY_PREFIX =
|
public static final String ZK_CFG_PROPERTY_PREFIX = "hbase.zookeeper.property.";
|
||||||
"hbase.zookeeper.property.";
|
|
||||||
|
|
||||||
public static final int ZK_CFG_PROPERTY_PREFIX_LEN =
|
public static final int ZK_CFG_PROPERTY_PREFIX_LEN = ZK_CFG_PROPERTY_PREFIX.length();
|
||||||
ZK_CFG_PROPERTY_PREFIX.length();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ZK client port key in the ZK properties map. The name reflects the
|
* The ZK client port key in the ZK properties map. The name reflects the fact that this is not an
|
||||||
* fact that this is not an HBase configuration key.
|
* HBase configuration key.
|
||||||
*/
|
*/
|
||||||
public static final String CLIENT_PORT_STR = "clientPort";
|
public static final String CLIENT_PORT_STR = "clientPort";
|
||||||
|
|
||||||
/** Parameter name for the client port that the zookeeper listens on */
|
/** Parameter name for the client port that the zookeeper listens on */
|
||||||
public static final String ZOOKEEPER_CLIENT_PORT =
|
public static final String ZOOKEEPER_CLIENT_PORT = ZK_CFG_PROPERTY_PREFIX + CLIENT_PORT_STR;
|
||||||
ZK_CFG_PROPERTY_PREFIX + CLIENT_PORT_STR;
|
|
||||||
|
|
||||||
/** Default client port that the zookeeper listens on */
|
/** Default client port that the zookeeper listens on */
|
||||||
public static final int DEFAULT_ZOOKEPER_CLIENT_PORT = 2181;
|
public static final int DEFAULT_ZOOKEPER_CLIENT_PORT = 2181;
|
||||||
|
@ -208,19 +197,15 @@ public final class HConstants {
|
||||||
public static final String DEFAULT_ZOOKEEPER_ZNODE_PARENT = "/hbase";
|
public static final String DEFAULT_ZOOKEEPER_ZNODE_PARENT = "/hbase";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parameter name for the limit on concurrent client-side zookeeper
|
* Parameter name for the limit on concurrent client-side zookeeper connections
|
||||||
* connections
|
|
||||||
*/
|
*/
|
||||||
public static final String ZOOKEEPER_MAX_CLIENT_CNXNS =
|
public static final String ZOOKEEPER_MAX_CLIENT_CNXNS = ZK_CFG_PROPERTY_PREFIX + "maxClientCnxns";
|
||||||
ZK_CFG_PROPERTY_PREFIX + "maxClientCnxns";
|
|
||||||
|
|
||||||
/** Parameter name for the ZK data directory */
|
/** Parameter name for the ZK data directory */
|
||||||
public static final String ZOOKEEPER_DATA_DIR =
|
public static final String ZOOKEEPER_DATA_DIR = ZK_CFG_PROPERTY_PREFIX + "dataDir";
|
||||||
ZK_CFG_PROPERTY_PREFIX + "dataDir";
|
|
||||||
|
|
||||||
/** Parameter name for the ZK tick time */
|
/** Parameter name for the ZK tick time */
|
||||||
public static final String ZOOKEEPER_TICK_TIME =
|
public static final String ZOOKEEPER_TICK_TIME = ZK_CFG_PROPERTY_PREFIX + "tickTime";
|
||||||
ZK_CFG_PROPERTY_PREFIX + "tickTime";
|
|
||||||
|
|
||||||
/** Default limit on concurrent client-side zookeeper connections */
|
/** Default limit on concurrent client-side zookeeper connections */
|
||||||
public static final int DEFAULT_ZOOKEPER_MAX_CLIENT_CNXNS = 300;
|
public static final int DEFAULT_ZOOKEPER_MAX_CLIENT_CNXNS = 300;
|
||||||
|
@ -244,21 +229,19 @@ public final class HConstants {
|
||||||
public static final int DEFAULT_REGIONSERVER_INFOPORT = 16030;
|
public static final int DEFAULT_REGIONSERVER_INFOPORT = 16030;
|
||||||
|
|
||||||
/** A configuration key for regionserver info port */
|
/** A configuration key for regionserver info port */
|
||||||
public static final String REGIONSERVER_INFO_PORT =
|
public static final String REGIONSERVER_INFO_PORT = "hbase.regionserver.info.port";
|
||||||
"hbase.regionserver.info.port";
|
|
||||||
|
|
||||||
/** A flag that enables automatic selection of regionserver info port */
|
/** A flag that enables automatic selection of regionserver info port */
|
||||||
public static final String REGIONSERVER_INFO_PORT_AUTO =
|
public static final String REGIONSERVER_INFO_PORT_AUTO = REGIONSERVER_INFO_PORT + ".auto";
|
||||||
REGIONSERVER_INFO_PORT + ".auto";
|
|
||||||
|
|
||||||
/** Parameter name for what region server implementation to use. */
|
/** Parameter name for what region server implementation to use. */
|
||||||
public static final String REGION_SERVER_IMPL= "hbase.regionserver.impl";
|
public static final String REGION_SERVER_IMPL = "hbase.regionserver.impl";
|
||||||
|
|
||||||
/** Parameter name for what master implementation to use. */
|
/** Parameter name for what master implementation to use. */
|
||||||
public static final String MASTER_IMPL= "hbase.master.impl";
|
public static final String MASTER_IMPL = "hbase.master.impl";
|
||||||
|
|
||||||
/** Parameter name for what hbase client implementation to use. */
|
/** Parameter name for what hbase client implementation to use. */
|
||||||
public static final String HBASECLIENT_IMPL= "hbase.hbaseclient.impl";
|
public static final String HBASECLIENT_IMPL = "hbase.hbaseclient.impl";
|
||||||
|
|
||||||
/** Parameter name for how often threads should wake up */
|
/** Parameter name for how often threads should wake up */
|
||||||
public static final String THREAD_WAKE_FREQUENCY = "hbase.server.thread.wakefrequency";
|
public static final String THREAD_WAKE_FREQUENCY = "hbase.server.thread.wakefrequency";
|
||||||
|
@ -293,7 +276,7 @@ public final class HConstants {
|
||||||
|
|
||||||
/** Parameter name for HBase client operation timeout, which overrides RPC timeout */
|
/** Parameter name for HBase client operation timeout, which overrides RPC timeout */
|
||||||
public static final String HBASE_CLIENT_META_OPERATION_TIMEOUT =
|
public static final String HBASE_CLIENT_META_OPERATION_TIMEOUT =
|
||||||
"hbase.client.meta.operation.timeout";
|
"hbase.client.meta.operation.timeout";
|
||||||
|
|
||||||
/** Default HBase client operation timeout, which is tantamount to a blocking call */
|
/** Default HBase client operation timeout, which is tantamount to a blocking call */
|
||||||
public static final int DEFAULT_HBASE_CLIENT_OPERATION_TIMEOUT = 1200000;
|
public static final int DEFAULT_HBASE_CLIENT_OPERATION_TIMEOUT = 1200000;
|
||||||
|
@ -316,9 +299,8 @@ public final class HConstants {
|
||||||
public static final String MIGRATION_NAME = ".migration";
|
public static final String MIGRATION_NAME = ".migration";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The directory from which co-processor/custom filter jars can be loaded
|
* The directory from which co-processor/custom filter jars can be loaded dynamically by the
|
||||||
* dynamically by the region servers. This value can be overridden by the
|
* region servers. This value can be overridden by the hbase.dynamic.jars.dir config.
|
||||||
* hbase.dynamic.jars.dir config.
|
|
||||||
*/
|
*/
|
||||||
public static final String LIB_DIR = "lib";
|
public static final String LIB_DIR = "lib";
|
||||||
|
|
||||||
|
@ -326,8 +308,7 @@ public final class HConstants {
|
||||||
public static final String HREGION_COMPACTIONDIR_NAME = "compaction.dir";
|
public static final String HREGION_COMPACTIONDIR_NAME = "compaction.dir";
|
||||||
|
|
||||||
/** Conf key for the max file size after which we split the region */
|
/** Conf key for the max file size after which we split the region */
|
||||||
public static final String HREGION_MAX_FILESIZE =
|
public static final String HREGION_MAX_FILESIZE = "hbase.hregion.max.filesize";
|
||||||
"hbase.hregion.max.filesize";
|
|
||||||
|
|
||||||
/** Default maximum file size */
|
/** Default maximum file size */
|
||||||
public static final long DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024 * 1024L;
|
public static final long DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024 * 1024L;
|
||||||
|
@ -343,25 +324,24 @@ public final class HConstants {
|
||||||
public static final long TABLE_MAX_ROWSIZE_DEFAULT = 1024 * 1024 * 1024L;
|
public static final long TABLE_MAX_ROWSIZE_DEFAULT = 1024 * 1024 * 1024L;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The max number of threads used for opening and closing stores or store
|
* The max number of threads used for opening and closing stores or store files in parallel
|
||||||
* files in parallel
|
|
||||||
*/
|
*/
|
||||||
public static final String HSTORE_OPEN_AND_CLOSE_THREADS_MAX =
|
public static final String HSTORE_OPEN_AND_CLOSE_THREADS_MAX =
|
||||||
"hbase.hstore.open.and.close.threads.max";
|
"hbase.hstore.open.and.close.threads.max";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default number for the max number of threads used for opening and
|
* The default number for the max number of threads used for opening and closing stores or store
|
||||||
* closing stores or store files in parallel
|
* files in parallel
|
||||||
*/
|
*/
|
||||||
public static final int DEFAULT_HSTORE_OPEN_AND_CLOSE_THREADS_MAX = 1;
|
public static final int DEFAULT_HSTORE_OPEN_AND_CLOSE_THREADS_MAX = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Block updates if memstore has hbase.hregion.memstore.block.multiplier
|
* Block updates if memstore has hbase.hregion.memstore.block.multiplier times
|
||||||
* times hbase.hregion.memstore.flush.size bytes. Useful preventing
|
* hbase.hregion.memstore.flush.size bytes. Useful preventing runaway memstore during spikes in
|
||||||
* runaway memstore during spikes in update traffic.
|
* update traffic.
|
||||||
*/
|
*/
|
||||||
public static final String HREGION_MEMSTORE_BLOCK_MULTIPLIER =
|
public static final String HREGION_MEMSTORE_BLOCK_MULTIPLIER =
|
||||||
"hbase.hregion.memstore.block.multiplier";
|
"hbase.hregion.memstore.block.multiplier";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default value for hbase.hregion.memstore.block.multiplier
|
* Default value for hbase.hregion.memstore.block.multiplier
|
||||||
|
@ -369,14 +349,12 @@ public final class HConstants {
|
||||||
public static final int DEFAULT_HREGION_MEMSTORE_BLOCK_MULTIPLIER = 4;
|
public static final int DEFAULT_HREGION_MEMSTORE_BLOCK_MULTIPLIER = 4;
|
||||||
|
|
||||||
/** Conf key for the memstore size at which we flush the memstore */
|
/** Conf key for the memstore size at which we flush the memstore */
|
||||||
public static final String HREGION_MEMSTORE_FLUSH_SIZE =
|
public static final String HREGION_MEMSTORE_FLUSH_SIZE = "hbase.hregion.memstore.flush.size";
|
||||||
"hbase.hregion.memstore.flush.size";
|
|
||||||
|
|
||||||
public static final String HREGION_EDITS_REPLAY_SKIP_ERRORS =
|
public static final String HREGION_EDITS_REPLAY_SKIP_ERRORS =
|
||||||
"hbase.hregion.edits.replay.skip.errors";
|
"hbase.hregion.edits.replay.skip.errors";
|
||||||
|
|
||||||
public static final boolean DEFAULT_HREGION_EDITS_REPLAY_SKIP_ERRORS =
|
public static final boolean DEFAULT_HREGION_EDITS_REPLAY_SKIP_ERRORS = false;
|
||||||
false;
|
|
||||||
|
|
||||||
/** Maximum value length, enforced on KeyValue construction */
|
/** Maximum value length, enforced on KeyValue construction */
|
||||||
public static final int MAXIMUM_VALUE_LENGTH = Integer.MAX_VALUE - 1;
|
public static final int MAXIMUM_VALUE_LENGTH = Integer.MAX_VALUE - 1;
|
||||||
|
@ -412,12 +390,12 @@ public final class HConstants {
|
||||||
// be the first to be reassigned if the server(s) they are being served by
|
// be the first to be reassigned if the server(s) they are being served by
|
||||||
// should go down.
|
// should go down.
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The hbase:meta table's name.
|
* The hbase:meta table's name.
|
||||||
* @deprecated For upgrades of 0.94 to 0.96
|
* @deprecated For upgrades of 0.94 to 0.96
|
||||||
*/
|
*/
|
||||||
@Deprecated // for compat from 0.94 -> 0.96.
|
@Deprecated
|
||||||
|
// for compat from 0.94 -> 0.96.
|
||||||
public static final byte[] META_TABLE_NAME = TableName.META_TABLE_NAME.getName();
|
public static final byte[] META_TABLE_NAME = TableName.META_TABLE_NAME.getName();
|
||||||
|
|
||||||
public static final String BASE_NAMESPACE_DIR = "data";
|
public static final String BASE_NAMESPACE_DIR = "data";
|
||||||
|
@ -425,52 +403,52 @@ public final class HConstants {
|
||||||
/** delimiter used between portions of a region name */
|
/** delimiter used between portions of a region name */
|
||||||
public static final int META_ROW_DELIMITER = ',';
|
public static final int META_ROW_DELIMITER = ',';
|
||||||
|
|
||||||
/** The catalog family as a string*/
|
/** The catalog family as a string */
|
||||||
public static final String CATALOG_FAMILY_STR = "info";
|
public static final String CATALOG_FAMILY_STR = "info";
|
||||||
|
|
||||||
/** The catalog family */
|
/** The catalog family */
|
||||||
public static final byte [] CATALOG_FAMILY = Bytes.toBytes(CATALOG_FAMILY_STR);
|
public static final byte[] CATALOG_FAMILY = Bytes.toBytes(CATALOG_FAMILY_STR);
|
||||||
|
|
||||||
/** The RegionInfo qualifier as a string */
|
/** The RegionInfo qualifier as a string */
|
||||||
public static final String REGIONINFO_QUALIFIER_STR = "regioninfo";
|
public static final String REGIONINFO_QUALIFIER_STR = "regioninfo";
|
||||||
|
|
||||||
/** The regioninfo column qualifier */
|
/** The regioninfo column qualifier */
|
||||||
public static final byte [] REGIONINFO_QUALIFIER = Bytes.toBytes(REGIONINFO_QUALIFIER_STR);
|
public static final byte[] REGIONINFO_QUALIFIER = Bytes.toBytes(REGIONINFO_QUALIFIER_STR);
|
||||||
|
|
||||||
/** The server column qualifier */
|
/** The server column qualifier */
|
||||||
public static final String SERVER_QUALIFIER_STR = "server";
|
public static final String SERVER_QUALIFIER_STR = "server";
|
||||||
/** The server column qualifier */
|
/** The server column qualifier */
|
||||||
public static final byte [] SERVER_QUALIFIER = Bytes.toBytes(SERVER_QUALIFIER_STR);
|
public static final byte[] SERVER_QUALIFIER = Bytes.toBytes(SERVER_QUALIFIER_STR);
|
||||||
|
|
||||||
/** The startcode column qualifier */
|
/** The startcode column qualifier */
|
||||||
public static final String STARTCODE_QUALIFIER_STR = "serverstartcode";
|
public static final String STARTCODE_QUALIFIER_STR = "serverstartcode";
|
||||||
/** The startcode column qualifier */
|
/** The startcode column qualifier */
|
||||||
public static final byte [] STARTCODE_QUALIFIER = Bytes.toBytes(STARTCODE_QUALIFIER_STR);
|
public static final byte[] STARTCODE_QUALIFIER = Bytes.toBytes(STARTCODE_QUALIFIER_STR);
|
||||||
|
|
||||||
/** The open seqnum column qualifier */
|
/** The open seqnum column qualifier */
|
||||||
public static final String SEQNUM_QUALIFIER_STR = "seqnumDuringOpen";
|
public static final String SEQNUM_QUALIFIER_STR = "seqnumDuringOpen";
|
||||||
/** The open seqnum column qualifier */
|
/** The open seqnum column qualifier */
|
||||||
public static final byte [] SEQNUM_QUALIFIER = Bytes.toBytes(SEQNUM_QUALIFIER_STR);
|
public static final byte[] SEQNUM_QUALIFIER = Bytes.toBytes(SEQNUM_QUALIFIER_STR);
|
||||||
|
|
||||||
/** The state column qualifier */
|
/** The state column qualifier */
|
||||||
public static final String STATE_QUALIFIER_STR = "state";
|
public static final String STATE_QUALIFIER_STR = "state";
|
||||||
|
|
||||||
public static final byte [] STATE_QUALIFIER = Bytes.toBytes(STATE_QUALIFIER_STR);
|
public static final byte[] STATE_QUALIFIER = Bytes.toBytes(STATE_QUALIFIER_STR);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The serverName column qualifier. Its the server where the region is
|
* The serverName column qualifier. Its the server where the region is transitioning on, while
|
||||||
* transitioning on, while column server is the server where the region is
|
* column server is the server where the region is opened on. They are the same when the region is
|
||||||
* opened on. They are the same when the region is in state OPEN.
|
* in state OPEN.
|
||||||
*/
|
*/
|
||||||
public static final String SERVERNAME_QUALIFIER_STR = "sn";
|
public static final String SERVERNAME_QUALIFIER_STR = "sn";
|
||||||
|
|
||||||
public static final byte [] SERVERNAME_QUALIFIER = Bytes.toBytes(SERVERNAME_QUALIFIER_STR);
|
public static final byte[] SERVERNAME_QUALIFIER = Bytes.toBytes(SERVERNAME_QUALIFIER_STR);
|
||||||
|
|
||||||
/** The lower-half split region column qualifier */
|
/** The lower-half split region column qualifier */
|
||||||
public static final byte [] SPLITA_QUALIFIER = Bytes.toBytes("splitA");
|
public static final byte[] SPLITA_QUALIFIER = Bytes.toBytes("splitA");
|
||||||
|
|
||||||
/** The upper-half split region column qualifier */
|
/** The upper-half split region column qualifier */
|
||||||
public static final byte [] SPLITB_QUALIFIER = Bytes.toBytes("splitB");
|
public static final byte[] SPLITB_QUALIFIER = Bytes.toBytes("splitB");
|
||||||
|
|
||||||
/** The lower-half merge region column qualifier */
|
/** The lower-half merge region column qualifier */
|
||||||
public static final byte[] MERGEA_QUALIFIER = Bytes.toBytes("mergeA");
|
public static final byte[] MERGEA_QUALIFIER = Bytes.toBytes("mergeA");
|
||||||
|
@ -478,32 +456,28 @@ public final class HConstants {
|
||||||
/** The upper-half merge region column qualifier */
|
/** The upper-half merge region column qualifier */
|
||||||
public static final byte[] MERGEB_QUALIFIER = Bytes.toBytes("mergeB");
|
public static final byte[] MERGEB_QUALIFIER = Bytes.toBytes("mergeB");
|
||||||
|
|
||||||
/** The catalog family as a string*/
|
/** The catalog family as a string */
|
||||||
public static final String TABLE_FAMILY_STR = "table";
|
public static final String TABLE_FAMILY_STR = "table";
|
||||||
|
|
||||||
/** The catalog family */
|
/** The catalog family */
|
||||||
public static final byte [] TABLE_FAMILY = Bytes.toBytes(TABLE_FAMILY_STR);
|
public static final byte[] TABLE_FAMILY = Bytes.toBytes(TABLE_FAMILY_STR);
|
||||||
|
|
||||||
/** The serialized table state qualifier */
|
/** The serialized table state qualifier */
|
||||||
public static final byte[] TABLE_STATE_QUALIFIER = Bytes.toBytes("state");
|
public static final byte[] TABLE_STATE_QUALIFIER = Bytes.toBytes("state");
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The meta table version column qualifier.
|
* The meta table version column qualifier. We keep current version of the meta table in this
|
||||||
* We keep current version of the meta table in this column in <code>-ROOT-</code>
|
* column in <code>-ROOT-</code> table: i.e. in the 'info:v' column.
|
||||||
* table: i.e. in the 'info:v' column.
|
|
||||||
*/
|
*/
|
||||||
public static final byte [] META_VERSION_QUALIFIER = Bytes.toBytes("v");
|
public static final byte[] META_VERSION_QUALIFIER = Bytes.toBytes("v");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current version of the meta table.
|
* The current version of the meta table. - pre-hbase 0.92. There is no META_VERSION column in the
|
||||||
* - pre-hbase 0.92. There is no META_VERSION column in the root table
|
* root table in this case. The meta has HTableDescriptor serialized into the HRegionInfo; -
|
||||||
* in this case. The meta has HTableDescriptor serialized into the HRegionInfo;
|
* version 0 is 0.92 and 0.94. Meta data has serialized HRegionInfo's using Writable
|
||||||
* - version 0 is 0.92 and 0.94. Meta data has serialized HRegionInfo's using
|
* serialization, and HRegionInfo's does not contain HTableDescriptors. - version 1 for 0.96+
|
||||||
* Writable serialization, and HRegionInfo's does not contain HTableDescriptors.
|
* keeps HRegionInfo data structures, but changes the byte[] serialization from Writables to
|
||||||
* - version 1 for 0.96+ keeps HRegionInfo data structures, but changes the
|
* Protobuf. See HRegionInfo.VERSION
|
||||||
* byte[] serialization from Writables to Protobuf.
|
|
||||||
* See HRegionInfo.VERSION
|
|
||||||
*/
|
*/
|
||||||
public static final short META_VERSION = 1;
|
public static final short META_VERSION = 1;
|
||||||
|
|
||||||
|
@ -512,25 +486,24 @@ public final class HConstants {
|
||||||
/**
|
/**
|
||||||
* An empty instance.
|
* An empty instance.
|
||||||
*/
|
*/
|
||||||
public static final byte [] EMPTY_BYTE_ARRAY = new byte [0];
|
public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
|
||||||
|
|
||||||
public static final ByteBuffer EMPTY_BYTE_BUFFER = ByteBuffer.wrap(EMPTY_BYTE_ARRAY);
|
public static final ByteBuffer EMPTY_BYTE_BUFFER = ByteBuffer.wrap(EMPTY_BYTE_ARRAY);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used by scanners, etc when they want to start at the beginning of a region
|
* Used by scanners, etc when they want to start at the beginning of a region
|
||||||
*/
|
*/
|
||||||
public static final byte [] EMPTY_START_ROW = EMPTY_BYTE_ARRAY;
|
public static final byte[] EMPTY_START_ROW = EMPTY_BYTE_ARRAY;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Last row in a table.
|
* Last row in a table.
|
||||||
*/
|
*/
|
||||||
public static final byte [] EMPTY_END_ROW = EMPTY_START_ROW;
|
public static final byte[] EMPTY_END_ROW = EMPTY_START_ROW;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used by scanners and others when they're trying to detect the end of a
|
* Used by scanners and others when they're trying to detect the end of a table
|
||||||
* table
|
*/
|
||||||
*/
|
public static final byte[] LAST_ROW = EMPTY_BYTE_ARRAY;
|
||||||
public static final byte [] LAST_ROW = EMPTY_BYTE_ARRAY;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Max length a row can have because of the limitation in TFile.
|
* Max length a row can have because of the limitation in TFile.
|
||||||
|
@ -538,9 +511,8 @@ public final class HConstants {
|
||||||
public static final int MAX_ROW_LENGTH = Short.MAX_VALUE;
|
public static final int MAX_ROW_LENGTH = Short.MAX_VALUE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Timestamp to use when we want to refer to the latest cell.
|
* Timestamp to use when we want to refer to the latest cell. This is the timestamp sent by
|
||||||
* This is the timestamp sent by clients when no timestamp is specified on
|
* clients when no timestamp is specified on commit.
|
||||||
* commit.
|
|
||||||
*/
|
*/
|
||||||
public static final long LATEST_TIMESTAMP = Long.MAX_VALUE;
|
public static final long LATEST_TIMESTAMP = Long.MAX_VALUE;
|
||||||
|
|
||||||
|
@ -552,17 +524,12 @@ public final class HConstants {
|
||||||
/**
|
/**
|
||||||
* LATEST_TIMESTAMP in bytes form
|
* LATEST_TIMESTAMP in bytes form
|
||||||
*/
|
*/
|
||||||
public static final byte [] LATEST_TIMESTAMP_BYTES = {
|
public static final byte[] LATEST_TIMESTAMP_BYTES = {
|
||||||
// big-endian
|
// big-endian
|
||||||
(byte) (LATEST_TIMESTAMP >>> 56),
|
(byte) (LATEST_TIMESTAMP >>> 56), (byte) (LATEST_TIMESTAMP >>> 48),
|
||||||
(byte) (LATEST_TIMESTAMP >>> 48),
|
(byte) (LATEST_TIMESTAMP >>> 40), (byte) (LATEST_TIMESTAMP >>> 32),
|
||||||
(byte) (LATEST_TIMESTAMP >>> 40),
|
(byte) (LATEST_TIMESTAMP >>> 24), (byte) (LATEST_TIMESTAMP >>> 16),
|
||||||
(byte) (LATEST_TIMESTAMP >>> 32),
|
(byte) (LATEST_TIMESTAMP >>> 8), (byte) LATEST_TIMESTAMP };
|
||||||
(byte) (LATEST_TIMESTAMP >>> 24),
|
|
||||||
(byte) (LATEST_TIMESTAMP >>> 16),
|
|
||||||
(byte) (LATEST_TIMESTAMP >>> 8),
|
|
||||||
(byte) LATEST_TIMESTAMP,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define for 'return-all-versions'.
|
* Define for 'return-all-versions'.
|
||||||
|
@ -572,7 +539,7 @@ public final class HConstants {
|
||||||
/**
|
/**
|
||||||
* Unlimited time-to-live.
|
* Unlimited time-to-live.
|
||||||
*/
|
*/
|
||||||
// public static final int FOREVER = -1;
|
// public static final int FOREVER = -1;
|
||||||
public static final int FOREVER = Integer.MAX_VALUE;
|
public static final int FOREVER = Integer.MAX_VALUE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -587,10 +554,10 @@ public final class HConstants {
|
||||||
public static final int HOUR_IN_SECONDS = 60 * 60;
|
public static final int HOUR_IN_SECONDS = 60 * 60;
|
||||||
public static final int MINUTE_IN_SECONDS = 60;
|
public static final int MINUTE_IN_SECONDS = 60;
|
||||||
|
|
||||||
//TODO: although the following are referenced widely to format strings for
|
// TODO: although the following are referenced widely to format strings for
|
||||||
// the shell. They really aren't a part of the public API. It would be
|
// the shell. They really aren't a part of the public API. It would be
|
||||||
// nice if we could put them somewhere where they did not need to be
|
// nice if we could put them somewhere where they did not need to be
|
||||||
// public. They could have package visibility
|
// public. They could have package visibility
|
||||||
public static final String NAME = "NAME";
|
public static final String NAME = "NAME";
|
||||||
public static final String VERSIONS = "VERSIONS";
|
public static final String VERSIONS = "VERSIONS";
|
||||||
public static final String IN_MEMORY = "IN_MEMORY";
|
public static final String IN_MEMORY = "IN_MEMORY";
|
||||||
|
@ -598,45 +565,38 @@ public final class HConstants {
|
||||||
public static final String CONFIGURATION = "CONFIGURATION";
|
public static final String CONFIGURATION = "CONFIGURATION";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrying we multiply hbase.client.pause setting by what we have in this array until we
|
* Retrying we multiply hbase.client.pause setting by what we have in this array until we run out
|
||||||
* run out of array items. Retries beyond this use the last number in the array. So, for
|
* of array items. Retries beyond this use the last number in the array. So, for example, if
|
||||||
* example, if hbase.client.pause is 1 second, and maximum retries count
|
* hbase.client.pause is 1 second, and maximum retries count hbase.client.retries.number is 10, we
|
||||||
* hbase.client.retries.number is 10, we will retry at the following intervals:
|
* will retry at the following intervals: 1, 2, 3, 5, 10, 20, 40, 100, 100, 100. With 100ms, a
|
||||||
* 1, 2, 3, 5, 10, 20, 40, 100, 100, 100.
|
* back-off of 200 means 20s
|
||||||
* With 100ms, a back-off of 200 means 20s
|
|
||||||
*/
|
*/
|
||||||
public static final int [] RETRY_BACKOFF = {1, 2, 3, 5, 10, 20, 40, 100, 100, 100, 100, 200, 200};
|
public static final int[] RETRY_BACKOFF =
|
||||||
|
{ 1, 2, 3, 5, 10, 20, 40, 100, 100, 100, 100, 200, 200 };
|
||||||
|
|
||||||
public static final String REGION_IMPL = "hbase.hregion.impl";
|
public static final String REGION_IMPL = "hbase.hregion.impl";
|
||||||
|
|
||||||
/** modifyTable op for replacing the table descriptor */
|
/** modifyTable op for replacing the table descriptor */
|
||||||
@InterfaceAudience.Private
|
@InterfaceAudience.Private
|
||||||
public static enum Modify {
|
public static enum Modify {
|
||||||
CLOSE_REGION,
|
CLOSE_REGION, TABLE_COMPACT, TABLE_FLUSH, TABLE_MAJOR_COMPACT, TABLE_SET_HTD, TABLE_SPLIT
|
||||||
TABLE_COMPACT,
|
|
||||||
TABLE_FLUSH,
|
|
||||||
TABLE_MAJOR_COMPACT,
|
|
||||||
TABLE_SET_HTD,
|
|
||||||
TABLE_SPLIT
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scope tag for locally scoped data.
|
* Scope tag for locally scoped data. This data will not be replicated.
|
||||||
* This data will not be replicated.
|
|
||||||
*/
|
*/
|
||||||
public static final int REPLICATION_SCOPE_LOCAL = 0;
|
public static final int REPLICATION_SCOPE_LOCAL = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scope tag for globally scoped data.
|
* Scope tag for globally scoped data. This data will be replicated to all peers.
|
||||||
* This data will be replicated to all peers.
|
|
||||||
*/
|
*/
|
||||||
public static final int REPLICATION_SCOPE_GLOBAL = 1;
|
public static final int REPLICATION_SCOPE_GLOBAL = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default cluster ID, cannot be used to identify a cluster so a key with
|
* Default cluster ID, cannot be used to identify a cluster so a key with this value means it
|
||||||
* this value means it wasn't meant for replication.
|
* wasn't meant for replication.
|
||||||
*/
|
*/
|
||||||
public static final UUID DEFAULT_CLUSTER_ID = new UUID(0L,0L);
|
public static final UUID DEFAULT_CLUSTER_ID = new UUID(0L, 0L);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parameter name for maximum number of bytes returned when calling a scanner's next method.
|
* Parameter name for maximum number of bytes returned when calling a scanner's next method.
|
||||||
|
@ -653,27 +613,22 @@ public final class HConstants {
|
||||||
"hbase.server.scanner.max.result.size";
|
"hbase.server.scanner.max.result.size";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maximum number of bytes returned when calling a scanner's next method.
|
* Maximum number of bytes returned when calling a scanner's next method. Note that when a single
|
||||||
* Note that when a single row is larger than this limit the row is still
|
* row is larger than this limit the row is still returned completely. The default value is 2MB.
|
||||||
* returned completely.
|
|
||||||
*
|
|
||||||
* The default value is 2MB.
|
|
||||||
*/
|
*/
|
||||||
public static final long DEFAULT_HBASE_CLIENT_SCANNER_MAX_RESULT_SIZE = 2 * 1024 * 1024;
|
public static final long DEFAULT_HBASE_CLIENT_SCANNER_MAX_RESULT_SIZE = 2 * 1024 * 1024;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maximum number of bytes returned when calling a scanner's next method.
|
* Maximum number of bytes returned when calling a scanner's next method. Note that when a single
|
||||||
* Note that when a single row is larger than this limit the row is still
|
* row is larger than this limit the row is still returned completely. Safety setting to protect
|
||||||
* returned completely.
|
* the region server. The default value is 100MB. (a client would rarely request larger chunks on
|
||||||
* Safety setting to protect the region server.
|
* purpose)
|
||||||
*
|
|
||||||
* The default value is 100MB. (a client would rarely request larger chunks on purpose)
|
|
||||||
*/
|
*/
|
||||||
public static final long DEFAULT_HBASE_SERVER_SCANNER_MAX_RESULT_SIZE = 100 * 1024 * 1024;
|
public static final long DEFAULT_HBASE_SERVER_SCANNER_MAX_RESULT_SIZE = 100 * 1024 * 1024;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parameter name for client pause value, used mostly as value to wait
|
* Parameter name for client pause value, used mostly as value to wait before running a retry of a
|
||||||
* before running a retry of a failed get, region lookup, etc.
|
* failed get, region lookup, etc.
|
||||||
*/
|
*/
|
||||||
public static final String HBASE_CLIENT_PAUSE = "hbase.client.pause";
|
public static final String HBASE_CLIENT_PAUSE = "hbase.client.pause";
|
||||||
|
|
||||||
|
@ -693,8 +648,7 @@ public final class HConstants {
|
||||||
public static final int DEFAULT_HBASE_CLIENT_MAX_TOTAL_TASKS = 100;
|
public static final int DEFAULT_HBASE_CLIENT_MAX_TOTAL_TASKS = 100;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The maximum number of concurrent connections the client will maintain to a single
|
* The maximum number of concurrent connections the client will maintain to a single RegionServer.
|
||||||
* RegionServer.
|
|
||||||
*/
|
*/
|
||||||
public static final String HBASE_CLIENT_MAX_PERSERVER_TASKS = "hbase.client.max.perserver.tasks";
|
public static final String HBASE_CLIENT_MAX_PERSERVER_TASKS = "hbase.client.max.perserver.tasks";
|
||||||
|
|
||||||
|
@ -704,8 +658,7 @@ public final class HConstants {
|
||||||
public static final int DEFAULT_HBASE_CLIENT_MAX_PERSERVER_TASKS = 2;
|
public static final int DEFAULT_HBASE_CLIENT_MAX_PERSERVER_TASKS = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The maximum number of concurrent connections the client will maintain to a single
|
* The maximum number of concurrent connections the client will maintain to a single Region.
|
||||||
* Region.
|
|
||||||
*/
|
*/
|
||||||
public static final String HBASE_CLIENT_MAX_PERREGION_TASKS = "hbase.client.max.perregion.tasks";
|
public static final String HBASE_CLIENT_MAX_PERREGION_TASKS = "hbase.client.max.perregion.tasks";
|
||||||
|
|
||||||
|
@ -715,8 +668,8 @@ public final class HConstants {
|
||||||
public static final int DEFAULT_HBASE_CLIENT_MAX_PERREGION_TASKS = 1;
|
public static final int DEFAULT_HBASE_CLIENT_MAX_PERREGION_TASKS = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parameter name for server pause value, used mostly as value to wait before
|
* Parameter name for server pause value, used mostly as value to wait before running a retry of a
|
||||||
* running a retry of a failed operation.
|
* failed operation.
|
||||||
*/
|
*/
|
||||||
public static final String HBASE_SERVER_PAUSE = "hbase.server.pause";
|
public static final String HBASE_SERVER_PAUSE = "hbase.server.pause";
|
||||||
|
|
||||||
|
@ -726,9 +679,9 @@ public final class HConstants {
|
||||||
public static final int DEFAULT_HBASE_SERVER_PAUSE = 1000;
|
public static final int DEFAULT_HBASE_SERVER_PAUSE = 1000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parameter name for maximum retries, used as maximum for all retryable
|
* Parameter name for maximum retries, used as maximum for all retryable operations such as
|
||||||
* operations such as fetching of the root region from root region server,
|
* fetching of the root region from root region server, getting a cell's value, starting a row
|
||||||
* getting a cell's value, starting a row update, etc.
|
* update, etc.
|
||||||
*/
|
*/
|
||||||
public static final String HBASE_CLIENT_RETRIES_NUMBER = "hbase.client.retries.number";
|
public static final String HBASE_CLIENT_RETRIES_NUMBER = "hbase.client.retries.number";
|
||||||
|
|
||||||
|
@ -748,10 +701,9 @@ public final class HConstants {
|
||||||
public static final int DEFAULT_HBASE_CLIENT_SCANNER_CACHING = Integer.MAX_VALUE;
|
public static final int DEFAULT_HBASE_CLIENT_SCANNER_CACHING = Integer.MAX_VALUE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parameter name for number of rows that will be fetched when calling next on
|
* Parameter name for number of rows that will be fetched when calling next on a scanner if it is
|
||||||
* a scanner if it is not served from memory. Higher caching values will
|
* not served from memory. Higher caching values will enable faster scanners but will eat up more
|
||||||
* enable faster scanners but will eat up more memory and some calls of next
|
* memory and some calls of next may take longer and longer times when the cache is empty.
|
||||||
* may take longer and longer times when the cache is empty.
|
|
||||||
*/
|
*/
|
||||||
public static final String HBASE_META_SCANNER_CACHING = "hbase.meta.scanner.caching";
|
public static final String HBASE_META_SCANNER_CACHING = "hbase.meta.scanner.caching";
|
||||||
|
|
||||||
|
@ -918,18 +870,13 @@ public final class HConstants {
|
||||||
public static final String LOCALHOST = "localhost";
|
public static final String LOCALHOST = "localhost";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If this parameter is set to true, then hbase will read
|
* If this parameter is set to true, then hbase will read data and then verify checksums. Checksum
|
||||||
* data and then verify checksums. Checksum verification
|
* verification inside hdfs will be switched off. However, if the hbase-checksum verification
|
||||||
* inside hdfs will be switched off. However, if the hbase-checksum
|
* fails, then it will switch back to using hdfs checksums for verifiying data that is being read
|
||||||
* verification fails, then it will switch back to using
|
* from storage. If this parameter is set to false, then hbase will not verify any checksums,
|
||||||
* hdfs checksums for verifiying data that is being read from storage.
|
* instead it will depend on checksum verification being done in the hdfs client.
|
||||||
*
|
|
||||||
* If this parameter is set to false, then hbase will not
|
|
||||||
* verify any checksums, instead it will depend on checksum verification
|
|
||||||
* being done in the hdfs client.
|
|
||||||
*/
|
*/
|
||||||
public static final String HBASE_CHECKSUM_VERIFICATION =
|
public static final String HBASE_CHECKSUM_VERIFICATION = "hbase.regionserver.checksum.verify";
|
||||||
"hbase.regionserver.checksum.verify";
|
|
||||||
|
|
||||||
public static final String LOCALHOST_IP = "127.0.0.1";
|
public static final String LOCALHOST_IP = "127.0.0.1";
|
||||||
|
|
||||||
|
@ -944,17 +891,15 @@ public final class HConstants {
|
||||||
public static final int DEFAULT_REGION_SERVER_HANDLER_COUNT = 30;
|
public static final int DEFAULT_REGION_SERVER_HANDLER_COUNT = 30;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* REGION_SERVER_HANDLER_ABORT_ON_ERROR_PERCENT:
|
* REGION_SERVER_HANDLER_ABORT_ON_ERROR_PERCENT: -1 => Disable aborting 0 => Abort if even a
|
||||||
* -1 => Disable aborting
|
* single handler has died 0.x => Abort only when this percent of handlers have died 1 => Abort
|
||||||
* 0 => Abort if even a single handler has died
|
* only all of the handers have died
|
||||||
* 0.x => Abort only when this percent of handlers have died
|
|
||||||
* 1 => Abort only all of the handers have died
|
|
||||||
*/
|
*/
|
||||||
public static final String REGION_SERVER_HANDLER_ABORT_ON_ERROR_PERCENT =
|
public static final String REGION_SERVER_HANDLER_ABORT_ON_ERROR_PERCENT =
|
||||||
"hbase.regionserver.handler.abort.on.error.percent";
|
"hbase.regionserver.handler.abort.on.error.percent";
|
||||||
public static final double DEFAULT_REGION_SERVER_HANDLER_ABORT_ON_ERROR_PERCENT = 0.5;
|
public static final double DEFAULT_REGION_SERVER_HANDLER_ABORT_ON_ERROR_PERCENT = 0.5;
|
||||||
|
|
||||||
//High priority handlers to deal with admin requests and system table operation requests
|
// High priority handlers to deal with admin requests and system table operation requests
|
||||||
public static final String REGION_SERVER_HIGH_PRIORITY_HANDLER_COUNT =
|
public static final String REGION_SERVER_HIGH_PRIORITY_HANDLER_COUNT =
|
||||||
"hbase.regionserver.metahandler.count";
|
"hbase.regionserver.metahandler.count";
|
||||||
public static final int DEFAULT_REGION_SERVER_HIGH_PRIORITY_HANDLER_COUNT = 20;
|
public static final int DEFAULT_REGION_SERVER_HIGH_PRIORITY_HANDLER_COUNT = 20;
|
||||||
|
@ -977,19 +922,16 @@ public final class HConstants {
|
||||||
public static final int DEFAULT_META_REPLICA_NUM = 1;
|
public static final int DEFAULT_META_REPLICA_NUM = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name of the configuration parameter that specifies
|
* The name of the configuration parameter that specifies the number of bytes in a newly created
|
||||||
* the number of bytes in a newly created checksum chunk.
|
* checksum chunk.
|
||||||
*/
|
*/
|
||||||
public static final String BYTES_PER_CHECKSUM =
|
public static final String BYTES_PER_CHECKSUM = "hbase.hstore.bytes.per.checksum";
|
||||||
"hbase.hstore.bytes.per.checksum";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name of the configuration parameter that specifies
|
* The name of the configuration parameter that specifies the name of an algorithm that is used to
|
||||||
* the name of an algorithm that is used to compute checksums
|
* compute checksums for newly created blocks.
|
||||||
* for newly created blocks.
|
|
||||||
*/
|
*/
|
||||||
public static final String CHECKSUM_TYPE_NAME =
|
public static final String CHECKSUM_TYPE_NAME = "hbase.hstore.checksum.algorithm";
|
||||||
"hbase.hstore.checksum.algorithm";
|
|
||||||
|
|
||||||
/** Enable file permission modification from standard hbase */
|
/** Enable file permission modification from standard hbase */
|
||||||
public static final String ENABLE_DATA_FILE_UMASK = "hbase.data.umask.enable";
|
public static final String ENABLE_DATA_FILE_UMASK = "hbase.data.umask.enable";
|
||||||
|
@ -997,16 +939,14 @@ public final class HConstants {
|
||||||
public static final String DATA_FILE_UMASK_KEY = "hbase.data.umask";
|
public static final String DATA_FILE_UMASK_KEY = "hbase.data.umask";
|
||||||
|
|
||||||
/** Configuration name of WAL Compression */
|
/** Configuration name of WAL Compression */
|
||||||
public static final String ENABLE_WAL_COMPRESSION =
|
public static final String ENABLE_WAL_COMPRESSION = "hbase.regionserver.wal.enablecompression";
|
||||||
"hbase.regionserver.wal.enablecompression";
|
|
||||||
|
|
||||||
/** Configuration name of WAL storage policy
|
/**
|
||||||
* Valid values are:
|
* Configuration name of WAL storage policy Valid values are: NONE: no preference in destination
|
||||||
* NONE: no preference in destination of block replicas
|
* of block replicas ONE_SSD: place only one block replica in SSD and the remaining in default
|
||||||
* ONE_SSD: place only one block replica in SSD and the remaining in default storage
|
* storage and ALL_SSD: place all block replicas on SSD See
|
||||||
* and ALL_SSD: place all block replicas on SSD
|
* http://hadoop.apache.org/docs/r2.6.0/hadoop-project-dist/hadoop-hdfs/ArchivalStorage.html
|
||||||
*
|
*/
|
||||||
* See http://hadoop.apache.org/docs/r2.6.0/hadoop-project-dist/hadoop-hdfs/ArchivalStorage.html*/
|
|
||||||
public static final String WAL_STORAGE_POLICY = "hbase.wal.storage.policy";
|
public static final String WAL_STORAGE_POLICY = "hbase.wal.storage.policy";
|
||||||
public static final String DEFAULT_WAL_STORAGE_POLICY = "NONE";
|
public static final String DEFAULT_WAL_STORAGE_POLICY = "NONE";
|
||||||
|
|
||||||
|
@ -1017,17 +957,16 @@ public final class HConstants {
|
||||||
public static final String LOAD_BALANCER_SLOP_KEY = "hbase.regions.slop";
|
public static final String LOAD_BALANCER_SLOP_KEY = "hbase.regions.slop";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The byte array represents for NO_NEXT_INDEXED_KEY;
|
* The byte array represents for NO_NEXT_INDEXED_KEY; The actual value is irrelevant because this
|
||||||
* The actual value is irrelevant because this is always compared by reference.
|
* is always compared by reference.
|
||||||
*/
|
*/
|
||||||
public static final Cell NO_NEXT_INDEXED_KEY = new KeyValue();
|
public static final Cell NO_NEXT_INDEXED_KEY = new KeyValue();
|
||||||
/** delimiter used between portions of a region name */
|
/** delimiter used between portions of a region name */
|
||||||
public static final int DELIMITER = ',';
|
public static final int DELIMITER = ',';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* QOS attributes: these attributes are used to demarcate RPC call processing
|
* QOS attributes: these attributes are used to demarcate RPC call processing by different set of
|
||||||
* by different set of handlers. For example, HIGH_QOS tagged methods are
|
* handlers. For example, HIGH_QOS tagged methods are handled by high priority handlers.
|
||||||
* handled by high priority handlers.
|
|
||||||
*/
|
*/
|
||||||
// normal_QOS < QOS_threshold < replication_QOS < replay_QOS < admin_QOS < high_QOS
|
// normal_QOS < QOS_threshold < replication_QOS < replay_QOS < admin_QOS < high_QOS
|
||||||
public static final int NORMAL_QOS = 0;
|
public static final int NORMAL_QOS = 0;
|
||||||
|
@ -1042,8 +981,8 @@ public final class HConstants {
|
||||||
public static final String HFILE_ARCHIVE_DIRECTORY = "archive";
|
public static final String HFILE_ARCHIVE_DIRECTORY = "archive";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Name of the directory to store all snapshots. See SnapshotDescriptionUtils for
|
* Name of the directory to store all snapshots. See SnapshotDescriptionUtils for remaining
|
||||||
* remaining snapshot constants; this is here to keep HConstants dependencies at a minimum and
|
* snapshot constants; this is here to keep HConstants dependencies at a minimum and
|
||||||
* uni-directional.
|
* uni-directional.
|
||||||
*/
|
*/
|
||||||
public static final String SNAPSHOT_DIR_NAME = ".hbase-snapshot";
|
public static final String SNAPSHOT_DIR_NAME = ".hbase-snapshot";
|
||||||
|
@ -1059,49 +998,42 @@ public final class HConstants {
|
||||||
public static final String REGIONSERVER_METRICS_PERIOD = "hbase.regionserver.metrics.period";
|
public static final String REGIONSERVER_METRICS_PERIOD = "hbase.regionserver.metrics.period";
|
||||||
public static final long DEFAULT_REGIONSERVER_METRICS_PERIOD = 5000;
|
public static final long DEFAULT_REGIONSERVER_METRICS_PERIOD = 5000;
|
||||||
/** Directories that are not HBase table directories */
|
/** Directories that are not HBase table directories */
|
||||||
public static final List<String> HBASE_NON_TABLE_DIRS =
|
public static final List<String> HBASE_NON_TABLE_DIRS = Collections.unmodifiableList(Arrays
|
||||||
Collections.unmodifiableList(Arrays.asList(new String[] {
|
.asList(new String[] { HBCK_SIDELINEDIR_NAME, HBASE_TEMP_DIRECTORY, MIGRATION_NAME }));
|
||||||
HBCK_SIDELINEDIR_NAME, HBASE_TEMP_DIRECTORY, MIGRATION_NAME
|
|
||||||
}));
|
|
||||||
|
|
||||||
/** Directories that are not HBase user table directories */
|
/** Directories that are not HBase user table directories */
|
||||||
public static final List<String> HBASE_NON_USER_TABLE_DIRS =
|
public static final List<String> HBASE_NON_USER_TABLE_DIRS = Collections.unmodifiableList(Arrays
|
||||||
Collections.unmodifiableList(Arrays.asList((String[])ArrayUtils.addAll(
|
.asList((String[]) ArrayUtils.addAll(
|
||||||
new String[] { TableName.META_TABLE_NAME.getNameAsString() },
|
new String[] { TableName.META_TABLE_NAME.getNameAsString() },
|
||||||
HBASE_NON_TABLE_DIRS.toArray())));
|
HBASE_NON_TABLE_DIRS.toArray())));
|
||||||
|
|
||||||
/** Health script related settings. */
|
/** Health script related settings. */
|
||||||
public static final String HEALTH_SCRIPT_LOC = "hbase.node.health.script.location";
|
public static final String HEALTH_SCRIPT_LOC = "hbase.node.health.script.location";
|
||||||
public static final String HEALTH_SCRIPT_TIMEOUT = "hbase.node.health.script.timeout";
|
public static final String HEALTH_SCRIPT_TIMEOUT = "hbase.node.health.script.timeout";
|
||||||
public static final String HEALTH_CHORE_WAKE_FREQ =
|
public static final String HEALTH_CHORE_WAKE_FREQ = "hbase.node.health.script.frequency";
|
||||||
"hbase.node.health.script.frequency";
|
|
||||||
public static final long DEFAULT_HEALTH_SCRIPT_TIMEOUT = 60000;
|
public static final long DEFAULT_HEALTH_SCRIPT_TIMEOUT = 60000;
|
||||||
/**
|
/**
|
||||||
* The maximum number of health check failures a server can encounter consecutively.
|
* The maximum number of health check failures a server can encounter consecutively.
|
||||||
*/
|
*/
|
||||||
public static final String HEALTH_FAILURE_THRESHOLD =
|
public static final String HEALTH_FAILURE_THRESHOLD = "hbase.node.health.failure.threshold";
|
||||||
"hbase.node.health.failure.threshold";
|
|
||||||
public static final int DEFAULT_HEALTH_FAILURE_THRESHOLD = 3;
|
public static final int DEFAULT_HEALTH_FAILURE_THRESHOLD = 3;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setting to activate, or not, the publication of the status by the master. Default
|
* Setting to activate, or not, the publication of the status by the master. Default notification
|
||||||
* notification is by a multicast message.
|
* is by a multicast message.
|
||||||
*/
|
*/
|
||||||
public static final String STATUS_PUBLISHED = "hbase.status.published";
|
public static final String STATUS_PUBLISHED = "hbase.status.published";
|
||||||
public static final boolean STATUS_PUBLISHED_DEFAULT = false;
|
public static final boolean STATUS_PUBLISHED_DEFAULT = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IP to use for the multicast status messages between the master and the clients.
|
* IP to use for the multicast status messages between the master and the clients. The default
|
||||||
* The default address is chosen as one among others within the ones suitable for multicast
|
* address is chosen as one among others within the ones suitable for multicast messages.
|
||||||
* messages.
|
|
||||||
*/
|
*/
|
||||||
public static final String STATUS_MULTICAST_ADDRESS = "hbase.status.multicast.address.ip";
|
public static final String STATUS_MULTICAST_ADDRESS = "hbase.status.multicast.address.ip";
|
||||||
public static final String DEFAULT_STATUS_MULTICAST_ADDRESS = "226.1.1.3";
|
public static final String DEFAULT_STATUS_MULTICAST_ADDRESS = "226.1.1.3";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The address to use for binding the local socket for receiving multicast. Defaults to
|
* The address to use for binding the local socket for receiving multicast. Defaults to 0.0.0.0.
|
||||||
* 0.0.0.0.
|
|
||||||
* @see <a href="https://issues.apache.org/jira/browse/HBASE-9961">HBASE-9961</a>
|
* @see <a href="https://issues.apache.org/jira/browse/HBASE-9961">HBASE-9961</a>
|
||||||
*/
|
*/
|
||||||
public static final String STATUS_MULTICAST_BIND_ADDRESS =
|
public static final String STATUS_MULTICAST_BIND_ADDRESS =
|
||||||
|
@ -1134,7 +1066,7 @@ public final class HConstants {
|
||||||
|
|
||||||
/** Configuration key for the name of the alternate master key for the cluster, a string */
|
/** Configuration key for the name of the alternate master key for the cluster, a string */
|
||||||
public static final String CRYPTO_MASTERKEY_ALTERNATE_NAME_CONF_KEY =
|
public static final String CRYPTO_MASTERKEY_ALTERNATE_NAME_CONF_KEY =
|
||||||
"hbase.crypto.master.alternate.key.name";
|
"hbase.crypto.master.alternate.key.name";
|
||||||
|
|
||||||
/** Configuration key for the algorithm to use when encrypting the WAL, a string */
|
/** Configuration key for the algorithm to use when encrypting the WAL, a string */
|
||||||
public static final String CRYPTO_WAL_ALGORITHM_CONF_KEY = "hbase.crypto.wal.algorithm";
|
public static final String CRYPTO_WAL_ALGORITHM_CONF_KEY = "hbase.crypto.wal.algorithm";
|
||||||
|
@ -1166,7 +1098,7 @@ public final class HConstants {
|
||||||
|
|
||||||
/** Config for pluggable consensus provider */
|
/** Config for pluggable consensus provider */
|
||||||
public static final String HBASE_COORDINATED_STATE_MANAGER_CLASS =
|
public static final String HBASE_COORDINATED_STATE_MANAGER_CLASS =
|
||||||
"hbase.coordinated.state.manager.class";
|
"hbase.coordinated.state.manager.class";
|
||||||
|
|
||||||
/** Configuration key for SplitLog manager timeout */
|
/** Configuration key for SplitLog manager timeout */
|
||||||
public static final String HBASE_SPLITLOG_MANAGER_TIMEOUT = "hbase.splitlog.manager.timeout";
|
public static final String HBASE_SPLITLOG_MANAGER_TIMEOUT = "hbase.splitlog.manager.timeout";
|
||||||
|
@ -1180,17 +1112,18 @@ public final class HConstants {
|
||||||
// hbase-common?
|
// hbase-common?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current ioengine options in include: heap, offheap and file:PATH (where PATH is the path
|
* Current ioengine options in include: heap, offheap and file:PATH (where PATH is the path to the
|
||||||
* to the file that will host the file-based cache. See BucketCache#getIOEngineFromName() for
|
* file that will host the file-based cache. See BucketCache#getIOEngineFromName() for list of
|
||||||
* list of supported ioengine options.
|
* supported ioengine options.
|
||||||
* <p>Set this option and a non-zero {@link #BUCKET_CACHE_SIZE_KEY} to enable bucket cache.
|
* <p>
|
||||||
|
* Set this option and a non-zero {@link #BUCKET_CACHE_SIZE_KEY} to enable bucket cache.
|
||||||
*/
|
*/
|
||||||
public static final String BUCKET_CACHE_IOENGINE_KEY = "hbase.bucketcache.ioengine";
|
public static final String BUCKET_CACHE_IOENGINE_KEY = "hbase.bucketcache.ioengine";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When using bucket cache, this is a float that EITHER represents a percentage of total heap
|
* When using bucket cache, this is a float that EITHER represents a percentage of total heap
|
||||||
* memory size to give to the cache (if < 1.0) OR, it is the capacity in
|
* memory size to give to the cache (if < 1.0) OR, it is the capacity in megabytes of the
|
||||||
* megabytes of the cache.
|
* cache.
|
||||||
*/
|
*/
|
||||||
public static final String BUCKET_CACHE_SIZE_KEY = "hbase.bucketcache.size";
|
public static final String BUCKET_CACHE_SIZE_KEY = "hbase.bucketcache.size";
|
||||||
|
|
||||||
|
@ -1203,26 +1136,25 @@ public final class HConstants {
|
||||||
public static final String HBASE_CLIENT_FAST_FAIL_MODE_ENABLED =
|
public static final String HBASE_CLIENT_FAST_FAIL_MODE_ENABLED =
|
||||||
"hbase.client.fast.fail.mode.enabled";
|
"hbase.client.fast.fail.mode.enabled";
|
||||||
|
|
||||||
public static final boolean HBASE_CLIENT_ENABLE_FAST_FAIL_MODE_DEFAULT =
|
public static final boolean HBASE_CLIENT_ENABLE_FAST_FAIL_MODE_DEFAULT = false;
|
||||||
false;
|
|
||||||
|
|
||||||
public static final String HBASE_CLIENT_FAST_FAIL_THREASHOLD_MS =
|
public static final String HBASE_CLIENT_FAST_FAIL_THREASHOLD_MS =
|
||||||
"hbase.client.fastfail.threshold";
|
"hbase.client.fastfail.threshold";
|
||||||
|
|
||||||
public static final long HBASE_CLIENT_FAST_FAIL_THREASHOLD_MS_DEFAULT =
|
public static final long HBASE_CLIENT_FAST_FAIL_THREASHOLD_MS_DEFAULT = 60000;
|
||||||
60000;
|
|
||||||
|
|
||||||
public static final String HBASE_CLIENT_FAST_FAIL_CLEANUP_MS_DURATION_MS =
|
public static final String HBASE_CLIENT_FAST_FAIL_CLEANUP_MS_DURATION_MS =
|
||||||
"hbase.client.fast.fail.cleanup.duration";
|
"hbase.client.fast.fail.cleanup.duration";
|
||||||
|
|
||||||
public static final long HBASE_CLIENT_FAST_FAIL_CLEANUP_DURATION_MS_DEFAULT =
|
public static final long HBASE_CLIENT_FAST_FAIL_CLEANUP_DURATION_MS_DEFAULT = 600000;
|
||||||
600000;
|
|
||||||
|
|
||||||
public static final String HBASE_CLIENT_FAST_FAIL_INTERCEPTOR_IMPL =
|
public static final String HBASE_CLIENT_FAST_FAIL_INTERCEPTOR_IMPL =
|
||||||
"hbase.client.fast.fail.interceptor.impl";
|
"hbase.client.fast.fail.interceptor.impl";
|
||||||
|
|
||||||
/** Config key for if the server should send backpressure and if the client should listen to
|
/**
|
||||||
* that backpressure from the server */
|
* Config key for if the server should send backpressure and if the client should listen to that
|
||||||
|
* backpressure from the server
|
||||||
|
*/
|
||||||
public static final String ENABLE_CLIENT_BACKPRESSURE = "hbase.client.backpressure.enabled";
|
public static final String ENABLE_CLIENT_BACKPRESSURE = "hbase.client.backpressure.enabled";
|
||||||
public static final boolean DEFAULT_ENABLE_CLIENT_BACKPRESSURE = false;
|
public static final boolean DEFAULT_ENABLE_CLIENT_BACKPRESSURE = false;
|
||||||
|
|
||||||
|
@ -1234,11 +1166,11 @@ public final class HConstants {
|
||||||
public static final float DEFAULT_HEAP_OCCUPANCY_HIGH_WATERMARK = 0.98f;
|
public static final float DEFAULT_HEAP_OCCUPANCY_HIGH_WATERMARK = 0.98f;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The max number of threads used for splitting storefiles in parallel during
|
* The max number of threads used for splitting storefiles in parallel during the region split
|
||||||
* the region split process.
|
* process.
|
||||||
*/
|
*/
|
||||||
public static final String REGION_SPLIT_THREADS_MAX =
|
public static final String REGION_SPLIT_THREADS_MAX =
|
||||||
"hbase.regionserver.region.split.threads.max";
|
"hbase.regionserver.region.split.threads.max";
|
||||||
|
|
||||||
/** Canary config keys */
|
/** Canary config keys */
|
||||||
public static final String HBASE_CANARY_WRITE_DATA_TTL_KEY = "hbase.canary.write.data.ttl";
|
public static final String HBASE_CANARY_WRITE_DATA_TTL_KEY = "hbase.canary.write.data.ttl";
|
||||||
|
@ -1263,6 +1195,15 @@ public final class HConstants {
|
||||||
public static final String ZK_SERVER_KEYTAB_FILE = "hbase.zookeeper.server.keytab.file";
|
public static final String ZK_SERVER_KEYTAB_FILE = "hbase.zookeeper.server.keytab.file";
|
||||||
public static final String ZK_SERVER_KERBEROS_PRINCIPAL =
|
public static final String ZK_SERVER_KERBEROS_PRINCIPAL =
|
||||||
"hbase.zookeeper.server.kerberos.principal";
|
"hbase.zookeeper.server.kerberos.principal";
|
||||||
|
/**
|
||||||
|
* Backup/Restore constants
|
||||||
|
*/
|
||||||
|
|
||||||
|
public final static String BACKUP_ENABLE_KEY = "hbase.backup.enable";
|
||||||
|
public final static boolean BACKUP_ENABLE_DEFAULT = true;
|
||||||
|
public final static String BACKUP_SYSTEM_TTL_KEY = "hbase.backup.system.ttl";
|
||||||
|
// Default TTL = 1 year
|
||||||
|
public final static int BACKUP_SYSTEM_TTL_DEFAULT = 365 * 24 * 3600;
|
||||||
|
|
||||||
private HConstants() {
|
private HConstants() {
|
||||||
// Can't be instantiated with this ctor.
|
// Can't be instantiated with this ctor.
|
||||||
|
|
|
@ -393,6 +393,11 @@
|
||||||
<artifactId>hbase-resource-bundle</artifactId>
|
<artifactId>hbase-resource-bundle</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.hadoop</groupId>
|
||||||
|
<artifactId>hadoop-distcp</artifactId>
|
||||||
|
<version>${hadoop-two.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>commons-httpclient</groupId>
|
<groupId>commons-httpclient</groupId>
|
||||||
|
@ -406,6 +411,11 @@
|
||||||
<groupId>commons-collections</groupId>
|
<groupId>commons-collections</groupId>
|
||||||
<artifactId>commons-collections</artifactId>
|
<artifactId>commons-collections</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.hadoop</groupId>
|
||||||
|
<artifactId>hadoop-distcp</artifactId>
|
||||||
|
<version>${hadoop-two.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.hbase</groupId>
|
<groupId>org.apache.hbase</groupId>
|
||||||
<artifactId>hbase-hadoop-compat</artifactId>
|
<artifactId>hbase-hadoop-compat</artifactId>
|
||||||
|
|
|
@ -0,0 +1,338 @@
|
||||||
|
/**
|
||||||
|
* 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.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.apache.commons.cli.CommandLine;
|
||||||
|
import org.apache.commons.cli.Options;
|
||||||
|
import org.apache.commons.cli.ParseException;
|
||||||
|
import org.apache.commons.cli.PosixParser;
|
||||||
|
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.TableName;
|
||||||
|
import org.apache.hadoop.hbase.backup.BackupRestoreConstants.BACKUP_COMMAND;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceAudience;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceStability;
|
||||||
|
import org.apache.hadoop.hbase.client.Connection;
|
||||||
|
import org.apache.hadoop.hbase.client.ConnectionFactory;
|
||||||
|
import org.apache.hadoop.hbase.client.HBaseAdmin;
|
||||||
|
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
|
||||||
|
import org.apache.log4j.Level;
|
||||||
|
import org.apache.log4j.Logger;
|
||||||
|
import org.apache.zookeeper.KeeperException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backup HBase tables locally or on a remote cluster Serve as client entry point for the following
|
||||||
|
* features: - Full Backup provide local and remote back/restore for a list of tables - Incremental
|
||||||
|
* backup to build on top of full backup as daily/weekly backup - Convert incremental backup WAL
|
||||||
|
* files into hfiles - Merge several backup images into one(like merge weekly into monthly) - Add
|
||||||
|
* and remove table to and from Backup image - Cancel a backup process - Full backup based on
|
||||||
|
* existing snapshot - Describe information of a backup image
|
||||||
|
*/
|
||||||
|
|
||||||
|
@InterfaceAudience.Public
|
||||||
|
@InterfaceStability.Evolving
|
||||||
|
public final class BackupClient {
|
||||||
|
private static final Log LOG = LogFactory.getLog(BackupClient.class);
|
||||||
|
private static Options opt;
|
||||||
|
private static Configuration conf = null;
|
||||||
|
|
||||||
|
private BackupClient() {
|
||||||
|
throw new AssertionError("Instantiating utility class...");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void init() throws IOException {
|
||||||
|
// define supported options
|
||||||
|
opt = new Options();
|
||||||
|
|
||||||
|
opt.addOption("all", false, "All tables");
|
||||||
|
opt.addOption("debug", false, "Enable debug loggings");
|
||||||
|
opt.addOption("t", true, "Table name");
|
||||||
|
|
||||||
|
// create configuration instance
|
||||||
|
conf = getConf();
|
||||||
|
|
||||||
|
// disable irrelevant loggers to avoid it mess up command output
|
||||||
|
disableUselessLoggers();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void main(String[] args) throws IOException {
|
||||||
|
init();
|
||||||
|
parseAndRun(args);
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the configuration from a given one.
|
||||||
|
* @param newConf A new given configuration
|
||||||
|
*/
|
||||||
|
public synchronized static void setConf(Configuration newConf) {
|
||||||
|
conf = newConf;
|
||||||
|
BackupUtil.setConf(newConf);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Configuration getConf() {
|
||||||
|
if (conf == null) {
|
||||||
|
conf = BackupUtil.getConf();
|
||||||
|
}
|
||||||
|
return conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void disableUselessLoggers() {
|
||||||
|
// disable zookeeper log to avoid it mess up command output
|
||||||
|
Logger zkLogger = Logger.getLogger("org.apache.zookeeper");
|
||||||
|
LOG.debug("Zookeeper log level before set: " + zkLogger.getLevel());
|
||||||
|
zkLogger.setLevel(Level.OFF);
|
||||||
|
LOG.debug("Zookeeper log level after set: " + zkLogger.getLevel());
|
||||||
|
|
||||||
|
// disable hbase zookeeper tool log to avoid it mess up command output
|
||||||
|
Logger hbaseZkLogger = Logger.getLogger("org.apache.hadoop.hbase.zookeeper");
|
||||||
|
LOG.debug("HBase zookeeper log level before set: " + hbaseZkLogger.getLevel());
|
||||||
|
hbaseZkLogger.setLevel(Level.OFF);
|
||||||
|
LOG.debug("HBase Zookeeper log level after set: " + hbaseZkLogger.getLevel());
|
||||||
|
|
||||||
|
// disable hbase client log to avoid it mess up command output
|
||||||
|
Logger hbaseClientLogger = Logger.getLogger("org.apache.hadoop.hbase.client");
|
||||||
|
LOG.debug("HBase client log level before set: " + hbaseClientLogger.getLevel());
|
||||||
|
hbaseClientLogger.setLevel(Level.OFF);
|
||||||
|
LOG.debug("HBase client log level after set: " + hbaseClientLogger.getLevel());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void parseAndRun(String[] args) throws IOException {
|
||||||
|
|
||||||
|
String cmd = null;
|
||||||
|
String[] remainArgs = null;
|
||||||
|
if (args == null || args.length == 0) {
|
||||||
|
BackupCommands.createCommand(BackupRestoreConstants.BACKUP_COMMAND.HELP, null).execute();
|
||||||
|
} else {
|
||||||
|
cmd = args[0];
|
||||||
|
remainArgs = new String[args.length - 1];
|
||||||
|
if (args.length > 1) {
|
||||||
|
System.arraycopy(args, 1, remainArgs, 0, args.length - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CommandLine cmdline = null;
|
||||||
|
try {
|
||||||
|
cmdline = new PosixParser().parse(opt, remainArgs);
|
||||||
|
} catch (ParseException e) {
|
||||||
|
LOG.error("Could not parse command", e);
|
||||||
|
System.exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
BACKUP_COMMAND type = BACKUP_COMMAND.HELP;
|
||||||
|
if (BACKUP_COMMAND.CREATE.name().equalsIgnoreCase(cmd)) {
|
||||||
|
type = BACKUP_COMMAND.CREATE;
|
||||||
|
} else if (BACKUP_COMMAND.HELP.name().equalsIgnoreCase(cmd)) {
|
||||||
|
type = BACKUP_COMMAND.HELP;
|
||||||
|
} else {
|
||||||
|
System.out.println("Unsupported command for backup: " + cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
// enable debug logging
|
||||||
|
Logger backupClientLogger = Logger.getLogger("org.apache.hadoop.hbase.backup");
|
||||||
|
if (cmdline.hasOption("debug")) {
|
||||||
|
backupClientLogger.setLevel(Level.DEBUG);
|
||||||
|
} else {
|
||||||
|
backupClientLogger.setLevel(Level.INFO);
|
||||||
|
}
|
||||||
|
|
||||||
|
BackupCommands.createCommand(type, cmdline).execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send backup request to server, and monitor the progress if necessary
|
||||||
|
* @param backupType : full or incremental
|
||||||
|
* @param backupRootPath : the rooPath specified by user
|
||||||
|
* @param tableListStr : the table list specified by user
|
||||||
|
* @param snapshot : using existing snapshot if specified by user (in future jira)
|
||||||
|
* @return backupId backup id
|
||||||
|
* @throws IOException exception
|
||||||
|
* @throws KeeperException excpetion
|
||||||
|
*/
|
||||||
|
public static String create(String backupType, String backupRootPath, String tableListStr,
|
||||||
|
String snapshot) throws IOException {
|
||||||
|
|
||||||
|
String backupId = BackupRestoreConstants.BACKUPID_PREFIX + EnvironmentEdgeManager.currentTime();
|
||||||
|
|
||||||
|
// check target path first, confirm it doesn't exist before backup
|
||||||
|
boolean isTargetExist = false;
|
||||||
|
try {
|
||||||
|
isTargetExist = HBackupFileSystem.checkPathExist(backupRootPath, conf);
|
||||||
|
} catch (IOException e) {
|
||||||
|
String expMsg = e.getMessage();
|
||||||
|
String newMsg = null;
|
||||||
|
if (expMsg.contains("No FileSystem for scheme")) {
|
||||||
|
newMsg =
|
||||||
|
"Unsupported filesystem scheme found in the backup target url. Error Message: "
|
||||||
|
+ newMsg;
|
||||||
|
LOG.error(newMsg);
|
||||||
|
throw new IOException(newMsg);
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
LOG.error(e.getMessage());
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isTargetExist) {
|
||||||
|
LOG.info("Using existing backup root dir: " + backupRootPath);
|
||||||
|
} else {
|
||||||
|
LOG.info("Backup root dir " + backupRootPath + " does not exist. Will be created.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// table list specified for backup, trigger backup on specified tables
|
||||||
|
String tableList = tableListStr;
|
||||||
|
// (tableListStr == null) ? null : tableListStr.replaceAll(
|
||||||
|
// BackupRestoreConstants.TABLENAME_DELIMITER_IN_COMMAND,
|
||||||
|
// BackupRestoreConstants.TABLENAME_DELIMITER_IN_ZNODE);
|
||||||
|
try {
|
||||||
|
requestBackup(backupId, backupType, tableList, backupRootPath, snapshot);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
String errMsg = e.getMessage();
|
||||||
|
if (errMsg != null
|
||||||
|
&& (errMsg.startsWith("Non-existing tables found") || errMsg
|
||||||
|
.startsWith("Snapshot is not found"))) {
|
||||||
|
LOG.error(errMsg + ", please check your command");
|
||||||
|
throw e;
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return backupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare and submit Backup request
|
||||||
|
* @param backupId : backup_timestame (something like backup_1398729212626)
|
||||||
|
* @param backupType : full or incremental
|
||||||
|
* @param tableList : tables to be backuped
|
||||||
|
* @param targetRootDir : specified by user
|
||||||
|
* @param snapshot : use existing snapshot if specified by user (for future jira)
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
protected static void requestBackup(String backupId, String backupType, String tableList,
|
||||||
|
String targetRootDir, String snapshot) throws IOException {
|
||||||
|
|
||||||
|
Configuration conf = getConf();
|
||||||
|
BackupManager backupManager = null;
|
||||||
|
BackupContext backupContext = null;
|
||||||
|
if (snapshot != null) {
|
||||||
|
LOG.warn("Snapshot option specified, backup type and table option will be ignored,\n"
|
||||||
|
+ "full backup will be taken based on the given snapshot.");
|
||||||
|
throw new IOException("backup using existing Snapshot will be implemented in future jira");
|
||||||
|
}
|
||||||
|
|
||||||
|
HBaseAdmin hbadmin = null;
|
||||||
|
Connection conn = null;
|
||||||
|
try {
|
||||||
|
|
||||||
|
backupManager = new BackupManager(conf);
|
||||||
|
String tables = tableList;
|
||||||
|
if (backupType.equals(BackupRestoreConstants.BACKUP_TYPE_INCR)) {
|
||||||
|
Set<String> incrTableSet = backupManager.getIncrementalBackupTableSet();
|
||||||
|
if (incrTableSet.isEmpty()) {
|
||||||
|
LOG.warn("Incremental backup table set contains no table.\n"
|
||||||
|
+ "Use 'backup create full' or 'backup stop' to \n "
|
||||||
|
+ "change the tables covered by incremental backup.");
|
||||||
|
throw new RuntimeException("No table covered by incremental backup.");
|
||||||
|
}
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (String tableName : incrTableSet) {
|
||||||
|
sb.append(tableName + " ");
|
||||||
|
}
|
||||||
|
LOG.info("Incremental backup for the following table set: " + sb.toString());
|
||||||
|
tables =
|
||||||
|
sb.toString().trim()
|
||||||
|
.replaceAll(" ", BackupRestoreConstants.TABLENAME_DELIMITER_IN_COMMAND);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check whether table exists first before starting real request
|
||||||
|
if (tables != null) {
|
||||||
|
String[] tableNames = tables.split(BackupRestoreConstants.TABLENAME_DELIMITER_IN_COMMAND);
|
||||||
|
ArrayList<String> noneExistingTableList = null;
|
||||||
|
conn = ConnectionFactory.createConnection(conf);
|
||||||
|
hbadmin = (HBaseAdmin) conn.getAdmin();
|
||||||
|
for (String tableName : tableNames) {
|
||||||
|
if (!hbadmin.tableExists(TableName.valueOf(tableName))) {
|
||||||
|
if (noneExistingTableList == null) {
|
||||||
|
noneExistingTableList = new ArrayList<String>();
|
||||||
|
}
|
||||||
|
noneExistingTableList.add(tableName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (noneExistingTableList != null) {
|
||||||
|
if (backupType.equals(BackupRestoreConstants.BACKUP_TYPE_INCR)) {
|
||||||
|
LOG.warn("Incremental backup table set contains no-exising table: "
|
||||||
|
+ noneExistingTableList);
|
||||||
|
} else {
|
||||||
|
// Throw exception only in full mode - we try to backup non-existing table
|
||||||
|
throw new RuntimeException("Non-existing tables found in the table list: "
|
||||||
|
+ noneExistingTableList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if any target table backup dir already exist, then no backup action taken
|
||||||
|
String[] tableNames = null;
|
||||||
|
if (tables != null && !tables.equals("")) {
|
||||||
|
tableNames = tables.split(BackupRestoreConstants.TABLENAME_DELIMITER_IN_COMMAND);
|
||||||
|
}
|
||||||
|
if (tableNames != null && tableNames.length > 0) {
|
||||||
|
for (String table : tableNames) {
|
||||||
|
String targetTableBackupDir =
|
||||||
|
HBackupFileSystem.getTableBackupDir(targetRootDir, backupId, table);
|
||||||
|
Path targetTableBackupDirPath = new Path(targetTableBackupDir);
|
||||||
|
FileSystem outputFs = FileSystem.get(targetTableBackupDirPath.toUri(), conf);
|
||||||
|
if (outputFs.exists(targetTableBackupDirPath)) {
|
||||||
|
throw new IOException("Target backup directory " + targetTableBackupDir
|
||||||
|
+ " exists already.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
backupContext =
|
||||||
|
backupManager.createBackupContext(backupId, backupType, tables, targetRootDir, snapshot);
|
||||||
|
backupManager.initialize();
|
||||||
|
backupManager.dispatchRequest(backupContext);
|
||||||
|
} catch (BackupException e) {
|
||||||
|
// suppress the backup exception wrapped within #initialize or #dispatchRequest, backup
|
||||||
|
// exception has already been handled normally
|
||||||
|
StackTraceElement[] stes = e.getStackTrace();
|
||||||
|
for (StackTraceElement ste : stes) {
|
||||||
|
LOG.info(ste);
|
||||||
|
}
|
||||||
|
LOG.error("Backup Exception " + e.getMessage());
|
||||||
|
} finally {
|
||||||
|
if (hbadmin != null) {
|
||||||
|
hbadmin.close();
|
||||||
|
}
|
||||||
|
if (conn != null) {
|
||||||
|
conn.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,147 @@
|
||||||
|
/**
|
||||||
|
* 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.io.IOException;
|
||||||
|
|
||||||
|
import org.apache.commons.cli.CommandLine;
|
||||||
|
import org.apache.hadoop.hbase.backup.BackupRestoreConstants.BACKUP_COMMAND;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceAudience;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceStability;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* General backup commands, options and usage messages
|
||||||
|
*/
|
||||||
|
@InterfaceAudience.Private
|
||||||
|
@InterfaceStability.Evolving
|
||||||
|
final class BackupCommands {
|
||||||
|
|
||||||
|
private static final String USAGE = "Usage: hbase backup COMMAND\n"
|
||||||
|
+ "where COMMAND is one of:\n" + " create create a new backup image\n"
|
||||||
|
+ "Enter \'help COMMAND\' to see help message for each command\n";
|
||||||
|
|
||||||
|
private static final String CREATE_CMD_USAGE =
|
||||||
|
"Usage: hbase backup create <type> <backup_root_path> [tables] [-s name] [-convert] "
|
||||||
|
+ "[-silent]\n" + " type \"full\" to create a full backup image;\n"
|
||||||
|
+ " \"incremental\" to create an incremental backup image\n"
|
||||||
|
+ " backup_root_path The full root path to store the backup image,\n"
|
||||||
|
+ " the prefix can be gpfs, hdfs or webhdfs\n" + " Options:\n"
|
||||||
|
+ " tables If no tables (\"\") are specified, all tables are backed up. "
|
||||||
|
+ "Otherwise it is a\n" + " comma separated list of tables.\n"
|
||||||
|
+ " -s name Use the specified snapshot for full backup\n"
|
||||||
|
+ " -convert For an incremental backup, convert WAL files to HFiles\n";
|
||||||
|
|
||||||
|
interface Command {
|
||||||
|
void execute() throws IOException;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BackupCommands() {
|
||||||
|
throw new AssertionError("Instantiating utility class...");
|
||||||
|
}
|
||||||
|
|
||||||
|
static Command createCommand(BACKUP_COMMAND type, CommandLine cmdline) {
|
||||||
|
Command cmd = null;
|
||||||
|
switch (type) {
|
||||||
|
case CREATE:
|
||||||
|
cmd = new CreateCommand(cmdline);
|
||||||
|
break;
|
||||||
|
case HELP:
|
||||||
|
default:
|
||||||
|
cmd = new HelpCommand(cmdline);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class CreateCommand implements Command {
|
||||||
|
CommandLine cmdline;
|
||||||
|
|
||||||
|
CreateCommand(CommandLine cmdline) {
|
||||||
|
this.cmdline = cmdline;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute() throws IOException {
|
||||||
|
if (cmdline == null || cmdline.getArgs() == null) {
|
||||||
|
System.out.println("ERROR: missing arguments");
|
||||||
|
System.out.println(CREATE_CMD_USAGE);
|
||||||
|
System.exit(-1);
|
||||||
|
}
|
||||||
|
String[] args = cmdline.getArgs();
|
||||||
|
if (args.length < 2 || args.length > 3) {
|
||||||
|
System.out.println("ERROR: wrong number of arguments");
|
||||||
|
System.out.println(CREATE_CMD_USAGE);
|
||||||
|
System.exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!BackupRestoreConstants.BACKUP_TYPE_FULL.equalsIgnoreCase(args[0])
|
||||||
|
&& !BackupRestoreConstants.BACKUP_TYPE_INCR.equalsIgnoreCase(args[0])) {
|
||||||
|
System.out.println("ERROR: invalid backup type");
|
||||||
|
System.out.println(CREATE_CMD_USAGE);
|
||||||
|
System.exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
String snapshot = cmdline.hasOption('s') ? cmdline.getOptionValue('s') : null;
|
||||||
|
String tables = (args.length == 3) ? args[2] : null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
BackupClient.create(args[0], args[1], tables, snapshot);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
System.out.println("ERROR: " + e.getMessage());
|
||||||
|
System.exit(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class HelpCommand implements Command {
|
||||||
|
CommandLine cmdline;
|
||||||
|
|
||||||
|
HelpCommand(CommandLine cmdline) {
|
||||||
|
this.cmdline = cmdline;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute() throws IOException {
|
||||||
|
if (cmdline == null) {
|
||||||
|
System.out.println(USAGE);
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] args = cmdline.getArgs();
|
||||||
|
if (args == null || args.length == 0) {
|
||||||
|
System.out.println(USAGE);
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length != 1) {
|
||||||
|
System.out.println("Only support check help message of a single command type");
|
||||||
|
System.out.println(USAGE);
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
String type = args[0];
|
||||||
|
|
||||||
|
if (BACKUP_COMMAND.CREATE.name().equalsIgnoreCase(type)) {
|
||||||
|
System.out.println(CREATE_CMD_USAGE);
|
||||||
|
} // other commands will be supported in future jira
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,318 @@
|
||||||
|
/**
|
||||||
|
* 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.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.ObjectInputStream;
|
||||||
|
import java.io.ObjectOutputStream;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceAudience;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceStability;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object to encapsulate the information for each backup request
|
||||||
|
*/
|
||||||
|
@InterfaceAudience.Private
|
||||||
|
@InterfaceStability.Evolving
|
||||||
|
public class BackupContext implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 2401435114454300992L;
|
||||||
|
|
||||||
|
// backup id: a timestamp when we request the backup
|
||||||
|
private String backupId;
|
||||||
|
|
||||||
|
// backup type, full or incremental
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
// target root directory for storing the backup files
|
||||||
|
private String targetRootDir;
|
||||||
|
|
||||||
|
// overall backup status
|
||||||
|
private BackupHandler.BACKUPSTATUS flag;
|
||||||
|
|
||||||
|
// overall backup phase
|
||||||
|
private BackupHandler.BACKUPPHASE phase;
|
||||||
|
|
||||||
|
// overall backup failure message
|
||||||
|
private String failedMsg;
|
||||||
|
|
||||||
|
// backup status map for all tables
|
||||||
|
private Map<String, BackupStatus> backupStatusMap;
|
||||||
|
|
||||||
|
// actual start timestamp of the backup process
|
||||||
|
private long startTs;
|
||||||
|
|
||||||
|
// actual end timestamp of the backup process, could be fail or complete
|
||||||
|
private long endTs;
|
||||||
|
|
||||||
|
// the total bytes of incremental logs copied
|
||||||
|
private long totalBytesCopied;
|
||||||
|
|
||||||
|
// for incremental backup, the location of the backed-up hlogs
|
||||||
|
private String hlogTargetDir = null;
|
||||||
|
|
||||||
|
// incremental backup file list
|
||||||
|
transient private List<String> incrBackupFileList;
|
||||||
|
|
||||||
|
// new region server log timestamps for table set after distributed log roll
|
||||||
|
// key - table name, value - map of RegionServer hostname -> last log rolled timestamp
|
||||||
|
transient private HashMap<String, HashMap<String, String>> tableSetTimestampMap;
|
||||||
|
|
||||||
|
// cancel flag
|
||||||
|
private boolean cancelled = false;
|
||||||
|
// backup progress string
|
||||||
|
|
||||||
|
private String progress;
|
||||||
|
|
||||||
|
public BackupContext() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public BackupContext(String backupId, String type, String[] tables, String targetRootDir,
|
||||||
|
String snapshot) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if (backupStatusMap == null) {
|
||||||
|
backupStatusMap = new HashMap<String, BackupStatus>();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.backupId = backupId;
|
||||||
|
this.type = type;
|
||||||
|
this.targetRootDir = targetRootDir;
|
||||||
|
|
||||||
|
this.addTables(tables);
|
||||||
|
|
||||||
|
if (type.equals(BackupRestoreConstants.BACKUP_TYPE_INCR)) {
|
||||||
|
setHlogTargetDir(HBackupFileSystem.getLogBackupDir(targetRootDir, backupId));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.startTs = 0;
|
||||||
|
this.endTs = 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set progress string
|
||||||
|
* @param msg progress message
|
||||||
|
*/
|
||||||
|
|
||||||
|
public void setProgress(String msg) {
|
||||||
|
this.progress = msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current progress msg
|
||||||
|
*/
|
||||||
|
public String getProgress() {
|
||||||
|
return progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark cancel flag.
|
||||||
|
*/
|
||||||
|
public void markCancel() {
|
||||||
|
this.cancelled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Has been marked as cancelled or not.
|
||||||
|
* @return True if marked as cancelled
|
||||||
|
*/
|
||||||
|
public boolean isCancelled() {
|
||||||
|
return this.cancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBackupId() {
|
||||||
|
return backupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBackupId(String backupId) {
|
||||||
|
this.backupId = backupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BackupStatus getBackupStatus(String table) {
|
||||||
|
return this.backupStatusMap.get(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFailedMsg() {
|
||||||
|
return failedMsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFailedMsg(String failedMsg) {
|
||||||
|
this.failedMsg = failedMsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getStartTs() {
|
||||||
|
return startTs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStartTs(long startTs) {
|
||||||
|
this.startTs = startTs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getEndTs() {
|
||||||
|
return endTs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEndTs(long endTs) {
|
||||||
|
this.endTs = endTs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTotalBytesCopied() {
|
||||||
|
return totalBytesCopied;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BackupHandler.BACKUPSTATUS getFlag() {
|
||||||
|
return flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFlag(BackupHandler.BACKUPSTATUS flag) {
|
||||||
|
this.flag = flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BackupHandler.BACKUPPHASE getPhase() {
|
||||||
|
return phase;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPhase(BackupHandler.BACKUPPHASE phase) {
|
||||||
|
this.phase = phase;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSnapshotName(String table, String snapshotName) {
|
||||||
|
this.backupStatusMap.get(table).setSnapshotName(snapshotName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSnapshotName(String table) {
|
||||||
|
return this.backupStatusMap.get(table).getSnapshotName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getSnapshotNames() {
|
||||||
|
List<String> snapshotNames = new ArrayList<String>();
|
||||||
|
for (BackupStatus backupStatus : this.backupStatusMap.values()) {
|
||||||
|
snapshotNames.add(backupStatus.getSnapshotName());
|
||||||
|
}
|
||||||
|
return snapshotNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getTables() {
|
||||||
|
return this.backupStatusMap.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTableListAsString() {
|
||||||
|
return BackupUtil.concat(backupStatusMap.keySet(), ";");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addTables(String[] tables) {
|
||||||
|
for (String table : tables) {
|
||||||
|
BackupStatus backupStatus = new BackupStatus(table, this.targetRootDir, this.backupId);
|
||||||
|
this.backupStatusMap.put(table, backupStatus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTargetRootDir() {
|
||||||
|
return targetRootDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHlogTargetDir(String hlogTagetDir) {
|
||||||
|
this.hlogTargetDir = hlogTagetDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHLogTargetDir() {
|
||||||
|
return hlogTargetDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getIncrBackupFileList() {
|
||||||
|
return incrBackupFileList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> setIncrBackupFileList(List<String> incrBackupFileList) {
|
||||||
|
this.incrBackupFileList = incrBackupFileList;
|
||||||
|
return this.incrBackupFileList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the new region server log timestamps after distributed log roll
|
||||||
|
* @param newTableSetTimestampMap table timestamp map
|
||||||
|
*/
|
||||||
|
public void setIncrTimestampMap(HashMap<String,
|
||||||
|
HashMap<String, String>> newTableSetTimestampMap) {
|
||||||
|
this.tableSetTimestampMap = newTableSetTimestampMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get new region server log timestamps after distributed log roll
|
||||||
|
* @return new region server log timestamps
|
||||||
|
*/
|
||||||
|
public HashMap<String, HashMap<String, String>> getIncrTimestampMap() {
|
||||||
|
return this.tableSetTimestampMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get existing snapshot if backing up from existing snapshot.
|
||||||
|
* @return The existing snapshot, null if not backing up from existing snapshot
|
||||||
|
*/
|
||||||
|
public String getExistingSnapshot() {
|
||||||
|
// this feature will be supported in another Jira
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether this backup context are for backing up from existing snapshot or not.
|
||||||
|
* @return true if it is for backing up from existing snapshot, otherwise false
|
||||||
|
*/
|
||||||
|
public boolean fromExistingSnapshot() {
|
||||||
|
// this feature will be supported in later jiras
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTableBySnapshot(String snapshotName) {
|
||||||
|
for (Entry<String, BackupStatus> entry : this.backupStatusMap.entrySet()) {
|
||||||
|
if (snapshotName.equals(entry.getValue().getSnapshotName())) {
|
||||||
|
return entry.getKey();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] toByteArray() throws IOException {
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
ObjectOutputStream oos = new ObjectOutputStream(baos);
|
||||||
|
oos.writeObject(this);
|
||||||
|
return baos.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BackupContext fromByteArray(byte[] data)
|
||||||
|
throws IOException, ClassNotFoundException {
|
||||||
|
ByteArrayInputStream bais = new ByteArrayInputStream(data);
|
||||||
|
ObjectInputStream ois = new ObjectInputStream(bais);
|
||||||
|
return (BackupContext) ois.readObject();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/**
|
||||||
|
* 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.io.IOException;
|
||||||
|
|
||||||
|
import org.apache.hadoop.conf.Configurable;
|
||||||
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceAudience;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceStability;
|
||||||
|
|
||||||
|
@InterfaceAudience.Private
|
||||||
|
@InterfaceStability.Evolving
|
||||||
|
public interface BackupCopyService extends Configurable {
|
||||||
|
static enum Type {
|
||||||
|
FULL, INCREMENTAL
|
||||||
|
}
|
||||||
|
|
||||||
|
public int copy(BackupHandler backupHandler, Configuration conf, BackupCopyService.Type copyType,
|
||||||
|
String[] options) throws IOException;
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
/**
|
||||||
|
* 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.hadoop.hbase.HBaseIOException;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceAudience;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceStability;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backup exception
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
@InterfaceAudience.Private
|
||||||
|
@InterfaceStability.Evolving
|
||||||
|
public class BackupException extends HBaseIOException {
|
||||||
|
private BackupContext description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some exception happened for a backup and don't even know the backup that it was about
|
||||||
|
* @param msg Full description of the failure
|
||||||
|
*/
|
||||||
|
public BackupException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some exception happened for a backup with a cause
|
||||||
|
* @param cause the cause
|
||||||
|
*/
|
||||||
|
public BackupException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception for the given backup that has no previous root cause
|
||||||
|
* @param msg reason why the backup failed
|
||||||
|
* @param desc description of the backup that is being failed
|
||||||
|
*/
|
||||||
|
public BackupException(String msg, BackupContext desc) {
|
||||||
|
super(msg);
|
||||||
|
this.description = desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception for the given backup due to another exception
|
||||||
|
* @param msg reason why the backup failed
|
||||||
|
* @param cause root cause of the failure
|
||||||
|
* @param desc description of the backup that is being failed
|
||||||
|
*/
|
||||||
|
public BackupException(String msg, Throwable cause, BackupContext desc) {
|
||||||
|
super(msg, cause);
|
||||||
|
this.description = desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception when the description of the backup cannot be determined, due to some other root
|
||||||
|
* cause
|
||||||
|
* @param message description of what caused the failure
|
||||||
|
* @param e root cause
|
||||||
|
*/
|
||||||
|
public BackupException(String message, Exception e) {
|
||||||
|
super(message, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BackupContext getBackupContext() {
|
||||||
|
return this.description;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,744 @@
|
||||||
|
/**
|
||||||
|
* 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.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
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.TableName;
|
||||||
|
import org.apache.hadoop.hbase.backup.BackupManifest.BackupImage;
|
||||||
|
import org.apache.hadoop.hbase.backup.master.LogRollMasterProcedureManager;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceAudience;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceStability;
|
||||||
|
import org.apache.hadoop.hbase.client.Admin;
|
||||||
|
import org.apache.hadoop.hbase.client.Connection;
|
||||||
|
import org.apache.hadoop.hbase.client.ConnectionFactory;
|
||||||
|
import org.apache.hadoop.hbase.client.HBaseAdmin;
|
||||||
|
import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos;
|
||||||
|
import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.SnapshotDescription;
|
||||||
|
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
|
||||||
|
import org.apache.hadoop.hbase.util.FSUtils;
|
||||||
|
import org.apache.zookeeper.KeeperException.NoNodeException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Handler to carry the operations of backup progress
|
||||||
|
*/
|
||||||
|
@InterfaceAudience.Private
|
||||||
|
@InterfaceStability.Evolving
|
||||||
|
public class BackupHandler implements Callable<Object> {
|
||||||
|
private static final Log LOG = LogFactory.getLog(BackupHandler.class);
|
||||||
|
|
||||||
|
// backup phase
|
||||||
|
// for overall backup (for table list, some table may go online, while some may go offline)
|
||||||
|
protected static enum BACKUPPHASE {
|
||||||
|
REQUEST, SNAPSHOT, PREPARE_INCREMENTAL, SNAPSHOTCOPY, INCREMENTAL_COPY, STORE_MANIFEST;
|
||||||
|
}
|
||||||
|
|
||||||
|
// backup status flag
|
||||||
|
protected static enum BACKUPSTATUS {
|
||||||
|
WAITING, ONGOING, COMPLETE, FAILED, CANCELLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected BackupContext backupContext;
|
||||||
|
private BackupManager backupManager;
|
||||||
|
private Configuration conf;
|
||||||
|
|
||||||
|
public BackupHandler(BackupContext backupContext,
|
||||||
|
BackupManager backupManager, Configuration conf) {
|
||||||
|
this.backupContext = backupContext;
|
||||||
|
this.backupManager = backupManager;
|
||||||
|
this.conf = conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BackupContext getBackupContext() {
|
||||||
|
return backupContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object call() throws Exception {
|
||||||
|
try {
|
||||||
|
// overall backup begin
|
||||||
|
this.beginBackup(backupContext);
|
||||||
|
HashMap<String, String> newTimestamps = null;
|
||||||
|
boolean fromExistingSnapshot = false; // supported by future jira
|
||||||
|
// handle full or incremental backup for table or table list
|
||||||
|
if (backupContext.getType().equals(BackupRestoreConstants.BACKUP_TYPE_FULL)) {
|
||||||
|
String savedStartCode = null;
|
||||||
|
HBaseAdmin hbadmin = null;
|
||||||
|
Connection conn = null;
|
||||||
|
boolean firstBackup = false;
|
||||||
|
// do snapshot for full table backup, if backing up from existing snapshot, then skip the
|
||||||
|
// step of taking snapshot
|
||||||
|
if (fromExistingSnapshot) {
|
||||||
|
LOG.error("Backup from existing snapshot, so skip the snapshot step. ");
|
||||||
|
LOG.error("This feature will be supported by a future jira");
|
||||||
|
throw new RuntimeException("Backup from existing snapshot is not supported");
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
savedStartCode = backupManager.readBackupStartCode();
|
||||||
|
firstBackup = savedStartCode == null;
|
||||||
|
if (firstBackup) {
|
||||||
|
// This is our first backup. Let's put some marker on ZK so that we can hold the logs
|
||||||
|
// while we do the backup.
|
||||||
|
backupManager.writeBackupStartCode("0");
|
||||||
|
}
|
||||||
|
// We roll log here before we do the snapshot. It is possible there is duplicate data
|
||||||
|
// in the log that is already in the snapshot. But if we do it after the snapshot, we
|
||||||
|
// could have data loss.
|
||||||
|
// A better approach is to do the roll log on each RS in the same global procedure as
|
||||||
|
// the snapshot.
|
||||||
|
LOG.info("Execute roll log procedure for full backup ...");
|
||||||
|
conn = ConnectionFactory.createConnection(conf);
|
||||||
|
hbadmin = (HBaseAdmin) conn.getAdmin();
|
||||||
|
hbadmin.execProcedure(LogRollMasterProcedureManager.ROLLLOG_PROCEDURE_SIGNATURE,
|
||||||
|
LogRollMasterProcedureManager.ROLLLOG_PROCEDURE_NAME, new HashMap<String, String>());
|
||||||
|
newTimestamps = backupManager.readRegionServerLastLogRollResult();
|
||||||
|
if (firstBackup) {
|
||||||
|
// Updates registered log files
|
||||||
|
// We record ALL old WAL files as registered, because
|
||||||
|
// this is a first full backup in the system and these
|
||||||
|
// files are not needed for next incremental backup
|
||||||
|
List<String> logFiles = BackupUtil.getWALFilesOlderThan(conf, newTimestamps);
|
||||||
|
backupManager.recordWALFiles(logFiles);
|
||||||
|
}
|
||||||
|
this.snapshotForFullBackup(backupContext);
|
||||||
|
} catch (BackupException e) {
|
||||||
|
// fail the overall backup and return
|
||||||
|
this.failBackup(backupContext, e, "Unexpected BackupException : ");
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
if (hbadmin != null) {
|
||||||
|
hbadmin.close();
|
||||||
|
}
|
||||||
|
if (conn != null) {
|
||||||
|
conn.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the faked progress currently for snapshot done
|
||||||
|
this.updateProgress("10.0%", 0);
|
||||||
|
// do snapshot copy
|
||||||
|
try {
|
||||||
|
this.snapshotCopy(backupContext);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// fail the overall backup and return
|
||||||
|
this.failBackup(backupContext, e, "Unexpected BackupException : ");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Updates incremental backup table set
|
||||||
|
backupManager.addIncrementalBackupTableSet(backupContext.getTables());
|
||||||
|
|
||||||
|
} else if (backupContext.getType().equals(BackupRestoreConstants.BACKUP_TYPE_INCR)) {
|
||||||
|
LOG.info("For incremental backup, current table set is "
|
||||||
|
+ backupManager.getIncrementalBackupTableSet());
|
||||||
|
// do incremental table backup preparation
|
||||||
|
backupContext.setPhase(BACKUPPHASE.PREPARE_INCREMENTAL);
|
||||||
|
// avoid action if has been cancelled
|
||||||
|
if (backupContext.isCancelled()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
IncrementalBackupManager incrBackupManager = new IncrementalBackupManager(backupManager);
|
||||||
|
|
||||||
|
newTimestamps = incrBackupManager.getIncrBackupLogFileList(backupContext);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// fail the overall backup and return
|
||||||
|
this.failBackup(backupContext, e, "Unexpected Exception : ");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// update the faked progress currently for incremental preparation done
|
||||||
|
this.updateProgress("10.0%", 0);
|
||||||
|
|
||||||
|
// do incremental copy
|
||||||
|
try {
|
||||||
|
// copy out the table and region info files for each table
|
||||||
|
BackupUtil.copyTableRegionInfo(backupContext, conf);
|
||||||
|
this.incrementalCopy(backupContext);
|
||||||
|
// Save list of WAL files copied
|
||||||
|
backupManager.recordWALFiles(backupContext.getIncrBackupFileList());
|
||||||
|
} catch (Exception e) {
|
||||||
|
// fail the overall backup and return
|
||||||
|
this.failBackup(backupContext, e, "Unexpected exception doing incremental copy : ");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
LOG.error("Unsupport backup type: " + backupContext.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
// set overall backup status: complete. Here we make sure to complete the backup. After this
|
||||||
|
// checkpoint, even if entering cancel process, will let the backup finished
|
||||||
|
backupContext.setFlag(BACKUPSTATUS.COMPLETE);
|
||||||
|
|
||||||
|
if (!fromExistingSnapshot) {
|
||||||
|
if (backupContext.getType().equals(BackupRestoreConstants.BACKUP_TYPE_INCR)) {
|
||||||
|
// Set the previousTimestampMap which is before this current log roll to the manifest.
|
||||||
|
HashMap<String, HashMap<String, String>> previousTimestampMap =
|
||||||
|
backupManager.readLogTimestampMap();
|
||||||
|
backupContext.setIncrTimestampMap(previousTimestampMap);
|
||||||
|
}
|
||||||
|
// The table list in backupContext is good for both full backup and incremental backup.
|
||||||
|
// For incremental backup, it contains the incremental backup table set.
|
||||||
|
|
||||||
|
backupManager.writeRegionServerLogTimestamp(backupContext.getTables(), newTimestamps);
|
||||||
|
|
||||||
|
HashMap<String, HashMap<String, String>> newTableSetTimestampMap =
|
||||||
|
backupManager.readLogTimestampMap();
|
||||||
|
|
||||||
|
String newStartCode =
|
||||||
|
BackupUtil.getMinValue(BackupUtil.getRSLogTimestampMins(newTableSetTimestampMap));
|
||||||
|
backupManager.writeBackupStartCode(newStartCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// backup complete
|
||||||
|
this.completeBackup(backupContext);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// even during completing backup (#completeBackup(backupContext)), exception may occur, or
|
||||||
|
// exception occur during other process, fail the backup finally
|
||||||
|
this.failBackup(backupContext, e, "Error caught during backup progress: ");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begin the overall backup.
|
||||||
|
* @param backupContext backup context
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
private void beginBackup(BackupContext backupContext) throws IOException {
|
||||||
|
|
||||||
|
// set the start timestamp of the overall backup
|
||||||
|
long startTs = EnvironmentEdgeManager.currentTime();
|
||||||
|
backupContext.setStartTs(startTs);
|
||||||
|
// set overall backup status: ongoing
|
||||||
|
backupContext.setFlag(BACKUPSTATUS.ONGOING);
|
||||||
|
LOG.info("Backup " + backupContext.getBackupId() + " starts at " + startTs + ".");
|
||||||
|
|
||||||
|
backupManager.updateBackupStatus(backupContext);
|
||||||
|
LOG.debug("Backup session " + backupContext.getBackupId() + " has been started.");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Snapshot for full table backup.
|
||||||
|
* @param backupContext backup context
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
private void snapshotForFullBackup(BackupContext backupContext) throws IOException {
|
||||||
|
|
||||||
|
LOG.info("HBase snapshot full backup for " + backupContext.getBackupId());
|
||||||
|
|
||||||
|
// avoid action if has been cancelled
|
||||||
|
if (backupContext.isCancelled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
HBaseAdmin hbadmin = null;
|
||||||
|
Connection conn = null;
|
||||||
|
|
||||||
|
// we do HBase snapshot for tables in the table list one by one currently
|
||||||
|
for (String table : backupContext.getTables()) {
|
||||||
|
|
||||||
|
// avoid action if it has been cancelled
|
||||||
|
if (backupContext.isCancelled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
HBaseProtos.SnapshotDescription backupSnapshot;
|
||||||
|
try {
|
||||||
|
// wrap a SnapshotDescription for offline/online snapshot
|
||||||
|
backupSnapshot = this.wrapSnapshotDescription(table);
|
||||||
|
|
||||||
|
// set the snapshot name in BackupStatus of this table
|
||||||
|
backupContext.setSnapshotName(table, backupSnapshot.getName());
|
||||||
|
|
||||||
|
// Kick off snapshot for backup
|
||||||
|
conn = ConnectionFactory.createConnection(conf);
|
||||||
|
hbadmin = (HBaseAdmin) conn.getAdmin();
|
||||||
|
hbadmin.snapshot(backupSnapshot);
|
||||||
|
|
||||||
|
if (LOG.isDebugEnabled() == false) {
|
||||||
|
// In DEBUG mode we log message already.
|
||||||
|
// This is not to duplicate that message.
|
||||||
|
LOG.info("Snapshot has been launched, waiting to finish ...");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("Snapshot failed to create " + getMessage(e));
|
||||||
|
|
||||||
|
// currently, we fail the overall backup if any table in the list failed, so throw the
|
||||||
|
// exception out for overall backup failing
|
||||||
|
throw new BackupException("Backup snapshot failed on table " + table, e);
|
||||||
|
} finally {
|
||||||
|
if (hbadmin != null) {
|
||||||
|
hbadmin.close();
|
||||||
|
}
|
||||||
|
if (conn != null) {
|
||||||
|
conn.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the snapshot name in BackupStatus of this table, only after snapshot success.
|
||||||
|
backupContext.setSnapshotName(table, backupSnapshot.getName());
|
||||||
|
|
||||||
|
} // for each table in the backup table list
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fail the overall backup.
|
||||||
|
* @param backupContext backup context
|
||||||
|
* @param e exception
|
||||||
|
* @throws Exception exception
|
||||||
|
*/
|
||||||
|
private void failBackup(BackupContext backupContext, Exception e, String msg) throws Exception {
|
||||||
|
|
||||||
|
LOG.error(msg + getMessage(e));
|
||||||
|
|
||||||
|
// If this is a cancel exception, then we've already cleaned.
|
||||||
|
|
||||||
|
if (this.backupContext.getFlag().equals(BACKUPSTATUS.CANCELLED)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the failure timestamp of the overall backup
|
||||||
|
backupContext.setEndTs(EnvironmentEdgeManager.currentTime());
|
||||||
|
|
||||||
|
// set failure message
|
||||||
|
backupContext.setFailedMsg(e.getMessage());
|
||||||
|
|
||||||
|
// set overall backup status: failed
|
||||||
|
backupContext.setFlag(BACKUPSTATUS.FAILED);
|
||||||
|
|
||||||
|
// compose the backup failed data
|
||||||
|
String backupFailedData =
|
||||||
|
"BackupId=" + backupContext.getBackupId() + ",startts=" + backupContext.getStartTs()
|
||||||
|
+ ",failedts=" + backupContext.getEndTs() + ",failedphase=" + backupContext.getPhase()
|
||||||
|
+ ",failedmessage=" + backupContext.getFailedMsg();
|
||||||
|
LOG.error(backupFailedData);
|
||||||
|
|
||||||
|
backupManager.updateBackupStatus(backupContext);
|
||||||
|
|
||||||
|
// if full backup, then delete HBase snapshots if there already have snapshots taken
|
||||||
|
// and also clean up export snapshot log files if exist
|
||||||
|
if (backupContext.getType().equals(BackupRestoreConstants.BACKUP_TYPE_FULL)) {
|
||||||
|
if (!backupContext.fromExistingSnapshot()) {
|
||||||
|
this.deleteSnapshot(backupContext);
|
||||||
|
}
|
||||||
|
this.cleanupExportSnapshotLog();
|
||||||
|
} /*
|
||||||
|
* else { // support incremental backup code in future jira // TODO. See HBASE-14124 }
|
||||||
|
*/
|
||||||
|
|
||||||
|
// clean up the uncompleted data at target directory if the ongoing backup has already entered
|
||||||
|
// the copy phase
|
||||||
|
// For incremental backup, DistCp logs will be cleaned with the targetDir.
|
||||||
|
this.cleanupTargetDir();
|
||||||
|
|
||||||
|
LOG.info("Backup " + backupContext.getBackupId() + " failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the ongoing back token znode with new progress.
|
||||||
|
* @param newProgress progress
|
||||||
|
* @param bytesCopied bytes copied
|
||||||
|
* @throws NoNodeException exception
|
||||||
|
*/
|
||||||
|
public void updateProgress(String newProgress, long bytesCopied) throws IOException {
|
||||||
|
|
||||||
|
// compose the new backup progress data, using fake number for now
|
||||||
|
String backupProgressData = newProgress;
|
||||||
|
|
||||||
|
backupContext.setProgress(newProgress);
|
||||||
|
backupManager.updateBackupStatus(backupContext);
|
||||||
|
LOG.debug("Backup progress data \"" + backupProgressData
|
||||||
|
+ "\" has been updated to hbase:backup for " + backupContext.getBackupId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Complete the overall backup.
|
||||||
|
* @param backupContext backup context
|
||||||
|
* @throws Exception exception
|
||||||
|
*/
|
||||||
|
private void completeBackup(BackupContext backupContext) throws Exception {
|
||||||
|
|
||||||
|
// set the complete timestamp of the overall backup
|
||||||
|
backupContext.setEndTs(EnvironmentEdgeManager.currentTime());
|
||||||
|
// set overall backup status: complete
|
||||||
|
backupContext.setFlag(BACKUPSTATUS.COMPLETE);
|
||||||
|
// add and store the manifest for the backup
|
||||||
|
this.addManifest(backupContext);
|
||||||
|
|
||||||
|
// after major steps done and manifest persisted, do convert if needed for incremental backup
|
||||||
|
/* in-fly convert code here, provided by future jira */
|
||||||
|
LOG.debug("in-fly convert code here, provided by future jira");
|
||||||
|
|
||||||
|
// compose the backup complete data
|
||||||
|
String backupCompleteData =
|
||||||
|
this.obtainBackupMetaDataStr(backupContext) + ",startts=" + backupContext.getStartTs()
|
||||||
|
+ ",completets=" + backupContext.getEndTs() + ",bytescopied="
|
||||||
|
+ backupContext.getTotalBytesCopied();
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("Backup " + backupContext.getBackupId() + " finished: " + backupCompleteData);
|
||||||
|
}
|
||||||
|
backupManager.updateBackupStatus(backupContext);
|
||||||
|
|
||||||
|
// when full backup is done:
|
||||||
|
// - delete HBase snapshot
|
||||||
|
// - clean up directories with prefix "exportSnapshot-", which are generated when exporting
|
||||||
|
// snapshots
|
||||||
|
if (backupContext.getType().equals(BackupRestoreConstants.BACKUP_TYPE_FULL)) {
|
||||||
|
if (!backupContext.fromExistingSnapshot()) {
|
||||||
|
this.deleteSnapshot(backupContext);
|
||||||
|
}
|
||||||
|
this.cleanupExportSnapshotLog();
|
||||||
|
} else if (backupContext.getType().equals(BackupRestoreConstants.BACKUP_TYPE_INCR)) {
|
||||||
|
this.cleanupDistCpLog();
|
||||||
|
} else {
|
||||||
|
LOG.error(" other backup types have not been implemented yet");
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.info("Backup " + backupContext.getBackupId() + " completed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get backup request meta data dir as string.
|
||||||
|
* @param backupContext backup context
|
||||||
|
* @return meta data dir
|
||||||
|
*/
|
||||||
|
private String obtainBackupMetaDataStr(BackupContext backupContext) {
|
||||||
|
StringBuffer sb = new StringBuffer();
|
||||||
|
sb.append("type=" + backupContext.getType() + ",tablelist=");
|
||||||
|
for (String table : backupContext.getTables()) {
|
||||||
|
sb.append(table + ";");
|
||||||
|
}
|
||||||
|
if (sb.lastIndexOf(";") > 0) {
|
||||||
|
sb.delete(sb.lastIndexOf(";"), sb.lastIndexOf(";") + 1);
|
||||||
|
}
|
||||||
|
sb.append(",targetRootDir=" + backupContext.getTargetRootDir());
|
||||||
|
if (backupContext.fromExistingSnapshot()) {
|
||||||
|
sb.append(",snapshot=" + backupContext.getExistingSnapshot());
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do snapshot copy.
|
||||||
|
* @param backupContext backup context
|
||||||
|
* @throws Exception exception
|
||||||
|
*/
|
||||||
|
private void snapshotCopy(BackupContext backupContext) throws Exception {
|
||||||
|
|
||||||
|
LOG.info("Snapshot copy is starting.");
|
||||||
|
|
||||||
|
// set overall backup phase: snapshot_copy
|
||||||
|
backupContext.setPhase(BACKUPPHASE.SNAPSHOTCOPY);
|
||||||
|
|
||||||
|
// avoid action if has been cancelled
|
||||||
|
if (backupContext.isCancelled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// call ExportSnapshot to copy files based on hbase snapshot for backup
|
||||||
|
// ExportSnapshot only support single snapshot export, need loop for multiple tables case
|
||||||
|
BackupCopyService copyService = BackupRestoreServiceFactory.getBackupCopyService(conf);
|
||||||
|
|
||||||
|
// number of snapshots matches number of tables
|
||||||
|
float numOfSnapshots = backupContext.getSnapshotNames().size();
|
||||||
|
|
||||||
|
LOG.debug("There are " + (int) numOfSnapshots + " snapshots to be copied.");
|
||||||
|
|
||||||
|
for (String table : backupContext.getTables()) {
|
||||||
|
// Currently we simply set the sub copy tasks by counting the table snapshot number, we can
|
||||||
|
// calculate the real files' size for the percentage in the future.
|
||||||
|
// TODO this below
|
||||||
|
// backupCopier.setSubTaskPercntgInWholeTask(1f / numOfSnapshots);
|
||||||
|
int res = 0;
|
||||||
|
String[] args = new String[4];
|
||||||
|
args[0] = "-snapshot";
|
||||||
|
args[1] = backupContext.getSnapshotName(table);
|
||||||
|
args[2] = "-copy-to";
|
||||||
|
args[3] = backupContext.getBackupStatus(table).getTargetDir();
|
||||||
|
|
||||||
|
LOG.debug("Copy snapshot " + args[1] + " to " + args[3]);
|
||||||
|
res = copyService.copy(this, conf, BackupCopyService.Type.FULL, args);
|
||||||
|
// if one snapshot export failed, do not continue for remained snapshots
|
||||||
|
if (res != 0) {
|
||||||
|
|
||||||
|
LOG.error("Exporting Snapshot " + args[1] + " failed with return code: " + res + ".");
|
||||||
|
|
||||||
|
throw new IOException("Failed of exporting snapshot " + args[1] + " to " + args[3]
|
||||||
|
+ " with reason code " + res);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.info("Snapshot copy " + args[1] + " finished.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap a SnapshotDescription for a target table.
|
||||||
|
* @param table table
|
||||||
|
* @return a SnapshotDescription especially for backup.
|
||||||
|
*/
|
||||||
|
private SnapshotDescription wrapSnapshotDescription(String table) {
|
||||||
|
// Mock a SnapshotDescription from backupContext to call SnapshotManager function,
|
||||||
|
// Name it in the format "snapshot_<timestamp>_<table>"
|
||||||
|
HBaseProtos.SnapshotDescription.Builder builder = HBaseProtos.SnapshotDescription.newBuilder();
|
||||||
|
builder.setTable(table);
|
||||||
|
TableName tableName = TableName.valueOf(table);
|
||||||
|
builder.setName("snapshot_" + Long.toString(EnvironmentEdgeManager.currentTime()) + "_"
|
||||||
|
+ tableName.getNamespaceAsString() + "_" + tableName.getQualifierAsString());
|
||||||
|
HBaseProtos.SnapshotDescription backupSnapshot = builder.build();
|
||||||
|
|
||||||
|
LOG.debug("Wrapped a SnapshotDescription " + backupSnapshot.getName()
|
||||||
|
+ " from backupContext to request snapshot for backup.");
|
||||||
|
|
||||||
|
return backupSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete HBase snapshot for backup.
|
||||||
|
* @param backupCtx backup context
|
||||||
|
* @throws Exception exception
|
||||||
|
*/
|
||||||
|
private void deleteSnapshot(BackupContext backupCtx) throws IOException {
|
||||||
|
|
||||||
|
LOG.debug("Trying to delete snapshot for full backup.");
|
||||||
|
Connection conn = null;
|
||||||
|
Admin admin = null;
|
||||||
|
try {
|
||||||
|
conn = ConnectionFactory.createConnection(conf);
|
||||||
|
admin = conn.getAdmin();
|
||||||
|
for (String snapshotName : backupCtx.getSnapshotNames()) {
|
||||||
|
if (snapshotName == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
LOG.debug("Trying to delete snapshot: " + snapshotName);
|
||||||
|
admin.deleteSnapshot(snapshotName);
|
||||||
|
LOG.debug("Deleting the snapshot " + snapshotName + " for backup "
|
||||||
|
+ backupCtx.getBackupId() + " succeeded.");
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (admin != null) {
|
||||||
|
admin.close();
|
||||||
|
}
|
||||||
|
if (conn != null) {
|
||||||
|
conn.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up directories with prefix "exportSnapshot-", which are generated when exporting
|
||||||
|
* snapshots.
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
private void cleanupExportSnapshotLog() throws IOException {
|
||||||
|
FileSystem fs = FSUtils.getCurrentFileSystem(conf);
|
||||||
|
Path stagingDir =
|
||||||
|
new Path(conf.get(BackupRestoreConstants.CONF_STAGING_ROOT, fs.getWorkingDirectory()
|
||||||
|
.toString()));
|
||||||
|
FileStatus[] files = FSUtils.listStatus(fs, stagingDir);
|
||||||
|
if (files == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (FileStatus file : files) {
|
||||||
|
if (file.getPath().getName().startsWith("exportSnapshot-")) {
|
||||||
|
LOG.debug("Delete log files of exporting snapshot: " + file.getPath().getName());
|
||||||
|
if (FSUtils.delete(fs, file.getPath(), true) == false) {
|
||||||
|
LOG.warn("Can not delete " + file.getPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up directories with prefix "_distcp_logs-", which are generated when DistCp copying
|
||||||
|
* hlogs.
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
private void cleanupDistCpLog() throws IOException {
|
||||||
|
Path rootPath = new Path(backupContext.getHLogTargetDir()).getParent();
|
||||||
|
FileSystem fs = FileSystem.get(rootPath.toUri(), conf);
|
||||||
|
FileStatus[] files = FSUtils.listStatus(fs, rootPath);
|
||||||
|
if (files == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (FileStatus file : files) {
|
||||||
|
if (file.getPath().getName().startsWith("_distcp_logs")) {
|
||||||
|
LOG.debug("Delete log files of DistCp: " + file.getPath().getName());
|
||||||
|
FSUtils.delete(fs, file.getPath(), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up the uncompleted data at target directory if the ongoing backup has already entered the
|
||||||
|
* copy phase.
|
||||||
|
*/
|
||||||
|
private void cleanupTargetDir() {
|
||||||
|
try {
|
||||||
|
// clean up the uncompleted data at target directory if the ongoing backup has already entered
|
||||||
|
// the copy phase
|
||||||
|
LOG.debug("Trying to cleanup up target dir. Current backup phase: "
|
||||||
|
+ backupContext.getPhase());
|
||||||
|
if (backupContext.getPhase().equals(BACKUPPHASE.SNAPSHOTCOPY)
|
||||||
|
|| backupContext.getPhase().equals(BACKUPPHASE.INCREMENTAL_COPY)
|
||||||
|
|| backupContext.getPhase().equals(BACKUPPHASE.STORE_MANIFEST)) {
|
||||||
|
FileSystem outputFs =
|
||||||
|
FileSystem.get(new Path(backupContext.getTargetRootDir()).toUri(), conf);
|
||||||
|
|
||||||
|
// now treat one backup as a transaction, clean up data that has been partially copied at
|
||||||
|
// table level
|
||||||
|
for (String table : backupContext.getTables()) {
|
||||||
|
Path targetDirPath =
|
||||||
|
new Path(HBackupFileSystem.getTableBackupDir(backupContext.getTargetRootDir(),
|
||||||
|
backupContext.getBackupId(), table));
|
||||||
|
if (outputFs.delete(targetDirPath, true)) {
|
||||||
|
LOG.info("Cleaning up uncompleted backup data at " + targetDirPath.toString()
|
||||||
|
+ " done.");
|
||||||
|
} else {
|
||||||
|
LOG.info("No data has been copied to " + targetDirPath.toString() + ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
Path tableDir = targetDirPath.getParent();
|
||||||
|
FileStatus[] backups = FSUtils.listStatus(outputFs, tableDir);
|
||||||
|
if (backups == null || backups.length == 0) {
|
||||||
|
outputFs.delete(tableDir, true);
|
||||||
|
LOG.debug(tableDir.toString() + " is empty, remove it.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException e1) {
|
||||||
|
LOG.error("Cleaning up uncompleted backup data of " + backupContext.getBackupId() + " at "
|
||||||
|
+ backupContext.getTargetRootDir() + " failed due to " + e1.getMessage() + ".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add manifest for the current backup. The manifest is stored
|
||||||
|
* within the table backup directory.
|
||||||
|
* @param backupContext The current backup context
|
||||||
|
* @throws IOException exception
|
||||||
|
* @throws BackupException exception
|
||||||
|
*/
|
||||||
|
private void addManifest(BackupContext backupContext) throws IOException, BackupException {
|
||||||
|
// set the overall backup phase : store manifest
|
||||||
|
backupContext.setPhase(BACKUPPHASE.STORE_MANIFEST);
|
||||||
|
|
||||||
|
// avoid action if has been cancelled
|
||||||
|
if (backupContext.isCancelled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BackupManifest manifest;
|
||||||
|
boolean fromExistingSnapshot = false; // to be implemented in future jira
|
||||||
|
|
||||||
|
// Since we have each table's backup in its own directory structure,
|
||||||
|
// we'll store its manifest with the table directory.
|
||||||
|
for (String table : backupContext.getTables()) {
|
||||||
|
manifest = new BackupManifest(backupContext, table);
|
||||||
|
if (fromExistingSnapshot) {
|
||||||
|
// mark backing up from existing snapshot in manifest, so that later, dependency analysis
|
||||||
|
// can skip this backup image
|
||||||
|
LOG.debug("backup using existing snapshot will be supported in future jira");
|
||||||
|
} else {
|
||||||
|
ArrayList<BackupImage> ancestors = this.backupManager.getAncestors(backupContext, table);
|
||||||
|
for (BackupImage image : ancestors) {
|
||||||
|
manifest.addDependentImage(image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (backupContext.getType().equals(BackupRestoreConstants.BACKUP_TYPE_INCR)) {
|
||||||
|
// We'll store the log timestamps for this table only in its manifest.
|
||||||
|
HashMap<String, HashMap<String, String>> tableTimestampMap =
|
||||||
|
new HashMap<String, HashMap<String, String>>();
|
||||||
|
tableTimestampMap.put(table, backupContext.getIncrTimestampMap().get(table));
|
||||||
|
manifest.setIncrTimestampMap(tableTimestampMap);
|
||||||
|
}
|
||||||
|
manifest.store(conf);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For incremental backup, we store a overall manifest in
|
||||||
|
// <backup-root-dir>/WALs/<backup-id>
|
||||||
|
// This is used when created the next incremental backup
|
||||||
|
if (backupContext.getType().equals(BackupRestoreConstants.BACKUP_TYPE_INCR)) {
|
||||||
|
manifest = new BackupManifest(backupContext);
|
||||||
|
// set the table region server start and end timestamps for incremental backup
|
||||||
|
manifest.setIncrTimestampMap(backupContext.getIncrTimestampMap());
|
||||||
|
ArrayList<BackupImage> ancestors = this.backupManager.getAncestors(backupContext);
|
||||||
|
for (BackupImage image : ancestors) {
|
||||||
|
manifest.addDependentImage(image);
|
||||||
|
}
|
||||||
|
manifest.store(conf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do incremental copy.
|
||||||
|
* @param backupContext backup context
|
||||||
|
*/
|
||||||
|
private void incrementalCopy(BackupContext backupContext) throws Exception {
|
||||||
|
|
||||||
|
LOG.info("Incremental copy is starting.");
|
||||||
|
|
||||||
|
// set overall backup phase: incremental_copy
|
||||||
|
backupContext.setPhase(BACKUPPHASE.INCREMENTAL_COPY);
|
||||||
|
|
||||||
|
// avoid action if has been cancelled
|
||||||
|
if (backupContext.isCancelled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get incremental backup file list and prepare parms for DistCp
|
||||||
|
List<String> incrBackupFileList = backupContext.getIncrBackupFileList();
|
||||||
|
String[] strArr = incrBackupFileList.toArray(new String[incrBackupFileList.size() + 1]);
|
||||||
|
strArr[strArr.length - 1] = backupContext.getHLogTargetDir();
|
||||||
|
|
||||||
|
BackupCopyService copyService = BackupRestoreServiceFactory.getBackupCopyService(conf);
|
||||||
|
int res = copyService.copy(this, conf, BackupCopyService.Type.INCREMENTAL, strArr);
|
||||||
|
|
||||||
|
if (res != 0) {
|
||||||
|
LOG.error("Copy incremental log files failed with return code: " + res + ".");
|
||||||
|
throw new IOException("Failed of Hadoop Distributed Copy from " + incrBackupFileList + " to "
|
||||||
|
+ backupContext.getHLogTargetDir());
|
||||||
|
}
|
||||||
|
LOG.info("Incremental copy from " + incrBackupFileList + " to "
|
||||||
|
+ backupContext.getHLogTargetDir() + " finished.");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getMessage(Exception e) {
|
||||||
|
String msg = e.getMessage();
|
||||||
|
if (msg == null || msg.equals("")) {
|
||||||
|
msg = e.getClass().getName();
|
||||||
|
}
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,488 @@
|
||||||
|
/**
|
||||||
|
* 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 com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.CancellationException;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
import org.apache.hadoop.fs.Path;
|
||||||
|
import org.apache.hadoop.hbase.HConstants;
|
||||||
|
import org.apache.hadoop.hbase.HTableDescriptor;
|
||||||
|
import org.apache.hadoop.hbase.backup.BackupHandler.BACKUPSTATUS;
|
||||||
|
import org.apache.hadoop.hbase.backup.BackupManifest.BackupImage;
|
||||||
|
import org.apache.hadoop.hbase.backup.BackupUtil.BackupCompleteData;
|
||||||
|
import org.apache.hadoop.hbase.backup.master.BackupLogCleaner;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceAudience;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceStability;
|
||||||
|
import org.apache.hadoop.hbase.client.Connection;
|
||||||
|
import org.apache.hadoop.hbase.client.ConnectionFactory;
|
||||||
|
import org.apache.hadoop.hbase.client.HBaseAdmin;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles backup requests on server-side, creates backup context records in hbase:backup
|
||||||
|
* to keep track backup. The timestamps kept in hbase:backup table will be used for future
|
||||||
|
* incremental backup. Creates BackupContext and DispatchRequest.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@InterfaceAudience.Private
|
||||||
|
@InterfaceStability.Evolving
|
||||||
|
public class BackupManager {
|
||||||
|
private static final Log LOG = LogFactory.getLog(BackupManager.class);
|
||||||
|
|
||||||
|
private Configuration conf = null;
|
||||||
|
private BackupContext backupContext = null;
|
||||||
|
private ExecutorService pool = null;
|
||||||
|
|
||||||
|
private boolean backupComplete = false;
|
||||||
|
|
||||||
|
private BackupSystemTable systemTable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backup manager constructor.
|
||||||
|
* @param conf configuration
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public BackupManager(Configuration conf) throws IOException {
|
||||||
|
if (!conf.getBoolean(HConstants.BACKUP_ENABLE_KEY, HConstants.BACKUP_ENABLE_DEFAULT)) {
|
||||||
|
throw new BackupException("HBase backup is not enabled. Check your " +
|
||||||
|
HConstants.BACKUP_ENABLE_KEY + " setting.");
|
||||||
|
}
|
||||||
|
this.conf = conf;
|
||||||
|
this.systemTable = BackupSystemTable.getTable(conf);
|
||||||
|
Runtime.getRuntime().addShutdownHook(new ExitHandler());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method modifies the master's configuration in order to inject backup-related features
|
||||||
|
* @param conf configuration
|
||||||
|
*/
|
||||||
|
public static void decorateMasterConfiguration(Configuration conf) {
|
||||||
|
if (!isBackupEnabled(conf)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String plugins = conf.get(HConstants.HBASE_MASTER_LOGCLEANER_PLUGINS);
|
||||||
|
String cleanerClass = BackupLogCleaner.class.getCanonicalName();
|
||||||
|
if (!plugins.contains(cleanerClass)) {
|
||||||
|
conf.set(HConstants.HBASE_MASTER_LOGCLEANER_PLUGINS, plugins + "," + cleanerClass);
|
||||||
|
}
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("Added log cleaner: " + cleanerClass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isBackupEnabled(Configuration conf) {
|
||||||
|
return conf.getBoolean(HConstants.BACKUP_ENABLE_KEY, HConstants.BACKUP_ENABLE_DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ExitHandler extends Thread {
|
||||||
|
public ExitHandler() {
|
||||||
|
super("Backup Manager Exit Handler");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
if (backupContext != null && !backupComplete) {
|
||||||
|
|
||||||
|
// program exit and backup is not complete, then mark as cancelled to avoid submitted backup
|
||||||
|
// handler's taking further action
|
||||||
|
backupContext.markCancel();
|
||||||
|
|
||||||
|
LOG.debug("Backup is cancelled due to force program exiting.");
|
||||||
|
try {
|
||||||
|
cancelBackup(backupContext.getBackupId());
|
||||||
|
} catch (Exception e) {
|
||||||
|
String msg = e.getMessage();
|
||||||
|
if (msg == null || msg.equals("")) {
|
||||||
|
msg = e.getClass().getName();
|
||||||
|
}
|
||||||
|
LOG.error("Failed to cancel backup " + backupContext.getBackupId() + " due to " + msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel the ongoing backup via backup id.
|
||||||
|
* @param backupId The id of the ongoing backup to be cancelled
|
||||||
|
* @throws Exception exception
|
||||||
|
*/
|
||||||
|
private void cancelBackup(String backupId) throws Exception {
|
||||||
|
// TODO: will be implemented in Phase 2: HBASE-14125
|
||||||
|
LOG.debug("Try to cancel the backup " + backupId + ". the feature is NOT implemented yet");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop all the work of backup.
|
||||||
|
*/
|
||||||
|
public void exit() {
|
||||||
|
|
||||||
|
// currently, we shutdown now for all ongoing back handlers, we may need to do something like
|
||||||
|
// record the failed list somewhere later
|
||||||
|
if (this.pool != null) {
|
||||||
|
this.pool.shutdownNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a BackupContext based on input backup request.
|
||||||
|
* @param backupId backup id
|
||||||
|
* @param type type
|
||||||
|
* @param tablelist table list
|
||||||
|
* @param targetRootDir root dir
|
||||||
|
* @param snapshot snapshot name
|
||||||
|
* @return BackupContext context
|
||||||
|
* @throws BackupException exception
|
||||||
|
*/
|
||||||
|
protected BackupContext createBackupContext(String backupId, String type, String tablelist,
|
||||||
|
String targetRootDir, String snapshot) throws BackupException {
|
||||||
|
|
||||||
|
if (targetRootDir == null) {
|
||||||
|
throw new BackupException("Wrong backup request parameter: target backup root directory");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type.equals(BackupRestoreConstants.BACKUP_TYPE_FULL) && tablelist == null) {
|
||||||
|
// If table list is null for full backup, which means backup all tables. Then fill the table
|
||||||
|
// list with all user tables from meta. It no table available, throw the request exception.
|
||||||
|
|
||||||
|
HTableDescriptor[] htds = null;
|
||||||
|
try (Connection conn = ConnectionFactory.createConnection(conf);
|
||||||
|
HBaseAdmin hbadmin = (HBaseAdmin) conn.getAdmin()) {
|
||||||
|
|
||||||
|
htds = hbadmin.listTables();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new BackupException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (htds == null) {
|
||||||
|
throw new BackupException("No table exists for full backup of all tables.");
|
||||||
|
} else {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (HTableDescriptor hTableDescriptor : htds) {
|
||||||
|
sb.append(hTableDescriptor.getNameAsString()
|
||||||
|
+ BackupRestoreConstants.TABLENAME_DELIMITER_IN_COMMAND);
|
||||||
|
}
|
||||||
|
sb.deleteCharAt(sb.lastIndexOf(BackupRestoreConstants.TABLENAME_DELIMITER_IN_COMMAND));
|
||||||
|
tablelist = sb.toString();
|
||||||
|
|
||||||
|
LOG.info("Full backup all the tables available in the cluster: " + tablelist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// there are one or more tables in the table list
|
||||||
|
return new BackupContext(backupId, type,
|
||||||
|
tablelist.split(BackupRestoreConstants.TABLENAME_DELIMITER_IN_COMMAND), targetRootDir,
|
||||||
|
snapshot);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if any ongoing backup. Currently, we only reply on checking status in hbase:backup. We
|
||||||
|
* need to consider to handle the case of orphan records in the future. Otherwise, all the coming
|
||||||
|
* request will fail.
|
||||||
|
* @return the ongoing backup id if on going backup exists, otherwise null
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
private String getOngoingBackupId() throws IOException {
|
||||||
|
|
||||||
|
ArrayList<BackupContext> sessions = systemTable.getBackupContexts(BACKUPSTATUS.ONGOING);
|
||||||
|
if (sessions.size() == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return sessions.get(0).getBackupId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the backup manager service.
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public void initialize() throws IOException {
|
||||||
|
String ongoingBackupId = this.getOngoingBackupId();
|
||||||
|
if (ongoingBackupId != null) {
|
||||||
|
LOG.info("There is a ongoing backup " + ongoingBackupId
|
||||||
|
+ ". Can not launch new backup until no ongoing backup remains.");
|
||||||
|
throw new BackupException("There is ongoing backup.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize thread pools
|
||||||
|
int nrThreads = this.conf.getInt("hbase.backup.threads.max", 1);
|
||||||
|
ThreadFactoryBuilder builder = new ThreadFactoryBuilder();
|
||||||
|
builder.setNameFormat("BackupHandler-%1$d");
|
||||||
|
this.pool =
|
||||||
|
new ThreadPoolExecutor(nrThreads, nrThreads, 60, TimeUnit.SECONDS,
|
||||||
|
new LinkedBlockingQueue<Runnable>(), builder.build());
|
||||||
|
((ThreadPoolExecutor) pool).allowCoreThreadTimeOut(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatch and handle a backup request.
|
||||||
|
* @param backupContext backup context
|
||||||
|
* @throws BackupException exception
|
||||||
|
*/
|
||||||
|
public void dispatchRequest(BackupContext backupContext) throws BackupException {
|
||||||
|
|
||||||
|
this.backupContext = backupContext;
|
||||||
|
|
||||||
|
LOG.info("Got a backup request: " + "Type: " + backupContext.getType() + "; Tables: "
|
||||||
|
+ backupContext.getTableListAsString() + "; TargetRootDir: "
|
||||||
|
+ backupContext.getTargetRootDir());
|
||||||
|
|
||||||
|
// dispatch the request to a backup handler and put it handler map
|
||||||
|
|
||||||
|
BackupHandler handler = new BackupHandler(this.backupContext, this, conf);
|
||||||
|
Future<Object> future = this.pool.submit(handler);
|
||||||
|
// wait for the execution to complete
|
||||||
|
try {
|
||||||
|
future.get();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new BackupException(e);
|
||||||
|
} catch (CancellationException e) {
|
||||||
|
throw new BackupException(e);
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
throw new BackupException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// mark the backup complete for exit handler's processing
|
||||||
|
backupComplete = true;
|
||||||
|
|
||||||
|
LOG.info("Backup request " + backupContext.getBackupId() + " has been executed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get direct ancestors of the current backup.
|
||||||
|
* @param backupCtx The backup context for the current backup
|
||||||
|
* @return The ancestors for the current backup
|
||||||
|
* @throws IOException exception
|
||||||
|
* @throws BackupException exception
|
||||||
|
*/
|
||||||
|
protected ArrayList<BackupImage> getAncestors(BackupContext backupCtx) throws IOException,
|
||||||
|
BackupException {
|
||||||
|
LOG.debug("Getting the direct ancestors of the current backup ...");
|
||||||
|
|
||||||
|
ArrayList<BackupImage> ancestors = new ArrayList<BackupImage>();
|
||||||
|
|
||||||
|
// full backup does not have ancestor
|
||||||
|
if (backupCtx.getType().equals(BackupRestoreConstants.BACKUP_TYPE_FULL)) {
|
||||||
|
LOG.debug("Current backup is a full backup, no direct ancestor for it.");
|
||||||
|
return ancestors;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get all backup history list in descending order
|
||||||
|
|
||||||
|
ArrayList<BackupCompleteData> allHistoryList = getBackupHistory();
|
||||||
|
for (BackupCompleteData backup : allHistoryList) {
|
||||||
|
BackupImage image =
|
||||||
|
new BackupImage(backup.getBackupToken(), backup.getType(), backup.getBackupRootPath(),
|
||||||
|
backup.getTableList(), Long.parseLong(backup.getStartTime()), Long.parseLong(backup
|
||||||
|
.getEndTime()));
|
||||||
|
|
||||||
|
// add the full backup image as an ancestor until the last incremental backup
|
||||||
|
if (backup.getType().equals(BackupRestoreConstants.BACKUP_TYPE_FULL)) {
|
||||||
|
|
||||||
|
// backup image from existing snapshot does not involve in dependency
|
||||||
|
if (backup.fromExistingSnapshot()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// check the backup image coverage, if previous image could be covered by the newer ones,
|
||||||
|
// then no need to add
|
||||||
|
if (!BackupManifest.canCoverImage(ancestors, image)) {
|
||||||
|
ancestors.add(image);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// found last incremental backup, if previously added full backup ancestor images can cover
|
||||||
|
// it, then this incremental ancestor is not the dependent of the current incremental
|
||||||
|
// backup, that is to say, this is the backup scope boundary of current table set.
|
||||||
|
// Otherwise, this incremental backup ancestor is the dependent ancestor of the ongoing
|
||||||
|
// incremental backup
|
||||||
|
if (BackupManifest.canCoverImage(ancestors, image)) {
|
||||||
|
LOG.debug("Met the backup boundary of the current table set. "
|
||||||
|
+ "The root full backup images for the current backup scope:");
|
||||||
|
for (BackupImage image1 : ancestors) {
|
||||||
|
LOG.debug(" BackupId: " + image1.getBackupId() + ", Backup directory: "
|
||||||
|
+ image1.getRootDir());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Path logBackupPath =
|
||||||
|
HBackupFileSystem.getLogBackupPath(backup.getBackupRootPath(),
|
||||||
|
backup.getBackupToken());
|
||||||
|
LOG.debug("Current backup has an incremental backup ancestor, "
|
||||||
|
+ "touching its image manifest in " + logBackupPath.toString()
|
||||||
|
+ " to construct the dependency.");
|
||||||
|
|
||||||
|
BackupManifest lastIncrImgManifest = new BackupManifest(conf, logBackupPath);
|
||||||
|
BackupImage lastIncrImage = lastIncrImgManifest.getBackupImage();
|
||||||
|
ancestors.add(lastIncrImage);
|
||||||
|
|
||||||
|
LOG.debug("Last dependent incremental backup image information:");
|
||||||
|
LOG.debug(" Token: " + lastIncrImage.getBackupId());
|
||||||
|
LOG.debug(" Backup directory: " + lastIncrImage.getRootDir());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG.debug("Got " + ancestors.size() + " ancestors for the current backup.");
|
||||||
|
return ancestors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the direct ancestors of this backup for one table involved.
|
||||||
|
* @param backupContext backup context
|
||||||
|
* @param table table
|
||||||
|
* @return backupImages on the dependency list
|
||||||
|
* @throws BackupException exception
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
protected ArrayList<BackupImage> getAncestors(BackupContext backupContext, String table)
|
||||||
|
throws BackupException, IOException {
|
||||||
|
ArrayList<BackupImage> ancestors = getAncestors(backupContext);
|
||||||
|
ArrayList<BackupImage> tableAncestors = new ArrayList<BackupImage>();
|
||||||
|
for (BackupImage image : ancestors) {
|
||||||
|
if (image.hasTable(table)) {
|
||||||
|
tableAncestors.add(image);
|
||||||
|
if (image.getType().equals(BackupRestoreConstants.BACKUP_TYPE_FULL)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tableAncestors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* hbase:backup operations
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates status (state) of a backup session in a persistent store
|
||||||
|
* @param context context
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public void updateBackupStatus(BackupContext context) throws IOException {
|
||||||
|
systemTable.updateBackupStatus(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the last backup start code (timestamp) of last successful backup. Will return null
|
||||||
|
* if there is no startcode stored in hbase:backup or the value is of length 0. These two
|
||||||
|
* cases indicate there is no successful backup completed so far.
|
||||||
|
* @return the timestamp of a last successful backup
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public String readBackupStartCode() throws IOException {
|
||||||
|
return systemTable.readBackupStartCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the start code (timestamp) to hbase:backup. If passed in null, then write 0 byte.
|
||||||
|
* @param startCode start code
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public void writeBackupStartCode(String startCode) throws IOException {
|
||||||
|
systemTable.writeBackupStartCode(startCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the RS log information after the last log roll from hbase:backup.
|
||||||
|
* @return RS log info
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public HashMap<String, String> readRegionServerLastLogRollResult() throws IOException {
|
||||||
|
return systemTable.readRegionServerLastLogRollResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all completed backup information (in desc order by time)
|
||||||
|
* @return history info of BackupCompleteData
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public ArrayList<BackupCompleteData> getBackupHistory() throws IOException {
|
||||||
|
return systemTable.getBackupHistory();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the current timestamps for each regionserver to hbase:backup after a successful full or
|
||||||
|
* incremental backup. Each table may have a different set of log timestamps. The saved timestamp
|
||||||
|
* is of the last log file that was backed up already.
|
||||||
|
* @param tables tables
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public void writeRegionServerLogTimestamp(Set<String> tables,
|
||||||
|
HashMap<String, String> newTimestamps) throws IOException {
|
||||||
|
systemTable.writeRegionServerLogTimestamp(tables, newTimestamps);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the timestamp for each region server log after the last successful backup. Each table has
|
||||||
|
* its own set of the timestamps. The info is stored for each table as a concatinated string on ZK
|
||||||
|
* under //hbase//backup//incr//tablelogtimestamp//table_name
|
||||||
|
* @return the timestamp for each region server. key: tableName value:
|
||||||
|
* RegionServer,PreviousTimeStamp
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public HashMap<String, HashMap<String, String>> readLogTimestampMap() throws IOException {
|
||||||
|
return systemTable.readLogTimestampMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the current tables covered by incremental backup.
|
||||||
|
* @return set of tableNames
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public Set<String> getIncrementalBackupTableSet() throws IOException {
|
||||||
|
return systemTable.getIncrementalBackupTableSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds set of tables to overall incremental backup table set
|
||||||
|
* @param tables tables
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public void addIncrementalBackupTableSet(Set<String> tables) throws IOException {
|
||||||
|
systemTable.addIncrementalBackupTableSet(tables);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves list of WAL files after incremental backup operation. These files will be stored until
|
||||||
|
* TTL expiration and are used by Backup Log Cleaner plugin to determine which WAL files can be
|
||||||
|
* safely purged.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public void recordWALFiles(List<String> files) throws IOException {
|
||||||
|
systemTable.addWALFiles(files, backupContext.getBackupId());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,814 @@
|
||||||
|
/**
|
||||||
|
* 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.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
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.HConstants;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceAudience;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceStability;
|
||||||
|
import org.apache.hadoop.hbase.util.FSUtils;
|
||||||
|
import org.codehaus.jackson.JsonGenerationException;
|
||||||
|
import org.codehaus.jackson.JsonParseException;
|
||||||
|
import org.codehaus.jackson.map.JsonMappingException;
|
||||||
|
import org.codehaus.jackson.map.ObjectMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backup manifest Contains all the meta data of a backup image. The manifest info will be bundled
|
||||||
|
* as manifest file together with data. So that each backup image will contain all the info needed
|
||||||
|
* for restore.
|
||||||
|
*/
|
||||||
|
@InterfaceAudience.Private
|
||||||
|
@InterfaceStability.Evolving
|
||||||
|
public class BackupManifest {
|
||||||
|
|
||||||
|
private static final Log LOG = LogFactory.getLog(BackupManifest.class);
|
||||||
|
|
||||||
|
// manifest file name
|
||||||
|
public static final String FILE_NAME = ".backup.manifest";
|
||||||
|
|
||||||
|
// manifest file version, current is 1.0
|
||||||
|
public static final String MANIFEST_VERSION = "1.0";
|
||||||
|
|
||||||
|
// tags of fields for manifest file
|
||||||
|
public static final String TAG_VERSION = "Manifest-Version";
|
||||||
|
public static final String TAG_BACKUPID = "Backup-Id";
|
||||||
|
public static final String TAG_BACKUPTYPE = "Backup-Type";
|
||||||
|
public static final String TAG_TABLESET = "Table-Set";
|
||||||
|
public static final String TAG_STARTTS = "Start-Timestamp";
|
||||||
|
public static final String TAG_COMPLETETS = "Complete-Timestamp";
|
||||||
|
public static final String TAG_TABLEBYTES = "Total-Table-Bytes";
|
||||||
|
public static final String TAG_LOGBYTES = "Total-Log-Bytes";
|
||||||
|
public static final String TAG_INCRTIMERANGE = "Incremental-Time-Range";
|
||||||
|
public static final String TAG_DEPENDENCY = "Dependency";
|
||||||
|
public static final String TAG_IMAGESTATE = "Image-State";
|
||||||
|
public static final String TAG_COMPACTION = "Compaction";
|
||||||
|
|
||||||
|
public static final String ERROR_DEPENDENCY = "DEPENDENCY_ERROR";
|
||||||
|
|
||||||
|
public static final int DELETE_SUCCESS = 0;
|
||||||
|
public static final int DELETE_FAILED = -1;
|
||||||
|
|
||||||
|
// currently only one state, will have CONVERTED, and MERGED in future JIRA
|
||||||
|
public static final String IMAGE_STATE_ORIG = "ORIGINAL";
|
||||||
|
public static final String IMAGE_STATE_CONVERT = "CONVERTED";
|
||||||
|
public static final String IMAGE_STATE_MERGE = "MERGED";
|
||||||
|
public static final String IMAGE_STATE_CONVERT_MERGE = "CONVERTED,MERGED";
|
||||||
|
|
||||||
|
// backup image, the dependency graph is made up by series of backup images
|
||||||
|
|
||||||
|
public static class BackupImage implements Comparable<BackupImage> {
|
||||||
|
|
||||||
|
private String backupId;
|
||||||
|
private String type;
|
||||||
|
private String rootDir;
|
||||||
|
private String tableSet;
|
||||||
|
private long startTs;
|
||||||
|
private long completeTs;
|
||||||
|
private ArrayList<BackupImage> ancestors;
|
||||||
|
|
||||||
|
public BackupImage() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BackupImage(String backupId, String type, String rootDir, String tableSet, long startTs,
|
||||||
|
long completeTs) {
|
||||||
|
this.backupId = backupId;
|
||||||
|
this.type = type;
|
||||||
|
this.rootDir = rootDir;
|
||||||
|
this.tableSet = tableSet;
|
||||||
|
this.startTs = startTs;
|
||||||
|
this.completeTs = completeTs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBackupId() {
|
||||||
|
return backupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBackupId(String backupId) {
|
||||||
|
this.backupId = backupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRootDir() {
|
||||||
|
return rootDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRootDir(String rootDir) {
|
||||||
|
this.rootDir = rootDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTableSet() {
|
||||||
|
return tableSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTableSet(String tableSet) {
|
||||||
|
this.tableSet = tableSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getStartTs() {
|
||||||
|
return startTs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStartTs(long startTs) {
|
||||||
|
this.startTs = startTs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCompleteTs() {
|
||||||
|
return completeTs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCompleteTs(long completeTs) {
|
||||||
|
this.completeTs = completeTs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayList<BackupImage> getAncestors() {
|
||||||
|
if (this.ancestors == null) {
|
||||||
|
this.ancestors = new ArrayList<BackupImage>();
|
||||||
|
}
|
||||||
|
return this.ancestors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addAncestor(BackupImage backupImage) {
|
||||||
|
this.getAncestors().add(backupImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasAncestor(String token) {
|
||||||
|
for (BackupImage image : this.getAncestors()) {
|
||||||
|
if (image.getBackupId().equals(token)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasTable(String table) {
|
||||||
|
String[] tables = this.getTableSet().split(";");
|
||||||
|
for (String t : tables) {
|
||||||
|
if (t.equals(table)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(BackupImage other) {
|
||||||
|
String thisBackupId = this.getBackupId();
|
||||||
|
String otherBackupId = other.getBackupId();
|
||||||
|
Long thisTS = new Long(thisBackupId.substring(thisBackupId.lastIndexOf("_") + 1));
|
||||||
|
Long otherTS = new Long(otherBackupId.substring(otherBackupId.lastIndexOf("_") + 1));
|
||||||
|
return thisTS.compareTo(otherTS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// manifest version
|
||||||
|
private String version = MANIFEST_VERSION;
|
||||||
|
|
||||||
|
// hadoop hbase configuration
|
||||||
|
protected Configuration config = null;
|
||||||
|
|
||||||
|
// backup root directory
|
||||||
|
private String rootDir = null;
|
||||||
|
|
||||||
|
// backup image directory
|
||||||
|
private String tableBackupDir = null;
|
||||||
|
|
||||||
|
// backup log directory if this is an incremental backup
|
||||||
|
private String logBackupDir = null;
|
||||||
|
|
||||||
|
// backup token
|
||||||
|
private String token;
|
||||||
|
|
||||||
|
// backup type, full or incremental
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
// the table set for the backup
|
||||||
|
private ArrayList<String> tableSet;
|
||||||
|
|
||||||
|
// actual start timestamp of the backup process
|
||||||
|
private long startTs;
|
||||||
|
|
||||||
|
// actual complete timestamp of the backup process
|
||||||
|
private long completeTs;
|
||||||
|
|
||||||
|
// total bytes for table backup image
|
||||||
|
private long tableBytes;
|
||||||
|
|
||||||
|
// total bytes for the backed-up logs for incremental backup
|
||||||
|
private long logBytes;
|
||||||
|
|
||||||
|
// the region server timestamp for tables:
|
||||||
|
// <table, <rs, timestamp>>
|
||||||
|
private Map<String, HashMap<String, String>> incrTimeRanges;
|
||||||
|
|
||||||
|
// dependency of this backup, including all the dependent images to do PIT recovery
|
||||||
|
private Map<String, BackupImage> dependency;
|
||||||
|
|
||||||
|
// the state of backup image
|
||||||
|
private String imageState;
|
||||||
|
|
||||||
|
// the indicator of the image compaction
|
||||||
|
private boolean isCompacted = false;
|
||||||
|
|
||||||
|
// the merge chain of the original backups, null if not a merged backup
|
||||||
|
private LinkedList<String> mergeChain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct manifest for a ongoing backup.
|
||||||
|
* @param backupCtx The ongoing backup context
|
||||||
|
*/
|
||||||
|
public BackupManifest(BackupContext backupCtx) {
|
||||||
|
this.token = backupCtx.getBackupId();
|
||||||
|
this.type = backupCtx.getType();
|
||||||
|
this.rootDir = backupCtx.getTargetRootDir();
|
||||||
|
if (this.type.equals(BackupRestoreConstants.BACKUP_TYPE_INCR)) {
|
||||||
|
this.logBackupDir = backupCtx.getHLogTargetDir();
|
||||||
|
this.logBytes = backupCtx.getTotalBytesCopied();
|
||||||
|
}
|
||||||
|
this.startTs = backupCtx.getStartTs();
|
||||||
|
this.completeTs = backupCtx.getEndTs();
|
||||||
|
this.loadTableSet(backupCtx.getTableListAsString());
|
||||||
|
this.setImageOriginal();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a table level manifest for a backup of the named table.
|
||||||
|
* @param backupCtx The ongoing backup context
|
||||||
|
*/
|
||||||
|
public BackupManifest(BackupContext backupCtx, String table) {
|
||||||
|
this.token = backupCtx.getBackupId();
|
||||||
|
this.type = backupCtx.getType();
|
||||||
|
this.rootDir = backupCtx.getTargetRootDir();
|
||||||
|
this.tableBackupDir = backupCtx.getBackupStatus(table).getTargetDir();
|
||||||
|
if (this.type.equals(BackupRestoreConstants.BACKUP_TYPE_INCR)) {
|
||||||
|
this.logBackupDir = backupCtx.getHLogTargetDir();
|
||||||
|
this.logBytes = backupCtx.getTotalBytesCopied();
|
||||||
|
}
|
||||||
|
this.startTs = backupCtx.getStartTs();
|
||||||
|
this.completeTs = backupCtx.getEndTs();
|
||||||
|
this.loadTableSet(table);
|
||||||
|
this.setImageOriginal();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct manifest from a backup directory.
|
||||||
|
* @param conf configuration
|
||||||
|
* @param backupPath backup path
|
||||||
|
* @throws BackupException exception
|
||||||
|
*/
|
||||||
|
public BackupManifest(Configuration conf, Path backupPath) throws BackupException {
|
||||||
|
|
||||||
|
LOG.debug("Loading manifest from: " + backupPath.toString());
|
||||||
|
// The input backupDir may not exactly be the backup table dir.
|
||||||
|
// It could be the backup log dir where there is also a manifest file stored.
|
||||||
|
// This variable's purpose is to keep the correct and original location so
|
||||||
|
// that we can store/persist it.
|
||||||
|
this.tableBackupDir = backupPath.toString();
|
||||||
|
this.config = conf;
|
||||||
|
try {
|
||||||
|
|
||||||
|
FileSystem fs = backupPath.getFileSystem(conf);
|
||||||
|
FileStatus[] subFiles = FSUtils.listStatus(fs, backupPath);
|
||||||
|
if (subFiles == null) {
|
||||||
|
String errorMsg = backupPath.toString() + " does not exist";
|
||||||
|
LOG.error(errorMsg);
|
||||||
|
throw new IOException(errorMsg);
|
||||||
|
}
|
||||||
|
for (FileStatus subFile : subFiles) {
|
||||||
|
if (subFile.getPath().getName().equals(FILE_NAME)) {
|
||||||
|
|
||||||
|
// load and set manifest field from file content
|
||||||
|
FSDataInputStream in = fs.open(subFile.getPath());
|
||||||
|
Properties props = new Properties();
|
||||||
|
try {
|
||||||
|
props.load(in);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error("Error when loading from manifest file!");
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
in.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.version = props.getProperty(TAG_VERSION);
|
||||||
|
this.token = props.getProperty(TAG_BACKUPID);
|
||||||
|
this.type = props.getProperty(TAG_BACKUPTYPE);
|
||||||
|
// Here the parameter backupDir is where the manifest file is.
|
||||||
|
// There should always be a manifest file under:
|
||||||
|
// backupRootDir/namespace/table/backupId/.backup.manifest
|
||||||
|
this.rootDir = backupPath.getParent().getParent().getParent().toString();
|
||||||
|
|
||||||
|
Path p = backupPath.getParent();
|
||||||
|
if (p.getName().equals(HConstants.HREGION_LOGDIR_NAME)) {
|
||||||
|
this.rootDir = p.getParent().toString();
|
||||||
|
} else {
|
||||||
|
this.rootDir = p.getParent().getParent().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loadTableSet(props.getProperty(TAG_TABLESET));
|
||||||
|
|
||||||
|
this.startTs = Long.parseLong(props.getProperty(TAG_STARTTS));
|
||||||
|
this.completeTs = Long.parseLong(props.getProperty(TAG_COMPLETETS));
|
||||||
|
this.tableBytes = Long.parseLong(props.getProperty(TAG_TABLEBYTES));
|
||||||
|
if (this.type.equals(BackupRestoreConstants.BACKUP_TYPE_INCR)) {
|
||||||
|
this.logBytes = (Long.parseLong(props.getProperty(TAG_LOGBYTES)));
|
||||||
|
LOG.debug("convert will be implemented by future jira");
|
||||||
|
}
|
||||||
|
this.loadIncrementalTimeRanges(props.getProperty(TAG_INCRTIMERANGE));
|
||||||
|
this.loadDependency(props.getProperty(TAG_DEPENDENCY));
|
||||||
|
this.imageState = props.getProperty(TAG_IMAGESTATE);
|
||||||
|
this.isCompacted =
|
||||||
|
props.getProperty(TAG_COMPACTION).equalsIgnoreCase("TRUE") ? true : false;
|
||||||
|
LOG.debug("merge and from existing snapshot will be implemented by future jira");
|
||||||
|
LOG.debug("Loaded manifest instance from manifest file: "
|
||||||
|
+ FSUtils.getPath(subFile.getPath()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String errorMsg = "No manifest file found in: " + backupPath.toString();
|
||||||
|
LOG.error(errorMsg);
|
||||||
|
throw new IOException(errorMsg);
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new BackupException(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load table set from a table set list string (t1;t2;t3;...).
|
||||||
|
* @param tableSetStr Table set list string
|
||||||
|
*/
|
||||||
|
private void loadTableSet(String tableSetStr) {
|
||||||
|
|
||||||
|
LOG.debug("Loading table set: " + tableSetStr);
|
||||||
|
|
||||||
|
String[] tableSet = tableSetStr.split(";");
|
||||||
|
this.tableSet = this.getTableSet();
|
||||||
|
if (this.tableSet.size() > 0) {
|
||||||
|
this.tableSet.clear();
|
||||||
|
}
|
||||||
|
for (int i = 0; i < tableSet.length; i++) {
|
||||||
|
this.tableSet.add(tableSet[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debug(tableSet.length + " tables exist in table set.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setImageOriginal() {
|
||||||
|
this.imageState = IMAGE_STATE_ORIG;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the table set of this image.
|
||||||
|
* @return The table set list
|
||||||
|
*/
|
||||||
|
public ArrayList<String> getTableSet() {
|
||||||
|
if (this.tableSet == null) {
|
||||||
|
this.tableSet = new ArrayList<String>();
|
||||||
|
}
|
||||||
|
return this.tableSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persist the manifest file.
|
||||||
|
* @throws IOException IOException when storing the manifest file.
|
||||||
|
*/
|
||||||
|
public void store(Configuration conf) throws BackupException {
|
||||||
|
Properties props = new Properties();
|
||||||
|
props.setProperty(TAG_VERSION, this.version);
|
||||||
|
props.setProperty(TAG_BACKUPID, this.token);
|
||||||
|
props.setProperty(TAG_BACKUPTYPE, this.type);
|
||||||
|
props.setProperty(TAG_TABLESET, this.getTableSetStr());
|
||||||
|
LOG.debug("convert will be supported in future jira");
|
||||||
|
// String convertedTables = this.getConvertedTableSetStr();
|
||||||
|
// if (convertedTables != null )
|
||||||
|
// props.setProperty(TAG_CONVERTEDTABLESET, convertedTables);
|
||||||
|
props.setProperty(TAG_STARTTS, Long.toString(this.startTs));
|
||||||
|
props.setProperty(TAG_COMPLETETS, Long.toString(this.completeTs));
|
||||||
|
props.setProperty(TAG_TABLEBYTES, Long.toString(this.tableBytes));
|
||||||
|
if (this.type.equals(BackupRestoreConstants.BACKUP_TYPE_INCR)) {
|
||||||
|
props.setProperty(TAG_LOGBYTES, Long.toString(this.logBytes));
|
||||||
|
}
|
||||||
|
props.setProperty(TAG_INCRTIMERANGE, this.getIncrTimestampStr());
|
||||||
|
props.setProperty(TAG_DEPENDENCY, this.getDependencyStr());
|
||||||
|
props.setProperty(TAG_IMAGESTATE, this.getImageState());
|
||||||
|
props.setProperty(TAG_COMPACTION, this.isCompacted ? "TRUE" : "FALSE");
|
||||||
|
LOG.debug("merge will be supported in future jira");
|
||||||
|
// props.setProperty(TAG_MERGECHAIN, this.getMergeChainStr());
|
||||||
|
LOG.debug("backup from existing snapshot will be supported in future jira");
|
||||||
|
// props.setProperty(TAG_FROMSNAPSHOT, this.isFromSnapshot() ? "TRUE" : "FALSE");
|
||||||
|
|
||||||
|
// write the file, overwrite if already exist
|
||||||
|
Path manifestFilePath =
|
||||||
|
new Path((this.tableBackupDir != null ? this.tableBackupDir : this.logBackupDir)
|
||||||
|
+ File.separator + FILE_NAME);
|
||||||
|
try {
|
||||||
|
FSDataOutputStream out = manifestFilePath.getFileSystem(conf).create(manifestFilePath, true);
|
||||||
|
props.store(out, "HBase backup manifest.");
|
||||||
|
out.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new BackupException(e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debug("Manifest file stored to " + this.tableBackupDir != null ? this.tableBackupDir
|
||||||
|
: this.logBackupDir + File.separator + FILE_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the table set string in the format of t1;t2;t3...
|
||||||
|
*/
|
||||||
|
private String getTableSetStr() {
|
||||||
|
return BackupUtil.concat(getTableSet(), ";");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getImageState() {
|
||||||
|
return imageState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get this backup image.
|
||||||
|
* @return the backup image.
|
||||||
|
*/
|
||||||
|
public BackupImage getBackupImage() {
|
||||||
|
return this.getDependency().get(this.token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add dependent backup image for this backup.
|
||||||
|
* @param image The direct dependent backup image
|
||||||
|
*/
|
||||||
|
public void addDependentImage(BackupImage image) {
|
||||||
|
this.getDependency().get(this.token).addAncestor(image);
|
||||||
|
this.setDependencyMap(this.getDependency(), image);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the dependency' string in the json format.
|
||||||
|
*/
|
||||||
|
private String getDependencyStr() {
|
||||||
|
BackupImage thisImage = this.getDependency().get(this.token);
|
||||||
|
if (thisImage == null) {
|
||||||
|
LOG.warn("There is no dependency set yet.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
try {
|
||||||
|
return mapper.writeValueAsString(thisImage);
|
||||||
|
} catch (JsonGenerationException e) {
|
||||||
|
LOG.error("Error when generating dependency string from backup image.", e);
|
||||||
|
return ERROR_DEPENDENCY;
|
||||||
|
} catch (JsonMappingException e) {
|
||||||
|
LOG.error("Error when generating dependency string from backup image.", e);
|
||||||
|
return ERROR_DEPENDENCY;
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error("Error when generating dependency string from backup image.", e);
|
||||||
|
return ERROR_DEPENDENCY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all dependent backup images. The image of this backup is also contained.
|
||||||
|
* @return The dependent backup images map
|
||||||
|
*/
|
||||||
|
public Map<String, BackupImage> getDependency() {
|
||||||
|
if (this.dependency == null) {
|
||||||
|
this.dependency = new HashMap<String, BackupImage>();
|
||||||
|
LOG.debug(this.rootDir + " " + this.token + " " + this.type);
|
||||||
|
this.dependency.put(this.token,
|
||||||
|
new BackupImage(this.token, this.type, this.rootDir, this.getTableSetStr(), this.startTs,
|
||||||
|
this.completeTs));
|
||||||
|
}
|
||||||
|
return this.dependency;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the incremental timestamp map directly.
|
||||||
|
* @param incrTimestampMap timestamp map
|
||||||
|
*/
|
||||||
|
public void setIncrTimestampMap(HashMap<String, HashMap<String, String>> incrTimestampMap) {
|
||||||
|
this.incrTimeRanges = incrTimestampMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the incremental time range string in the format of:
|
||||||
|
* t1,rs1:ts,rs2:ts,...;t2,rs1:ts,rs2:ts,...;t3,rs1:ts,rs2:ts,...
|
||||||
|
*/
|
||||||
|
private String getIncrTimestampStr() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (Entry<String, HashMap<String, String>> tableEntry : this.getIncrTimestamps().entrySet()) {
|
||||||
|
sb.append(tableEntry.getKey() + ","); // table
|
||||||
|
for (Entry<String, String> rsEntry : tableEntry.getValue().entrySet()) {
|
||||||
|
sb.append(rsEntry.getKey() + ":"); // region server
|
||||||
|
sb.append(rsEntry.getValue() + ","); // timestamp
|
||||||
|
}
|
||||||
|
if (sb.length() > 1 && sb.charAt(sb.length() - 1) == ',') {
|
||||||
|
sb.deleteCharAt(sb.length() - 1);
|
||||||
|
}
|
||||||
|
sb.append(";");
|
||||||
|
}
|
||||||
|
if (sb.length() > 1 && sb.charAt(sb.length() - 1) == ';') {
|
||||||
|
sb.deleteCharAt(sb.length() - 1);
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, HashMap<String, String>> getIncrTimestamps() {
|
||||||
|
if (this.incrTimeRanges == null) {
|
||||||
|
this.incrTimeRanges = new HashMap<String, HashMap<String, String>>();
|
||||||
|
}
|
||||||
|
return this.incrTimeRanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load incremental timestamps from a given string, and store them in the collection. The
|
||||||
|
* timestamps in string is in the format of
|
||||||
|
* t1,rs1:ts,rs2:ts,...;t2,rs1:ts,rs2:ts,...;t3,rs1:ts,rs2:ts,...
|
||||||
|
* @param timeRangesInStr Incremental time ranges in string
|
||||||
|
*/
|
||||||
|
private void loadIncrementalTimeRanges(String timeRangesStr) throws IOException {
|
||||||
|
|
||||||
|
LOG.debug("Loading table's incremental time ranges of region servers from string in manifest: "
|
||||||
|
+ timeRangesStr);
|
||||||
|
|
||||||
|
Map<String, HashMap<String, String>> timeRangeMap = this.getIncrTimestamps();
|
||||||
|
|
||||||
|
String[] entriesOfTables = timeRangesStr.split(";");
|
||||||
|
for (int i = 0; i < entriesOfTables.length; i++) {
|
||||||
|
String[] itemsForTable = entriesOfTables[i].split(",");
|
||||||
|
|
||||||
|
// validate the incremental timestamps string format for a table:
|
||||||
|
// t1,rs1:ts,rs2:ts,...
|
||||||
|
if (itemsForTable.length < 1) {
|
||||||
|
String errorMsg = "Wrong incremental time range format: " + timeRangesStr;
|
||||||
|
LOG.error(errorMsg);
|
||||||
|
throw new IOException(errorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
HashMap<String, String> rsTimestampMap = new HashMap<String, String>();
|
||||||
|
for (int j = 1; j < itemsForTable.length; j++) {
|
||||||
|
String[] rsTsEntry = itemsForTable[j].split(":");
|
||||||
|
|
||||||
|
// validate the incremental timestamps string format for a region server:
|
||||||
|
// rs1:ts
|
||||||
|
if (rsTsEntry.length != 2) {
|
||||||
|
String errorMsg = "Wrong incremental timestamp format: " + itemsForTable[j];
|
||||||
|
LOG.error(errorMsg);
|
||||||
|
throw new IOException(errorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// an entry for timestamp of a region server
|
||||||
|
rsTimestampMap.put(rsTsEntry[0], rsTsEntry[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
timeRangeMap.put(itemsForTable[0], rsTimestampMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
// all entries have been loaded
|
||||||
|
LOG.debug(entriesOfTables.length + " tables' incremental time ranges have been loaded.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the image list of this backup for restore in time order.
|
||||||
|
* @param reverse If true, then output in reverse order, otherwise in time order from old to new
|
||||||
|
* @return the backup image list for restore in time order
|
||||||
|
*/
|
||||||
|
public ArrayList<BackupImage> getRestoreDependentList(boolean reverse) {
|
||||||
|
TreeMap<Long, BackupImage> restoreImages = new TreeMap<Long, BackupImage>();
|
||||||
|
for (BackupImage image : this.getDependency().values()) {
|
||||||
|
restoreImages.put(Long.valueOf(image.startTs), image);
|
||||||
|
}
|
||||||
|
return new ArrayList<BackupImage>(reverse ? (restoreImages.descendingMap().values())
|
||||||
|
: (restoreImages.values()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the dependent image list for a specific table of this backup in time order from old to new
|
||||||
|
* if want to restore to this backup image level.
|
||||||
|
* @param table table
|
||||||
|
* @return the backup image list for a table in time order
|
||||||
|
*/
|
||||||
|
public ArrayList<BackupImage> getDependentListByTable(String table) {
|
||||||
|
ArrayList<BackupImage> tableImageList = new ArrayList<BackupImage>();
|
||||||
|
ArrayList<BackupImage> imageList = getRestoreDependentList(true);
|
||||||
|
for (BackupImage image : imageList) {
|
||||||
|
if (image.hasTable(table)) {
|
||||||
|
tableImageList.add(image);
|
||||||
|
if (image.getType().equals(BackupRestoreConstants.BACKUP_TYPE_FULL)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Collections.reverse(tableImageList);
|
||||||
|
return tableImageList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the full dependent image list in the whole dependency scope for a specific table of this
|
||||||
|
* backup in time order from old to new.
|
||||||
|
* @param table table
|
||||||
|
* @return the full backup image list for a table in time order in the whole scope of the
|
||||||
|
* dependency of this image
|
||||||
|
*/
|
||||||
|
public ArrayList<BackupImage> getAllDependentListByTable(String table) {
|
||||||
|
ArrayList<BackupImage> tableImageList = new ArrayList<BackupImage>();
|
||||||
|
ArrayList<BackupImage> imageList = getRestoreDependentList(false);
|
||||||
|
for (BackupImage image : imageList) {
|
||||||
|
if (image.hasTable(table)) {
|
||||||
|
tableImageList.add(image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tableImageList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load dependency from a dependency json string.
|
||||||
|
* @param dependencyStr The dependency string
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
private void loadDependency(String dependencyStr) throws IOException {
|
||||||
|
|
||||||
|
LOG.debug("Loading dependency: " + dependencyStr);
|
||||||
|
|
||||||
|
String msg = "Dependency is broken in the manifest.";
|
||||||
|
if (dependencyStr.equals(ERROR_DEPENDENCY)) {
|
||||||
|
throw new IOException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
BackupImage image = null;
|
||||||
|
try {
|
||||||
|
image = mapper.readValue(dependencyStr, BackupImage.class);
|
||||||
|
} catch (JsonParseException e) {
|
||||||
|
LOG.error(msg);
|
||||||
|
throw new IOException(e.getMessage());
|
||||||
|
} catch (JsonMappingException e) {
|
||||||
|
LOG.error(msg);
|
||||||
|
throw new IOException(e.getMessage());
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error(msg);
|
||||||
|
throw new IOException(e.getMessage());
|
||||||
|
}
|
||||||
|
LOG.debug("Manifest's current backup image information:");
|
||||||
|
LOG.debug(" Token: " + image.getBackupId());
|
||||||
|
LOG.debug(" Backup directory: " + image.getRootDir());
|
||||||
|
this.setDependencyMap(this.getDependency(), image);
|
||||||
|
|
||||||
|
LOG.debug("Dependent images map:");
|
||||||
|
for (Entry<String, BackupImage> entry : this.getDependency().entrySet()) {
|
||||||
|
LOG.debug(" " + entry.getKey() + " : " + entry.getValue().getBackupId() + " -- "
|
||||||
|
+ entry.getValue().getRootDir());
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debug("Dependency has been loaded.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively set the dependency map of the backup images.
|
||||||
|
* @param map The dependency map
|
||||||
|
* @param image The backup image
|
||||||
|
*/
|
||||||
|
private void setDependencyMap(Map<String, BackupImage> map, BackupImage image) {
|
||||||
|
if (image == null) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
map.put(image.getBackupId(), image);
|
||||||
|
for (BackupImage img : image.getAncestors()) {
|
||||||
|
setDependencyMap(map, img);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether backup image1 could cover backup image2 or not.
|
||||||
|
* @param image1 backup image 1
|
||||||
|
* @param image2 backup image 2
|
||||||
|
* @return true if image1 can cover image2, otherwise false
|
||||||
|
*/
|
||||||
|
public static boolean canCoverImage(BackupImage image1, BackupImage image2) {
|
||||||
|
// image1 can cover image2 only when the following conditions are satisfied:
|
||||||
|
// - image1 must not be an incremental image;
|
||||||
|
// - image1 must be taken after image2 has been taken;
|
||||||
|
// - table set of image1 must cover the table set of image2.
|
||||||
|
if (image1.getType().equals(BackupRestoreConstants.BACKUP_TYPE_INCR)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (image1.getStartTs() < image2.getStartTs()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String[] image1TableSet = image1.getTableSet().split(";");
|
||||||
|
String[] image2TableSet = image2.getTableSet().split(";");
|
||||||
|
boolean found = false;
|
||||||
|
for (int i = 0; i < image2TableSet.length; i++) {
|
||||||
|
found = false;
|
||||||
|
for (int j = 0; j < image1TableSet.length; j++) {
|
||||||
|
if (image2TableSet[i].equals(image1TableSet[j])) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debug("Backup image " + image1.getBackupId() + " can cover " + image2.getBackupId());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether backup image set could cover a backup image or not.
|
||||||
|
* @param fullImages The backup image set
|
||||||
|
* @param image The target backup image
|
||||||
|
* @return true if fullImages can cover image, otherwise false
|
||||||
|
*/
|
||||||
|
public static boolean canCoverImage(ArrayList<BackupImage> fullImages, BackupImage image) {
|
||||||
|
// fullImages can cover image only when the following conditions are satisfied:
|
||||||
|
// - each image of fullImages must not be an incremental image;
|
||||||
|
// - each image of fullImages must be taken after image has been taken;
|
||||||
|
// - sum table set of fullImages must cover the table set of image.
|
||||||
|
for (BackupImage image1 : fullImages) {
|
||||||
|
if (image1.getType().equals(BackupRestoreConstants.BACKUP_TYPE_INCR)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (image1.getStartTs() < image.getStartTs()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayList<String> image1TableSet = new ArrayList<String>();
|
||||||
|
for (BackupImage image1 : fullImages) {
|
||||||
|
String[] tableSet = image1.getTableSet().split(";");
|
||||||
|
for (String table : tableSet) {
|
||||||
|
image1TableSet.add(table);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ArrayList<String> image2TableSet = new ArrayList<String>();
|
||||||
|
String[] tableSet = image.getTableSet().split(";");
|
||||||
|
for (String table : tableSet) {
|
||||||
|
image2TableSet.add(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < image2TableSet.size(); i++) {
|
||||||
|
if (image1TableSet.contains(image2TableSet.get(i)) == false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debug("Full image set can cover image " + image.getBackupId());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
/**
|
||||||
|
* 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.hadoop.hbase.classification.InterfaceAudience;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceStability;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HConstants holds a bunch of HBase Backup and Restore constants
|
||||||
|
*/
|
||||||
|
@InterfaceAudience.Public
|
||||||
|
@InterfaceStability.Stable
|
||||||
|
public final class BackupRestoreConstants {
|
||||||
|
|
||||||
|
// constants for znode data keys in backup znode
|
||||||
|
public static final String BACKUP_PROGRESS = "progress";
|
||||||
|
public static final String BACKUP_START_TIME = "startTs";
|
||||||
|
public static final String BACKUP_INPROGRESS_PHASE = "phase";
|
||||||
|
public static final String BACKUP_COMPLETE_TIME = "completeTs";
|
||||||
|
public static final String BACKUP_FAIL_TIME = "failedTs";
|
||||||
|
public static final String BACKUP_FAIL_PHASE = "failedphase";
|
||||||
|
public static final String BACKUP_FAIL_MSG = "failedmessage";
|
||||||
|
public static final String BACKUP_ROOT_PATH = "targetRootDir";
|
||||||
|
public static final String BACKUP_REQUEST_TABLE_LIST = "tablelist";
|
||||||
|
public static final String BACKUP_REQUEST_TYPE = "type";
|
||||||
|
public static final String BACKUP_BYTES_COPIED = "bytescopied";
|
||||||
|
public static final String BACKUP_ANCESTORS = "ancestors";
|
||||||
|
public static final String BACKUP_EXISTINGSNAPSHOT = "snapshot";
|
||||||
|
|
||||||
|
public static final String BACKUP_TYPE_FULL = "full";
|
||||||
|
public static final String BACKUP_TYPE_INCR = "incremental";
|
||||||
|
|
||||||
|
// delimiter in tablename list in restore command
|
||||||
|
public static final String TABLENAME_DELIMITER_IN_COMMAND = ",";
|
||||||
|
|
||||||
|
// delimiter in znode data
|
||||||
|
public static final String ZNODE_DATA_DELIMITER = ",";
|
||||||
|
|
||||||
|
public static final String CONF_STAGING_ROOT = "snapshot.export.staging.root";
|
||||||
|
|
||||||
|
public static final String BACKUPID_PREFIX = "backup_";
|
||||||
|
|
||||||
|
public static enum BACKUP_COMMAND {
|
||||||
|
CREATE, CANCEL, DELETE, DESCRIBE, HISTORY, STATUS, CONVERT, MERGE, STOP, SHOW, HELP,
|
||||||
|
}
|
||||||
|
|
||||||
|
private BackupRestoreConstants() {
|
||||||
|
// Can't be instantiated with this ctor.
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
/**
|
||||||
|
* 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.hadoop.conf.Configuration;
|
||||||
|
import org.apache.hadoop.hbase.backup.mapreduce.MapReduceBackupCopyService;
|
||||||
|
import org.apache.hadoop.hbase.backup.mapreduce.MapReduceRestoreService;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceAudience;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceStability;
|
||||||
|
import org.apache.hadoop.util.ReflectionUtils;
|
||||||
|
|
||||||
|
@InterfaceAudience.Private
|
||||||
|
@InterfaceStability.Evolving
|
||||||
|
public final class BackupRestoreServiceFactory {
|
||||||
|
|
||||||
|
public final static String HBASE_INCR_RESTORE_IMPL_CLASS = "hbase.incremental.restore.class";
|
||||||
|
public final static String HBASE_BACKUP_COPY_IMPL_CLASS = "hbase.backup.copy.class";
|
||||||
|
|
||||||
|
private BackupRestoreServiceFactory(){
|
||||||
|
throw new AssertionError("Instantiating utility class...");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets incremental restore service
|
||||||
|
* @param conf - configuration
|
||||||
|
* @return incremental backup service instance
|
||||||
|
*/
|
||||||
|
public static IncrementalRestoreService getIncrementalRestoreService(Configuration conf) {
|
||||||
|
Class<? extends IncrementalRestoreService> cls =
|
||||||
|
conf.getClass(HBASE_INCR_RESTORE_IMPL_CLASS, MapReduceRestoreService.class,
|
||||||
|
IncrementalRestoreService.class);
|
||||||
|
return ReflectionUtils.newInstance(cls, conf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets backup copy service
|
||||||
|
* @param conf - configuration
|
||||||
|
* @return backup copy service
|
||||||
|
*/
|
||||||
|
public static BackupCopyService getBackupCopyService(Configuration conf) {
|
||||||
|
Class<? extends BackupCopyService> cls =
|
||||||
|
conf.getClass(HBASE_BACKUP_COPY_IMPL_CLASS, MapReduceBackupCopyService.class,
|
||||||
|
BackupCopyService.class);
|
||||||
|
return ReflectionUtils.newInstance(cls, conf);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
/**
|
||||||
|
* 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.io.Serializable;
|
||||||
|
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceAudience;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceStability;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backup status and related information encapsulated for a table.
|
||||||
|
* At this moment only TargetDir and SnapshotName is encapsulated here.
|
||||||
|
* future Jira will be implemented for progress, bytesCopies, phase, etc.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@InterfaceAudience.Private
|
||||||
|
@InterfaceStability.Evolving
|
||||||
|
public class BackupStatus implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -5968397963548535982L;
|
||||||
|
|
||||||
|
// table name for backup
|
||||||
|
private String table;
|
||||||
|
|
||||||
|
// target directory of the backup image for this table
|
||||||
|
private String targetDir;
|
||||||
|
|
||||||
|
// snapshot name for offline/online snapshot
|
||||||
|
private String snapshotName = null;
|
||||||
|
|
||||||
|
public BackupStatus(String table, String targetRootDir, String backupId) {
|
||||||
|
this.table = table;
|
||||||
|
this.targetDir = HBackupFileSystem.getTableBackupDir(targetRootDir, backupId, table);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSnapshotName() {
|
||||||
|
return snapshotName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSnapshotName(String snapshotName) {
|
||||||
|
this.snapshotName = snapshotName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTargetDir() {
|
||||||
|
return targetDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTable() {
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,642 @@
|
||||||
|
/**
|
||||||
|
* 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.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
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.Configuration;
|
||||||
|
import org.apache.hadoop.hbase.Cell;
|
||||||
|
import org.apache.hadoop.hbase.CellUtil;
|
||||||
|
import org.apache.hadoop.hbase.HColumnDescriptor;
|
||||||
|
import org.apache.hadoop.hbase.HConstants;
|
||||||
|
import org.apache.hadoop.hbase.HTableDescriptor;
|
||||||
|
import org.apache.hadoop.hbase.TableName;
|
||||||
|
import org.apache.hadoop.hbase.backup.BackupHandler.BACKUPSTATUS;
|
||||||
|
import org.apache.hadoop.hbase.backup.BackupUtil.BackupCompleteData;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceAudience;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceStability;
|
||||||
|
import org.apache.hadoop.hbase.client.Admin;
|
||||||
|
import org.apache.hadoop.hbase.client.Connection;
|
||||||
|
import org.apache.hadoop.hbase.client.ConnectionFactory;
|
||||||
|
import org.apache.hadoop.hbase.client.Delete;
|
||||||
|
import org.apache.hadoop.hbase.client.Get;
|
||||||
|
import org.apache.hadoop.hbase.client.Put;
|
||||||
|
import org.apache.hadoop.hbase.client.Result;
|
||||||
|
import org.apache.hadoop.hbase.client.ResultScanner;
|
||||||
|
import org.apache.hadoop.hbase.client.Scan;
|
||||||
|
import org.apache.hadoop.hbase.client.Table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class provides 'hbase:backup' table API
|
||||||
|
*/
|
||||||
|
@InterfaceAudience.Private
|
||||||
|
@InterfaceStability.Evolving
|
||||||
|
public final class BackupSystemTable {
|
||||||
|
|
||||||
|
private static final Log LOG = LogFactory.getLog(BackupSystemTable.class);
|
||||||
|
private final static String TABLE_NAMESPACE = "hbase";
|
||||||
|
private final static String TABLE_NAME = "backup";
|
||||||
|
private final static TableName tableName = TableName.valueOf(TABLE_NAMESPACE, TABLE_NAME);
|
||||||
|
public final static byte[] familyName = "f".getBytes();
|
||||||
|
|
||||||
|
// Connection to HBase cluster
|
||||||
|
private static Connection connection;
|
||||||
|
// Cluster configuration
|
||||||
|
private static Configuration config;
|
||||||
|
// singleton
|
||||||
|
private static BackupSystemTable table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get instance by a given configuration
|
||||||
|
* @param conf - HBase configuration
|
||||||
|
* @return instance of BackupSystemTable
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public synchronized static BackupSystemTable getTable(Configuration conf) throws IOException {
|
||||||
|
if (connection == null) {
|
||||||
|
connection = ConnectionFactory.createConnection(conf);
|
||||||
|
config = conf;
|
||||||
|
// Verify hbase:system exists
|
||||||
|
createSystemTableIfNotExists();
|
||||||
|
table = new BackupSystemTable();
|
||||||
|
}
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: refactor
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public static void close() throws IOException {
|
||||||
|
connection.close();
|
||||||
|
table = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets table name
|
||||||
|
* @return table name
|
||||||
|
*/
|
||||||
|
public static TableName getTableName() {
|
||||||
|
return tableName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void createSystemTableIfNotExists() throws IOException {
|
||||||
|
Admin admin = null;
|
||||||
|
try {
|
||||||
|
admin = connection.getAdmin();
|
||||||
|
if (admin.tableExists(tableName) == false) {
|
||||||
|
HTableDescriptor tableDesc = new HTableDescriptor(tableName);
|
||||||
|
HColumnDescriptor colDesc = new HColumnDescriptor(familyName);
|
||||||
|
colDesc.setMaxVersions(1);
|
||||||
|
int ttl =
|
||||||
|
config.getInt(HConstants.BACKUP_SYSTEM_TTL_KEY, HConstants.BACKUP_SYSTEM_TTL_DEFAULT);
|
||||||
|
colDesc.setTimeToLive(ttl);
|
||||||
|
tableDesc.addFamily(colDesc);
|
||||||
|
admin.createTable(tableDesc);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error(e);
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
if (admin != null) {
|
||||||
|
admin.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BackupSystemTable() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates status (state) of a backup session in hbase:backup table
|
||||||
|
* @param context context
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public void updateBackupStatus(BackupContext context) throws IOException {
|
||||||
|
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("update backup status in hbase:backup for: " + context.getBackupId()
|
||||||
|
+ " set status=" + context.getFlag());
|
||||||
|
}
|
||||||
|
Table table = null;
|
||||||
|
try {
|
||||||
|
table = connection.getTable(tableName);
|
||||||
|
Put put = BackupSystemTableHelper.createPutForBackupContext(context);
|
||||||
|
table.put(put);
|
||||||
|
} finally {
|
||||||
|
if (table != null) {
|
||||||
|
table.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes backup status from hbase:backup table
|
||||||
|
* @param backupId backup id
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
|
||||||
|
public void deleteBackupStatus(String backupId) throws IOException {
|
||||||
|
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("delete backup status in hbase:backup for " + backupId);
|
||||||
|
}
|
||||||
|
Table table = null;
|
||||||
|
try {
|
||||||
|
table = connection.getTable(tableName);
|
||||||
|
Delete del = BackupSystemTableHelper.createDeletForBackupContext(backupId);
|
||||||
|
table.delete(del);
|
||||||
|
} finally {
|
||||||
|
if (table != null) {
|
||||||
|
table.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads backup status object (instance of BackupContext) from hbase:backup table
|
||||||
|
* @param backupId - backupId
|
||||||
|
* @return Current status of backup session or null
|
||||||
|
*/
|
||||||
|
|
||||||
|
public BackupContext readBackupStatus(String backupId) throws IOException {
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("read backup status from hbase:backup for: " + backupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Table table = null;
|
||||||
|
try {
|
||||||
|
table = connection.getTable(tableName);
|
||||||
|
Get get = BackupSystemTableHelper.createGetForBackupContext(backupId);
|
||||||
|
Result res = table.get(get);
|
||||||
|
if(res.isEmpty()){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return BackupSystemTableHelper.resultToBackupContext(res);
|
||||||
|
} finally {
|
||||||
|
if (table != null) {
|
||||||
|
table.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the last backup start code (timestamp) of last successful backup. Will return null if
|
||||||
|
* there is no start code stored on hbase or the value is of length 0. These two cases indicate
|
||||||
|
* there is no successful backup completed so far.
|
||||||
|
* @return the timestamp of last successful backup
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public String readBackupStartCode() throws IOException {
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("read backup start code from hbase:backup");
|
||||||
|
}
|
||||||
|
Table table = null;
|
||||||
|
try {
|
||||||
|
table = connection.getTable(tableName);
|
||||||
|
Get get = BackupSystemTableHelper.createGetForStartCode();
|
||||||
|
Result res = table.get(get);
|
||||||
|
if (res.isEmpty()){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Cell cell = res.listCells().get(0);
|
||||||
|
byte[] val = CellUtil.cloneValue(cell);
|
||||||
|
if (val.length == 0){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new String(val);
|
||||||
|
} finally {
|
||||||
|
if (table != null) {
|
||||||
|
table.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the start code (timestamp) to hbase:backup. If passed in null, then write 0 byte.
|
||||||
|
* @param startCode start code
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public void writeBackupStartCode(String startCode) throws IOException {
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("write backup start code to hbase:backup " + startCode);
|
||||||
|
}
|
||||||
|
Table table = null;
|
||||||
|
try {
|
||||||
|
table = connection.getTable(tableName);
|
||||||
|
Put put = BackupSystemTableHelper.createPutForStartCode(startCode);
|
||||||
|
table.put(put);
|
||||||
|
} finally {
|
||||||
|
if (table != null) {
|
||||||
|
table.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Region Servers log information after the last log roll from hbase:backup.
|
||||||
|
* @return RS log info
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public HashMap<String, String> readRegionServerLastLogRollResult()
|
||||||
|
throws IOException {
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("read region server last roll log result to hbase:backup");
|
||||||
|
}
|
||||||
|
Table table = null;
|
||||||
|
ResultScanner scanner = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
table = connection.getTable(tableName);
|
||||||
|
Scan scan = BackupSystemTableHelper.createScanForReadRegionServerLastLogRollResult();
|
||||||
|
scan.setMaxVersions(1);
|
||||||
|
scanner = table.getScanner(scan);
|
||||||
|
Result res = null;
|
||||||
|
HashMap<String, String> rsTimestampMap = new HashMap<String, String>();
|
||||||
|
while ((res = scanner.next()) != null) {
|
||||||
|
res.advance();
|
||||||
|
Cell cell = res.current();
|
||||||
|
byte[] row = CellUtil.cloneRow(cell);
|
||||||
|
String server =
|
||||||
|
BackupSystemTableHelper.getServerNameForReadRegionServerLastLogRollResult(row);
|
||||||
|
|
||||||
|
byte[] data = CellUtil.cloneValue(cell);
|
||||||
|
rsTimestampMap.put(server, new String(data));
|
||||||
|
}
|
||||||
|
return rsTimestampMap;
|
||||||
|
} finally {
|
||||||
|
if (scanner != null) {
|
||||||
|
scanner.close();
|
||||||
|
}
|
||||||
|
if (table != null) {
|
||||||
|
table.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes Region Server last roll log result (timestamp) to hbase:backup table
|
||||||
|
* @param server - Region Server name
|
||||||
|
* @param fileName - last log timestamp
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public void writeRegionServerLastLogRollResult(String server, String fileName)
|
||||||
|
throws IOException {
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("write region server last roll log result to hbase:backup");
|
||||||
|
}
|
||||||
|
Table table = null;
|
||||||
|
try {
|
||||||
|
table = connection.getTable(tableName);
|
||||||
|
Put put =
|
||||||
|
BackupSystemTableHelper.createPutForRegionServerLastLogRollResult(server, fileName);
|
||||||
|
table.put(put);
|
||||||
|
} finally {
|
||||||
|
if (table != null) {
|
||||||
|
table.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all completed backup information (in desc order by time)
|
||||||
|
* @return history info of BackupCompleteData
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public ArrayList<BackupCompleteData> getBackupHistory() throws IOException {
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("get backup history from hbase:backup");
|
||||||
|
}
|
||||||
|
Table table = null;
|
||||||
|
ResultScanner scanner = null;
|
||||||
|
ArrayList<BackupCompleteData> list = new ArrayList<BackupCompleteData>();
|
||||||
|
try {
|
||||||
|
table = connection.getTable(tableName);
|
||||||
|
Scan scan = BackupSystemTableHelper.createScanForBackupHistory();
|
||||||
|
scan.setMaxVersions(1);
|
||||||
|
scanner = table.getScanner(scan);
|
||||||
|
Result res = null;
|
||||||
|
while ((res = scanner.next()) != null) {
|
||||||
|
res.advance();
|
||||||
|
BackupContext context = BackupSystemTableHelper.cellToBackupContext(res.current());
|
||||||
|
if (context.getFlag() != BACKUPSTATUS.COMPLETE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
BackupCompleteData history = new BackupCompleteData();
|
||||||
|
history.setBackupToken(context.getBackupId());
|
||||||
|
history.setStartTime(Long.toString(context.getStartTs()));
|
||||||
|
history.setEndTime(Long.toString(context.getEndTs()));
|
||||||
|
history.setBackupRootPath(context.getTargetRootDir());
|
||||||
|
history.setTableList(context.getTableListAsString());
|
||||||
|
history.setType(context.getType());
|
||||||
|
history.setBytesCopied(Long.toString(context.getTotalBytesCopied()));
|
||||||
|
|
||||||
|
if (context.fromExistingSnapshot()) {
|
||||||
|
history.markFromExistingSnapshot();
|
||||||
|
}
|
||||||
|
list.add(history);
|
||||||
|
}
|
||||||
|
return BackupUtil.sortHistoryListDesc(list);
|
||||||
|
} finally {
|
||||||
|
if (scanner != null) {
|
||||||
|
scanner.close();
|
||||||
|
}
|
||||||
|
if (table != null) {
|
||||||
|
table.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all backup session with a given status (in desc order by time)
|
||||||
|
* @param status status
|
||||||
|
* @return history info of backup contexts
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public ArrayList<BackupContext> getBackupContexts(BACKUPSTATUS status) throws IOException {
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("get backup contexts from hbase:backup");
|
||||||
|
}
|
||||||
|
Table table = null;
|
||||||
|
ResultScanner scanner = null;
|
||||||
|
ArrayList<BackupContext> list = new ArrayList<BackupContext>();
|
||||||
|
try {
|
||||||
|
table = connection.getTable(tableName);
|
||||||
|
Scan scan = BackupSystemTableHelper.createScanForBackupHistory();
|
||||||
|
scan.setMaxVersions(1);
|
||||||
|
scanner = table.getScanner(scan);
|
||||||
|
Result res = null;
|
||||||
|
while ((res = scanner.next()) != null) {
|
||||||
|
res.advance();
|
||||||
|
BackupContext context = BackupSystemTableHelper.cellToBackupContext(res.current());
|
||||||
|
if (context.getFlag() != status){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
list.add(context);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
} finally {
|
||||||
|
if (scanner != null) {
|
||||||
|
scanner.close();
|
||||||
|
}
|
||||||
|
if (table != null) {
|
||||||
|
table.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the current timestamps for each regionserver to hbase:backup after a successful full or
|
||||||
|
* incremental backup. The saved timestamp is of the last log file that was backed up already.
|
||||||
|
* @param tables tables
|
||||||
|
* @param newTimestamps timestamps
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public void writeRegionServerLogTimestamp(Set<String> tables,
|
||||||
|
HashMap<String, String> newTimestamps) throws IOException {
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("write RS log ts to HBASE_BACKUP");
|
||||||
|
}
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (Map.Entry<String, String> entry : newTimestamps.entrySet()) {
|
||||||
|
String host = entry.getKey();
|
||||||
|
String timestamp = entry.getValue();
|
||||||
|
sb.append(host).append(BackupUtil.FIELD_SEPARATOR).append(timestamp)
|
||||||
|
.append(BackupUtil.RECORD_SEPARATOR);
|
||||||
|
}
|
||||||
|
String smap = sb.toString();
|
||||||
|
List<Put> puts = new ArrayList<Put>();
|
||||||
|
for (String table : tables) {
|
||||||
|
Put put = BackupSystemTableHelper.createPutForWriteRegionServerLogTimestamp(table, smap);
|
||||||
|
puts.add(put);
|
||||||
|
}
|
||||||
|
Table table = null;
|
||||||
|
try {
|
||||||
|
table = connection.getTable(tableName);
|
||||||
|
table.put(puts);
|
||||||
|
} finally {
|
||||||
|
if (table != null) {
|
||||||
|
table.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the timestamp for each region server log after the last successful backup. Each table has
|
||||||
|
* its own set of the timestamps. The info is stored for each table as a concatenated string of
|
||||||
|
* rs->timestapmp
|
||||||
|
* @return the timestamp for each region server. key: tableName value:
|
||||||
|
* RegionServer,PreviousTimeStamp
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public HashMap<String, HashMap<String, String>> readLogTimestampMap() throws IOException {
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("read RS log ts from HBASE_BACKUP");
|
||||||
|
}
|
||||||
|
|
||||||
|
Table table = null;
|
||||||
|
ResultScanner scanner = null;
|
||||||
|
HashMap<String, HashMap<String, String>> tableTimestampMap =
|
||||||
|
new HashMap<String, HashMap<String, String>>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
table = connection.getTable(tableName);
|
||||||
|
Scan scan = BackupSystemTableHelper.createScanForReadLogTimestampMap();
|
||||||
|
scanner = table.getScanner(scan);
|
||||||
|
Result res = null;
|
||||||
|
while ((res = scanner.next()) != null) {
|
||||||
|
res.advance();
|
||||||
|
Cell cell = res.current();
|
||||||
|
byte[] row = CellUtil.cloneRow(cell);
|
||||||
|
String tabName = BackupSystemTableHelper.getTableNameForReadLogTimestampMap(row);
|
||||||
|
HashMap<String, String> lastBackup = new HashMap<String, String>();
|
||||||
|
byte[] data = CellUtil.cloneValue(cell);
|
||||||
|
if (data == null) {
|
||||||
|
// TODO
|
||||||
|
throw new IOException("Data of last backup data from HBASE_BACKUP "
|
||||||
|
+ "is empty. Create a backup first.");
|
||||||
|
}
|
||||||
|
if (data != null && data.length > 0) {
|
||||||
|
String s = new String(data);
|
||||||
|
String[] records = s.split(BackupUtil.RECORD_SEPARATOR);
|
||||||
|
for (String record : records) {
|
||||||
|
String[] flds = record.split(BackupUtil.FIELD_SEPARATOR);
|
||||||
|
if (flds.length != 2) {
|
||||||
|
throw new IOException("data from HBASE_BACKUP is corrupted: "
|
||||||
|
+ Arrays.toString(flds));
|
||||||
|
}
|
||||||
|
lastBackup.put(flds[0], flds[1]);
|
||||||
|
}
|
||||||
|
tableTimestampMap.put(tabName, lastBackup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tableTimestampMap;
|
||||||
|
} finally {
|
||||||
|
if (scanner != null) {
|
||||||
|
scanner.close();
|
||||||
|
}
|
||||||
|
if (table != null) {
|
||||||
|
table.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the current tables covered by incremental backup.
|
||||||
|
* @return set of tableNames
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public Set<String> getIncrementalBackupTableSet() throws IOException {
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("get incr backup table set from hbase:backup");
|
||||||
|
}
|
||||||
|
Table table = null;
|
||||||
|
TreeSet<String> set = new TreeSet<String>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
table = connection.getTable(tableName);
|
||||||
|
Get get = BackupSystemTableHelper.createGetForIncrBackupTableSet();
|
||||||
|
Result res = table.get(get);
|
||||||
|
if (res.isEmpty()) {
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
List<Cell> cells = res.listCells();
|
||||||
|
for (Cell cell : cells) {
|
||||||
|
// qualifier = table name - we use table names as qualifiers
|
||||||
|
// TODO ns:table as qualifier?
|
||||||
|
set.add(new String(CellUtil.cloneQualifier(cell)));
|
||||||
|
}
|
||||||
|
return set;
|
||||||
|
} finally {
|
||||||
|
if (table != null) {
|
||||||
|
table.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add tables to global incremental backup set
|
||||||
|
* @param tables - set of tables
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public void addIncrementalBackupTableSet(Set<String> tables) throws IOException {
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("add incr backup table set to hbase:backup");
|
||||||
|
}
|
||||||
|
Table table = null;
|
||||||
|
try {
|
||||||
|
table = connection.getTable(tableName);
|
||||||
|
Put put = BackupSystemTableHelper.createPutForIncrBackupTableSet(tables);
|
||||||
|
table.put(put);
|
||||||
|
} finally {
|
||||||
|
if (table != null) {
|
||||||
|
table.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register WAL files as eligible for deletion
|
||||||
|
* @param files files
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public void addWALFiles(List<String> files, String backupId) throws IOException {
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("add WAL files to hbase:backup");
|
||||||
|
}
|
||||||
|
Table table = null;
|
||||||
|
try {
|
||||||
|
table = connection.getTable(tableName);
|
||||||
|
List<Put> puts = BackupSystemTableHelper.createPutsForAddWALFiles(files, backupId);
|
||||||
|
table.put(puts);
|
||||||
|
} finally {
|
||||||
|
if (table != null) {
|
||||||
|
table.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if WAL file is eligible for deletion
|
||||||
|
* @param file file
|
||||||
|
* @return true, if - yes.
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public boolean checkWALFile(String file) throws IOException {
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("Check if WAL file has been already backuped in hbase:backup");
|
||||||
|
}
|
||||||
|
Table table = null;
|
||||||
|
try {
|
||||||
|
table = connection.getTable(tableName);
|
||||||
|
Get get = BackupSystemTableHelper.createGetForCheckWALFile(file);
|
||||||
|
Result res = table.get(get);
|
||||||
|
if (res.isEmpty()){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} finally {
|
||||||
|
if (table != null) {
|
||||||
|
table.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if we have at least one backup session in hbase:backup This API is used by
|
||||||
|
* BackupLogCleaner
|
||||||
|
* @return true, if - at least one session exists in hbase:backup table
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public boolean hasBackupSessions() throws IOException {
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("has backup sessions from hbase:backup");
|
||||||
|
}
|
||||||
|
Table table = null;
|
||||||
|
ResultScanner scanner = null;
|
||||||
|
boolean result = false;
|
||||||
|
try {
|
||||||
|
table = connection.getTable(tableName);
|
||||||
|
Scan scan = BackupSystemTableHelper.createScanForBackupHistory();
|
||||||
|
scan.setMaxVersions(1);
|
||||||
|
scan.setCaching(1);
|
||||||
|
scanner = table.getScanner(scan);
|
||||||
|
if (scanner.next() != null) {
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} finally {
|
||||||
|
if (scanner != null) {
|
||||||
|
scanner.close();
|
||||||
|
}
|
||||||
|
if (table != null) {
|
||||||
|
table.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,314 @@
|
||||||
|
/**
|
||||||
|
* 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.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.apache.hadoop.hbase.Cell;
|
||||||
|
import org.apache.hadoop.hbase.CellUtil;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceAudience;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceStability;
|
||||||
|
import org.apache.hadoop.hbase.client.Delete;
|
||||||
|
import org.apache.hadoop.hbase.client.Get;
|
||||||
|
import org.apache.hadoop.hbase.client.Put;
|
||||||
|
import org.apache.hadoop.hbase.client.Result;
|
||||||
|
import org.apache.hadoop.hbase.client.Scan;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A collection for methods used by BackupSystemTable.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@InterfaceAudience.Private
|
||||||
|
@InterfaceStability.Evolving
|
||||||
|
public final class BackupSystemTableHelper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* hbase:backup schema:
|
||||||
|
* 1. Backup sessions rowkey= "session." + backupId; value = serialized
|
||||||
|
* BackupContext
|
||||||
|
* 2. Backup start code rowkey = "startcode"; value = startcode
|
||||||
|
* 3. Incremental backup set rowkey="incrbackupset"; value=[list of tables]
|
||||||
|
* 4. Table-RS-timestamp map rowkey="trslm"+ table_name; value = map[RS-> last WAL timestamp]
|
||||||
|
* 5. RS - WAL ts map rowkey="rslogts."+server; value = last WAL timestamp
|
||||||
|
* 6. WALs recorded rowkey="wals."+WAL unique file name; value = NULL (value is not used)
|
||||||
|
*/
|
||||||
|
private static final Log LOG = LogFactory.getLog(BackupSystemTableHelper.class);
|
||||||
|
|
||||||
|
private final static String BACKUP_CONTEXT_PREFIX = "session.";
|
||||||
|
private final static String START_CODE_ROW = "startcode";
|
||||||
|
private final static String INCR_BACKUP_SET = "incrbackupset";
|
||||||
|
private final static String TABLE_RS_LOG_MAP_PREFIX = "trslm.";
|
||||||
|
private final static String RS_LOG_TS_PREFIX = "rslogts.";
|
||||||
|
private final static String WALS_PREFIX = "wals.";
|
||||||
|
|
||||||
|
private final static byte[] q0 = "0".getBytes();
|
||||||
|
private final static byte[] EMPTY_VALUE = new byte[] {};
|
||||||
|
|
||||||
|
private BackupSystemTableHelper() {
|
||||||
|
throw new AssertionError("Instantiating utility class...");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates Put operation for a given backup context object
|
||||||
|
* @param context backup context
|
||||||
|
* @return put operation
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
static Put createPutForBackupContext(BackupContext context) throws IOException {
|
||||||
|
|
||||||
|
Put put = new Put((BACKUP_CONTEXT_PREFIX + context.getBackupId()).getBytes());
|
||||||
|
put.addColumn(BackupSystemTable.familyName, q0, context.toByteArray());
|
||||||
|
return put;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates Get operation for a given backup id
|
||||||
|
* @param backupId - backup's ID
|
||||||
|
* @return get operation
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
static Get createGetForBackupContext(String backupId) throws IOException {
|
||||||
|
Get get = new Get((BACKUP_CONTEXT_PREFIX + backupId).getBytes());
|
||||||
|
get.addFamily(BackupSystemTable.familyName);
|
||||||
|
get.setMaxVersions(1);
|
||||||
|
return get;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates Delete operation for a given backup id
|
||||||
|
* @param backupId - backup's ID
|
||||||
|
* @return delete operation
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public static Delete createDeletForBackupContext(String backupId) {
|
||||||
|
Delete del = new Delete((BACKUP_CONTEXT_PREFIX + backupId).getBytes());
|
||||||
|
del.addFamily(BackupSystemTable.familyName);
|
||||||
|
return del;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts Result to BackupContext
|
||||||
|
* @param res - HBase result
|
||||||
|
* @return backup context instance
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
static BackupContext resultToBackupContext(Result res) throws IOException {
|
||||||
|
res.advance();
|
||||||
|
Cell cell = res.current();
|
||||||
|
return cellToBackupContext(cell);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates Get operation to retrieve start code from hbase:backup
|
||||||
|
* @return get operation
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
static Get createGetForStartCode() throws IOException {
|
||||||
|
Get get = new Get(START_CODE_ROW.getBytes());
|
||||||
|
get.addFamily(BackupSystemTable.familyName);
|
||||||
|
get.setMaxVersions(1);
|
||||||
|
return get;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates Put operation to store start code to hbase:backup
|
||||||
|
* @return put operation
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
static Put createPutForStartCode(String startCode) {
|
||||||
|
Put put = new Put(START_CODE_ROW.getBytes());
|
||||||
|
put.addColumn(BackupSystemTable.familyName, q0, startCode.getBytes());
|
||||||
|
return put;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates Get to retrieve incremental backup table set from hbase:backup
|
||||||
|
* @return get operation
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
static Get createGetForIncrBackupTableSet() throws IOException {
|
||||||
|
Get get = new Get(INCR_BACKUP_SET.getBytes());
|
||||||
|
get.addFamily(BackupSystemTable.familyName);
|
||||||
|
get.setMaxVersions(1);
|
||||||
|
return get;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates Put to store incremental backup table set
|
||||||
|
* @param tables tables
|
||||||
|
* @return put operation
|
||||||
|
*/
|
||||||
|
static Put createPutForIncrBackupTableSet(Set<String> tables) {
|
||||||
|
Put put = new Put(INCR_BACKUP_SET.getBytes());
|
||||||
|
for (String table : tables) {
|
||||||
|
put.addColumn(BackupSystemTable.familyName, table.getBytes(), EMPTY_VALUE);
|
||||||
|
}
|
||||||
|
return put;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates Scan operation to load backup history
|
||||||
|
* @return scan operation
|
||||||
|
*/
|
||||||
|
static Scan createScanForBackupHistory() {
|
||||||
|
Scan scan = new Scan();
|
||||||
|
byte[] startRow = BACKUP_CONTEXT_PREFIX.getBytes();
|
||||||
|
byte[] stopRow = Arrays.copyOf(startRow, startRow.length);
|
||||||
|
stopRow[stopRow.length - 1] = (byte) (stopRow[stopRow.length - 1] + 1);
|
||||||
|
scan.setStartRow(startRow);
|
||||||
|
scan.setStopRow(stopRow);
|
||||||
|
scan.addFamily(BackupSystemTable.familyName);
|
||||||
|
|
||||||
|
return scan;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts cell to backup context instance.
|
||||||
|
* @param current - cell
|
||||||
|
* @return backup context instance
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
static BackupContext cellToBackupContext(Cell current) throws IOException {
|
||||||
|
byte[] data = CellUtil.cloneValue(current);
|
||||||
|
try {
|
||||||
|
BackupContext ctxt = BackupContext.fromByteArray(data);
|
||||||
|
return ctxt;
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates Put to write RS last roll log timestamp map
|
||||||
|
* @param table - table
|
||||||
|
* @param smap - map, containing RS:ts
|
||||||
|
* @return put operation
|
||||||
|
*/
|
||||||
|
static Put createPutForWriteRegionServerLogTimestamp(String table, String smap) {
|
||||||
|
Put put = new Put((TABLE_RS_LOG_MAP_PREFIX + table).getBytes());
|
||||||
|
put.addColumn(BackupSystemTable.familyName, q0, smap.getBytes());
|
||||||
|
return put;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates Scan to load table-> { RS -> ts} map of maps
|
||||||
|
* @return scan operation
|
||||||
|
*/
|
||||||
|
static Scan createScanForReadLogTimestampMap() {
|
||||||
|
Scan scan = new Scan();
|
||||||
|
byte[] startRow = TABLE_RS_LOG_MAP_PREFIX.getBytes();
|
||||||
|
byte[] stopRow = Arrays.copyOf(startRow, startRow.length);
|
||||||
|
stopRow[stopRow.length - 1] = (byte) (stopRow[stopRow.length - 1] + 1);
|
||||||
|
scan.setStartRow(startRow);
|
||||||
|
scan.setStopRow(stopRow);
|
||||||
|
scan.addFamily(BackupSystemTable.familyName);
|
||||||
|
|
||||||
|
return scan;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get table name from rowkey
|
||||||
|
* @param cloneRow rowkey
|
||||||
|
* @return table name
|
||||||
|
*/
|
||||||
|
static String getTableNameForReadLogTimestampMap(byte[] cloneRow) {
|
||||||
|
int prefixSize = TABLE_RS_LOG_MAP_PREFIX.length();
|
||||||
|
return new String(cloneRow, prefixSize, cloneRow.length - prefixSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates Put to store RS last log result
|
||||||
|
* @param server - server name
|
||||||
|
* @param fileName - log roll result (timestamp)
|
||||||
|
* @return put operation
|
||||||
|
*/
|
||||||
|
static Put createPutForRegionServerLastLogRollResult(String server, String fileName) {
|
||||||
|
Put put = new Put((RS_LOG_TS_PREFIX + server).getBytes());
|
||||||
|
put.addColumn(BackupSystemTable.familyName, q0, fileName.getBytes());
|
||||||
|
return put;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates Scan operation to load last RS log roll results
|
||||||
|
* @return scan operation
|
||||||
|
*/
|
||||||
|
static Scan createScanForReadRegionServerLastLogRollResult() {
|
||||||
|
Scan scan = new Scan();
|
||||||
|
byte[] startRow = RS_LOG_TS_PREFIX.getBytes();
|
||||||
|
byte[] stopRow = Arrays.copyOf(startRow, startRow.length);
|
||||||
|
stopRow[stopRow.length - 1] = (byte) (stopRow[stopRow.length - 1] + 1);
|
||||||
|
scan.setStartRow(startRow);
|
||||||
|
scan.setStopRow(stopRow);
|
||||||
|
scan.addFamily(BackupSystemTable.familyName);
|
||||||
|
|
||||||
|
return scan;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get server's name from rowkey
|
||||||
|
* @param row - rowkey
|
||||||
|
* @return server's name
|
||||||
|
*/
|
||||||
|
static String getServerNameForReadRegionServerLastLogRollResult(byte[] row) {
|
||||||
|
int prefixSize = RS_LOG_TS_PREFIX.length();
|
||||||
|
return new String(row, prefixSize, row.length - prefixSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates put list for list of WAL files
|
||||||
|
* @param files list of WAL file paths
|
||||||
|
* @param backupId backup id
|
||||||
|
* @return put list
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public static List<Put> createPutsForAddWALFiles(List<String> files, String backupId)
|
||||||
|
throws IOException {
|
||||||
|
|
||||||
|
List<Put> puts = new ArrayList<Put>();
|
||||||
|
for (String file : files) {
|
||||||
|
LOG.debug("+++ put: " + BackupUtil.getUniqueWALFileNamePart(file));
|
||||||
|
byte[] row = (WALS_PREFIX + BackupUtil.getUniqueWALFileNamePart(file)).getBytes();
|
||||||
|
Put put = new Put(row);
|
||||||
|
put.addColumn(BackupSystemTable.familyName, q0, backupId.getBytes());
|
||||||
|
puts.add(put);
|
||||||
|
}
|
||||||
|
return puts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates Get operation for a given wal file name
|
||||||
|
* @param file file
|
||||||
|
* @return get operation
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public static Get createGetForCheckWALFile(String file) throws IOException {
|
||||||
|
byte[] row = (WALS_PREFIX + BackupUtil.getUniqueWALFileNamePart(file)).getBytes();
|
||||||
|
Get get = new Get(row);
|
||||||
|
get.addFamily(BackupSystemTable.familyName);
|
||||||
|
get.setMaxVersions(1);
|
||||||
|
return get;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,564 @@
|
||||||
|
/**
|
||||||
|
* 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.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
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.FileStatus;
|
||||||
|
import org.apache.hadoop.fs.FileSystem;
|
||||||
|
import org.apache.hadoop.fs.LocatedFileStatus;
|
||||||
|
import org.apache.hadoop.fs.Path;
|
||||||
|
import org.apache.hadoop.fs.PathFilter;
|
||||||
|
import org.apache.hadoop.fs.RemoteIterator;
|
||||||
|
import org.apache.hadoop.fs.permission.FsPermission;
|
||||||
|
import org.apache.hadoop.hbase.HBaseConfiguration;
|
||||||
|
import org.apache.hadoop.hbase.HConstants;
|
||||||
|
import org.apache.hadoop.hbase.HRegionInfo;
|
||||||
|
import org.apache.hadoop.hbase.ServerName;
|
||||||
|
import org.apache.hadoop.hbase.TableDescriptor;
|
||||||
|
import org.apache.hadoop.hbase.TableName;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceAudience;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceStability;
|
||||||
|
import org.apache.hadoop.hbase.client.Connection;
|
||||||
|
import org.apache.hadoop.hbase.client.ConnectionFactory;
|
||||||
|
import org.apache.hadoop.hbase.client.HBaseAdmin;
|
||||||
|
import org.apache.hadoop.hbase.regionserver.HRegion;
|
||||||
|
import org.apache.hadoop.hbase.util.FSTableDescriptors;
|
||||||
|
import org.apache.hadoop.hbase.util.FSUtils;
|
||||||
|
import org.apache.hadoop.hbase.wal.DefaultWALProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A collection for methods used by multiple classes to backup HBase tables.
|
||||||
|
*/
|
||||||
|
@InterfaceAudience.Private
|
||||||
|
@InterfaceStability.Evolving
|
||||||
|
public final class BackupUtil {
|
||||||
|
protected static final Log LOG = LogFactory.getLog(BackupUtil.class);
|
||||||
|
|
||||||
|
public static final String FIELD_SEPARATOR = "\001";
|
||||||
|
public static final String RECORD_SEPARATOR = "\002";
|
||||||
|
public static final String LOGNAME_SEPARATOR = ".";
|
||||||
|
protected static final String HDFS = "hdfs://";
|
||||||
|
protected static Configuration conf = null;
|
||||||
|
|
||||||
|
private BackupUtil(){
|
||||||
|
throw new AssertionError("Instantiating utility class...");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the configuration from a given one.
|
||||||
|
* @param newConf A new given configuration
|
||||||
|
*/
|
||||||
|
public synchronized static void setConf(Configuration newConf) {
|
||||||
|
conf = newConf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get and merge Hadoop and HBase configuration.
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
protected static Configuration getConf() {
|
||||||
|
if (conf == null) {
|
||||||
|
conf = new Configuration();
|
||||||
|
HBaseConfiguration.merge(conf, HBaseConfiguration.create());
|
||||||
|
}
|
||||||
|
return conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loop through the RS log timestamp map for the tables, for each RS, find the min timestamp value
|
||||||
|
* for the RS among the tables.
|
||||||
|
* @param rsLogTimestampMap timestamp map
|
||||||
|
* @return the min timestamp of each RS
|
||||||
|
*/
|
||||||
|
protected static HashMap<String, String> getRSLogTimestampMins(
|
||||||
|
HashMap<String, HashMap<String, String>> rsLogTimestampMap) {
|
||||||
|
|
||||||
|
if (rsLogTimestampMap == null || rsLogTimestampMap.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
HashMap<String, String> rsLogTimestamptMins = new HashMap<String, String>();
|
||||||
|
HashMap<String, HashMap<String, String>> rsLogTimestampMapByRS =
|
||||||
|
new HashMap<String, HashMap<String, String>>();
|
||||||
|
|
||||||
|
for (Entry<String, HashMap<String, String>> tableEntry : rsLogTimestampMap.entrySet()) {
|
||||||
|
String table = tableEntry.getKey();
|
||||||
|
HashMap<String, String> rsLogTimestamp = tableEntry.getValue();
|
||||||
|
for (Entry<String, String> rsEntry : rsLogTimestamp.entrySet()) {
|
||||||
|
String rs = rsEntry.getKey();
|
||||||
|
String ts = rsEntry.getValue();
|
||||||
|
if (!rsLogTimestampMapByRS.containsKey(rs)) {
|
||||||
|
rsLogTimestampMapByRS.put(rs, new HashMap<String, String>());
|
||||||
|
rsLogTimestampMapByRS.get(rs).put(table, ts);
|
||||||
|
} else {
|
||||||
|
rsLogTimestampMapByRS.get(rs).put(table, ts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String rs : rsLogTimestampMapByRS.keySet()) {
|
||||||
|
rsLogTimestamptMins.put(rs, getMinValue(rsLogTimestampMapByRS.get(rs)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return rsLogTimestamptMins;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the min value for all the Values a map.
|
||||||
|
* @param map map
|
||||||
|
* @return the min value
|
||||||
|
*/
|
||||||
|
protected static String getMinValue(HashMap<String, String> map) {
|
||||||
|
String minTimestamp = null;
|
||||||
|
if (map != null) {
|
||||||
|
ArrayList<String> timestampList = new ArrayList<String>(map.values());
|
||||||
|
Collections.sort(timestampList, new Comparator<String>() {
|
||||||
|
@Override
|
||||||
|
public int compare(String s1, String s2) {
|
||||||
|
long l1 = Long.valueOf(s1);
|
||||||
|
long l2 = Long.valueOf(s2);
|
||||||
|
if (l1 > l2) {
|
||||||
|
return 1;
|
||||||
|
} else if (l1 < l2) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// The min among all the RS log timestamps will be kept in ZK.
|
||||||
|
minTimestamp = timestampList.get(0);
|
||||||
|
}
|
||||||
|
return minTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* copy out Table RegionInfo into incremental backup image need to consider move this logic into
|
||||||
|
* HBackupFileSystem
|
||||||
|
* @param backupContext backup context
|
||||||
|
* @param conf configuration
|
||||||
|
* @throws IOException exception
|
||||||
|
* @throws InterruptedException exception
|
||||||
|
*/
|
||||||
|
protected static void copyTableRegionInfo(BackupContext backupContext, Configuration conf)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
|
|
||||||
|
Path rootDir = FSUtils.getRootDir(conf);
|
||||||
|
FileSystem fs = rootDir.getFileSystem(conf);
|
||||||
|
|
||||||
|
// for each table in the table set, copy out the table info and region info files in the correct
|
||||||
|
// directory structure
|
||||||
|
for (String table : backupContext.getTables()) {
|
||||||
|
|
||||||
|
LOG.debug("Attempting to copy table info for:" + table);
|
||||||
|
TableDescriptor orig =
|
||||||
|
FSTableDescriptors.getTableDescriptorFromFs(fs, rootDir, TableName.valueOf(table));
|
||||||
|
|
||||||
|
// write a copy of descriptor to the target directory
|
||||||
|
Path target = new Path(backupContext.getBackupStatus(table).getTargetDir());
|
||||||
|
FileSystem targetFs = target.getFileSystem(conf);
|
||||||
|
FSTableDescriptors descriptors =
|
||||||
|
new FSTableDescriptors(conf, targetFs, FSUtils.getRootDir(conf));
|
||||||
|
descriptors.createTableDescriptorForTableDirectory(target, orig, false);
|
||||||
|
LOG.debug("Finished copying tableinfo.");
|
||||||
|
|
||||||
|
HBaseAdmin hbadmin = null;
|
||||||
|
// TODO: optimize
|
||||||
|
Connection conn = null;
|
||||||
|
List<HRegionInfo> regions = null;
|
||||||
|
try {
|
||||||
|
conn = ConnectionFactory.createConnection(conf);
|
||||||
|
hbadmin = (HBaseAdmin) conn.getAdmin();
|
||||||
|
regions = hbadmin.getTableRegions(TableName.valueOf(table));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new BackupException(e);
|
||||||
|
} finally {
|
||||||
|
if (hbadmin != null) {
|
||||||
|
hbadmin.close();
|
||||||
|
}
|
||||||
|
if(conn != null){
|
||||||
|
conn.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each region, write the region info to disk
|
||||||
|
LOG.debug("Starting to write region info for table " + table);
|
||||||
|
for (HRegionInfo regionInfo : regions) {
|
||||||
|
Path regionDir =
|
||||||
|
HRegion.getRegionDir(new Path(backupContext.getBackupStatus(table).getTargetDir()),
|
||||||
|
regionInfo);
|
||||||
|
regionDir =
|
||||||
|
new Path(backupContext.getBackupStatus(table).getTargetDir(), regionDir.getName());
|
||||||
|
writeRegioninfoOnFilesystem(conf, targetFs, regionDir, regionInfo);
|
||||||
|
}
|
||||||
|
LOG.debug("Finished writing region info for table " + table);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the .regioninfo file on-disk.
|
||||||
|
*/
|
||||||
|
public static void writeRegioninfoOnFilesystem(final Configuration conf, final FileSystem fs,
|
||||||
|
final Path regionInfoDir, HRegionInfo regionInfo) throws IOException {
|
||||||
|
final byte[] content = regionInfo.toDelimitedByteArray();
|
||||||
|
Path regionInfoFile = new Path(regionInfoDir, ".regioninfo");
|
||||||
|
// First check to get the permissions
|
||||||
|
FsPermission perms = FSUtils.getFilePermissions(fs, conf, HConstants.DATA_FILE_UMASK_KEY);
|
||||||
|
// Write the RegionInfo file content
|
||||||
|
FSDataOutputStream out = FSUtils.create(conf, fs, regionInfoFile, perms, null);
|
||||||
|
try {
|
||||||
|
out.write(content);
|
||||||
|
} finally {
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: verify the code
|
||||||
|
* @param p path
|
||||||
|
* @return host name
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
protected static String parseHostFromOldLog(Path p) throws IOException {
|
||||||
|
String n = p.getName();
|
||||||
|
int idx = n.lastIndexOf(LOGNAME_SEPARATOR);
|
||||||
|
String s = URLDecoder.decode(n.substring(0, idx), "UTF8");
|
||||||
|
return ServerName.parseHostname(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String parseHostNameFromLogFile(Path p) throws IOException {
|
||||||
|
if (isArchivedLogFile(p)) {
|
||||||
|
return parseHostFromOldLog(p);
|
||||||
|
} else {
|
||||||
|
return DefaultWALProvider.getServerNameFromWALDirectoryName(p).getHostname();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isArchivedLogFile(Path p) {
|
||||||
|
String oldLog = Path.SEPARATOR + HConstants.HREGION_OLDLOGDIR_NAME + Path.SEPARATOR;
|
||||||
|
return p.toString().contains(oldLog);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return WAL file name
|
||||||
|
* @param walFileName WAL file name
|
||||||
|
* @return WAL file name
|
||||||
|
* @throws IOException exception
|
||||||
|
* @throws IllegalArgumentException exception
|
||||||
|
*/
|
||||||
|
public static String getUniqueWALFileNamePart(String walFileName) throws IOException {
|
||||||
|
return new Path(walFileName).getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return WAL file name
|
||||||
|
* @param p - WAL file path
|
||||||
|
* @return WAL file name
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public static String getUniqueWALFileNamePart(Path p) throws IOException {
|
||||||
|
return p.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given the log file, parse the timestamp from the file name. The timestamp is the last number.
|
||||||
|
* @param p a path to the log file
|
||||||
|
* @return the timestamp
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
protected static String getCreationTime(Path p, Configuration conf) throws IOException {
|
||||||
|
int idx = p.getName().lastIndexOf(LOGNAME_SEPARATOR);
|
||||||
|
if (idx < 0) {
|
||||||
|
throw new IOException("Cannot parse timestamp from path " + p);
|
||||||
|
}
|
||||||
|
String ts = p.getName().substring(idx + 1);
|
||||||
|
return ts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the total length of files under the given directory recursively.
|
||||||
|
* @param fs The hadoop file system
|
||||||
|
* @param dir The target directory
|
||||||
|
* @return the total length of files
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public static long getFilesLength(FileSystem fs, Path dir) throws IOException {
|
||||||
|
long totalLength = 0;
|
||||||
|
FileStatus[] files = FSUtils.listStatus(fs, dir);
|
||||||
|
if (files != null) {
|
||||||
|
for (FileStatus fileStatus : files) {
|
||||||
|
if (fileStatus.isDir()) {
|
||||||
|
totalLength += getFilesLength(fs, fileStatus.getPath());
|
||||||
|
} else {
|
||||||
|
totalLength += fileStatus.getLen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return totalLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep the record for dependency for incremental backup and history info p.s, we may be able to
|
||||||
|
* merge this class into backupImage class later
|
||||||
|
*/
|
||||||
|
public static class BackupCompleteData implements Comparable<BackupCompleteData> {
|
||||||
|
private String startTime;
|
||||||
|
private String endTime;
|
||||||
|
private String type;
|
||||||
|
private String backupRootPath;
|
||||||
|
private String tableList;
|
||||||
|
private String backupToken;
|
||||||
|
private String bytesCopied;
|
||||||
|
private List<String> ancestors;
|
||||||
|
private boolean fromExistingSnapshot = false;
|
||||||
|
|
||||||
|
public List<String> getAncestors() {
|
||||||
|
if (fromExistingSnapshot) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (this.ancestors == null) {
|
||||||
|
this.ancestors = new ArrayList<String>();
|
||||||
|
}
|
||||||
|
return this.ancestors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addAncestor(String backupToken) {
|
||||||
|
this.getAncestors().add(backupToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBytesCopied() {
|
||||||
|
return bytesCopied;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBytesCopied(String bytesCopied) {
|
||||||
|
this.bytesCopied = bytesCopied;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBackupToken() {
|
||||||
|
return backupToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBackupToken(String backupToken) {
|
||||||
|
this.backupToken = backupToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStartTime() {
|
||||||
|
return startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStartTime(String startTime) {
|
||||||
|
this.startTime = startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEndTime() {
|
||||||
|
return endTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEndTime(String endTime) {
|
||||||
|
this.endTime = endTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBackupRootPath() {
|
||||||
|
return backupRootPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBackupRootPath(String backupRootPath) {
|
||||||
|
this.backupRootPath = backupRootPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTableList() {
|
||||||
|
return tableList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTableList(String tableList) {
|
||||||
|
this.tableList = tableList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean fromExistingSnapshot() {
|
||||||
|
return this.fromExistingSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markFromExistingSnapshot() {
|
||||||
|
this.fromExistingSnapshot = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(BackupCompleteData o) {
|
||||||
|
Long thisTS =
|
||||||
|
new Long(this.getBackupToken().substring(this.getBackupToken().lastIndexOf("_") + 1));
|
||||||
|
Long otherTS =
|
||||||
|
new Long(o.getBackupToken().substring(o.getBackupToken().lastIndexOf("_") + 1));
|
||||||
|
return thisTS.compareTo(otherTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort history list by start time in descending order.
|
||||||
|
* @param historyList history list
|
||||||
|
* @return sorted list of BackupCompleteData
|
||||||
|
*/
|
||||||
|
public static ArrayList<BackupCompleteData> sortHistoryListDesc(
|
||||||
|
ArrayList<BackupCompleteData> historyList) {
|
||||||
|
ArrayList<BackupCompleteData> list = new ArrayList<BackupCompleteData>();
|
||||||
|
TreeMap<String, BackupCompleteData> map = new TreeMap<String, BackupCompleteData>();
|
||||||
|
for (BackupCompleteData h : historyList) {
|
||||||
|
map.put(h.getStartTime(), h);
|
||||||
|
}
|
||||||
|
Iterator<String> i = map.descendingKeySet().iterator();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
list.add(map.get(i.next()));
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get list of all WAL files (WALs and archive)
|
||||||
|
* @param c - configuration
|
||||||
|
* @return list of WAL files
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public static List<String> getListOfWALFiles(Configuration c) throws IOException {
|
||||||
|
Path rootDir = FSUtils.getRootDir(c);
|
||||||
|
Path logDir = new Path(rootDir, HConstants.HREGION_LOGDIR_NAME);
|
||||||
|
Path oldLogDir = new Path(rootDir, HConstants.HREGION_OLDLOGDIR_NAME);
|
||||||
|
List<String> logFiles = new ArrayList<String>();
|
||||||
|
|
||||||
|
FileSystem fs = FileSystem.get(c);
|
||||||
|
logFiles = getFiles(fs, logDir, logFiles, null);
|
||||||
|
logFiles = getFiles(fs, oldLogDir, logFiles, null);
|
||||||
|
return logFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get list of all WAL files (WALs and archive)
|
||||||
|
* @param c - configuration
|
||||||
|
* @return list of WAL files
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public static List<String> getListOfWALFiles(Configuration c, PathFilter filter)
|
||||||
|
throws IOException {
|
||||||
|
Path rootDir = FSUtils.getRootDir(c);
|
||||||
|
Path logDir = new Path(rootDir, HConstants.HREGION_LOGDIR_NAME);
|
||||||
|
Path oldLogDir = new Path(rootDir, HConstants.HREGION_OLDLOGDIR_NAME);
|
||||||
|
List<String> logFiles = new ArrayList<String>();
|
||||||
|
|
||||||
|
FileSystem fs = FileSystem.get(c);
|
||||||
|
logFiles = getFiles(fs, logDir, logFiles, filter);
|
||||||
|
logFiles = getFiles(fs, oldLogDir, logFiles, filter);
|
||||||
|
return logFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get list of all old WAL files (WALs and archive)
|
||||||
|
* @param c - configuration
|
||||||
|
* @return list of WAL files
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public static List<String> getWALFilesOlderThan(final Configuration c,
|
||||||
|
final HashMap<String, String> hostTimestampMap) throws IOException {
|
||||||
|
Path rootDir = FSUtils.getRootDir(c);
|
||||||
|
Path logDir = new Path(rootDir, HConstants.HREGION_LOGDIR_NAME);
|
||||||
|
Path oldLogDir = new Path(rootDir, HConstants.HREGION_OLDLOGDIR_NAME);
|
||||||
|
List<String> logFiles = new ArrayList<String>();
|
||||||
|
|
||||||
|
PathFilter filter = new PathFilter() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean accept(Path p) {
|
||||||
|
try {
|
||||||
|
if (DefaultWALProvider.isMetaFile(p)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String host = BackupUtil.parseHostNameFromLogFile(p);
|
||||||
|
String oldTimestamp = hostTimestampMap.get(host);
|
||||||
|
String currentLogTS = getCreationTime(p, c);
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("path=" + p);
|
||||||
|
LOG.debug("oldTimestamp=" + oldTimestamp);
|
||||||
|
LOG.debug("currentLogTS=" + currentLogTS);
|
||||||
|
}
|
||||||
|
return Long.parseLong(currentLogTS) <= Long.parseLong(oldTimestamp);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
FileSystem fs = FileSystem.get(c);
|
||||||
|
logFiles = getFiles(fs, logDir, logFiles, filter);
|
||||||
|
logFiles = getFiles(fs, oldLogDir, logFiles, filter);
|
||||||
|
return logFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> getFiles(FileSystem fs, Path rootDir, List<String> files,
|
||||||
|
PathFilter filter) throws FileNotFoundException, IOException {
|
||||||
|
|
||||||
|
RemoteIterator<LocatedFileStatus> it = fs.listFiles(rootDir, true);
|
||||||
|
|
||||||
|
while (it.hasNext()) {
|
||||||
|
LocatedFileStatus lfs = it.next();
|
||||||
|
if (lfs.isDirectory()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// apply filter
|
||||||
|
if (filter.accept(lfs.getPath())) {
|
||||||
|
files.add(lfs.getPath().toString());
|
||||||
|
LOG.info(lfs.getPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String concat(Collection<String> col, String separator) {
|
||||||
|
if (col.size() == 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (String s : col) {
|
||||||
|
sb.append(s + separator);
|
||||||
|
}
|
||||||
|
sb.deleteCharAt(sb.lastIndexOf(";"));
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,511 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
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.FileUtil;
|
||||||
|
import org.apache.hadoop.fs.Path;
|
||||||
|
import org.apache.hadoop.hbase.HConstants;
|
||||||
|
import org.apache.hadoop.hbase.HTableDescriptor;
|
||||||
|
import org.apache.hadoop.hbase.TableName;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceAudience;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceStability;
|
||||||
|
import org.apache.hadoop.hbase.io.HFileLink;
|
||||||
|
import org.apache.hadoop.hbase.io.hfile.CacheConfig;
|
||||||
|
import org.apache.hadoop.hbase.io.hfile.HFile;
|
||||||
|
import org.apache.hadoop.hbase.mapreduce.LoadIncrementalHFiles;
|
||||||
|
import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.SnapshotDescription;
|
||||||
|
import org.apache.hadoop.hbase.regionserver.HRegionFileSystem;
|
||||||
|
import org.apache.hadoop.hbase.regionserver.HStore;
|
||||||
|
import org.apache.hadoop.hbase.regionserver.StoreFileInfo;
|
||||||
|
import org.apache.hadoop.hbase.snapshot.SnapshotDescriptionUtils;
|
||||||
|
import org.apache.hadoop.hbase.snapshot.SnapshotManifest;
|
||||||
|
import org.apache.hadoop.hbase.util.Bytes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View to an on-disk Backup Image FileSytem
|
||||||
|
* Provides the set of methods necessary to interact with the on-disk Backup Image data.
|
||||||
|
*/
|
||||||
|
@InterfaceAudience.Private
|
||||||
|
@InterfaceStability.Evolving
|
||||||
|
public class HBackupFileSystem {
|
||||||
|
public static final Log LOG = LogFactory.getLog(HBackupFileSystem.class);
|
||||||
|
|
||||||
|
private final String RESTORE_TMP_PATH = "/tmp/restoreTemp";
|
||||||
|
private final String[] ignoreDirs = { "recovered.edits" };
|
||||||
|
|
||||||
|
private final Configuration conf;
|
||||||
|
private final FileSystem fs;
|
||||||
|
private final Path backupRootPath;
|
||||||
|
private final String backupId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a view to the on-disk Backup Image.
|
||||||
|
* @param conf to use
|
||||||
|
* @param backupPath to where the backup Image stored
|
||||||
|
* @param backupId represent backup Image
|
||||||
|
*/
|
||||||
|
HBackupFileSystem(final Configuration conf, final Path backupRootPath, final String backupId)
|
||||||
|
throws IOException {
|
||||||
|
this.conf = conf;
|
||||||
|
this.fs = backupRootPath.getFileSystem(conf);
|
||||||
|
this.backupRootPath = backupRootPath;
|
||||||
|
this.backupId = backupId; // the backup ID for the lead backup Image
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param tableName is the table backuped
|
||||||
|
* @return {@link HTableDescriptor} saved in backup image of the table
|
||||||
|
*/
|
||||||
|
protected HTableDescriptor getTableDesc(String tableName) throws FileNotFoundException,
|
||||||
|
IOException {
|
||||||
|
|
||||||
|
Path tableInfoPath = this.getTableInfoPath(tableName);
|
||||||
|
LOG.debug("tableInfoPath = " + tableInfoPath.toString());
|
||||||
|
SnapshotDescription desc = SnapshotDescriptionUtils.readSnapshotInfo(fs, tableInfoPath);
|
||||||
|
LOG.debug("desc = " + desc.getName());
|
||||||
|
SnapshotManifest manifest = SnapshotManifest.open(conf, fs, tableInfoPath, desc);
|
||||||
|
HTableDescriptor tableDescriptor = manifest.getTableDescriptor();
|
||||||
|
/*
|
||||||
|
* for HBase 0.96 or 0.98 HTableDescriptor tableDescriptor =
|
||||||
|
* FSTableDescriptors.getTableDescriptorFromFs(fs, tableInfoPath);
|
||||||
|
*/
|
||||||
|
if (!tableDescriptor.getNameAsString().equals(tableName)) {
|
||||||
|
LOG.error("couldn't find Table Desc for table: " + tableName + " under tableInfoPath: "
|
||||||
|
+ tableInfoPath.toString());
|
||||||
|
LOG.error("tableDescriptor.getNameAsString() = " + tableDescriptor.getNameAsString());
|
||||||
|
}
|
||||||
|
return tableDescriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given the backup root dir, backup id and the table name, return the backup image location,
|
||||||
|
* which is also where the backup manifest file is. return value look like:
|
||||||
|
* "hdfs://backup.hbase.org:9000/user/biadmin/backup1/default/t1_dn/backup_1396650096738"
|
||||||
|
* @param backupRootDir backup root directory
|
||||||
|
* @param backupId backup id
|
||||||
|
* @param table table name
|
||||||
|
* @return backupPath String for the particular table
|
||||||
|
*/
|
||||||
|
protected static String getTableBackupDir(String backupRootDir, String backupId, String table) {
|
||||||
|
TableName tableName = TableName.valueOf(table);
|
||||||
|
return backupRootDir + File.separator + tableName.getNamespaceAsString() + File.separator
|
||||||
|
+ tableName.getQualifierAsString() + File.separator + backupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given the backup root dir, backup id and the table name, return the backup image location,
|
||||||
|
* which is also where the backup manifest file is. return value look like:
|
||||||
|
* "hdfs://backup.hbase.org:9000/user/biadmin/backup1/default/t1_dn/backup_1396650096738"
|
||||||
|
* @param tableN table name
|
||||||
|
* @return backupPath for the particular table
|
||||||
|
*/
|
||||||
|
protected Path getTableBackupPath(String tableN) {
|
||||||
|
TableName tableName = TableName.valueOf(tableN);
|
||||||
|
return new Path(this.backupRootPath, tableName.getNamespaceAsString() + File.separator
|
||||||
|
+ tableName.getQualifierAsString() + File.separator + backupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return value represent path for:
|
||||||
|
* ".../user/biadmin/backup1/default/t1_dn/backup_1396650096738/.hbase-snapshot"
|
||||||
|
* @param tableName table name
|
||||||
|
* @return path for snapshot
|
||||||
|
*/
|
||||||
|
protected Path getTableSnapshotPath(String tableName) {
|
||||||
|
return new Path(this.getTableBackupPath(tableName), HConstants.SNAPSHOT_DIR_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return value represent path for:
|
||||||
|
* "..../default/t1_dn/backup_1396650096738/.hbase-snapshot/snapshot_1396650097621_default_t1_dn"
|
||||||
|
* this path contains .snapshotinfo, .tabledesc (0.96 and 0.98) this path contains .snapshotinfo,
|
||||||
|
* .data.manifest (trunk)
|
||||||
|
* @param tableName table name
|
||||||
|
* @return path to table info
|
||||||
|
* @throws FileNotFoundException exception
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
protected Path getTableInfoPath(String tableName) throws FileNotFoundException, IOException {
|
||||||
|
|
||||||
|
Path tableSnapShotPath = this.getTableSnapshotPath(tableName);
|
||||||
|
Path tableInfoPath = null;
|
||||||
|
|
||||||
|
// can't build the path directly as the timestamp values are different
|
||||||
|
FileStatus[] snapshots = fs.listStatus(tableSnapShotPath);
|
||||||
|
for (FileStatus snapshot : snapshots) {
|
||||||
|
tableInfoPath = snapshot.getPath();
|
||||||
|
// SnapshotManifest.DATA_MANIFEST_NAME = "data.manifest";
|
||||||
|
if (tableInfoPath.getName().endsWith("data.manifest")) {
|
||||||
|
LOG.debug("find Snapshot Manifest");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tableInfoPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return value represent path for:
|
||||||
|
* ".../user/biadmin/backup1/default/t1_dn/backup_1396650096738/archive/data/default/t1_dn"
|
||||||
|
* @param tabelName table name
|
||||||
|
* @return path to table archive
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
protected Path getTableArchivePath(String tableName) throws IOException {
|
||||||
|
Path baseDir = new Path(getTableBackupPath(tableName), HConstants.HFILE_ARCHIVE_DIRECTORY);
|
||||||
|
Path dataDir = new Path(baseDir, HConstants.BASE_NAMESPACE_DIR);
|
||||||
|
Path archivePath = new Path(dataDir, TableName.valueOf(tableName).getNamespaceAsString());
|
||||||
|
Path tableArchivePath =
|
||||||
|
new Path(archivePath, TableName.valueOf(tableName).getQualifierAsString());
|
||||||
|
if (!fs.exists(tableArchivePath) || !fs.getFileStatus(tableArchivePath).isDirectory()) {
|
||||||
|
LOG.debug("Folder tableArchivePath: " + tableArchivePath.toString() + " does not exists");
|
||||||
|
tableArchivePath = null; // empty table has no archive
|
||||||
|
}
|
||||||
|
return tableArchivePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given the backup root dir and the backup id, return the log file location for an incremental
|
||||||
|
* backup.
|
||||||
|
* @param backupRootDir backup root directory
|
||||||
|
* @param backupId backup id
|
||||||
|
* @return logBackupDir: ".../user/biadmin/backup1/WALs/backup_1396650096738"
|
||||||
|
*/
|
||||||
|
protected static String getLogBackupDir(String backupRootDir, String backupId) {
|
||||||
|
return backupRootDir + File.separator + HConstants.HREGION_LOGDIR_NAME + File.separator
|
||||||
|
+ backupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static Path getLogBackupPath(String backupRootDir, String backupId) {
|
||||||
|
return new Path(getLogBackupDir(backupRootDir, backupId));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path getManifestPath(String tableName) throws IOException {
|
||||||
|
Path manifestPath = new Path(getTableBackupPath(tableName), BackupManifest.FILE_NAME);
|
||||||
|
|
||||||
|
LOG.debug("Looking for " + manifestPath.toString());
|
||||||
|
if (!fs.exists(manifestPath)) {
|
||||||
|
// check log dir for incremental backup case
|
||||||
|
manifestPath =
|
||||||
|
new Path(getLogBackupDir(this.backupRootPath.toString(), this.backupId) + File.separator
|
||||||
|
+ BackupManifest.FILE_NAME);
|
||||||
|
LOG.debug("Looking for " + manifestPath.toString());
|
||||||
|
if (!fs.exists(manifestPath)) {
|
||||||
|
String errorMsg =
|
||||||
|
"Could not find backup manifest for " + backupId + " in " + backupRootPath.toString();
|
||||||
|
throw new IOException(errorMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return manifestPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected BackupManifest getManifest(String tableName) throws IOException {
|
||||||
|
BackupManifest manifest = new BackupManifest(conf, this.getManifestPath(tableName));
|
||||||
|
return manifest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets region list
|
||||||
|
* @param tableName table name
|
||||||
|
* @return RegionList region list
|
||||||
|
* @throws FileNotFoundException exception
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
|
||||||
|
protected ArrayList<Path> getRegionList(String tableName) throws FileNotFoundException,
|
||||||
|
IOException {
|
||||||
|
Path tableArchivePath = this.getTableArchivePath(tableName);
|
||||||
|
ArrayList<Path> regionDirList = new ArrayList<Path>();
|
||||||
|
FileStatus[] children = fs.listStatus(tableArchivePath);
|
||||||
|
for (FileStatus childStatus : children) {
|
||||||
|
// here child refer to each region(Name)
|
||||||
|
Path child = childStatus.getPath();
|
||||||
|
regionDirList.add(child);
|
||||||
|
}
|
||||||
|
return regionDirList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets region list
|
||||||
|
* @param tableArchivePath table archive path
|
||||||
|
* @return RegionList region list
|
||||||
|
* @throws FileNotFoundException exception
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
protected ArrayList<Path> getRegionList(Path tableArchivePath) throws FileNotFoundException,
|
||||||
|
IOException {
|
||||||
|
ArrayList<Path> regionDirList = new ArrayList<Path>();
|
||||||
|
FileStatus[] children = fs.listStatus(tableArchivePath);
|
||||||
|
for (FileStatus childStatus : children) {
|
||||||
|
// here child refer to each region(Name)
|
||||||
|
Path child = childStatus.getPath();
|
||||||
|
regionDirList.add(child);
|
||||||
|
}
|
||||||
|
return regionDirList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts the number of files in all subdirectories of an HBase tables, i.e. HFiles. And finds the
|
||||||
|
* maximum number of files in one HBase table.
|
||||||
|
* @param tableArchivePath archive path
|
||||||
|
* @return the maximum number of files found in 1 HBase table
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
protected int getMaxNumberOfFilesInSubDir(Path tableArchivePath) throws IOException {
|
||||||
|
int result = 1;
|
||||||
|
ArrayList<Path> regionPathList = this.getRegionList(tableArchivePath);
|
||||||
|
// tableArchivePath = this.getTableArchivePath(tableName);
|
||||||
|
|
||||||
|
if (regionPathList == null || regionPathList.size() == 0) {
|
||||||
|
throw new IllegalStateException("Cannot restore hbase table because directory '"
|
||||||
|
+ tableArchivePath + "' is not a directory.");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Path regionPath : regionPathList) {
|
||||||
|
result = Math.max(result, getNumberOfFilesInDir(regionPath));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts the number of files in all subdirectories of an HBase table, i.e. HFiles.
|
||||||
|
* @param regionPath Path to an HBase table directory
|
||||||
|
* @return the number of files all directories
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
protected int getNumberOfFilesInDir(Path regionPath) throws IOException {
|
||||||
|
int result = 0;
|
||||||
|
|
||||||
|
if (!fs.exists(regionPath) || !fs.getFileStatus(regionPath).isDirectory()) {
|
||||||
|
throw new IllegalStateException("Cannot restore hbase table because directory '"
|
||||||
|
+ regionPath.toString() + "' is not a directory.");
|
||||||
|
}
|
||||||
|
|
||||||
|
FileStatus[] tableDirContent = fs.listStatus(regionPath);
|
||||||
|
for (FileStatus subDirStatus : tableDirContent) {
|
||||||
|
FileStatus[] colFamilies = fs.listStatus(subDirStatus.getPath());
|
||||||
|
for (FileStatus colFamilyStatus : colFamilies) {
|
||||||
|
FileStatus[] colFamilyContent = fs.listStatus(colFamilyStatus.getPath());
|
||||||
|
result += colFamilyContent.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Duplicate the backup image if it's on local cluster
|
||||||
|
* @see HStore#bulkLoadHFile(String, long)
|
||||||
|
* @see HRegionFileSystem#bulkLoadStoreFile(String familyName, Path srcPath, long seqNum)
|
||||||
|
* @param tableArchivePath archive path
|
||||||
|
* @return the new tableArchivePath
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
protected Path checkLocalAndBackup(Path tableArchivePath) throws IOException {
|
||||||
|
// Move the file if it's on local cluster
|
||||||
|
boolean isCopyNeeded = false;
|
||||||
|
|
||||||
|
FileSystem srcFs = tableArchivePath.getFileSystem(conf);
|
||||||
|
FileSystem desFs = FileSystem.get(conf);
|
||||||
|
if (tableArchivePath.getName().startsWith("/")) {
|
||||||
|
isCopyNeeded = true;
|
||||||
|
} else {
|
||||||
|
// This should match what is done in @see HRegionFileSystem#bulkLoadStoreFile(String, Path,
|
||||||
|
// long)
|
||||||
|
if (srcFs.getUri().equals(desFs.getUri())) {
|
||||||
|
LOG.debug("cluster hold the backup image: " + srcFs.getUri() + "; local cluster node: "
|
||||||
|
+ desFs.getUri());
|
||||||
|
isCopyNeeded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isCopyNeeded) {
|
||||||
|
LOG.debug("File " + tableArchivePath + " on local cluster, back it up before restore");
|
||||||
|
Path tmpPath = new Path(RESTORE_TMP_PATH);
|
||||||
|
if (desFs.exists(tmpPath)) {
|
||||||
|
try {
|
||||||
|
desFs.delete(tmpPath, true);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.debug("Failed to delete path: " + tmpPath
|
||||||
|
+ ", need to check whether restore target DFS cluster is healthy");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FileUtil.copy(srcFs, tableArchivePath, desFs, tmpPath, false, conf);
|
||||||
|
LOG.debug("Copied to temporary path on local cluster: " + tmpPath);
|
||||||
|
tableArchivePath = tmpPath;
|
||||||
|
}
|
||||||
|
return tableArchivePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate region boundaries and add all the column families to the table descriptor
|
||||||
|
* @param regionDirList region dir list
|
||||||
|
* @return a set of keys to store the boundaries
|
||||||
|
*/
|
||||||
|
protected byte[][] generateBoundaryKeys(ArrayList<Path> regionDirList)
|
||||||
|
throws FileNotFoundException, IOException {
|
||||||
|
TreeMap<byte[], Integer> map = new TreeMap<byte[], Integer>(Bytes.BYTES_COMPARATOR);
|
||||||
|
// Build a set of keys to store the boundaries
|
||||||
|
byte[][] keys = null;
|
||||||
|
// calculate region boundaries and add all the column families to the table descriptor
|
||||||
|
for (Path regionDir : regionDirList) {
|
||||||
|
LOG.debug("Parsing region dir: " + regionDir);
|
||||||
|
Path hfofDir = regionDir;
|
||||||
|
|
||||||
|
if (!fs.exists(hfofDir)) {
|
||||||
|
LOG.warn("HFileOutputFormat dir " + hfofDir + " not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
FileStatus[] familyDirStatuses = fs.listStatus(hfofDir);
|
||||||
|
if (familyDirStatuses == null) {
|
||||||
|
throw new IOException("No families found in " + hfofDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (FileStatus stat : familyDirStatuses) {
|
||||||
|
if (!stat.isDirectory()) {
|
||||||
|
LOG.warn("Skipping non-directory " + stat.getPath());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
boolean isIgnore = false;
|
||||||
|
String pathName = stat.getPath().getName();
|
||||||
|
for (String ignore : ignoreDirs) {
|
||||||
|
if (pathName.contains(ignore)) {
|
||||||
|
LOG.warn("Skipping non-family directory" + pathName);
|
||||||
|
isIgnore = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isIgnore) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Path familyDir = stat.getPath();
|
||||||
|
LOG.debug("Parsing family dir [" + familyDir.toString() + " in region [" + regionDir + "]");
|
||||||
|
// Skip _logs, etc
|
||||||
|
if (familyDir.getName().startsWith("_") || familyDir.getName().startsWith(".")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// start to parse hfile inside one family dir
|
||||||
|
Path[] hfiles = FileUtil.stat2Paths(fs.listStatus(familyDir));
|
||||||
|
for (Path hfile : hfiles) {
|
||||||
|
if (hfile.getName().startsWith("_") || hfile.getName().startsWith(".")
|
||||||
|
|| StoreFileInfo.isReference(hfile.getName())
|
||||||
|
|| HFileLink.isHFileLink(hfile.getName())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
HFile.Reader reader = HFile.createReader(fs, hfile, new CacheConfig(conf), conf);
|
||||||
|
final byte[] first, last;
|
||||||
|
try {
|
||||||
|
reader.loadFileInfo();
|
||||||
|
first = reader.getFirstRowKey();
|
||||||
|
last = reader.getLastRowKey();
|
||||||
|
LOG.debug("Trying to figure out region boundaries hfile=" + hfile + " first="
|
||||||
|
+ Bytes.toStringBinary(first) + " last=" + Bytes.toStringBinary(last));
|
||||||
|
|
||||||
|
// To eventually infer start key-end key boundaries
|
||||||
|
Integer value = map.containsKey(first) ? (Integer) map.get(first) : 0;
|
||||||
|
map.put(first, value + 1);
|
||||||
|
value = map.containsKey(last) ? (Integer) map.get(last) : 0;
|
||||||
|
map.put(last, value - 1);
|
||||||
|
} finally {
|
||||||
|
reader.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
keys = LoadIncrementalHFiles.inferBoundaries(map);
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the backup path exist
|
||||||
|
* @param backupStr backup
|
||||||
|
* @param conf configuration
|
||||||
|
* @return Yes if path exists
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
protected static boolean checkPathExist(String backupStr, Configuration conf)
|
||||||
|
throws IOException {
|
||||||
|
boolean isExist = false;
|
||||||
|
Path backupPath = new Path(backupStr);
|
||||||
|
FileSystem fileSys = backupPath.getFileSystem(conf);
|
||||||
|
String targetFsScheme = fileSys.getUri().getScheme();
|
||||||
|
LOG.debug("Schema of given url: " + backupStr + " is: " + targetFsScheme);
|
||||||
|
if (fileSys.exists(backupPath)) {
|
||||||
|
isExist = true;
|
||||||
|
}
|
||||||
|
return isExist;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the backup image path and there is manifest file in the path.
|
||||||
|
* @param backupManifestMap If all the manifests are found, then they are put into this map
|
||||||
|
* @param tableArray the tables involved
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
protected void checkImageManifestExist(HashMap<String, BackupManifest> backupManifestMap,
|
||||||
|
String[] tableArray) throws IOException {
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (String tableName : tableArray) {
|
||||||
|
BackupManifest manifest = this.getManifest(tableName);
|
||||||
|
backupManifestMap.put(tableName, manifest);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
String expMsg = e.getMessage();
|
||||||
|
if (expMsg.contains("No FileSystem for scheme")) {
|
||||||
|
if (expMsg.contains("gpfs")) {
|
||||||
|
LOG.error("Please change to use webhdfs url when "
|
||||||
|
+ "the backup image to restore locates on gpfs cluster");
|
||||||
|
} else {
|
||||||
|
LOG.error("Unsupported filesystem scheme found in the backup target url, "
|
||||||
|
+ "please check the url to make sure no typo in it");
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
} else if (expMsg.contains("no authority supported")) {
|
||||||
|
LOG.error("Please change to use webhdfs url when "
|
||||||
|
+ "the backup image to restore locates on gpfs cluster");
|
||||||
|
throw e;
|
||||||
|
} else {
|
||||||
|
LOG.error(expMsg);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String join(String[] names) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
String sep = BackupRestoreConstants.TABLENAME_DELIMITER_IN_COMMAND;
|
||||||
|
for (String s : names) {
|
||||||
|
sb.append(sep).append(s);
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,269 @@
|
||||||
|
/**
|
||||||
|
* 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.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
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.FileStatus;
|
||||||
|
import org.apache.hadoop.fs.FileSystem;
|
||||||
|
import org.apache.hadoop.fs.Path;
|
||||||
|
import org.apache.hadoop.fs.PathFilter;
|
||||||
|
import org.apache.hadoop.hbase.HConstants;
|
||||||
|
import org.apache.hadoop.hbase.backup.master.LogRollMasterProcedureManager;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceAudience;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceStability;
|
||||||
|
import org.apache.hadoop.hbase.client.Connection;
|
||||||
|
import org.apache.hadoop.hbase.client.ConnectionFactory;
|
||||||
|
import org.apache.hadoop.hbase.client.HBaseAdmin;
|
||||||
|
import org.apache.hadoop.hbase.util.FSUtils;
|
||||||
|
import org.apache.hadoop.hbase.wal.DefaultWALProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* After a full backup was created, the incremental backup will only store the changes made
|
||||||
|
* after the last full or incremental backup.
|
||||||
|
*
|
||||||
|
* Creating the backup copies the logfiles in .logs and .oldlogs since the last backup timestamp.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@InterfaceAudience.Public
|
||||||
|
@InterfaceStability.Evolving
|
||||||
|
public class IncrementalBackupManager {
|
||||||
|
// parent manager
|
||||||
|
private BackupManager backupManager;
|
||||||
|
|
||||||
|
public static final Log LOG = LogFactory.getLog(IncrementalBackupManager.class);
|
||||||
|
|
||||||
|
public IncrementalBackupManager(BackupManager bm) {
|
||||||
|
this.backupManager = bm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtain the list of logs that need to be copied out for this incremental backup. The list is set
|
||||||
|
* in BackupContext.
|
||||||
|
* @param backupContext backup context
|
||||||
|
* @return The new HashMap of RS log timestamps after the log roll for this incremental backup.
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public HashMap<String, String> getIncrBackupLogFileList(BackupContext backupContext)
|
||||||
|
throws IOException {
|
||||||
|
List<String> logList;
|
||||||
|
HashMap<String, String> newTimestamps;
|
||||||
|
HashMap<String, String> previousTimestampMins;
|
||||||
|
|
||||||
|
Configuration conf = BackupUtil.getConf();
|
||||||
|
String savedStartCode = backupManager.readBackupStartCode();
|
||||||
|
|
||||||
|
// key: tableName
|
||||||
|
// value: <RegionServer,PreviousTimeStamp>
|
||||||
|
HashMap<String, HashMap<String, String>> previousTimestampMap =
|
||||||
|
backupManager.readLogTimestampMap();
|
||||||
|
|
||||||
|
previousTimestampMins = BackupUtil.getRSLogTimestampMins(previousTimestampMap);
|
||||||
|
|
||||||
|
LOG.debug("StartCode " + savedStartCode + "for backupID " + backupContext.getBackupId());
|
||||||
|
LOG.debug("Timestamps " + previousTimestampMap);
|
||||||
|
// get all new log files from .logs and .oldlogs after last TS and before new timestamp
|
||||||
|
if (savedStartCode == null ||
|
||||||
|
previousTimestampMins == null ||
|
||||||
|
previousTimestampMins.isEmpty()) {
|
||||||
|
throw new IOException("Cannot read any previous back up timestamps from hbase:backup. "
|
||||||
|
+ "In order to create an incremental backup, at least one full backup is needed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
HBaseAdmin hbadmin = null;
|
||||||
|
Connection conn = null;
|
||||||
|
try {
|
||||||
|
LOG.info("Execute roll log procedure for incremental backup ...");
|
||||||
|
conn = ConnectionFactory.createConnection(conf);
|
||||||
|
hbadmin = (HBaseAdmin) conn.getAdmin();
|
||||||
|
hbadmin.execProcedure(LogRollMasterProcedureManager.ROLLLOG_PROCEDURE_SIGNATURE,
|
||||||
|
LogRollMasterProcedureManager.ROLLLOG_PROCEDURE_NAME, new HashMap<String, String>());
|
||||||
|
} finally {
|
||||||
|
if (hbadmin != null) {
|
||||||
|
hbadmin.close();
|
||||||
|
}
|
||||||
|
if(conn != null){
|
||||||
|
conn.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newTimestamps = backupManager.readRegionServerLastLogRollResult();
|
||||||
|
|
||||||
|
logList = getLogFilesForNewBackup(previousTimestampMins, newTimestamps, conf, savedStartCode);
|
||||||
|
|
||||||
|
backupContext.setIncrBackupFileList(logList);
|
||||||
|
|
||||||
|
return newTimestamps;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For each region server: get all log files newer than the last timestamps but not newer than the
|
||||||
|
* newest timestamps.
|
||||||
|
* @param olderTimestamps the timestamp for each region server of the last backup.
|
||||||
|
* @param newestTimestamps the timestamp for each region server that the backup should lead to.
|
||||||
|
* @param conf the Hadoop and Hbase configuration
|
||||||
|
* @param savedStartCode the startcode (timestamp) of last successful backup.
|
||||||
|
* @return a list of log files to be backed up
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
private List<String> getLogFilesForNewBackup(HashMap<String, String> olderTimestamps,
|
||||||
|
HashMap<String, String> newestTimestamps, Configuration conf, String savedStartCode)
|
||||||
|
throws IOException {
|
||||||
|
LOG.debug("In getLogFilesForNewBackup()\n" + "olderTimestamps: " + olderTimestamps
|
||||||
|
+ "\n newestTimestamps: " + newestTimestamps);
|
||||||
|
Path rootdir = FSUtils.getRootDir(conf);
|
||||||
|
Path logDir = new Path(rootdir, HConstants.HREGION_LOGDIR_NAME);
|
||||||
|
Path oldLogDir = new Path(rootdir, HConstants.HREGION_OLDLOGDIR_NAME);
|
||||||
|
FileSystem fs = rootdir.getFileSystem(conf);
|
||||||
|
NewestLogFilter pathFilter = new NewestLogFilter(conf);
|
||||||
|
|
||||||
|
List<String> resultLogFiles = new ArrayList<String>();
|
||||||
|
List<String> newestLogs = new ArrayList<String>();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The old region servers and timestamps info we kept in hbase:backup may be out of sync if new
|
||||||
|
* region server is added or existing one lost. We'll deal with it here when processing the
|
||||||
|
* logs. If data in hbase:backup has more hosts, just ignore it. If the .logs directory includes
|
||||||
|
* more hosts, the additional hosts will not have old timestamps to compare with. We'll just use
|
||||||
|
* all the logs in that directory. We always write up-to-date region server and timestamp info
|
||||||
|
* to hbase:backup at the end of successful backup.
|
||||||
|
*/
|
||||||
|
|
||||||
|
FileStatus[] rss;
|
||||||
|
Path p;
|
||||||
|
String host;
|
||||||
|
String oldTimeStamp;
|
||||||
|
String currentLogFile;
|
||||||
|
String currentLogTS;
|
||||||
|
|
||||||
|
// Get the files in .logs.
|
||||||
|
rss = fs.listStatus(logDir);
|
||||||
|
for (FileStatus rs : rss) {
|
||||||
|
p = rs.getPath();
|
||||||
|
host = DefaultWALProvider.getServerNameFromWALDirectoryName(p).getHostname();
|
||||||
|
FileStatus[] logs;
|
||||||
|
oldTimeStamp = olderTimestamps.get(host);
|
||||||
|
// It is possible that there is no old timestamp in hbase:backup for this host if
|
||||||
|
// this region server is newly added after our last backup.
|
||||||
|
if (oldTimeStamp == null) {
|
||||||
|
logs = fs.listStatus(p);
|
||||||
|
} else {
|
||||||
|
pathFilter.setLastBackupTS(oldTimeStamp);
|
||||||
|
logs = fs.listStatus(p, pathFilter);
|
||||||
|
}
|
||||||
|
for (FileStatus log : logs) {
|
||||||
|
LOG.debug("currentLogFile: " + log.getPath().toString());
|
||||||
|
if (DefaultWALProvider.isMetaFile(log.getPath())) {
|
||||||
|
LOG.debug("Skip hbase:meta log file: " + log.getPath().getName());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
currentLogFile = log.getPath().toString();
|
||||||
|
resultLogFiles.add(currentLogFile);
|
||||||
|
currentLogTS = BackupUtil.getCreationTime(log.getPath(), conf);
|
||||||
|
// newestTimestamps is up-to-date with the current list of hosts
|
||||||
|
// so newestTimestamps.get(host) will not be null.
|
||||||
|
if (Long.valueOf(currentLogTS) > Long.valueOf(newestTimestamps.get(host))) {
|
||||||
|
newestLogs.add(currentLogFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Include the .oldlogs files too.
|
||||||
|
FileStatus[] oldlogs = fs.listStatus(oldLogDir);
|
||||||
|
for (FileStatus oldlog : oldlogs) {
|
||||||
|
p = oldlog.getPath();
|
||||||
|
currentLogFile = p.toString();
|
||||||
|
if (DefaultWALProvider.isMetaFile(p)) {
|
||||||
|
LOG.debug("Skip .meta log file: " + currentLogFile);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
host = BackupUtil.parseHostFromOldLog(p);
|
||||||
|
currentLogTS = BackupUtil.getCreationTime(p, conf);
|
||||||
|
oldTimeStamp = olderTimestamps.get(host);
|
||||||
|
/*
|
||||||
|
* It is possible that there is no old timestamp in hbase:backup for this host. At the time of
|
||||||
|
* our last backup operation, this rs did not exist. The reason can be one of the two: 1. The
|
||||||
|
* rs already left/crashed. Its logs were moved to .oldlogs. 2. The rs was added after our
|
||||||
|
* last backup.
|
||||||
|
*/
|
||||||
|
if (oldTimeStamp == null) {
|
||||||
|
if (Long.valueOf(currentLogTS) < Long.valueOf(savedStartCode)) {
|
||||||
|
// This log file is really old, its region server was before our last backup.
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
resultLogFiles.add(currentLogFile);
|
||||||
|
}
|
||||||
|
} else if (Long.valueOf(currentLogTS) > Long.valueOf(oldTimeStamp)) {
|
||||||
|
resultLogFiles.add(currentLogFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debug("resultLogFiles before removal of newestLogs: " + resultLogFiles);
|
||||||
|
// It is possible that a host in .oldlogs is an obsolete region server
|
||||||
|
// so newestTimestamps.get(host) here can be null.
|
||||||
|
// Even if these logs belong to a obsolete region server, we still need
|
||||||
|
// to include they to avoid loss of edits for backup.
|
||||||
|
String newTimestamp = newestTimestamps.get(host);
|
||||||
|
if (newTimestamp != null && Long.valueOf(currentLogTS) > Long.valueOf(newTimestamp)) {
|
||||||
|
newestLogs.add(currentLogFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG.debug("newestLogs: " + newestLogs);
|
||||||
|
// remove newest log per host because they are still in use
|
||||||
|
resultLogFiles.removeAll(newestLogs);
|
||||||
|
LOG.debug("resultLogFiles after removal of newestLogs: " + resultLogFiles);
|
||||||
|
return resultLogFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
class NewestLogFilter implements PathFilter {
|
||||||
|
private String lastBackupTS = "0";
|
||||||
|
final private Configuration conf;
|
||||||
|
|
||||||
|
public NewestLogFilter(Configuration conf) {
|
||||||
|
this.conf = conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setLastBackupTS(String ts) {
|
||||||
|
this.lastBackupTS = ts;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean accept(Path path) {
|
||||||
|
// skip meta table log -- ts.meta file
|
||||||
|
if (DefaultWALProvider.isMetaFile(path)) {
|
||||||
|
LOG.debug("Skip .meta log file: " + path.getName());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String timestamp;
|
||||||
|
try {
|
||||||
|
timestamp = BackupUtil.getCreationTime(path, conf);
|
||||||
|
return Long.valueOf(timestamp) > Long.valueOf(lastBackupTS);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.warn("Cannot read timestamp of log file " + path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
/**
|
||||||
|
* 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.io.IOException;
|
||||||
|
|
||||||
|
import org.apache.hadoop.conf.Configurable;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceAudience;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceStability;
|
||||||
|
|
||||||
|
@InterfaceAudience.Private
|
||||||
|
@InterfaceStability.Evolving
|
||||||
|
public interface IncrementalRestoreService extends Configurable{
|
||||||
|
|
||||||
|
public void run(String logDirectory, String[] fromTables, String[] toTables)
|
||||||
|
throws IOException;
|
||||||
|
}
|
|
@ -0,0 +1,496 @@
|
||||||
|
/**
|
||||||
|
* 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.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
import org.apache.commons.cli.CommandLine;
|
||||||
|
import org.apache.commons.cli.Options;
|
||||||
|
import org.apache.commons.cli.ParseException;
|
||||||
|
import org.apache.commons.cli.PosixParser;
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
import org.apache.hadoop.fs.Path;
|
||||||
|
import org.apache.hadoop.hbase.HBaseConfiguration;
|
||||||
|
import org.apache.hadoop.hbase.TableName;
|
||||||
|
import org.apache.hadoop.hbase.backup.BackupManifest.BackupImage;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceAudience;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceStability;
|
||||||
|
import org.apache.hadoop.hbase.client.Connection;
|
||||||
|
import org.apache.hadoop.hbase.client.ConnectionFactory;
|
||||||
|
import org.apache.hadoop.hbase.client.HBaseAdmin;
|
||||||
|
import org.apache.log4j.Level;
|
||||||
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main class which interprets the given arguments and trigger restore operation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@InterfaceAudience.Public
|
||||||
|
@InterfaceStability.Evolving
|
||||||
|
public final class RestoreClient {
|
||||||
|
|
||||||
|
private static final Log LOG = LogFactory.getLog(RestoreClient.class);
|
||||||
|
|
||||||
|
private static Options opt;
|
||||||
|
private static Configuration conf;
|
||||||
|
private static Set<BackupImage> lastRestoreImagesSet;
|
||||||
|
|
||||||
|
// delimiter in tablename list in restore command
|
||||||
|
private static final String DELIMITER_IN_COMMAND = ",";
|
||||||
|
|
||||||
|
private static final String OPTION_OVERWRITE = "overwrite";
|
||||||
|
private static final String OPTION_CHECK = "check";
|
||||||
|
private static final String OPTION_AUTOMATIC = "automatic";
|
||||||
|
|
||||||
|
private static final String USAGE =
|
||||||
|
"Usage: hbase restore <backup_root_path> <backup_id> <tables> [tableMapping] \n"
|
||||||
|
+ " [-overwrite] [-check] [-automatic]\n"
|
||||||
|
+ " backup_root_path The parent location where the backup images are stored\n"
|
||||||
|
+ " backup_id The id identifying the backup image\n"
|
||||||
|
+ " table(s) Table(s) from the backup image to be restored.\n"
|
||||||
|
+ " Tables are separated by comma.\n"
|
||||||
|
+ " Options:\n"
|
||||||
|
+ " tableMapping A comma separated list of target tables.\n"
|
||||||
|
+ " If specified, each table in <tables> must have a mapping.\n"
|
||||||
|
+ " -overwrite With this option, restore overwrites to the existing table "
|
||||||
|
+ "if there's any in\n"
|
||||||
|
+ " restore target. The existing table must be online before restore.\n"
|
||||||
|
+ " -check With this option, restore sequence and dependencies are checked\n"
|
||||||
|
+ " and verified without executing the restore\n"
|
||||||
|
+ " -automatic With this option, all the dependencies are automatically restored\n"
|
||||||
|
+ " together with this backup image following the correct order.\n"
|
||||||
|
+ " The restore dependencies can be checked by using \"-check\" "
|
||||||
|
+ "option,\n"
|
||||||
|
+ " or using \"hbase backup describe\" command. Without this option, "
|
||||||
|
+ "only\n" + " this backup image is restored\n";
|
||||||
|
|
||||||
|
private RestoreClient(){
|
||||||
|
throw new AssertionError("Instantiating utility class...");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void init() throws IOException {
|
||||||
|
// define supported options
|
||||||
|
opt = new Options();
|
||||||
|
opt.addOption(OPTION_OVERWRITE, false,
|
||||||
|
"Overwrite the data if any of the restore target tables exists");
|
||||||
|
opt.addOption(OPTION_CHECK, false, "Check restore sequence and dependencies");
|
||||||
|
opt.addOption(OPTION_AUTOMATIC, false, "Restore all dependencies");
|
||||||
|
opt.addOption("debug", false, "Enable debug logging");
|
||||||
|
|
||||||
|
conf = getConf();
|
||||||
|
|
||||||
|
// disable irrelevant loggers to avoid it mess up command output
|
||||||
|
disableUselessLoggers();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws IOException {
|
||||||
|
init();
|
||||||
|
parseAndRun(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void parseAndRun(String[] args) {
|
||||||
|
CommandLine cmd = null;
|
||||||
|
try {
|
||||||
|
cmd = new PosixParser().parse(opt, args);
|
||||||
|
} catch (ParseException e) {
|
||||||
|
LOG.error("Could not parse command", e);
|
||||||
|
System.exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// enable debug logging
|
||||||
|
Logger backupClientLogger = Logger.getLogger("org.apache.hadoop.hbase.backup");
|
||||||
|
if (cmd.hasOption("debug")) {
|
||||||
|
backupClientLogger.setLevel(Level.DEBUG);
|
||||||
|
}
|
||||||
|
|
||||||
|
// whether to overwrite to existing table if any, false by default
|
||||||
|
boolean isOverwrite = cmd.hasOption(OPTION_OVERWRITE);
|
||||||
|
if (isOverwrite) {
|
||||||
|
LOG.debug("Found -overwrite option in restore command, "
|
||||||
|
+ "will overwrite to existing table if any in the restore target");
|
||||||
|
}
|
||||||
|
|
||||||
|
// whether to only check the dependencies, false by default
|
||||||
|
boolean check = cmd.hasOption(OPTION_CHECK);
|
||||||
|
if (check) {
|
||||||
|
LOG.debug("Found -check option in restore command, "
|
||||||
|
+ "will check and verify the dependencies");
|
||||||
|
}
|
||||||
|
|
||||||
|
// whether to restore all dependencies, false by default
|
||||||
|
boolean autoRestore = cmd.hasOption(OPTION_AUTOMATIC);
|
||||||
|
if (autoRestore) {
|
||||||
|
LOG.debug("Found -automatic option in restore command, "
|
||||||
|
+ "will automatically retore all the dependencies");
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse main restore command options
|
||||||
|
String[] remainArgs = cmd.getArgs();
|
||||||
|
if (remainArgs.length < 3) {
|
||||||
|
System.out.println("ERROR: missing arguments");
|
||||||
|
System.out.println(USAGE);
|
||||||
|
System.exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
String backupRootDir = remainArgs[0];
|
||||||
|
String backupId = remainArgs[1];
|
||||||
|
String tables = remainArgs[2];
|
||||||
|
|
||||||
|
String tableMapping = (remainArgs.length > 3) ? remainArgs[3] : null;
|
||||||
|
|
||||||
|
String[] sTableArray = (tables != null) ? tables.split(DELIMITER_IN_COMMAND) : null;
|
||||||
|
String[] tTableArray = (tableMapping != null) ? tableMapping.split(DELIMITER_IN_COMMAND) : null;
|
||||||
|
|
||||||
|
if (tableMapping != null && tTableArray != null && (sTableArray.length != tTableArray.length)) {
|
||||||
|
System.err.println("ERROR: table mapping mismatch: " + tables + " : " + tableMapping);
|
||||||
|
System.out.println(USAGE);
|
||||||
|
System.exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
HBackupFileSystem hBackupFS = new HBackupFileSystem(conf, new Path(backupRootDir), backupId);
|
||||||
|
restore_stage1(hBackupFS, backupRootDir, backupId, check, autoRestore, sTableArray,
|
||||||
|
tTableArray, isOverwrite);
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println("ERROR: " + e.getMessage());
|
||||||
|
System.exit(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore operation. Stage 1: validate backupManifest, and check target tables
|
||||||
|
* @param hBackupFS to access the backup image
|
||||||
|
* @param backupRootDir The root dir for backup image
|
||||||
|
* @param backupId The backup id for image to be restored
|
||||||
|
* @param check True if only do dependency check
|
||||||
|
* @param autoRestore True if automatically restore following the dependency
|
||||||
|
* @param sTableArray The array of tables to be restored
|
||||||
|
* @param tTableArray The array of mapping tables to restore to
|
||||||
|
* @param isOverwrite True then do restore overwrite if target table exists, otherwise fail the
|
||||||
|
* request if target table exists
|
||||||
|
* @return True if only do dependency check
|
||||||
|
* @throws IOException if any failure during restore
|
||||||
|
*/
|
||||||
|
public static boolean restore_stage1(HBackupFileSystem hBackupFS, String backupRootDir,
|
||||||
|
String backupId, boolean check, boolean autoRestore, String[] sTableArray,
|
||||||
|
String[] tTableArray, boolean isOverwrite) throws IOException {
|
||||||
|
|
||||||
|
HashMap<String, BackupManifest> backupManifestMap = new HashMap<String, BackupManifest>();
|
||||||
|
// check and load backup image manifest for the tables
|
||||||
|
hBackupFS.checkImageManifestExist(backupManifestMap, sTableArray);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check and validate the backup image and its dependencies
|
||||||
|
if (check || autoRestore) {
|
||||||
|
if (validate(backupManifestMap)) {
|
||||||
|
LOG.info("Checking backup images: ok");
|
||||||
|
} else {
|
||||||
|
String errMsg = "Some dependencies are missing for restore";
|
||||||
|
LOG.error(errMsg);
|
||||||
|
throw new IOException(errMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return true if only for check
|
||||||
|
if (check) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tTableArray == null) {
|
||||||
|
tTableArray = sTableArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the target tables
|
||||||
|
checkTargetTables(tTableArray, isOverwrite);
|
||||||
|
|
||||||
|
// start restore process
|
||||||
|
Set<BackupImage> restoreImageSet =
|
||||||
|
restore_stage2(hBackupFS, backupManifestMap, sTableArray, tTableArray, autoRestore);
|
||||||
|
|
||||||
|
LOG.info("Restore for " + Arrays.asList(sTableArray) + " are successful!");
|
||||||
|
lastRestoreImagesSet = restoreImageSet;
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error("ERROR: restore failed with error: " + e.getMessage());
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
// not only for check, return false
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get last restore image set. The value is globally set for the latest finished restore.
|
||||||
|
* @return the last restore image set
|
||||||
|
*/
|
||||||
|
public static Set<BackupImage> getLastRestoreImagesSet() {
|
||||||
|
return lastRestoreImagesSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean validate(HashMap<String, BackupManifest> backupManifestMap)
|
||||||
|
throws IOException {
|
||||||
|
boolean isValid = true;
|
||||||
|
|
||||||
|
for (Entry<String, BackupManifest> manifestEntry : backupManifestMap.entrySet()) {
|
||||||
|
|
||||||
|
String table = manifestEntry.getKey();
|
||||||
|
TreeSet<BackupImage> imageSet = new TreeSet<BackupImage>();
|
||||||
|
|
||||||
|
ArrayList<BackupImage> depList = manifestEntry.getValue().getDependentListByTable(table);
|
||||||
|
if (depList != null && !depList.isEmpty()) {
|
||||||
|
imageSet.addAll(depList);
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo merge
|
||||||
|
LOG.debug("merge will be implemented in future jira");
|
||||||
|
// BackupUtil.clearMergedImages(table, imageSet, conf);
|
||||||
|
|
||||||
|
LOG.info("Dependent image(s) from old to new:");
|
||||||
|
for (BackupImage image : imageSet) {
|
||||||
|
String imageDir =
|
||||||
|
HBackupFileSystem.getTableBackupDir(image.getRootDir(), image.getBackupId(), table);
|
||||||
|
if (!HBackupFileSystem.checkPathExist(imageDir, getConf())) {
|
||||||
|
LOG.error("ERROR: backup image does not exist: " + imageDir);
|
||||||
|
isValid = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// TODO More validation?
|
||||||
|
LOG.info("Backup image: " + image.getBackupId() + " for '" + table + "' is available");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate target Tables
|
||||||
|
* @param tTableArray: target tables
|
||||||
|
* @param isOverwrite overwrite existing table
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
private static void checkTargetTables(String[] tTableArray, boolean isOverwrite)
|
||||||
|
throws IOException {
|
||||||
|
ArrayList<String> existTableList = new ArrayList<String>();
|
||||||
|
ArrayList<String> disabledTableList = new ArrayList<String>();
|
||||||
|
|
||||||
|
// check if the tables already exist
|
||||||
|
HBaseAdmin admin = null;
|
||||||
|
Connection conn = null;
|
||||||
|
try {
|
||||||
|
conn = ConnectionFactory.createConnection(conf);
|
||||||
|
admin = (HBaseAdmin) conn.getAdmin();
|
||||||
|
for (String tableName : tTableArray) {
|
||||||
|
if (admin.tableExists(TableName.valueOf(tableName))) {
|
||||||
|
existTableList.add(tableName);
|
||||||
|
if (admin.isTableDisabled(TableName.valueOf(tableName))) {
|
||||||
|
disabledTableList.add(tableName);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG.info("HBase table " + tableName
|
||||||
|
+ " does not exist. It will be create during backup process");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (admin != null) {
|
||||||
|
admin.close();
|
||||||
|
}
|
||||||
|
if (conn != null) {
|
||||||
|
conn.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existTableList.size() > 0) {
|
||||||
|
if (!isOverwrite) {
|
||||||
|
LOG.error("Existing table found in the restore target, please add \"-overwrite\" "
|
||||||
|
+ "option in the command if you mean to restore to these existing tables");
|
||||||
|
LOG.info("Existing table list in restore target: " + existTableList);
|
||||||
|
throw new IOException("Existing table found in target while no \"-overwrite\" "
|
||||||
|
+ "option found");
|
||||||
|
} else {
|
||||||
|
if (disabledTableList.size() > 0) {
|
||||||
|
LOG.error("Found offline table in the restore target, "
|
||||||
|
+ "please enable them before restore with \"-overwrite\" option");
|
||||||
|
LOG.info("Offline table list in restore target: " + disabledTableList);
|
||||||
|
throw new IOException(
|
||||||
|
"Found offline table in the target when restore with \"-overwrite\" option");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore operation. Stage 2: resolved Backup Image dependency
|
||||||
|
* @param hBackupFS to access the backup image
|
||||||
|
* @param backupManifestMap : tableName, Manifest
|
||||||
|
* @param sTableArray The array of tables to be restored
|
||||||
|
* @param tTableArray The array of mapping tables to restore to
|
||||||
|
* @param autoRestore : yes, restore all the backup images on the dependency list
|
||||||
|
* @return set of BackupImages restored
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
private static Set<BackupImage> restore_stage2(HBackupFileSystem hBackupFS,
|
||||||
|
HashMap<String, BackupManifest> backupManifestMap, String[] sTableArray,
|
||||||
|
String[] tTableArray, boolean autoRestore) throws IOException {
|
||||||
|
TreeSet<BackupImage> restoreImageSet = new TreeSet<BackupImage>();
|
||||||
|
|
||||||
|
for (int i = 0; i < sTableArray.length; i++) {
|
||||||
|
restoreImageSet.clear();
|
||||||
|
String table = sTableArray[i];
|
||||||
|
BackupManifest manifest = backupManifestMap.get(table);
|
||||||
|
if (autoRestore) {
|
||||||
|
// Get the image list of this backup for restore in time order from old
|
||||||
|
// to new.
|
||||||
|
TreeSet<BackupImage> restoreList =
|
||||||
|
new TreeSet<BackupImage>(manifest.getDependentListByTable(table));
|
||||||
|
LOG.debug("need to clear merged Image. to be implemented in future jira");
|
||||||
|
|
||||||
|
for (BackupImage image : restoreList) {
|
||||||
|
restoreImage(image, table, tTableArray[i]);
|
||||||
|
}
|
||||||
|
restoreImageSet.addAll(restoreList);
|
||||||
|
} else {
|
||||||
|
BackupImage image = manifest.getBackupImage();
|
||||||
|
List<BackupImage> depList = manifest.getDependentListByTable(table);
|
||||||
|
// The dependency list always contains self.
|
||||||
|
if (depList != null && depList.size() > 1) {
|
||||||
|
LOG.warn("Backup image " + image.getBackupId() + " depends on other images.\n"
|
||||||
|
+ "this operation will only restore the delta contained within backupImage "
|
||||||
|
+ image.getBackupId());
|
||||||
|
}
|
||||||
|
restoreImage(image, table, tTableArray[i]);
|
||||||
|
restoreImageSet.add(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (autoRestore) {
|
||||||
|
if (restoreImageSet != null && !restoreImageSet.isEmpty()) {
|
||||||
|
LOG.info("Restore includes the following image(s):");
|
||||||
|
for (BackupImage image : restoreImageSet) {
|
||||||
|
LOG.info(" Backup: "
|
||||||
|
+ image.getBackupId()
|
||||||
|
+ " "
|
||||||
|
+ HBackupFileSystem.getTableBackupDir(image.getRootDir(), image.getBackupId(),
|
||||||
|
table));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return restoreImageSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore operation handle each backupImage
|
||||||
|
* @param image: backupImage
|
||||||
|
* @param sTable: table to be restored
|
||||||
|
* @param tTable: table to be restored to
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
private static void restoreImage(BackupImage image, String sTable, String tTable)
|
||||||
|
throws IOException {
|
||||||
|
|
||||||
|
Configuration conf = getConf();
|
||||||
|
|
||||||
|
String rootDir = image.getRootDir();
|
||||||
|
LOG.debug("Image root dir " + rootDir);
|
||||||
|
String backupId = image.getBackupId();
|
||||||
|
|
||||||
|
HBackupFileSystem hFS = new HBackupFileSystem(conf, new Path(rootDir), backupId);
|
||||||
|
RestoreUtil restoreTool = new RestoreUtil(conf, hFS);
|
||||||
|
BackupManifest manifest = hFS.getManifest(sTable);
|
||||||
|
|
||||||
|
Path tableBackupPath = hFS.getTableBackupPath(sTable);
|
||||||
|
|
||||||
|
// todo: convert feature will be provided in a future jira
|
||||||
|
boolean converted = false;
|
||||||
|
|
||||||
|
if (manifest.getType().equals(BackupRestoreConstants.BACKUP_TYPE_FULL) || converted) {
|
||||||
|
LOG.info("Restoring '" + sTable + "' to '" + tTable + "' from "
|
||||||
|
+ (converted ? "converted" : "full") + " backup image " + tableBackupPath.toString());
|
||||||
|
restoreTool.fullRestoreTable(tableBackupPath, sTable, tTable, converted);
|
||||||
|
} else { // incremental Backup
|
||||||
|
String logBackupDir =
|
||||||
|
HBackupFileSystem.getLogBackupDir(image.getRootDir(), image.getBackupId());
|
||||||
|
LOG.info("Restoring '" + sTable + "' to '" + tTable + "' from incremental backup image "
|
||||||
|
+ logBackupDir);
|
||||||
|
restoreTool.incrementalRestoreTable(logBackupDir, new String[] { sTable },
|
||||||
|
new String[] { tTable });
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.info(sTable + " has been successfully restored to " + tTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the configuration from a given one.
|
||||||
|
* @param newConf A new given configuration
|
||||||
|
*/
|
||||||
|
public synchronized static void setConf(Configuration newConf) {
|
||||||
|
conf = newConf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get and merge Hadoop and HBase configuration.
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
protected static Configuration getConf() {
|
||||||
|
if (conf == null) {
|
||||||
|
synchronized (RestoreClient.class) {
|
||||||
|
conf = new Configuration();
|
||||||
|
HBaseConfiguration.merge(conf, HBaseConfiguration.create());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void disableUselessLoggers() {
|
||||||
|
// disable zookeeper log to avoid it mess up command output
|
||||||
|
Logger zkLogger = Logger.getLogger("org.apache.zookeeper");
|
||||||
|
LOG.debug("Zookeeper log level before set: " + zkLogger.getLevel());
|
||||||
|
zkLogger.setLevel(Level.OFF);
|
||||||
|
LOG.debug("Zookeeper log level after set: " + zkLogger.getLevel());
|
||||||
|
|
||||||
|
// disable hbase zookeeper tool log to avoid it mess up command output
|
||||||
|
Logger hbaseZkLogger = Logger.getLogger("org.apache.hadoop.hbase.zookeeper");
|
||||||
|
LOG.debug("HBase zookeeper log level before set: " + hbaseZkLogger.getLevel());
|
||||||
|
hbaseZkLogger.setLevel(Level.OFF);
|
||||||
|
LOG.debug("HBase Zookeeper log level after set: " + hbaseZkLogger.getLevel());
|
||||||
|
|
||||||
|
// disable hbase client log to avoid it mess up command output
|
||||||
|
Logger hbaseClientLogger = Logger.getLogger("org.apache.hadoop.hbase.client");
|
||||||
|
LOG.debug("HBase client log level before set: " + hbaseClientLogger.getLevel());
|
||||||
|
hbaseClientLogger.setLevel(Level.OFF);
|
||||||
|
LOG.debug("HBase client log level after set: " + hbaseClientLogger.getLevel());
|
||||||
|
|
||||||
|
// disable other related log to avoid mess up command output
|
||||||
|
Logger otherLogger = Logger.getLogger("org.apache.hadoop.hbase.io.hfile");
|
||||||
|
otherLogger.setLevel(Level.OFF);
|
||||||
|
otherLogger = Logger.getLogger("org.apache.hadoop.hbase.util");
|
||||||
|
otherLogger.setLevel(Level.OFF);
|
||||||
|
otherLogger = Logger.getLogger("org.apache.hadoop.hbase.mapreduce");
|
||||||
|
otherLogger.setLevel(Level.OFF);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,503 @@
|
||||||
|
/**
|
||||||
|
* 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.io.EOFException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.NavigableSet;
|
||||||
|
|
||||||
|
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.Cell;
|
||||||
|
import org.apache.hadoop.hbase.CellUtil;
|
||||||
|
import org.apache.hadoop.hbase.HConstants;
|
||||||
|
import org.apache.hadoop.hbase.HTableDescriptor;
|
||||||
|
import org.apache.hadoop.hbase.TableName;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceAudience;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceStability;
|
||||||
|
import org.apache.hadoop.hbase.client.Connection;
|
||||||
|
import org.apache.hadoop.hbase.client.ConnectionFactory;
|
||||||
|
import org.apache.hadoop.hbase.client.Delete;
|
||||||
|
import org.apache.hadoop.hbase.client.Durability;
|
||||||
|
import org.apache.hadoop.hbase.client.HBaseAdmin;
|
||||||
|
import org.apache.hadoop.hbase.client.HTable;
|
||||||
|
import org.apache.hadoop.hbase.client.Mutation;
|
||||||
|
import org.apache.hadoop.hbase.client.Put;
|
||||||
|
import org.apache.hadoop.hbase.mapreduce.LoadIncrementalHFiles;
|
||||||
|
import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.SnapshotDescription;
|
||||||
|
import org.apache.hadoop.hbase.regionserver.wal.WALEdit;
|
||||||
|
import org.apache.hadoop.hbase.snapshot.SnapshotDescriptionUtils;
|
||||||
|
import org.apache.hadoop.hbase.snapshot.SnapshotManifest;
|
||||||
|
import org.apache.hadoop.hbase.util.Bytes;
|
||||||
|
import org.apache.hadoop.hbase.wal.WAL;
|
||||||
|
import org.apache.hadoop.hbase.wal.WALFactory;
|
||||||
|
import org.apache.hadoop.hbase.wal.WALKey;
|
||||||
|
import org.apache.hadoop.hbase.wal.WALSplitter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A collection for methods used by multiple classes to restore HBase tables.
|
||||||
|
*/
|
||||||
|
@InterfaceAudience.Private
|
||||||
|
@InterfaceStability.Evolving
|
||||||
|
public class RestoreUtil {
|
||||||
|
|
||||||
|
public static final Log LOG = LogFactory.getLog(RestoreUtil.class);
|
||||||
|
|
||||||
|
protected Configuration conf = null;
|
||||||
|
|
||||||
|
protected HBackupFileSystem hBackupFS = null;
|
||||||
|
|
||||||
|
// store table name and snapshot dir mapping
|
||||||
|
private final HashMap<String, Path> snapshotMap = new HashMap<String, Path>();
|
||||||
|
|
||||||
|
public RestoreUtil(Configuration conf, HBackupFileSystem hBackupFS) throws IOException {
|
||||||
|
this.conf = conf;
|
||||||
|
this.hBackupFS = hBackupFS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* During incremental backup operation. Call WalPlayer to replay WAL in backup image Currently
|
||||||
|
* tableNames and newTablesNames only contain single table, will be expanded to multiple tables in
|
||||||
|
* the future
|
||||||
|
* @param logDir : incremental backup folders, which contains WAL
|
||||||
|
* @param tableNames : source tableNames(table names were backuped)
|
||||||
|
* @param newTableNames : target tableNames(table names to be restored to)
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
public void incrementalRestoreTable(String logDir, String[] tableNames, String[] newTableNames)
|
||||||
|
throws IOException {
|
||||||
|
|
||||||
|
if (tableNames.length != newTableNames.length) {
|
||||||
|
throw new IOException("Number of source tables adn taget Tables does not match!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// for incremental backup image, expect the table already created either by user or previous
|
||||||
|
// full backup. Here, check that all new tables exists
|
||||||
|
HBaseAdmin admin = null;
|
||||||
|
Connection conn = null;
|
||||||
|
try {
|
||||||
|
conn = ConnectionFactory.createConnection(conf);
|
||||||
|
admin = (HBaseAdmin) conn.getAdmin();
|
||||||
|
for (String tableName : newTableNames) {
|
||||||
|
if (!admin.tableExists(TableName.valueOf(tableName))) {
|
||||||
|
admin.close();
|
||||||
|
throw new IOException("HBase table " + tableName
|
||||||
|
+ " does not exist. Create the table first, e.g. by restoring a full backup.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IncrementalRestoreService restoreService =
|
||||||
|
BackupRestoreServiceFactory.getIncrementalRestoreService(conf);
|
||||||
|
|
||||||
|
restoreService.run(logDir, tableNames, newTableNames);
|
||||||
|
} finally {
|
||||||
|
if (admin != null) {
|
||||||
|
admin.close();
|
||||||
|
}
|
||||||
|
if(conn != null){
|
||||||
|
conn.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fullRestoreTable(Path tableBackupPath, String tableName, String newTableName,
|
||||||
|
boolean converted) throws IOException {
|
||||||
|
|
||||||
|
restoreTableAndCreate(tableName, newTableName, tableBackupPath, converted);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void restoreTableAndCreate(String tableName, String newTableName, Path tableBackupPath,
|
||||||
|
boolean converted) throws IOException {
|
||||||
|
if (newTableName == null || newTableName.equals("")) {
|
||||||
|
newTableName = tableName;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileSystem fileSys = tableBackupPath.getFileSystem(this.conf);
|
||||||
|
|
||||||
|
// get table descriptor first
|
||||||
|
HTableDescriptor tableDescriptor = null;
|
||||||
|
|
||||||
|
Path tableSnapshotPath = hBackupFS.getTableSnapshotPath(tableName);
|
||||||
|
|
||||||
|
if (fileSys.exists(tableSnapshotPath)) {
|
||||||
|
// snapshot path exist means the backup path is in HDFS
|
||||||
|
// check whether snapshot dir already recorded for target table
|
||||||
|
if (snapshotMap.get(tableName) != null) {
|
||||||
|
SnapshotDescription desc =
|
||||||
|
SnapshotDescriptionUtils.readSnapshotInfo(fileSys, tableSnapshotPath);
|
||||||
|
SnapshotManifest manifest = SnapshotManifest.open(conf, fileSys, tableSnapshotPath, desc);
|
||||||
|
tableDescriptor = manifest.getTableDescriptor();
|
||||||
|
LOG.debug("tableDescriptor.getNameAsString() = " + tableDescriptor.getNameAsString()
|
||||||
|
+ " while tableName = " + tableName);
|
||||||
|
// HBase 96.0 and 98.0
|
||||||
|
// tableDescriptor =
|
||||||
|
// FSTableDescriptors.getTableDescriptorFromFs(fileSys, snapshotMap.get(tableName));
|
||||||
|
} else {
|
||||||
|
tableDescriptor = hBackupFS.getTableDesc(tableName);
|
||||||
|
LOG.debug("tableSnapshotPath=" + tableSnapshotPath.toString());
|
||||||
|
snapshotMap.put(tableName, hBackupFS.getTableInfoPath(tableName));
|
||||||
|
}
|
||||||
|
if (tableDescriptor == null) {
|
||||||
|
LOG.debug("Found no table descriptor in the snapshot dir, previous schema would be lost");
|
||||||
|
}
|
||||||
|
} else if (converted) {
|
||||||
|
// first check if this is a converted backup image
|
||||||
|
LOG.error("convert will be supported in a future jira");
|
||||||
|
}
|
||||||
|
|
||||||
|
Path tableArchivePath = hBackupFS.getTableArchivePath(tableName);
|
||||||
|
if (tableArchivePath == null) {
|
||||||
|
if (tableDescriptor != null) {
|
||||||
|
// find table descriptor but no archive dir means the table is empty, create table and exit
|
||||||
|
LOG.debug("find table descriptor but no archive dir for table " + tableName
|
||||||
|
+ ", will only create table");
|
||||||
|
tableDescriptor.setName(Bytes.toBytes(newTableName));
|
||||||
|
checkAndCreateTable(tableBackupPath, tableName, newTableName, null, tableDescriptor);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Cannot restore hbase table because directory '"
|
||||||
|
+ " tableArchivePath is null.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tableDescriptor == null) {
|
||||||
|
tableDescriptor = new HTableDescriptor(newTableName);
|
||||||
|
} else {
|
||||||
|
tableDescriptor.setName(Bytes.toBytes(newTableName));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!converted) {
|
||||||
|
// record all region dirs:
|
||||||
|
// load all files in dir
|
||||||
|
try {
|
||||||
|
ArrayList<Path> regionPathList = hBackupFS.getRegionList(tableName);
|
||||||
|
|
||||||
|
// should only try to create the table with all region informations, so we could pre-split
|
||||||
|
// the regions in fine grain
|
||||||
|
checkAndCreateTable(tableBackupPath, tableName, newTableName, regionPathList,
|
||||||
|
tableDescriptor);
|
||||||
|
if (tableArchivePath != null) {
|
||||||
|
// start real restore through bulkload
|
||||||
|
// if the backup target is on local cluster, special action needed
|
||||||
|
Path tempTableArchivePath = hBackupFS.checkLocalAndBackup(tableArchivePath);
|
||||||
|
if (tempTableArchivePath.equals(tableArchivePath)) {
|
||||||
|
LOG.debug("TableArchivePath for bulkload using existPath: " + tableArchivePath);
|
||||||
|
} else {
|
||||||
|
regionPathList = hBackupFS.getRegionList(tempTableArchivePath); // point to the tempDir
|
||||||
|
LOG.debug("TableArchivePath for bulkload using tempPath: " + tempTableArchivePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadIncrementalHFiles loader = createLoader(tempTableArchivePath, false);
|
||||||
|
for (Path regionPath : regionPathList) {
|
||||||
|
String regionName = regionPath.toString();
|
||||||
|
LOG.debug("Restoring HFiles from directory " + regionName);
|
||||||
|
String[] args = { regionName, newTableName };
|
||||||
|
loader.run(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// restore the recovered.edits if exists
|
||||||
|
replayRecoveredEditsIfAny(tableBackupPath, tableName, tableDescriptor);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalStateException("Cannot restore hbase table", e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG.debug("convert will be supported in a future jira");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replay recovered edits from backup.
|
||||||
|
*/
|
||||||
|
private void replayRecoveredEditsIfAny(Path tableBackupPath, String tableName,
|
||||||
|
HTableDescriptor newTableHtd) throws IOException {
|
||||||
|
|
||||||
|
LOG.debug("Trying to replay the recovered.edits if exist to the target table "
|
||||||
|
+ newTableHtd.getNameAsString() + " from the backup of table " + tableName + ".");
|
||||||
|
|
||||||
|
FileSystem fs = tableBackupPath.getFileSystem(this.conf);
|
||||||
|
ArrayList<Path> regionDirs = hBackupFS.getRegionList(tableName);
|
||||||
|
|
||||||
|
if (regionDirs == null || regionDirs.size() == 0) {
|
||||||
|
LOG.warn("No recovered.edits to be replayed for empty backup of table " + tableName + ".");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Connection conn = null;
|
||||||
|
try {
|
||||||
|
|
||||||
|
conn = ConnectionFactory.createConnection(conf);
|
||||||
|
|
||||||
|
for (Path regionDir : regionDirs) {
|
||||||
|
// OLD: NavigableSet<Path> files = HLogUtil.getSplitEditFilesSorted(fs, regionDir);
|
||||||
|
NavigableSet<Path> files = WALSplitter.getSplitEditFilesSorted(fs, regionDir);
|
||||||
|
|
||||||
|
if (files == null || files.isEmpty()) {
|
||||||
|
LOG.warn("No recovered.edits found for the region " + regionDir.getName() + ".");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Path edits : files) {
|
||||||
|
if (edits == null || !fs.exists(edits)) {
|
||||||
|
LOG.warn("Null or non-existent edits file: " + edits);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
HTable table = null;
|
||||||
|
try {
|
||||||
|
table = (HTable) conn.getTable(newTableHtd.getTableName());
|
||||||
|
replayRecoveredEdits(table, fs, edits);
|
||||||
|
table.flushCommits();
|
||||||
|
table.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
boolean skipErrors = conf.getBoolean("hbase.skip.errors", false);
|
||||||
|
if (skipErrors) {
|
||||||
|
Path p = WALSplitter.moveAsideBadEditsFile(fs, edits);
|
||||||
|
LOG.error(HConstants.HREGION_EDITS_REPLAY_SKIP_ERRORS
|
||||||
|
+ "=true so continuing. Renamed " + edits + " as " + p, e);
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (table != null) {
|
||||||
|
table.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // for each edit file under a region
|
||||||
|
} // for each region
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
if (conn != null) {
|
||||||
|
conn.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore process for an edit entry.
|
||||||
|
* @param htable The target table of restore
|
||||||
|
* @param key HLog key
|
||||||
|
* @param val KVs
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
private void restoreEdit(HTable htable, WALKey key, WALEdit val) throws IOException {
|
||||||
|
Put put = null;
|
||||||
|
Delete del = null;
|
||||||
|
Cell lastKV = null;
|
||||||
|
for (Cell kv : val.getCells()) {
|
||||||
|
// filtering HLog meta entries, see HLog.completeCacheFlushLogEdit
|
||||||
|
if (WALEdit.isMetaEditFamily(CellUtil.cloneFamily(kv))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A WALEdit may contain multiple operations (HBASE-3584) and/or
|
||||||
|
// multiple rows (HBASE-5229).
|
||||||
|
// Aggregate as much as possible into a single Put/Delete
|
||||||
|
// operation before apply the action to the table.
|
||||||
|
if (lastKV == null || lastKV.getTypeByte() != kv.getTypeByte()
|
||||||
|
|| !CellUtil.matchingRow(lastKV, kv)) {
|
||||||
|
// row or type changed, write out aggregate KVs.
|
||||||
|
if (put != null) {
|
||||||
|
applyAction(htable, put);
|
||||||
|
}
|
||||||
|
if (del != null) {
|
||||||
|
applyAction(htable, del);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CellUtil.isDelete(kv)) {
|
||||||
|
del = new Delete(CellUtil.cloneRow(kv));
|
||||||
|
} else {
|
||||||
|
put = new Put(CellUtil.cloneRow(kv));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (CellUtil.isDelete(kv)) {
|
||||||
|
del.addDeleteMarker(kv);
|
||||||
|
} else {
|
||||||
|
put.add(kv);
|
||||||
|
}
|
||||||
|
lastKV = kv;
|
||||||
|
}
|
||||||
|
// write residual KVs
|
||||||
|
if (put != null) {
|
||||||
|
applyAction(htable, put);
|
||||||
|
}
|
||||||
|
if (del != null) {
|
||||||
|
applyAction(htable, del);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply an action (Put/Delete) to table.
|
||||||
|
* @param table table
|
||||||
|
* @param action action
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
private void applyAction(HTable table, Mutation action) throws IOException {
|
||||||
|
// The actions are not immutable, so we defensively copy them
|
||||||
|
if (action instanceof Put) {
|
||||||
|
Put put = new Put((Put) action);
|
||||||
|
// put.setWriteToWAL(false);
|
||||||
|
// why do not we do WAL?
|
||||||
|
put.setDurability(Durability.SKIP_WAL);
|
||||||
|
table.put(put);
|
||||||
|
} else if (action instanceof Delete) {
|
||||||
|
Delete delete = new Delete((Delete) action);
|
||||||
|
table.delete(delete);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("action must be either Delete or Put");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replay the given edits.
|
||||||
|
* @param htable The target table of restore
|
||||||
|
* @param fs File system
|
||||||
|
* @param edits Recovered.edits to be replayed
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
private void replayRecoveredEdits(HTable htable, FileSystem fs, Path edits) throws IOException {
|
||||||
|
LOG.debug("Replaying edits from " + edits + "; path=" + edits);
|
||||||
|
|
||||||
|
WAL.Reader reader = null;
|
||||||
|
try {
|
||||||
|
reader = WALFactory.createReader(fs, edits, this.conf);
|
||||||
|
long editsCount = 0;
|
||||||
|
WAL.Entry entry;
|
||||||
|
|
||||||
|
try {
|
||||||
|
while ((entry = reader.next()) != null) {
|
||||||
|
restoreEdit(htable, entry.getKey(), entry.getEdit());
|
||||||
|
editsCount++;
|
||||||
|
}
|
||||||
|
LOG.debug(editsCount + " edits from " + edits + " have been replayed.");
|
||||||
|
|
||||||
|
} catch (EOFException eof) {
|
||||||
|
Path p = WALSplitter.moveAsideBadEditsFile(fs, edits);
|
||||||
|
String msg =
|
||||||
|
"Encountered EOF. Most likely due to Master failure during "
|
||||||
|
+ "log spliting, so we have this data in another edit. "
|
||||||
|
+ "Continuing, but renaming " + edits + " as " + p;
|
||||||
|
LOG.warn(msg, eof);
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
// If the IOE resulted from bad file format,
|
||||||
|
// then this problem is idempotent and retrying won't help
|
||||||
|
if (ioe.getCause() instanceof ParseException) {
|
||||||
|
Path p = WALSplitter.moveAsideBadEditsFile(fs, edits);
|
||||||
|
String msg =
|
||||||
|
"File corruption encountered! " + "Continuing, but renaming " + edits + " as " + p;
|
||||||
|
LOG.warn(msg, ioe);
|
||||||
|
} else {
|
||||||
|
// other IO errors may be transient (bad network connection,
|
||||||
|
// checksum exception on one datanode, etc). throw & retry
|
||||||
|
throw ioe;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (reader != null) {
|
||||||
|
reader.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@link LoadIncrementalHFiles} instance to be used to restore the HFiles of a full
|
||||||
|
* backup.
|
||||||
|
* @return the {@link LoadIncrementalHFiles} instance
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
private LoadIncrementalHFiles createLoader(Path tableArchivePath, boolean multipleTables)
|
||||||
|
throws IOException {
|
||||||
|
// set configuration for restore:
|
||||||
|
// LoadIncrementalHFile needs more time
|
||||||
|
// <name>hbase.rpc.timeout</name> <value>600000</value>
|
||||||
|
// calculates
|
||||||
|
Integer milliSecInMin = 60000;
|
||||||
|
Integer previousMillis = this.conf.getInt("hbase.rpc.timeout", 0);
|
||||||
|
Integer numberOfFilesInDir =
|
||||||
|
multipleTables ? hBackupFS.getMaxNumberOfFilesInSubDir(tableArchivePath) : hBackupFS
|
||||||
|
.getNumberOfFilesInDir(tableArchivePath);
|
||||||
|
Integer calculatedMillis = numberOfFilesInDir * milliSecInMin; // 1 minute per file
|
||||||
|
Integer resultMillis = Math.max(calculatedMillis, previousMillis);
|
||||||
|
if (resultMillis > previousMillis) {
|
||||||
|
LOG.info("Setting configuration for restore with LoadIncrementalHFile: "
|
||||||
|
+ "hbase.rpc.timeout to " + calculatedMillis / milliSecInMin
|
||||||
|
+ " minutes, to handle the number of files in backup " + tableArchivePath);
|
||||||
|
this.conf.setInt("hbase.rpc.timeout", resultMillis);
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadIncrementalHFiles loader = null;
|
||||||
|
try {
|
||||||
|
loader = new LoadIncrementalHFiles(this.conf);
|
||||||
|
} catch (Exception e1) {
|
||||||
|
throw new IOException(e1);
|
||||||
|
}
|
||||||
|
return loader;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare the table for bulkload, most codes copied from
|
||||||
|
* {@link LoadIncrementalHFiles#createTable(String, String)}
|
||||||
|
* @param tableBackupPath path
|
||||||
|
* @param tableName table name
|
||||||
|
* @param targetTableName target table name
|
||||||
|
* @param regionDirList region directory list
|
||||||
|
* @param htd table descriptor
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
private void checkAndCreateTable(Path tableBackupPath, String tableName, String targetTableName,
|
||||||
|
ArrayList<Path> regionDirList, HTableDescriptor htd) throws IOException {
|
||||||
|
HBaseAdmin hbadmin = null;
|
||||||
|
Connection conn = null;
|
||||||
|
try {
|
||||||
|
conn = ConnectionFactory.createConnection(conf);
|
||||||
|
hbadmin = (HBaseAdmin) conn.getAdmin();
|
||||||
|
if (hbadmin.tableExists(TableName.valueOf(targetTableName))) {
|
||||||
|
LOG.info("Using exising target table '" + targetTableName + "'");
|
||||||
|
} else {
|
||||||
|
LOG.info("Creating target table '" + targetTableName + "'");
|
||||||
|
|
||||||
|
// if no region dir given, create the table and return
|
||||||
|
if (regionDirList == null || regionDirList.size() == 0) {
|
||||||
|
|
||||||
|
hbadmin.createTable(htd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[][] keys = hBackupFS.generateBoundaryKeys(regionDirList);
|
||||||
|
|
||||||
|
// create table using table decriptor and region boundaries
|
||||||
|
hbadmin.createTable(htd, keys);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
} finally {
|
||||||
|
if (hbadmin != null) {
|
||||||
|
hbadmin.close();
|
||||||
|
}
|
||||||
|
if(conn != null){
|
||||||
|
conn.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,292 @@
|
||||||
|
/**
|
||||||
|
* 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.mapreduce;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Arrays;
|
||||||
|
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.FileSystem;
|
||||||
|
import org.apache.hadoop.fs.Path;
|
||||||
|
import org.apache.hadoop.hbase.backup.BackupCopyService;
|
||||||
|
import org.apache.hadoop.hbase.backup.BackupHandler;
|
||||||
|
import org.apache.hadoop.hbase.backup.BackupUtil;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceAudience;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceStability;
|
||||||
|
import org.apache.hadoop.hbase.snapshot.ExportSnapshot;
|
||||||
|
import org.apache.hadoop.mapreduce.Job;
|
||||||
|
import org.apache.hadoop.tools.DistCp;
|
||||||
|
import org.apache.hadoop.tools.DistCpConstants;
|
||||||
|
import org.apache.hadoop.tools.DistCpOptions;
|
||||||
|
/**
|
||||||
|
* Copier for backup operation. Basically, there are 2 types of copy. One is copying from snapshot,
|
||||||
|
* which bases on extending ExportSnapshot's function with copy progress reporting to ZooKeeper
|
||||||
|
* implementation. The other is copying for incremental log files, which bases on extending
|
||||||
|
* DistCp's function with copy progress reporting to ZooKeeper implementation.
|
||||||
|
*
|
||||||
|
* For now this is only a wrapper. The other features such as progress and increment backup will be
|
||||||
|
* implemented in future jira
|
||||||
|
*/
|
||||||
|
|
||||||
|
@InterfaceAudience.Private
|
||||||
|
@InterfaceStability.Evolving
|
||||||
|
public class MapReduceBackupCopyService implements BackupCopyService {
|
||||||
|
private static final Log LOG = LogFactory.getLog(MapReduceBackupCopyService.class);
|
||||||
|
|
||||||
|
private Configuration conf;
|
||||||
|
// private static final long BYTES_PER_MAP = 2 * 256 * 1024 * 1024;
|
||||||
|
|
||||||
|
// Accumulated progress within the whole backup process for the copy operation
|
||||||
|
private float progressDone = 0.1f;
|
||||||
|
private long bytesCopied = 0;
|
||||||
|
private static float INIT_PROGRESS = 0.1f;
|
||||||
|
|
||||||
|
// The percentage of the current copy task within the whole task if multiple time copies are
|
||||||
|
// needed. The default value is 100%, which means only 1 copy task for the whole.
|
||||||
|
private float subTaskPercntgInWholeTask = 1f;
|
||||||
|
|
||||||
|
public MapReduceBackupCopyService() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Configuration getConf() {
|
||||||
|
return conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setConf(Configuration conf) {
|
||||||
|
this.conf = conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current copy task percentage within the whole task if multiple copies are needed.
|
||||||
|
* @return the current copy task percentage
|
||||||
|
*/
|
||||||
|
public float getSubTaskPercntgInWholeTask() {
|
||||||
|
return subTaskPercntgInWholeTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the current copy task percentage within the whole task if multiple copies are needed. Must
|
||||||
|
* be called before calling
|
||||||
|
* {@link #copy(BackupHandler, Configuration, Type, String[])}
|
||||||
|
* @param subTaskPercntgInWholeTask The percentage of the copy subtask
|
||||||
|
*/
|
||||||
|
public void setSubTaskPercntgInWholeTask(float subTaskPercntgInWholeTask) {
|
||||||
|
this.subTaskPercntgInWholeTask = subTaskPercntgInWholeTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SnapshotCopy extends ExportSnapshot {
|
||||||
|
private BackupHandler backupHandler;
|
||||||
|
private String table;
|
||||||
|
|
||||||
|
public SnapshotCopy(BackupHandler backupHandler, String table) {
|
||||||
|
super();
|
||||||
|
this.backupHandler = backupHandler;
|
||||||
|
this.table = table;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BackupHandler getBackupHandler() {
|
||||||
|
return this.backupHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTable() {
|
||||||
|
return this.table;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extends DistCp for progress updating to hbase:backup
|
||||||
|
// during backup. Using DistCpV2 (MAPREDUCE-2765).
|
||||||
|
// Simply extend it and override execute() method to get the
|
||||||
|
// Job reference for progress updating.
|
||||||
|
// Only the argument "src1, [src2, [...]] dst" is supported,
|
||||||
|
// no more DistCp options.
|
||||||
|
class BackupDistCp extends DistCp {
|
||||||
|
|
||||||
|
private BackupHandler backupHandler;
|
||||||
|
|
||||||
|
public BackupDistCp(Configuration conf, DistCpOptions options, BackupHandler backupHandler)
|
||||||
|
throws Exception {
|
||||||
|
super(conf, options);
|
||||||
|
this.backupHandler = backupHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Job execute() throws Exception {
|
||||||
|
|
||||||
|
// reflection preparation for private methods and fields
|
||||||
|
Class<?> classDistCp = org.apache.hadoop.tools.DistCp.class;
|
||||||
|
Method methodCreateMetaFolderPath = classDistCp.getDeclaredMethod("createMetaFolderPath");
|
||||||
|
Method methodCreateJob = classDistCp.getDeclaredMethod("createJob");
|
||||||
|
Method methodCreateInputFileListing =
|
||||||
|
classDistCp.getDeclaredMethod("createInputFileListing", Job.class);
|
||||||
|
Method methodCleanup = classDistCp.getDeclaredMethod("cleanup");
|
||||||
|
|
||||||
|
Field fieldInputOptions = classDistCp.getDeclaredField("inputOptions");
|
||||||
|
Field fieldMetaFolder = classDistCp.getDeclaredField("metaFolder");
|
||||||
|
Field fieldJobFS = classDistCp.getDeclaredField("jobFS");
|
||||||
|
Field fieldSubmitted = classDistCp.getDeclaredField("submitted");
|
||||||
|
|
||||||
|
methodCreateMetaFolderPath.setAccessible(true);
|
||||||
|
methodCreateJob.setAccessible(true);
|
||||||
|
methodCreateInputFileListing.setAccessible(true);
|
||||||
|
methodCleanup.setAccessible(true);
|
||||||
|
|
||||||
|
fieldInputOptions.setAccessible(true);
|
||||||
|
fieldMetaFolder.setAccessible(true);
|
||||||
|
fieldJobFS.setAccessible(true);
|
||||||
|
fieldSubmitted.setAccessible(true);
|
||||||
|
|
||||||
|
// execute() logic starts here
|
||||||
|
assert fieldInputOptions.get(this) != null;
|
||||||
|
assert getConf() != null;
|
||||||
|
|
||||||
|
Job job = null;
|
||||||
|
try {
|
||||||
|
synchronized (this) {
|
||||||
|
// Don't cleanup while we are setting up.
|
||||||
|
fieldMetaFolder.set(this, methodCreateMetaFolderPath.invoke(this));
|
||||||
|
fieldJobFS.set(this, ((Path) fieldMetaFolder.get(this)).getFileSystem(getConf()));
|
||||||
|
|
||||||
|
job = (Job) methodCreateJob.invoke(this);
|
||||||
|
}
|
||||||
|
methodCreateInputFileListing.invoke(this, job);
|
||||||
|
|
||||||
|
// Get the total length of the source files
|
||||||
|
List<Path> srcs = ((DistCpOptions) fieldInputOptions.get(this)).getSourcePaths();
|
||||||
|
long totalSrcLgth = 0;
|
||||||
|
for (Path aSrc : srcs) {
|
||||||
|
totalSrcLgth += BackupUtil.getFilesLength(aSrc.getFileSystem(getConf()), aSrc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// submit the copy job
|
||||||
|
job.submit();
|
||||||
|
fieldSubmitted.set(this, true);
|
||||||
|
|
||||||
|
// after submit the MR job, set its handler in backup handler for cancel process
|
||||||
|
// this.backupHandler.copyJob = job;
|
||||||
|
|
||||||
|
// Update the copy progress to ZK every 0.5s if progress value changed
|
||||||
|
int progressReportFreq =
|
||||||
|
this.getConf().getInt("hbase.backup.progressreport.frequency", 500);
|
||||||
|
float lastProgress = progressDone;
|
||||||
|
while (!job.isComplete()) {
|
||||||
|
float newProgress =
|
||||||
|
progressDone + job.mapProgress() * subTaskPercntgInWholeTask * (1 - INIT_PROGRESS);
|
||||||
|
|
||||||
|
if (newProgress > lastProgress) {
|
||||||
|
|
||||||
|
BigDecimal progressData =
|
||||||
|
new BigDecimal(newProgress * 100).setScale(1, BigDecimal.ROUND_HALF_UP);
|
||||||
|
String newProgressStr = progressData + "%";
|
||||||
|
LOG.info("Progress: " + newProgressStr);
|
||||||
|
this.backupHandler.updateProgress(newProgressStr, bytesCopied);
|
||||||
|
LOG.debug("Backup progress data updated to hbase:backup: \"Progress: " + newProgressStr
|
||||||
|
+ ".\"");
|
||||||
|
lastProgress = newProgress;
|
||||||
|
}
|
||||||
|
Thread.sleep(progressReportFreq);
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the progress data after copy job complete
|
||||||
|
float newProgress =
|
||||||
|
progressDone + job.mapProgress() * subTaskPercntgInWholeTask * (1 - INIT_PROGRESS);
|
||||||
|
BigDecimal progressData =
|
||||||
|
new BigDecimal(newProgress * 100).setScale(1, BigDecimal.ROUND_HALF_UP);
|
||||||
|
|
||||||
|
String newProgressStr = progressData + "%";
|
||||||
|
LOG.info("Progress: " + newProgressStr);
|
||||||
|
|
||||||
|
// accumulate the overall backup progress
|
||||||
|
progressDone = newProgress;
|
||||||
|
bytesCopied += totalSrcLgth;
|
||||||
|
|
||||||
|
this.backupHandler.updateProgress(newProgressStr, bytesCopied);
|
||||||
|
LOG.debug("Backup progress data updated to hbase:backup: \"Progress: " + newProgressStr
|
||||||
|
+ " - " + bytesCopied + " bytes copied.\"");
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
if (!fieldSubmitted.getBoolean(this)) {
|
||||||
|
methodCleanup.invoke(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String jobID = job.getJobID().toString();
|
||||||
|
job.getConfiguration().set(DistCpConstants.CONF_LABEL_DISTCP_JOB_ID, jobID);
|
||||||
|
|
||||||
|
LOG.debug("DistCp job-id: " + jobID);
|
||||||
|
return job;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do backup copy based on different types.
|
||||||
|
* @param handler The backup handler reference
|
||||||
|
* @param conf The hadoop configuration
|
||||||
|
* @param copyType The backup copy type
|
||||||
|
* @param options Options for customized ExportSnapshot or DistCp
|
||||||
|
* @throws Exception exception
|
||||||
|
*/
|
||||||
|
public int copy(BackupHandler handler, Configuration conf, BackupCopyService.Type copyType,
|
||||||
|
String[] options) throws IOException {
|
||||||
|
|
||||||
|
int res = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (copyType == Type.FULL) {
|
||||||
|
SnapshotCopy snapshotCp =
|
||||||
|
new SnapshotCopy(handler, handler.getBackupContext().getTableBySnapshot(options[1]));
|
||||||
|
LOG.debug("Doing SNAPSHOT_COPY");
|
||||||
|
// Make a new instance of conf to be used by the snapshot copy class.
|
||||||
|
snapshotCp.setConf(new Configuration(conf));
|
||||||
|
res = snapshotCp.run(options);
|
||||||
|
} else if (copyType == Type.INCREMENTAL) {
|
||||||
|
LOG.debug("Doing COPY_TYPE_DISTCP");
|
||||||
|
setSubTaskPercntgInWholeTask(1f);
|
||||||
|
|
||||||
|
BackupDistCp distcp = new BackupDistCp(new Configuration(conf), null, handler);
|
||||||
|
// Handle a special case where the source file is a single file.
|
||||||
|
// In this case, distcp will not create the target dir. It just take the
|
||||||
|
// target as a file name and copy source file to the target (as a file name).
|
||||||
|
// We need to create the target dir before run distcp.
|
||||||
|
LOG.debug("DistCp options: " + Arrays.toString(options));
|
||||||
|
if (options.length == 2) {
|
||||||
|
Path dest = new Path(options[1]);
|
||||||
|
FileSystem destfs = dest.getFileSystem(conf);
|
||||||
|
if (!destfs.exists(dest)) {
|
||||||
|
destfs.mkdirs(dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res = distcp.run(options);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
/**
|
||||||
|
* 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.mapreduce;
|
||||||
|
|
||||||
|
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.hbase.backup.HBackupFileSystem;
|
||||||
|
import org.apache.hadoop.hbase.backup.IncrementalRestoreService;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceAudience;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceStability;
|
||||||
|
import org.apache.hadoop.hbase.mapreduce.WALPlayer;
|
||||||
|
|
||||||
|
@InterfaceAudience.Private
|
||||||
|
@InterfaceStability.Evolving
|
||||||
|
public class MapReduceRestoreService implements IncrementalRestoreService {
|
||||||
|
public static final Log LOG = LogFactory.getLog(MapReduceRestoreService.class);
|
||||||
|
|
||||||
|
private WALPlayer player;
|
||||||
|
|
||||||
|
public MapReduceRestoreService() {
|
||||||
|
this.player = new WALPlayer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(String logDir, String[] tableNames, String[] newTableNames) throws IOException {
|
||||||
|
String tableStr = HBackupFileSystem.join(tableNames);
|
||||||
|
String newTableStr = HBackupFileSystem.join(newTableNames);
|
||||||
|
|
||||||
|
// WALPlayer reads all files in arbitrary directory structure and creates a Map task for each
|
||||||
|
// log file
|
||||||
|
|
||||||
|
String[] playerArgs = { logDir, tableStr, newTableStr };
|
||||||
|
LOG.info("Restore incremental backup from directory " + logDir + " from hbase tables "
|
||||||
|
+ HBackupFileSystem.join(tableNames) + " to tables "
|
||||||
|
+ HBackupFileSystem.join(newTableNames));
|
||||||
|
try {
|
||||||
|
player.run(playerArgs);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IOException("cannot restore from backup directory " + logDir
|
||||||
|
+ " (check Hadoop and HBase logs) " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Configuration getConf() {
|
||||||
|
return player.getConf();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setConf(Configuration conf) {
|
||||||
|
this.player.setConf(conf);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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.master;
|
||||||
|
|
||||||
|
import com.google.common.base.Predicate;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
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.hbase.HBaseInterfaceAudience;
|
||||||
|
import org.apache.hadoop.hbase.HConstants;
|
||||||
|
import org.apache.hadoop.hbase.backup.BackupSystemTable;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceAudience;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceStability;
|
||||||
|
import org.apache.hadoop.hbase.master.cleaner.BaseLogCleanerDelegate;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of a log cleaner that checks if a log is still scheduled for
|
||||||
|
* incremental backup before deleting it when its TTL is over.
|
||||||
|
*/
|
||||||
|
@InterfaceStability.Evolving
|
||||||
|
@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG)
|
||||||
|
public class BackupLogCleaner extends BaseLogCleanerDelegate {
|
||||||
|
private static final Log LOG = LogFactory.getLog(BackupLogCleaner.class);
|
||||||
|
|
||||||
|
private boolean stopped = false;
|
||||||
|
|
||||||
|
public BackupLogCleaner() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterable<FileStatus> getDeletableFiles(Iterable<FileStatus> files) {
|
||||||
|
// all members of this class are null if backup is disabled,
|
||||||
|
// so we cannot filter the files
|
||||||
|
if (this.getConf() == null) {
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final BackupSystemTable table = BackupSystemTable.getTable(getConf());
|
||||||
|
// If we do not have recorded backup sessions
|
||||||
|
if (table.hasBackupSessions() == false) {
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
return Iterables.filter(files, new Predicate<FileStatus>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(FileStatus file) {
|
||||||
|
try {
|
||||||
|
String wal = file.getPath().toString();
|
||||||
|
boolean logInSystemTable = table.checkWALFile(wal);
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
if (logInSystemTable) {
|
||||||
|
LOG.debug("Found log file in hbase:backup, deleting: " + wal);
|
||||||
|
} else {
|
||||||
|
LOG.debug("Didn't find this log in hbase:backup, keeping: " + wal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return logInSystemTable;
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error(e);
|
||||||
|
return false;// keep file for a while, HBase failed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error("Failed to get hbase:backup table, therefore will keep all files", e);
|
||||||
|
// nothing to delete
|
||||||
|
return new ArrayList<FileStatus>();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setConf(Configuration config) {
|
||||||
|
// If backup is disabled, keep all members null
|
||||||
|
if (!config.getBoolean(HConstants.BACKUP_ENABLE_KEY, HConstants.BACKUP_ENABLE_DEFAULT)) {
|
||||||
|
LOG.warn("Backup is disabled - allowing all wals to be deleted");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.setConf(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop(String why) {
|
||||||
|
if (this.stopped) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.stopped = true;
|
||||||
|
LOG.info("Stopping BackupLogCleaner");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isStopped() {
|
||||||
|
return this.stopped;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
/**
|
||||||
|
* 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.master;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.apache.hadoop.hbase.CoordinatedStateManagerFactory;
|
||||||
|
import org.apache.hadoop.hbase.ServerName;
|
||||||
|
import org.apache.hadoop.hbase.coordination.BaseCoordinatedStateManager;
|
||||||
|
import org.apache.hadoop.hbase.errorhandling.ForeignException;
|
||||||
|
import org.apache.hadoop.hbase.errorhandling.ForeignExceptionDispatcher;
|
||||||
|
import org.apache.hadoop.hbase.master.MasterServices;
|
||||||
|
import org.apache.hadoop.hbase.master.MetricsMaster;
|
||||||
|
import org.apache.hadoop.hbase.procedure.MasterProcedureManager;
|
||||||
|
import org.apache.hadoop.hbase.procedure.Procedure;
|
||||||
|
import org.apache.hadoop.hbase.procedure.ProcedureCoordinator;
|
||||||
|
import org.apache.hadoop.hbase.procedure.ProcedureCoordinatorRpcs;
|
||||||
|
import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.ProcedureDescription;
|
||||||
|
import org.apache.zookeeper.KeeperException;
|
||||||
|
|
||||||
|
public class LogRollMasterProcedureManager extends MasterProcedureManager {
|
||||||
|
|
||||||
|
public static final String ROLLLOG_PROCEDURE_SIGNATURE = "rolllog-proc";
|
||||||
|
public static final String ROLLLOG_PROCEDURE_NAME = "rolllog";
|
||||||
|
private static final Log LOG = LogFactory.getLog(LogRollMasterProcedureManager.class);
|
||||||
|
|
||||||
|
private MasterServices master;
|
||||||
|
private ProcedureCoordinator coordinator;
|
||||||
|
private boolean done;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop(String why) {
|
||||||
|
LOG.info("stop: " + why);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isStopped() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(MasterServices master, MetricsMaster metricsMaster)
|
||||||
|
throws KeeperException, IOException, UnsupportedOperationException {
|
||||||
|
this.master = master;
|
||||||
|
this.done = false;
|
||||||
|
|
||||||
|
// setup the default procedure coordinator
|
||||||
|
String name = master.getServerName().toString();
|
||||||
|
ThreadPoolExecutor tpool = ProcedureCoordinator.defaultPool(name, 1);
|
||||||
|
BaseCoordinatedStateManager coordManager =
|
||||||
|
(BaseCoordinatedStateManager) CoordinatedStateManagerFactory
|
||||||
|
.getCoordinatedStateManager(master.getConfiguration());
|
||||||
|
coordManager.initialize(master);
|
||||||
|
|
||||||
|
ProcedureCoordinatorRpcs comms =
|
||||||
|
coordManager.getProcedureCoordinatorRpcs(getProcedureSignature(), name);
|
||||||
|
|
||||||
|
this.coordinator = new ProcedureCoordinator(comms, tpool);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getProcedureSignature() {
|
||||||
|
return ROLLLOG_PROCEDURE_SIGNATURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execProcedure(ProcedureDescription desc) throws IOException {
|
||||||
|
this.done = false;
|
||||||
|
// start the process on the RS
|
||||||
|
ForeignExceptionDispatcher monitor = new ForeignExceptionDispatcher(desc.getInstance());
|
||||||
|
List<ServerName> serverNames = master.getServerManager().getOnlineServersList();
|
||||||
|
List<String> servers = new ArrayList<String>();
|
||||||
|
for (ServerName sn : serverNames) {
|
||||||
|
servers.add(sn.toString());
|
||||||
|
}
|
||||||
|
Procedure proc = coordinator.startProcedure(monitor, desc.getInstance(), new byte[0], servers);
|
||||||
|
if (proc == null) {
|
||||||
|
String msg = "Failed to submit distributed procedure for '" + desc.getInstance() + "'";
|
||||||
|
LOG.error(msg);
|
||||||
|
throw new IOException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// wait for the procedure to complete. A timer thread is kicked off that should cancel this
|
||||||
|
// if it takes too long.
|
||||||
|
proc.waitForCompleted();
|
||||||
|
LOG.info("Done waiting - exec procedure for " + desc.getInstance());
|
||||||
|
LOG.info("Distributed roll log procedure is successful!");
|
||||||
|
this.done = true;
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
ForeignException ee =
|
||||||
|
new ForeignException("Interrupted while waiting for roll log procdure to finish", e);
|
||||||
|
monitor.receive(ee);
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
} catch (ForeignException e) {
|
||||||
|
ForeignException ee =
|
||||||
|
new ForeignException("Exception while waiting for roll log procdure to finish", e);
|
||||||
|
monitor.receive(ee);
|
||||||
|
}
|
||||||
|
monitor.rethrowException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isProcedureDone(ProcedureDescription desc) throws IOException {
|
||||||
|
return done;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,138 @@
|
||||||
|
/**
|
||||||
|
* 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.regionserver;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.apache.hadoop.hbase.backup.BackupSystemTable;
|
||||||
|
import org.apache.hadoop.hbase.backup.master.LogRollMasterProcedureManager;
|
||||||
|
import org.apache.hadoop.hbase.errorhandling.ForeignException;
|
||||||
|
import org.apache.hadoop.hbase.errorhandling.ForeignExceptionDispatcher;
|
||||||
|
import org.apache.hadoop.hbase.procedure.ProcedureMember;
|
||||||
|
import org.apache.hadoop.hbase.procedure.Subprocedure;
|
||||||
|
import org.apache.hadoop.hbase.regionserver.RegionServerServices;
|
||||||
|
import org.apache.hadoop.hbase.regionserver.wal.FSHLog;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This backup subprocedure implementation forces a log roll on the RS.
|
||||||
|
*/
|
||||||
|
public class LogRollBackupSubprocedure extends Subprocedure {
|
||||||
|
private static final Log LOG = LogFactory.getLog(LogRollBackupSubprocedure.class);
|
||||||
|
|
||||||
|
private final RegionServerServices rss;
|
||||||
|
private final LogRollBackupSubprocedurePool taskManager;
|
||||||
|
private FSHLog hlog;
|
||||||
|
|
||||||
|
public LogRollBackupSubprocedure(RegionServerServices rss, ProcedureMember member,
|
||||||
|
ForeignExceptionDispatcher errorListener, long wakeFrequency, long timeout,
|
||||||
|
LogRollBackupSubprocedurePool taskManager) {
|
||||||
|
|
||||||
|
super(member, LogRollMasterProcedureManager.ROLLLOG_PROCEDURE_NAME, errorListener,
|
||||||
|
wakeFrequency, timeout);
|
||||||
|
LOG.info("Constructing a LogRollBackupSubprocedure.");
|
||||||
|
this.rss = rss;
|
||||||
|
this.taskManager = taskManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callable task. TODO. We don't need a thread pool to execute roll log. This can be simplified
|
||||||
|
* with no use of subprocedurepool.
|
||||||
|
*/
|
||||||
|
class RSRollLogTask implements Callable<Void> {
|
||||||
|
RSRollLogTask() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void call() throws Exception {
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("++ DRPC started: " + rss.getServerName());
|
||||||
|
}
|
||||||
|
hlog = (FSHLog) rss.getWAL(null);
|
||||||
|
long filenum = hlog.getFilenum();
|
||||||
|
|
||||||
|
LOG.info("Trying to roll log in backup subprocedure, current log number: " + filenum);
|
||||||
|
hlog.rollWriter(true);
|
||||||
|
LOG.info("After roll log in backup subprocedure, current log number: " + hlog.getFilenum());
|
||||||
|
// write the log number to hbase:backup.
|
||||||
|
BackupSystemTable table = BackupSystemTable.getTable(rss.getConfiguration());
|
||||||
|
// sanity check, good for testing
|
||||||
|
HashMap<String, String> serverTimestampMap = table.readRegionServerLastLogRollResult();
|
||||||
|
String host = rss.getServerName().getHostname();
|
||||||
|
String sts = serverTimestampMap.get(host);
|
||||||
|
if (sts != null && Long.parseLong(sts) > filenum) {
|
||||||
|
LOG.warn("Won't update server's last roll log result: current=" + sts + " new=" + filenum);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
table.writeRegionServerLastLogRollResult(host, Long.toString(filenum));
|
||||||
|
// TODO: potential leak of HBase connection
|
||||||
|
// BackupSystemTable.close();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rolllog() throws ForeignException {
|
||||||
|
|
||||||
|
monitor.rethrowException();
|
||||||
|
|
||||||
|
taskManager.submitTask(new RSRollLogTask());
|
||||||
|
monitor.rethrowException();
|
||||||
|
|
||||||
|
// wait for everything to complete.
|
||||||
|
taskManager.waitForOutstandingTasks();
|
||||||
|
monitor.rethrowException();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void acquireBarrier() throws ForeignException {
|
||||||
|
// do nothing, executing in inside barrier step.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* do a log roll.
|
||||||
|
* @return some bytes
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public byte[] insideBarrier() throws ForeignException {
|
||||||
|
rolllog();
|
||||||
|
// FIXME
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel threads if they haven't finished.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void cleanup(Exception e) {
|
||||||
|
taskManager.abort("Aborting log roll subprocedure tasks for backup due to error", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hooray!
|
||||||
|
*/
|
||||||
|
public void releaseBarrier() {
|
||||||
|
// NO OP
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,137 @@
|
||||||
|
/**
|
||||||
|
* 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.regionserver;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ExecutorCompletionService;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
import org.apache.hadoop.hbase.Abortable;
|
||||||
|
import org.apache.hadoop.hbase.DaemonThreadFactory;
|
||||||
|
import org.apache.hadoop.hbase.errorhandling.ForeignException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle running each of the individual tasks for completing a backup procedure
|
||||||
|
* on a regionserver.
|
||||||
|
*/
|
||||||
|
public class LogRollBackupSubprocedurePool implements Closeable, Abortable {
|
||||||
|
private static final Log LOG = LogFactory.getLog(LogRollBackupSubprocedurePool.class);
|
||||||
|
|
||||||
|
/** Maximum number of concurrent snapshot region tasks that can run concurrently */
|
||||||
|
private static final String CONCURENT_BACKUP_TASKS_KEY = "hbase.backup.region.concurrentTasks";
|
||||||
|
private static final int DEFAULT_CONCURRENT_BACKUP_TASKS = 3;
|
||||||
|
|
||||||
|
private final ExecutorCompletionService<Void> taskPool;
|
||||||
|
private final ThreadPoolExecutor executor;
|
||||||
|
private volatile boolean aborted;
|
||||||
|
private final List<Future<Void>> futures = new ArrayList<Future<Void>>();
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
public LogRollBackupSubprocedurePool(String name, Configuration conf) {
|
||||||
|
// configure the executor service
|
||||||
|
long keepAlive =
|
||||||
|
conf.getLong(LogRollRegionServerProcedureManager.BACKUP_TIMEOUT_MILLIS_KEY,
|
||||||
|
LogRollRegionServerProcedureManager.BACKUP_TIMEOUT_MILLIS_DEFAULT);
|
||||||
|
int threads = conf.getInt(CONCURENT_BACKUP_TASKS_KEY, DEFAULT_CONCURRENT_BACKUP_TASKS);
|
||||||
|
this.name = name;
|
||||||
|
executor =
|
||||||
|
new ThreadPoolExecutor(1, threads, keepAlive, TimeUnit.SECONDS,
|
||||||
|
new LinkedBlockingQueue<Runnable>(), new DaemonThreadFactory("rs(" + name
|
||||||
|
+ ")-backup-pool"));
|
||||||
|
taskPool = new ExecutorCompletionService<Void>(executor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit a task to the pool.
|
||||||
|
*/
|
||||||
|
public void submitTask(final Callable<Void> task) {
|
||||||
|
Future<Void> f = this.taskPool.submit(task);
|
||||||
|
futures.add(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for all of the currently outstanding tasks submitted via {@link #submitTask(Callable)}
|
||||||
|
* @return <tt>true</tt> on success, <tt>false</tt> otherwise
|
||||||
|
* @throws ForeignException exception
|
||||||
|
*/
|
||||||
|
public boolean waitForOutstandingTasks() throws ForeignException {
|
||||||
|
LOG.debug("Waiting for backup procedure to finish.");
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (Future<Void> f : futures) {
|
||||||
|
f.get();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
if (aborted) {
|
||||||
|
throw new ForeignException("Interrupted and found to be aborted while waiting for tasks!",
|
||||||
|
e);
|
||||||
|
}
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
if (e.getCause() instanceof ForeignException) {
|
||||||
|
throw (ForeignException) e.getCause();
|
||||||
|
}
|
||||||
|
throw new ForeignException(name, e.getCause());
|
||||||
|
} finally {
|
||||||
|
// close off remaining tasks
|
||||||
|
for (Future<Void> f : futures) {
|
||||||
|
if (!f.isDone()) {
|
||||||
|
f.cancel(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to cleanly shutdown any running tasks - allows currently running tasks to cleanly
|
||||||
|
* finish
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
executor.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void abort(String why, Throwable e) {
|
||||||
|
if (this.aborted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.aborted = true;
|
||||||
|
LOG.warn("Aborting because: " + why, e);
|
||||||
|
this.executor.shutdownNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAborted() {
|
||||||
|
return this.aborted;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,168 @@
|
||||||
|
/**
|
||||||
|
* 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.regionserver;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
import org.apache.hadoop.hbase.CoordinatedStateManagerFactory;
|
||||||
|
import org.apache.hadoop.hbase.backup.master.LogRollMasterProcedureManager;
|
||||||
|
import org.apache.hadoop.hbase.coordination.BaseCoordinatedStateManager;
|
||||||
|
import org.apache.hadoop.hbase.errorhandling.ForeignExceptionDispatcher;
|
||||||
|
import org.apache.hadoop.hbase.procedure.ProcedureMember;
|
||||||
|
import org.apache.hadoop.hbase.procedure.ProcedureMemberRpcs;
|
||||||
|
import org.apache.hadoop.hbase.procedure.RegionServerProcedureManager;
|
||||||
|
import org.apache.hadoop.hbase.procedure.Subprocedure;
|
||||||
|
import org.apache.hadoop.hbase.procedure.SubprocedureFactory;
|
||||||
|
import org.apache.hadoop.hbase.regionserver.HRegionServer;
|
||||||
|
import org.apache.hadoop.hbase.regionserver.RegionServerServices;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This manager class handles the work dealing with backup for a {@link HRegionServer}.
|
||||||
|
* <p>
|
||||||
|
* This provides the mechanism necessary to kick off a backup specific {@link Subprocedure} that is
|
||||||
|
* responsible by this region server. If any failures occur with the subprocedure, the manager's
|
||||||
|
* procedure member notifies the procedure coordinator to abort all others.
|
||||||
|
* <p>
|
||||||
|
* On startup, requires {@link #start()} to be called.
|
||||||
|
* <p>
|
||||||
|
* On shutdown, requires org.apache.hadoop.hbase.procedure.ProcedureMember.close() to be
|
||||||
|
* called
|
||||||
|
*/
|
||||||
|
public class LogRollRegionServerProcedureManager extends RegionServerProcedureManager {
|
||||||
|
|
||||||
|
private static final Log LOG = LogFactory.getLog(LogRollRegionServerProcedureManager.class);
|
||||||
|
|
||||||
|
/** Conf key for number of request threads to start backup on regionservers */
|
||||||
|
public static final String BACKUP_REQUEST_THREADS_KEY = "hbase.backup.region.pool.threads";
|
||||||
|
/** # of threads for backup work on the rs. */
|
||||||
|
public static final int BACKUP_REQUEST_THREADS_DEFAULT = 10;
|
||||||
|
|
||||||
|
public static final String BACKUP_TIMEOUT_MILLIS_KEY = "hbase.backup.timeout";
|
||||||
|
public static final long BACKUP_TIMEOUT_MILLIS_DEFAULT = 60000;
|
||||||
|
|
||||||
|
/** Conf key for millis between checks to see if backup work completed or if there are errors */
|
||||||
|
public static final String BACKUP_REQUEST_WAKE_MILLIS_KEY = "hbase.backup.region.wakefrequency";
|
||||||
|
/** Default amount of time to check for errors while regions finish backup work */
|
||||||
|
private static final long BACKUP_REQUEST_WAKE_MILLIS_DEFAULT = 500;
|
||||||
|
|
||||||
|
private RegionServerServices rss;
|
||||||
|
private ProcedureMemberRpcs memberRpcs;
|
||||||
|
private ProcedureMember member;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a default backup procedure manager
|
||||||
|
*/
|
||||||
|
public LogRollRegionServerProcedureManager() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start accepting backup procedure requests.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
this.memberRpcs.start(rss.getServerName().toString(), member);
|
||||||
|
LOG.info("Started region server backup manager.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close <tt>this</tt> and all running backup procedure tasks
|
||||||
|
* @param force forcefully stop all running tasks
|
||||||
|
* @throws IOException exception
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void stop(boolean force) throws IOException {
|
||||||
|
String mode = force ? "abruptly" : "gracefully";
|
||||||
|
LOG.info("Stopping RegionServerBackupManager " + mode + ".");
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.member.close();
|
||||||
|
} finally {
|
||||||
|
this.memberRpcs.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If in a running state, creates the specified subprocedure for handling a backup procedure.
|
||||||
|
* @return Subprocedure to submit to the ProcedureMemeber.
|
||||||
|
*/
|
||||||
|
public Subprocedure buildSubprocedure() {
|
||||||
|
|
||||||
|
// don't run a backup if the parent is stop(ping)
|
||||||
|
if (rss.isStopping() || rss.isStopped()) {
|
||||||
|
throw new IllegalStateException("Can't start backup procedure on RS: " + rss.getServerName()
|
||||||
|
+ ", because stopping/stopped!");
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.info("Attempting to run a roll log procedure for backup.");
|
||||||
|
ForeignExceptionDispatcher errorDispatcher = new ForeignExceptionDispatcher();
|
||||||
|
Configuration conf = rss.getConfiguration();
|
||||||
|
long timeoutMillis = conf.getLong(BACKUP_TIMEOUT_MILLIS_KEY, BACKUP_TIMEOUT_MILLIS_DEFAULT);
|
||||||
|
long wakeMillis =
|
||||||
|
conf.getLong(BACKUP_REQUEST_WAKE_MILLIS_KEY, BACKUP_REQUEST_WAKE_MILLIS_DEFAULT);
|
||||||
|
|
||||||
|
LogRollBackupSubprocedurePool taskManager =
|
||||||
|
new LogRollBackupSubprocedurePool(rss.getServerName().toString(), conf);
|
||||||
|
return new LogRollBackupSubprocedure(rss, member, errorDispatcher, wakeMillis, timeoutMillis,
|
||||||
|
taskManager);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the actual backup procedure runner that will do all the 'hard' work
|
||||||
|
*/
|
||||||
|
public class BackupSubprocedureBuilder implements SubprocedureFactory {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Subprocedure buildSubprocedure(String name, byte[] data) {
|
||||||
|
return LogRollRegionServerProcedureManager.this.buildSubprocedure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(RegionServerServices rss) throws IOException {
|
||||||
|
this.rss = rss;
|
||||||
|
BaseCoordinatedStateManager coordManager =
|
||||||
|
(BaseCoordinatedStateManager) CoordinatedStateManagerFactory.getCoordinatedStateManager(rss
|
||||||
|
.getConfiguration());
|
||||||
|
coordManager.initialize(rss);
|
||||||
|
this.memberRpcs =
|
||||||
|
coordManager
|
||||||
|
.getProcedureMemberRpcs(LogRollMasterProcedureManager.ROLLLOG_PROCEDURE_SIGNATURE);
|
||||||
|
|
||||||
|
// read in the backup handler configuration properties
|
||||||
|
Configuration conf = rss.getConfiguration();
|
||||||
|
long keepAlive = conf.getLong(BACKUP_TIMEOUT_MILLIS_KEY, BACKUP_TIMEOUT_MILLIS_DEFAULT);
|
||||||
|
int opThreads = conf.getInt(BACKUP_REQUEST_THREADS_KEY, BACKUP_REQUEST_THREADS_DEFAULT);
|
||||||
|
// create the actual cohort member
|
||||||
|
ThreadPoolExecutor pool =
|
||||||
|
ProcedureMember.defaultPool(rss.getServerName().toString(), opThreads, keepAlive);
|
||||||
|
this.member = new ProcedureMember(memberRpcs, pool, new BackupSubprocedureBuilder());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getProcedureSignature() {
|
||||||
|
return "backup-proc";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -17,7 +17,11 @@
|
||||||
*/
|
*/
|
||||||
package org.apache.hadoop.hbase.coordination;
|
package org.apache.hadoop.hbase.coordination;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.apache.hadoop.hbase.classification.InterfaceAudience;
|
import org.apache.hadoop.hbase.classification.InterfaceAudience;
|
||||||
|
import org.apache.hadoop.hbase.procedure.ProcedureCoordinatorRpcs;
|
||||||
|
import org.apache.hadoop.hbase.procedure.ProcedureMemberRpcs;
|
||||||
import org.apache.hadoop.hbase.CoordinatedStateManager;
|
import org.apache.hadoop.hbase.CoordinatedStateManager;
|
||||||
import org.apache.hadoop.hbase.Server;
|
import org.apache.hadoop.hbase.Server;
|
||||||
|
|
||||||
|
@ -51,8 +55,21 @@ public abstract class BaseCoordinatedStateManager implements CoordinatedStateMan
|
||||||
* Method to retrieve coordination for split log worker
|
* Method to retrieve coordination for split log worker
|
||||||
*/
|
*/
|
||||||
public abstract SplitLogWorkerCoordination getSplitLogWorkerCoordination();
|
public abstract SplitLogWorkerCoordination getSplitLogWorkerCoordination();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to retrieve coordination for split log manager
|
* Method to retrieve coordination for split log manager
|
||||||
*/
|
*/
|
||||||
public abstract SplitLogManagerCoordination getSplitLogManagerCoordination();
|
public abstract SplitLogManagerCoordination getSplitLogManagerCoordination();
|
||||||
|
/**
|
||||||
|
* Method to retrieve {@link org.apache.hadoop.hbase.procedure.ProcedureCoordinatorRpcs}
|
||||||
|
*/
|
||||||
|
public abstract ProcedureCoordinatorRpcs
|
||||||
|
getProcedureCoordinatorRpcs(String procType, String coordNode) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to retrieve {@link org.apache.hadoop.hbase.procedure.ProcedureMemberRpc}
|
||||||
|
*/
|
||||||
|
public abstract ProcedureMemberRpcs
|
||||||
|
getProcedureMemberRpcs(String procType) throws IOException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,9 +17,15 @@
|
||||||
*/
|
*/
|
||||||
package org.apache.hadoop.hbase.coordination;
|
package org.apache.hadoop.hbase.coordination;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.apache.hadoop.hbase.classification.InterfaceAudience;
|
import org.apache.hadoop.hbase.classification.InterfaceAudience;
|
||||||
import org.apache.hadoop.hbase.HBaseInterfaceAudience;
|
import org.apache.hadoop.hbase.HBaseInterfaceAudience;
|
||||||
import org.apache.hadoop.hbase.Server;
|
import org.apache.hadoop.hbase.Server;
|
||||||
|
import org.apache.hadoop.hbase.procedure.ProcedureCoordinatorRpcs;
|
||||||
|
import org.apache.hadoop.hbase.procedure.ProcedureMemberRpcs;
|
||||||
|
import org.apache.hadoop.hbase.procedure.ZKProcedureCoordinatorRpcs;
|
||||||
|
import org.apache.hadoop.hbase.procedure.ZKProcedureMemberRpcs;
|
||||||
import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
|
import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -49,9 +55,21 @@ public class ZkCoordinatedStateManager extends BaseCoordinatedStateManager {
|
||||||
@Override
|
@Override
|
||||||
public SplitLogWorkerCoordination getSplitLogWorkerCoordination() {
|
public SplitLogWorkerCoordination getSplitLogWorkerCoordination() {
|
||||||
return splitLogWorkerCoordination;
|
return splitLogWorkerCoordination;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SplitLogManagerCoordination getSplitLogManagerCoordination() {
|
public SplitLogManagerCoordination getSplitLogManagerCoordination() {
|
||||||
return splitLogManagerCoordination;
|
return splitLogManagerCoordination;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProcedureCoordinatorRpcs getProcedureCoordinatorRpcs(String procType, String coordNode)
|
||||||
|
throws IOException {
|
||||||
|
return new ZKProcedureCoordinatorRpcs(watcher, procType, coordNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProcedureMemberRpcs getProcedureMemberRpcs(String procType) throws IOException {
|
||||||
|
return new ZKProcedureMemberRpcs(watcher, procType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,6 +85,9 @@ public class WALPlayer extends Configured implements Tool {
|
||||||
|
|
||||||
private final static String JOB_NAME_CONF_KEY = "mapreduce.job.name";
|
private final static String JOB_NAME_CONF_KEY = "mapreduce.job.name";
|
||||||
|
|
||||||
|
public WALPlayer(){
|
||||||
|
}
|
||||||
|
|
||||||
protected WALPlayer(final Configuration c) {
|
protected WALPlayer(final Configuration c) {
|
||||||
super(c);
|
super(c);
|
||||||
}
|
}
|
||||||
|
@ -94,7 +97,7 @@ public class WALPlayer extends Configured implements Tool {
|
||||||
* This one can be used together with {@link KeyValueSortReducer}
|
* This one can be used together with {@link KeyValueSortReducer}
|
||||||
*/
|
*/
|
||||||
static class WALKeyValueMapper
|
static class WALKeyValueMapper
|
||||||
extends Mapper<WALKey, WALEdit, ImmutableBytesWritable, KeyValue> {
|
extends Mapper<WALKey, WALEdit, ImmutableBytesWritable, KeyValue> {
|
||||||
private byte[] table;
|
private byte[] table;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -106,7 +109,9 @@ public class WALPlayer extends Configured implements Tool {
|
||||||
if (Bytes.equals(table, key.getTablename().getName())) {
|
if (Bytes.equals(table, key.getTablename().getName())) {
|
||||||
for (Cell cell : value.getCells()) {
|
for (Cell cell : value.getCells()) {
|
||||||
KeyValue kv = KeyValueUtil.ensureKeyValue(cell);
|
KeyValue kv = KeyValueUtil.ensureKeyValue(cell);
|
||||||
if (WALEdit.isMetaEditFamily(kv)) continue;
|
if (WALEdit.isMetaEditFamily(kv)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
context.write(new ImmutableBytesWritable(CellUtil.cloneRow(kv)), kv);
|
context.write(new ImmutableBytesWritable(CellUtil.cloneRow(kv)), kv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,7 +137,7 @@ public class WALPlayer extends Configured implements Tool {
|
||||||
* a running HBase instance.
|
* a running HBase instance.
|
||||||
*/
|
*/
|
||||||
protected static class WALMapper
|
protected static class WALMapper
|
||||||
extends Mapper<WALKey, WALEdit, ImmutableBytesWritable, Mutation> {
|
extends Mapper<WALKey, WALEdit, ImmutableBytesWritable, Mutation> {
|
||||||
private Map<TableName, TableName> tables = new TreeMap<TableName, TableName>();
|
private Map<TableName, TableName> tables = new TreeMap<TableName, TableName>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -149,7 +154,9 @@ public class WALPlayer extends Configured implements Tool {
|
||||||
Cell lastCell = null;
|
Cell lastCell = null;
|
||||||
for (Cell cell : value.getCells()) {
|
for (Cell cell : value.getCells()) {
|
||||||
// filtering WAL meta entries
|
// filtering WAL meta entries
|
||||||
if (WALEdit.isMetaEditFamily(cell)) continue;
|
if (WALEdit.isMetaEditFamily(cell)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Allow a subclass filter out this cell.
|
// Allow a subclass filter out this cell.
|
||||||
if (filter(context, cell)) {
|
if (filter(context, cell)) {
|
||||||
|
@ -160,8 +167,12 @@ public class WALPlayer extends Configured implements Tool {
|
||||||
if (lastCell == null || lastCell.getTypeByte() != cell.getTypeByte()
|
if (lastCell == null || lastCell.getTypeByte() != cell.getTypeByte()
|
||||||
|| !CellUtil.matchingRow(lastCell, cell)) {
|
|| !CellUtil.matchingRow(lastCell, cell)) {
|
||||||
// row or type changed, write out aggregate KVs.
|
// row or type changed, write out aggregate KVs.
|
||||||
if (put != null) context.write(tableOut, put);
|
if (put != null) {
|
||||||
if (del != null) context.write(tableOut, del);
|
context.write(tableOut, put);
|
||||||
|
}
|
||||||
|
if (del != null) {
|
||||||
|
context.write(tableOut, del);
|
||||||
|
}
|
||||||
if (CellUtil.isDelete(cell)) {
|
if (CellUtil.isDelete(cell)) {
|
||||||
del = new Delete(CellUtil.cloneRow(cell));
|
del = new Delete(CellUtil.cloneRow(cell));
|
||||||
} else {
|
} else {
|
||||||
|
@ -177,8 +188,12 @@ public class WALPlayer extends Configured implements Tool {
|
||||||
lastCell = cell;
|
lastCell = cell;
|
||||||
}
|
}
|
||||||
// write residual KVs
|
// write residual KVs
|
||||||
if (put != null) context.write(tableOut, put);
|
if (put != null) {
|
||||||
if (del != null) context.write(tableOut, del);
|
context.write(tableOut, put);
|
||||||
|
}
|
||||||
|
if (del != null) {
|
||||||
|
context.write(tableOut, del);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
@ -186,7 +201,8 @@ public class WALPlayer extends Configured implements Tool {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param cell
|
* Filter cell
|
||||||
|
* @param cell cell
|
||||||
* @return Return true if we are to emit this cell.
|
* @return Return true if we are to emit this cell.
|
||||||
*/
|
*/
|
||||||
protected boolean filter(Context context, final Cell cell) {
|
protected boolean filter(Context context, final Cell cell) {
|
||||||
|
@ -197,9 +213,7 @@ public class WALPlayer extends Configured implements Tool {
|
||||||
public void setup(Context context) throws IOException {
|
public void setup(Context context) throws IOException {
|
||||||
String[] tableMap = context.getConfiguration().getStrings(TABLE_MAP_KEY);
|
String[] tableMap = context.getConfiguration().getStrings(TABLE_MAP_KEY);
|
||||||
String[] tablesToUse = context.getConfiguration().getStrings(TABLES_KEY);
|
String[] tablesToUse = context.getConfiguration().getStrings(TABLES_KEY);
|
||||||
if (tablesToUse == null && tableMap == null) {
|
if (tablesToUse == null || tableMap == null || tablesToUse.length != tableMap.length) {
|
||||||
// Then user wants all tables.
|
|
||||||
} else if (tablesToUse == null || tableMap == null || tablesToUse.length != tableMap.length) {
|
|
||||||
// this can only happen when WALMapper is used directly by a class other than WALPlayer
|
// this can only happen when WALMapper is used directly by a class other than WALPlayer
|
||||||
throw new IOException("No tables or incorrect table mapping specified.");
|
throw new IOException("No tables or incorrect table mapping specified.");
|
||||||
}
|
}
|
||||||
|
@ -215,7 +229,9 @@ public class WALPlayer extends Configured implements Tool {
|
||||||
|
|
||||||
void setupTime(Configuration conf, String option) throws IOException {
|
void setupTime(Configuration conf, String option) throws IOException {
|
||||||
String val = conf.get(option);
|
String val = conf.get(option);
|
||||||
if (null == val) return;
|
if (null == val) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
long ms;
|
long ms;
|
||||||
try {
|
try {
|
||||||
// first try to parse in user friendly form
|
// first try to parse in user friendly form
|
||||||
|
@ -295,7 +311,8 @@ public class WALPlayer extends Configured implements Tool {
|
||||||
return job;
|
return job;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
|
* Print usage
|
||||||
* @param errorMsg Error message. Can be null.
|
* @param errorMsg Error message. Can be null.
|
||||||
*/
|
*/
|
||||||
private void usage(final String errorMsg) {
|
private void usage(final String errorMsg) {
|
||||||
|
@ -305,7 +322,8 @@ public class WALPlayer extends Configured implements Tool {
|
||||||
System.err.println("Usage: " + NAME + " [options] <wal inputdir> <tables> [<tableMappings>]");
|
System.err.println("Usage: " + NAME + " [options] <wal inputdir> <tables> [<tableMappings>]");
|
||||||
System.err.println("Read all WAL entries for <tables>.");
|
System.err.println("Read all WAL entries for <tables>.");
|
||||||
System.err.println("If no tables (\"\") are specific, all tables are imported.");
|
System.err.println("If no tables (\"\") are specific, all tables are imported.");
|
||||||
System.err.println("(Careful, even -ROOT- and hbase:meta entries will be imported in that case.)");
|
System.err.println("(Careful, even -ROOT- and hbase:meta entries will be imported"+
|
||||||
|
" in that case.)");
|
||||||
System.err.println("Otherwise <tables> is a comma separated list of tables.\n");
|
System.err.println("Otherwise <tables> is a comma separated list of tables.\n");
|
||||||
System.err.println("The WAL entries can be mapped to new set of tables via <tableMapping>.");
|
System.err.println("The WAL entries can be mapped to new set of tables via <tableMapping>.");
|
||||||
System.err.println("<tableMapping> is a command separated list of targettables.");
|
System.err.println("<tableMapping> is a command separated list of targettables.");
|
||||||
|
@ -318,10 +336,10 @@ public class WALPlayer extends Configured implements Tool {
|
||||||
System.err.println(" -D" + WALInputFormat.START_TIME_KEY + "=[date|ms]");
|
System.err.println(" -D" + WALInputFormat.START_TIME_KEY + "=[date|ms]");
|
||||||
System.err.println(" -D" + WALInputFormat.END_TIME_KEY + "=[date|ms]");
|
System.err.println(" -D" + WALInputFormat.END_TIME_KEY + "=[date|ms]");
|
||||||
System.err.println(" -D " + JOB_NAME_CONF_KEY
|
System.err.println(" -D " + JOB_NAME_CONF_KEY
|
||||||
+ "=jobName - use the specified mapreduce job name for the wal player");
|
+ "=jobName - use the specified mapreduce job name for the wal player");
|
||||||
System.err.println("For performance also consider the following options:\n"
|
System.err.println("For performance also consider the following options:\n"
|
||||||
+ " -Dmapreduce.map.speculative=false\n"
|
+ " -Dmapreduce.map.speculative=false\n"
|
||||||
+ " -Dmapreduce.reduce.speculative=false");
|
+ " -Dmapreduce.reduce.speculative=false");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -75,6 +75,7 @@ import org.apache.hadoop.hbase.TableName;
|
||||||
import org.apache.hadoop.hbase.TableNotDisabledException;
|
import org.apache.hadoop.hbase.TableNotDisabledException;
|
||||||
import org.apache.hadoop.hbase.TableNotFoundException;
|
import org.apache.hadoop.hbase.TableNotFoundException;
|
||||||
import org.apache.hadoop.hbase.UnknownRegionException;
|
import org.apache.hadoop.hbase.UnknownRegionException;
|
||||||
|
import org.apache.hadoop.hbase.backup.BackupManager;
|
||||||
import org.apache.hadoop.hbase.classification.InterfaceAudience;
|
import org.apache.hadoop.hbase.classification.InterfaceAudience;
|
||||||
import org.apache.hadoop.hbase.client.RegionReplicaUtil;
|
import org.apache.hadoop.hbase.client.RegionReplicaUtil;
|
||||||
import org.apache.hadoop.hbase.client.Result;
|
import org.apache.hadoop.hbase.client.Result;
|
||||||
|
@ -384,6 +385,7 @@ public class HMaster extends HRegionServer implements MasterServices, Server {
|
||||||
this.conf.setBoolean(HConstants.USE_META_REPLICAS, false);
|
this.conf.setBoolean(HConstants.USE_META_REPLICAS, false);
|
||||||
|
|
||||||
Replication.decorateMasterConfiguration(this.conf);
|
Replication.decorateMasterConfiguration(this.conf);
|
||||||
|
BackupManager.decorateMasterConfiguration(this.conf);
|
||||||
|
|
||||||
// Hack! Maps DFSClient => Master for logs. HDFS made this
|
// Hack! Maps DFSClient => Master for logs. HDFS made this
|
||||||
// config param for task trackers, but we can piggyback off of it.
|
// config param for task trackers, but we can piggyback off of it.
|
||||||
|
|
|
@ -37,7 +37,7 @@ public abstract class RegionServerProcedureManager extends ProcedureManager {
|
||||||
* @param rss Region Server service interface
|
* @param rss Region Server service interface
|
||||||
* @throws KeeperException
|
* @throws KeeperException
|
||||||
*/
|
*/
|
||||||
public abstract void initialize(RegionServerServices rss) throws KeeperException;
|
public abstract void initialize(RegionServerServices rss) throws IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start accepting procedure requests.
|
* Start accepting procedure requests.
|
||||||
|
|
|
@ -25,7 +25,6 @@ import org.apache.hadoop.conf.Configuration;
|
||||||
import org.apache.hadoop.hbase.procedure.flush.RegionServerFlushTableProcedureManager;
|
import org.apache.hadoop.hbase.procedure.flush.RegionServerFlushTableProcedureManager;
|
||||||
import org.apache.hadoop.hbase.regionserver.RegionServerServices;
|
import org.apache.hadoop.hbase.regionserver.RegionServerServices;
|
||||||
import org.apache.hadoop.hbase.regionserver.snapshot.RegionServerSnapshotManager;
|
import org.apache.hadoop.hbase.regionserver.snapshot.RegionServerSnapshotManager;
|
||||||
import org.apache.zookeeper.KeeperException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides the globally barriered procedure framework and environment
|
* Provides the globally barriered procedure framework and environment
|
||||||
|
@ -39,7 +38,7 @@ public class RegionServerProcedureManagerHost extends
|
||||||
private static final Log LOG = LogFactory
|
private static final Log LOG = LogFactory
|
||||||
.getLog(RegionServerProcedureManagerHost.class);
|
.getLog(RegionServerProcedureManagerHost.class);
|
||||||
|
|
||||||
public void initialize(RegionServerServices rss) throws KeeperException {
|
public void initialize(RegionServerServices rss) throws IOException {
|
||||||
for (RegionServerProcedureManager proc : procedures) {
|
for (RegionServerProcedureManager proc : procedures) {
|
||||||
LOG.debug("Procedure " + proc.getProcedureSignature() + " is initializing");
|
LOG.debug("Procedure " + proc.getProcedureSignature() + " is initializing");
|
||||||
proc.initialize(rss);
|
proc.initialize(rss);
|
||||||
|
|
|
@ -54,7 +54,7 @@ public class ZKProcedureCoordinatorRpcs implements ProcedureCoordinatorRpcs {
|
||||||
* @throws KeeperException if an unexpected zk error occurs
|
* @throws KeeperException if an unexpected zk error occurs
|
||||||
*/
|
*/
|
||||||
public ZKProcedureCoordinatorRpcs(ZooKeeperWatcher watcher,
|
public ZKProcedureCoordinatorRpcs(ZooKeeperWatcher watcher,
|
||||||
String procedureClass, String coordName) throws KeeperException {
|
String procedureClass, String coordName) throws IOException {
|
||||||
this.watcher = watcher;
|
this.watcher = watcher;
|
||||||
this.procedureType = procedureClass;
|
this.procedureType = procedureClass;
|
||||||
this.coordName = coordName;
|
this.coordName = coordName;
|
||||||
|
|
|
@ -68,49 +68,54 @@ public class ZKProcedureMemberRpcs implements ProcedureMemberRpcs {
|
||||||
* @throws KeeperException if we can't reach zookeeper
|
* @throws KeeperException if we can't reach zookeeper
|
||||||
*/
|
*/
|
||||||
public ZKProcedureMemberRpcs(final ZooKeeperWatcher watcher, final String procType)
|
public ZKProcedureMemberRpcs(final ZooKeeperWatcher watcher, final String procType)
|
||||||
throws KeeperException {
|
throws IOException {
|
||||||
this.zkController = new ZKProcedureUtil(watcher, procType) {
|
try {
|
||||||
@Override
|
this.zkController = new ZKProcedureUtil(watcher, procType) {
|
||||||
public void nodeCreated(String path) {
|
@Override
|
||||||
if (!isInProcedurePath(path)) {
|
public void nodeCreated(String path) {
|
||||||
return;
|
if (!isInProcedurePath(path)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.info("Received created event:" + path);
|
||||||
|
// if it is a simple start/end/abort then we just rewatch the node
|
||||||
|
if (isAcquiredNode(path)) {
|
||||||
|
waitForNewProcedures();
|
||||||
|
return;
|
||||||
|
} else if (isAbortNode(path)) {
|
||||||
|
watchForAbortedProcedures();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String parent = ZKUtil.getParent(path);
|
||||||
|
// if its the end barrier, the procedure can be completed
|
||||||
|
if (isReachedNode(parent)) {
|
||||||
|
receivedReachedGlobalBarrier(path);
|
||||||
|
return;
|
||||||
|
} else if (isAbortNode(parent)) {
|
||||||
|
abort(path);
|
||||||
|
return;
|
||||||
|
} else if (isAcquiredNode(parent)) {
|
||||||
|
startNewSubprocedure(path);
|
||||||
|
} else {
|
||||||
|
LOG.debug("Ignoring created notification for node:" + path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG.info("Received created event:" + path);
|
@Override
|
||||||
// if it is a simple start/end/abort then we just rewatch the node
|
public void nodeChildrenChanged(String path) {
|
||||||
if (isAcquiredNode(path)) {
|
if (path.equals(this.acquiredZnode)) {
|
||||||
waitForNewProcedures();
|
LOG.info("Received procedure start children changed event: " + path);
|
||||||
return;
|
waitForNewProcedures();
|
||||||
} else if (isAbortNode(path)) {
|
} else if (path.equals(this.abortZnode)) {
|
||||||
watchForAbortedProcedures();
|
LOG.info("Received procedure abort children changed event: " + path);
|
||||||
return;
|
watchForAbortedProcedures();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
String parent = ZKUtil.getParent(path);
|
};
|
||||||
// if its the end barrier, the procedure can be completed
|
} catch (KeeperException e) {
|
||||||
if (isReachedNode(parent)) {
|
// TODO Auto-generated catch block
|
||||||
receivedReachedGlobalBarrier(path);
|
throw new IOException(e);
|
||||||
return;
|
}
|
||||||
} else if (isAbortNode(parent)) {
|
|
||||||
abort(path);
|
|
||||||
return;
|
|
||||||
} else if (isAcquiredNode(parent)) {
|
|
||||||
startNewSubprocedure(path);
|
|
||||||
} else {
|
|
||||||
LOG.debug("Ignoring created notification for node:" + path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void nodeChildrenChanged(String path) {
|
|
||||||
if (path.equals(this.acquiredZnode)) {
|
|
||||||
LOG.info("Received procedure start children changed event: " + path);
|
|
||||||
waitForNewProcedures();
|
|
||||||
} else if (path.equals(this.abortZnode)) {
|
|
||||||
LOG.info("Received procedure abort children changed event: " + path);
|
|
||||||
watchForAbortedProcedures();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ZKProcedureUtil getZkController() {
|
public ZKProcedureUtil getZkController() {
|
||||||
|
|
|
@ -317,7 +317,7 @@ public class RegionServerFlushTableProcedureManager extends RegionServerProcedur
|
||||||
* @throws KeeperException if the zookeeper cannot be reached
|
* @throws KeeperException if the zookeeper cannot be reached
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void initialize(RegionServerServices rss) throws KeeperException {
|
public void initialize(RegionServerServices rss) throws IOException {
|
||||||
this.rss = rss;
|
this.rss = rss;
|
||||||
ZooKeeperWatcher zkw = rss.getZooKeeper();
|
ZooKeeperWatcher zkw = rss.getZooKeeper();
|
||||||
this.memberRpcs = new ZKProcedureMemberRpcs(zkw,
|
this.memberRpcs = new ZKProcedureMemberRpcs(zkw,
|
||||||
|
|
|
@ -807,8 +807,8 @@ public class HRegionServer extends HasThread implements
|
||||||
rspmHost = new RegionServerProcedureManagerHost();
|
rspmHost = new RegionServerProcedureManagerHost();
|
||||||
rspmHost.loadProcedures(conf);
|
rspmHost.loadProcedures(conf);
|
||||||
rspmHost.initialize(this);
|
rspmHost.initialize(this);
|
||||||
} catch (KeeperException e) {
|
} catch (IOException e) {
|
||||||
this.abort("Failed to reach zk cluster when creating procedure handler.", e);
|
this.abort("Failed to reach coordination cluster when creating procedure handler.", e);
|
||||||
}
|
}
|
||||||
// register watcher for recovering regions
|
// register watcher for recovering regions
|
||||||
this.recoveringRegionWatcher = new RecoveringRegionWatcher(this.zooKeeper, this);
|
this.recoveringRegionWatcher = new RecoveringRegionWatcher(this.zooKeeper, this);
|
||||||
|
|
|
@ -390,7 +390,7 @@ public class RegionServerSnapshotManager extends RegionServerProcedureManager {
|
||||||
* @throws KeeperException if the zookeeper cluster cannot be reached
|
* @throws KeeperException if the zookeeper cluster cannot be reached
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void initialize(RegionServerServices rss) throws KeeperException {
|
public void initialize(RegionServerServices rss) throws IOException {
|
||||||
this.rss = rss;
|
this.rss = rss;
|
||||||
ZooKeeperWatcher zkw = rss.getZooKeeper();
|
ZooKeeperWatcher zkw = rss.getZooKeeper();
|
||||||
this.memberRpcs = new ZKProcedureMemberRpcs(zkw,
|
this.memberRpcs = new ZKProcedureMemberRpcs(zkw,
|
||||||
|
|
|
@ -97,6 +97,8 @@ import com.lmax.disruptor.TimeoutException;
|
||||||
import com.lmax.disruptor.dsl.Disruptor;
|
import com.lmax.disruptor.dsl.Disruptor;
|
||||||
import com.lmax.disruptor.dsl.ProducerType;
|
import com.lmax.disruptor.dsl.ProducerType;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of {@link WAL} to go against {@link FileSystem}; i.e. keep WALs in HDFS.
|
* Implementation of {@link WAL} to go against {@link FileSystem}; i.e. keep WALs in HDFS.
|
||||||
* Only one WAL is ever being written at a time. When a WAL hits a configured maximum size,
|
* Only one WAL is ever being written at a time. When a WAL hits a configured maximum size,
|
||||||
|
@ -359,7 +361,9 @@ public class FSHLog implements WAL {
|
||||||
public int compare(Path o1, Path o2) {
|
public int compare(Path o1, Path o2) {
|
||||||
long t1 = getFileNumFromFileName(o1);
|
long t1 = getFileNumFromFileName(o1);
|
||||||
long t2 = getFileNumFromFileName(o2);
|
long t2 = getFileNumFromFileName(o2);
|
||||||
if (t1 == t2) return 0;
|
if (t1 == t2) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
return (t1 > t2) ? 1 : -1;
|
return (t1 > t2) ? 1 : -1;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -402,7 +406,7 @@ public class FSHLog implements WAL {
|
||||||
* @param root path for stored and archived wals
|
* @param root path for stored and archived wals
|
||||||
* @param logDir dir where wals are stored
|
* @param logDir dir where wals are stored
|
||||||
* @param conf configuration to use
|
* @param conf configuration to use
|
||||||
* @throws IOException
|
* @throws IOException exception
|
||||||
*/
|
*/
|
||||||
public FSHLog(final FileSystem fs, final Path root, final String logDir, final Configuration conf)
|
public FSHLog(final FileSystem fs, final Path root, final String logDir, final Configuration conf)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
@ -410,7 +414,7 @@ public class FSHLog implements WAL {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an edit log at the given <code>dir</code> location.
|
* Create an edit log at the given directory location.
|
||||||
*
|
*
|
||||||
* You should never have to load an existing log. If there is a log at
|
* You should never have to load an existing log. If there is a log at
|
||||||
* startup, it should have already been processed and deleted by the time the
|
* startup, it should have already been processed and deleted by the time the
|
||||||
|
@ -425,13 +429,13 @@ public class FSHLog implements WAL {
|
||||||
* be registered before we do anything else; e.g. the
|
* be registered before we do anything else; e.g. the
|
||||||
* Constructor {@link #rollWriter()}.
|
* Constructor {@link #rollWriter()}.
|
||||||
* @param failIfWALExists If true IOException will be thrown if files related to this wal
|
* @param failIfWALExists If true IOException will be thrown if files related to this wal
|
||||||
* already exist.
|
* already exist.
|
||||||
* @param prefix should always be hostname and port in distributed env and
|
* @param prefix should always be hostname and port in distributed env and
|
||||||
* it will be URL encoded before being used.
|
* it will be URL encoded before being used.
|
||||||
* If prefix is null, "wal" will be used
|
* If prefix is null, "wal" will be used
|
||||||
* @param suffix will be url encoded. null is treated as empty. non-empty must start with
|
* @param suffix will be url encoded. null is treated as empty. non-empty must start with
|
||||||
* {@link DefaultWALProvider#WAL_FILE_NAME_DELIMITER}
|
* {@link DefaultWALProvider#WAL_FILE_NAME_DELIMITER}
|
||||||
* @throws IOException
|
* @throws IOException exception
|
||||||
*/
|
*/
|
||||||
public FSHLog(final FileSystem fs, final Path rootDir, final String logDir,
|
public FSHLog(final FileSystem fs, final Path rootDir, final String logDir,
|
||||||
final String archiveDir, final Configuration conf,
|
final String archiveDir, final Configuration conf,
|
||||||
|
@ -593,7 +597,9 @@ public class FSHLog implements WAL {
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
OutputStream getOutputStream() {
|
OutputStream getOutputStream() {
|
||||||
FSDataOutputStream fsdos = this.hdfs_out;
|
FSDataOutputStream fsdos = this.hdfs_out;
|
||||||
if (fsdos == null) return null;
|
if (fsdos == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return fsdos.getWrappedStream();
|
return fsdos.getWrappedStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -628,7 +634,7 @@ public class FSHLog implements WAL {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tell listeners about pre log roll.
|
* Tell listeners about pre log roll.
|
||||||
* @throws IOException
|
* @throws IOException exception
|
||||||
*/
|
*/
|
||||||
private void tellListenersAboutPreLogRoll(final Path oldPath, final Path newPath)
|
private void tellListenersAboutPreLogRoll(final Path oldPath, final Path newPath)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
@ -641,7 +647,7 @@ public class FSHLog implements WAL {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tell listeners about post log roll.
|
* Tell listeners about post log roll.
|
||||||
* @throws IOException
|
* @throws IOException exception
|
||||||
*/
|
*/
|
||||||
private void tellListenersAboutPostLogRoll(final Path oldPath, final Path newPath)
|
private void tellListenersAboutPostLogRoll(final Path oldPath, final Path newPath)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
@ -654,8 +660,7 @@ public class FSHLog implements WAL {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run a sync after opening to set up the pipeline.
|
* Run a sync after opening to set up the pipeline.
|
||||||
* @param nextWriter
|
* @param nextWriter next writer
|
||||||
* @param startTimeNanos
|
|
||||||
*/
|
*/
|
||||||
private void preemptiveSync(final ProtobufLogWriter nextWriter) {
|
private void preemptiveSync(final ProtobufLogWriter nextWriter) {
|
||||||
long startTimeNanos = System.nanoTime();
|
long startTimeNanos = System.nanoTime();
|
||||||
|
@ -673,7 +678,9 @@ public class FSHLog implements WAL {
|
||||||
rollWriterLock.lock();
|
rollWriterLock.lock();
|
||||||
try {
|
try {
|
||||||
// Return if nothing to flush.
|
// Return if nothing to flush.
|
||||||
if (!force && (this.writer != null && this.numEntries.get() <= 0)) return null;
|
if (!force && (this.writer != null && this.numEntries.get() <= 0)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
byte [][] regionsToFlush = null;
|
byte [][] regionsToFlush = null;
|
||||||
if (this.closed) {
|
if (this.closed) {
|
||||||
LOG.debug("WAL closed. Skipping rolling of writer");
|
LOG.debug("WAL closed. Skipping rolling of writer");
|
||||||
|
@ -728,7 +735,7 @@ public class FSHLog implements WAL {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Archive old logs. A WAL is eligible for archiving if all its WALEdits have been flushed.
|
* Archive old logs. A WAL is eligible for archiving if all its WALEdits have been flushed.
|
||||||
* @throws IOException
|
* @throws IOException exception
|
||||||
*/
|
*/
|
||||||
private void cleanOldLogs() throws IOException {
|
private void cleanOldLogs() throws IOException {
|
||||||
List<Path> logsToArchive = null;
|
List<Path> logsToArchive = null;
|
||||||
|
@ -738,9 +745,13 @@ public class FSHLog implements WAL {
|
||||||
Path log = e.getKey();
|
Path log = e.getKey();
|
||||||
Map<byte[], Long> sequenceNums = e.getValue();
|
Map<byte[], Long> sequenceNums = e.getValue();
|
||||||
if (this.sequenceIdAccounting.areAllLower(sequenceNums)) {
|
if (this.sequenceIdAccounting.areAllLower(sequenceNums)) {
|
||||||
if (logsToArchive == null) logsToArchive = new ArrayList<Path>();
|
if (logsToArchive == null) {
|
||||||
|
logsToArchive = new ArrayList<Path>();
|
||||||
|
}
|
||||||
logsToArchive.add(log);
|
logsToArchive.add(log);
|
||||||
if (LOG.isTraceEnabled()) LOG.trace("WAL file ready for archiving " + log);
|
if (LOG.isTraceEnabled()) {
|
||||||
|
LOG.trace("WAL file ready for archiving " + log);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (logsToArchive != null) {
|
if (logsToArchive != null) {
|
||||||
|
@ -770,7 +781,9 @@ public class FSHLog implements WAL {
|
||||||
if (regions != null) {
|
if (regions != null) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
for (int i = 0; i < regions.length; i++) {
|
for (int i = 0; i < regions.length; i++) {
|
||||||
if (i > 0) sb.append(", ");
|
if (i > 0) {
|
||||||
|
sb.append(", ");
|
||||||
|
}
|
||||||
sb.append(Bytes.toStringBinary(regions[i]));
|
sb.append(Bytes.toStringBinary(regions[i]));
|
||||||
}
|
}
|
||||||
LOG.info("Too many WALs; count=" + logCount + ", max=" + this.maxLogs +
|
LOG.info("Too many WALs; count=" + logCount + ", max=" + this.maxLogs +
|
||||||
|
@ -836,7 +849,9 @@ public class FSHLog implements WAL {
|
||||||
}
|
}
|
||||||
} catch (FailedSyncBeforeLogCloseException e) {
|
} catch (FailedSyncBeforeLogCloseException e) {
|
||||||
// If unflushed/unsynced entries on close, it is reason to abort.
|
// If unflushed/unsynced entries on close, it is reason to abort.
|
||||||
if (isUnflushedEntries()) throw e;
|
if (isUnflushedEntries()) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
LOG.warn("Failed sync-before-close but no outstanding appends; closing WAL: " +
|
LOG.warn("Failed sync-before-close but no outstanding appends; closing WAL: " +
|
||||||
e.getMessage());
|
e.getMessage());
|
||||||
}
|
}
|
||||||
|
@ -897,7 +912,9 @@ public class FSHLog implements WAL {
|
||||||
try {
|
try {
|
||||||
blockOnSync(syncFuture);
|
blockOnSync(syncFuture);
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
if (LOG.isTraceEnabled()) LOG.trace("Stale sync exception", ioe);
|
if (LOG.isTraceEnabled()) {
|
||||||
|
LOG.trace("Stale sync exception", ioe);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -969,6 +986,14 @@ public class FSHLog implements WAL {
|
||||||
return computeFilename(this.filenum.get());
|
return computeFilename(this.filenum.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To support old API compatibility
|
||||||
|
* @return current file number (timestamp)
|
||||||
|
*/
|
||||||
|
public long getFilenum() {
|
||||||
|
return filenum.get();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "FSHLog " + logFilePrefix + ":" + logFileSuffix + "(num " + filenum + ")";
|
return "FSHLog " + logFilePrefix + ":" + logFileSuffix + "(num " + filenum + ")";
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
/**
|
||||||
|
* 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.snapshot;
|
||||||
|
|
||||||
|
import org.apache.hadoop.hbase.backup.BackupHandler;
|
||||||
|
|
||||||
|
/* this class will be extended in future jira to support progress report */
|
||||||
|
public class SnapshotCopy extends ExportSnapshot {
|
||||||
|
private BackupHandler backupHandler;
|
||||||
|
private String table;
|
||||||
|
|
||||||
|
public SnapshotCopy(BackupHandler backupHandler, String table) {
|
||||||
|
super();
|
||||||
|
this.backupHandler = backupHandler;
|
||||||
|
this.table = table;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BackupHandler getBackupHandler() {
|
||||||
|
return this.backupHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTable() {
|
||||||
|
return this.table;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -209,6 +209,11 @@ public class DefaultWALProvider implements WALProvider {
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public static long extractFileNumFromWAL(final WAL wal) {
|
public static long extractFileNumFromWAL(final WAL wal) {
|
||||||
final Path walName = ((FSHLog)wal).getCurrentFileName();
|
final Path walName = ((FSHLog)wal).getCurrentFileName();
|
||||||
|
return extractFileNumFromWAL(walName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public static long extractFileNumFromWAL(final Path walName) {
|
||||||
if (walName == null) {
|
if (walName == null) {
|
||||||
throw new IllegalArgumentException("The WAL path couldn't be null");
|
throw new IllegalArgumentException("The WAL path couldn't be null");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.backup;
|
||||||
|
|
||||||
|
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.hbase.HBaseConfiguration;
|
||||||
|
import org.apache.hadoop.hbase.HBaseTestingUtility;
|
||||||
|
import org.apache.hadoop.hbase.HColumnDescriptor;
|
||||||
|
import org.apache.hadoop.hbase.HConstants;
|
||||||
|
import org.apache.hadoop.hbase.HTableDescriptor;
|
||||||
|
import org.apache.hadoop.hbase.TableName;
|
||||||
|
import org.apache.hadoop.hbase.backup.BackupHandler.BACKUPSTATUS;
|
||||||
|
import org.apache.hadoop.hbase.backup.master.LogRollMasterProcedureManager;
|
||||||
|
import org.apache.hadoop.hbase.backup.regionserver.LogRollRegionServerProcedureManager;
|
||||||
|
import org.apache.hadoop.hbase.client.Connection;
|
||||||
|
import org.apache.hadoop.hbase.client.ConnectionFactory;
|
||||||
|
import org.apache.hadoop.hbase.client.HBaseAdmin;
|
||||||
|
import org.apache.hadoop.hbase.client.HTable;
|
||||||
|
import org.apache.hadoop.hbase.client.Put;
|
||||||
|
import org.apache.hadoop.hbase.snapshot.SnapshotTestingUtils;
|
||||||
|
import org.apache.hadoop.hbase.util.Bytes;
|
||||||
|
import org.apache.hadoop.hbase.zookeeper.MiniZooKeeperCluster;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is only a base for other integration-level backup tests.
|
||||||
|
* Do not add tests here.
|
||||||
|
* TestBackupSmallTests is where tests that don't require bring machines up/down should go
|
||||||
|
* All other tests should have their own classes and extend this one
|
||||||
|
*/
|
||||||
|
public class TestBackupBase {
|
||||||
|
|
||||||
|
private static final Log LOG = LogFactory.getLog(TestBackupBase.class);
|
||||||
|
|
||||||
|
protected static Configuration conf1;
|
||||||
|
protected static Configuration conf2;
|
||||||
|
|
||||||
|
protected static HBaseTestingUtility TEST_UTIL;
|
||||||
|
protected static HBaseTestingUtility TEST_UTIL2;
|
||||||
|
|
||||||
|
protected static TableName table1;
|
||||||
|
protected static TableName table2;
|
||||||
|
protected static TableName table3;
|
||||||
|
protected static TableName table4;
|
||||||
|
|
||||||
|
protected static String table1_restore = "table1_restore";
|
||||||
|
protected static String table2_restore = "table2_restore";
|
||||||
|
protected static String table3_restore = "table3_restore";
|
||||||
|
protected static String table4_restore = "table4_restore";
|
||||||
|
|
||||||
|
protected static final int NB_ROWS_IN_BATCH = 100;
|
||||||
|
protected static final byte[] qualName = Bytes.toBytes("q1");
|
||||||
|
protected static final byte[] famName = Bytes.toBytes("f");
|
||||||
|
|
||||||
|
protected static String BACKUP_ROOT_DIR = "/backupUT";
|
||||||
|
protected static String BACKUP_REMOTE_ROOT_DIR = "/backupUT";
|
||||||
|
|
||||||
|
protected static final String BACKUP_ZNODE = "/backup/hbase";
|
||||||
|
protected static final String BACKUP_SUCCEED_NODE = "complete";
|
||||||
|
protected static final String BACKUP_FAILED_NODE = "failed";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws java.lang.Exception
|
||||||
|
*/
|
||||||
|
@BeforeClass
|
||||||
|
public static void setUpBeforeClass() throws Exception {
|
||||||
|
|
||||||
|
TEST_UTIL = new HBaseTestingUtility();
|
||||||
|
TEST_UTIL.getConfiguration().set("hbase.procedure.regionserver.classes",
|
||||||
|
LogRollRegionServerProcedureManager.class.getName());
|
||||||
|
TEST_UTIL.getConfiguration().set("hbase.procedure.master.classes",
|
||||||
|
LogRollMasterProcedureManager.class.getName());
|
||||||
|
TEST_UTIL.getConfiguration().set(HConstants.ZOOKEEPER_ZNODE_PARENT, "/1");
|
||||||
|
TEST_UTIL.startMiniZKCluster();
|
||||||
|
MiniZooKeeperCluster miniZK = TEST_UTIL.getZkCluster();
|
||||||
|
|
||||||
|
conf1 = TEST_UTIL.getConfiguration();
|
||||||
|
conf2 = HBaseConfiguration.create(conf1);
|
||||||
|
conf2.set(HConstants.ZOOKEEPER_ZNODE_PARENT, "/2");
|
||||||
|
TEST_UTIL2 = new HBaseTestingUtility(conf2);
|
||||||
|
TEST_UTIL2.setZkCluster(miniZK);
|
||||||
|
TEST_UTIL.startMiniCluster();
|
||||||
|
TEST_UTIL2.startMiniCluster();
|
||||||
|
conf1 = TEST_UTIL.getConfiguration();
|
||||||
|
|
||||||
|
TEST_UTIL.startMiniMapReduceCluster();
|
||||||
|
BACKUP_ROOT_DIR = TEST_UTIL.getConfiguration().get("fs.defaultFS") + "/backupUT";
|
||||||
|
LOG.info("ROOTDIR " + BACKUP_ROOT_DIR);
|
||||||
|
BACKUP_REMOTE_ROOT_DIR = TEST_UTIL2.getConfiguration().get("fs.defaultFS") + "/backupUT";
|
||||||
|
LOG.info("REMOTE ROOTDIR " + BACKUP_REMOTE_ROOT_DIR);
|
||||||
|
|
||||||
|
BackupClient.setConf(conf1);
|
||||||
|
RestoreClient.setConf(conf1);
|
||||||
|
createTables();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws java.lang.Exception
|
||||||
|
*/
|
||||||
|
@AfterClass
|
||||||
|
public static void tearDownAfterClass() throws Exception {
|
||||||
|
SnapshotTestingUtils.deleteAllSnapshots(TEST_UTIL.getHBaseAdmin());
|
||||||
|
SnapshotTestingUtils.deleteArchiveDirectory(TEST_UTIL);
|
||||||
|
//zkw1.close();
|
||||||
|
TEST_UTIL2.shutdownMiniCluster();
|
||||||
|
TEST_UTIL.shutdownMiniCluster();
|
||||||
|
TEST_UTIL.shutdownMiniMapReduceCluster();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void loadTable(HTable table) throws Exception {
|
||||||
|
|
||||||
|
Put p; // 100 + 1 row to t1_syncup
|
||||||
|
for (int i = 0; i < NB_ROWS_IN_BATCH; i++) {
|
||||||
|
p = new Put(Bytes.toBytes("row" + i));
|
||||||
|
p.addColumn(famName, qualName, Bytes.toBytes("val" + i));
|
||||||
|
table.put(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void createTables() throws Exception {
|
||||||
|
|
||||||
|
long tid = System.currentTimeMillis();
|
||||||
|
table1 = TableName.valueOf("test-" + tid);
|
||||||
|
HBaseAdmin ha = TEST_UTIL.getHBaseAdmin();
|
||||||
|
HTableDescriptor desc = new HTableDescriptor(table1);
|
||||||
|
HColumnDescriptor fam = new HColumnDescriptor(famName);
|
||||||
|
desc.addFamily(fam);
|
||||||
|
ha.createTable(desc);
|
||||||
|
Connection conn = ConnectionFactory.createConnection(conf1);
|
||||||
|
HTable table = (HTable) conn.getTable(table1);
|
||||||
|
loadTable(table);
|
||||||
|
table.close();
|
||||||
|
table2 = TableName.valueOf("test-" + tid + 1);
|
||||||
|
desc = new HTableDescriptor(table2);
|
||||||
|
desc.addFamily(fam);
|
||||||
|
ha.createTable(desc);
|
||||||
|
table = (HTable) conn.getTable(table2);
|
||||||
|
loadTable(table);
|
||||||
|
table.close();
|
||||||
|
table3 = TableName.valueOf("test-" + tid + 2);
|
||||||
|
table = TEST_UTIL.createTable(table3, famName);
|
||||||
|
table.close();
|
||||||
|
table4 = TableName.valueOf("test-" + tid + 3);
|
||||||
|
table = TEST_UTIL.createTable(table4, famName);
|
||||||
|
table.close();
|
||||||
|
ha.close();
|
||||||
|
conn.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean checkSucceeded(String backupId) throws IOException
|
||||||
|
{
|
||||||
|
BackupContext status = getBackupContext(backupId);
|
||||||
|
if(status == null) return false;
|
||||||
|
return status.getFlag() == BACKUPSTATUS.COMPLETE;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean checkFailed(String backupId) throws IOException
|
||||||
|
{
|
||||||
|
BackupContext status = getBackupContext(backupId);
|
||||||
|
if(status == null) return false;
|
||||||
|
return status.getFlag() == BACKUPSTATUS.FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BackupContext getBackupContext(String backupId) throws IOException
|
||||||
|
{
|
||||||
|
Configuration conf = BackupClient.getConf();
|
||||||
|
BackupSystemTable table = BackupSystemTable.getTable(conf);
|
||||||
|
BackupContext status = table.readBackupStatus(backupId);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
/**
|
||||||
|
* 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 static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.apache.hadoop.hbase.testclassification.LargeTests;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.experimental.categories.Category;
|
||||||
|
|
||||||
|
@Category(LargeTests.class)
|
||||||
|
public class TestBackupBoundaryTests extends TestBackupBase {
|
||||||
|
|
||||||
|
private static final Log LOG = LogFactory.getLog(TestBackupBoundaryTests.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that full backup is created on a single empty table correctly.
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testFullBackupSingleEmpty() throws Exception {
|
||||||
|
|
||||||
|
LOG.info("create full backup image on single table");
|
||||||
|
|
||||||
|
String backupId =
|
||||||
|
BackupClient.create("full", BACKUP_ROOT_DIR, table3.getNameAsString(), null);
|
||||||
|
LOG.info("Finished Backup");
|
||||||
|
assertTrue(checkSucceeded(backupId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that full backup is created on multiple empty tables correctly.
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testFullBackupMultipleEmpty() throws Exception {
|
||||||
|
LOG.info("create full backup image on mulitple empty tables");
|
||||||
|
String tableset =
|
||||||
|
table3.getNameAsString() + BackupRestoreConstants.TABLENAME_DELIMITER_IN_COMMAND
|
||||||
|
+ table4.getNameAsString();
|
||||||
|
String backupId = BackupClient.create("full", BACKUP_ROOT_DIR, tableset, null);
|
||||||
|
assertTrue(checkSucceeded(backupId));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that full backup fails on a single table that does not exist.
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Test(expected = RuntimeException.class)
|
||||||
|
public void testFullBackupSingleDNE() throws Exception {
|
||||||
|
|
||||||
|
LOG.info("test full backup fails on a single table that does not exist");
|
||||||
|
BackupClient.create("full", BACKUP_ROOT_DIR, "tabledne", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that full backup fails on multiple tables that do not exist.
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Test(expected = RuntimeException.class)
|
||||||
|
public void testFullBackupMultipleDNE() throws Exception {
|
||||||
|
|
||||||
|
LOG.info("test full backup fails on multiple tables that do not exist");
|
||||||
|
BackupClient.create("full", BACKUP_ROOT_DIR, "table1dne,table2dne", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that full backup fails on tableset containing real and fake tables.
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Test(expected = RuntimeException.class)
|
||||||
|
public void testFullBackupMixExistAndDNE() throws Exception {
|
||||||
|
LOG.info("create full backup fails on tableset containing real and fake table");
|
||||||
|
String tableset =
|
||||||
|
table1.getNameAsString() + BackupRestoreConstants.TABLENAME_DELIMITER_IN_COMMAND
|
||||||
|
+ "tabledne";
|
||||||
|
BackupClient.create("full", BACKUP_ROOT_DIR, tableset, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,161 @@
|
||||||
|
/**
|
||||||
|
* 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 static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
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.FileStatus;
|
||||||
|
import org.apache.hadoop.fs.FileSystem;
|
||||||
|
import org.apache.hadoop.fs.LocatedFileStatus;
|
||||||
|
import org.apache.hadoop.fs.Path;
|
||||||
|
import org.apache.hadoop.fs.RemoteIterator;
|
||||||
|
import org.apache.hadoop.hbase.HConstants;
|
||||||
|
import org.apache.hadoop.hbase.backup.master.BackupLogCleaner;
|
||||||
|
import org.apache.hadoop.hbase.client.Connection;
|
||||||
|
import org.apache.hadoop.hbase.client.ConnectionFactory;
|
||||||
|
import org.apache.hadoop.hbase.client.HTable;
|
||||||
|
import org.apache.hadoop.hbase.client.Put;
|
||||||
|
import org.apache.hadoop.hbase.testclassification.LargeTests;
|
||||||
|
import org.apache.hadoop.hbase.util.Bytes;
|
||||||
|
import org.apache.hadoop.hbase.util.FSUtils;
|
||||||
|
import org.apache.hadoop.hbase.wal.DefaultWALProvider;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.experimental.categories.Category;
|
||||||
|
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
|
@Category(LargeTests.class)
|
||||||
|
public class TestBackupLogCleaner extends TestBackupBase {
|
||||||
|
private static final Log LOG = LogFactory.getLog(TestBackupLogCleaner.class);
|
||||||
|
|
||||||
|
// implements all test cases in 1 test since incremental full backup/
|
||||||
|
// incremental backup has dependencies
|
||||||
|
@Test
|
||||||
|
public void testBackupLogCleaner() throws Exception {
|
||||||
|
|
||||||
|
// #1 - create full backup for all tables
|
||||||
|
LOG.info("create full backup image for all tables");
|
||||||
|
String tablesetFull =
|
||||||
|
table1.getNameAsString() + BackupRestoreConstants.TABLENAME_DELIMITER_IN_COMMAND
|
||||||
|
+ table2.getNameAsString() + BackupRestoreConstants.TABLENAME_DELIMITER_IN_COMMAND
|
||||||
|
+ table3.getNameAsString() + BackupRestoreConstants.TABLENAME_DELIMITER_IN_COMMAND
|
||||||
|
+ table4.getNameAsString();
|
||||||
|
|
||||||
|
BackupSystemTable systemTable = BackupSystemTable.getTable(TEST_UTIL.getConfiguration());
|
||||||
|
// Verify that we have no backup sessions yet
|
||||||
|
assertFalse(systemTable.hasBackupSessions());
|
||||||
|
|
||||||
|
List<FileStatus> walFiles = getListOfWALFiles(TEST_UTIL.getConfiguration());
|
||||||
|
List<String> swalFiles = convert(walFiles);
|
||||||
|
BackupLogCleaner cleaner = new BackupLogCleaner();
|
||||||
|
cleaner.setConf(TEST_UTIL.getConfiguration());
|
||||||
|
|
||||||
|
Iterable<FileStatus> deletable = cleaner.getDeletableFiles(walFiles);
|
||||||
|
// We can delete all files because we do not have yet recorded backup sessions
|
||||||
|
assertTrue(Iterables.size(deletable) == walFiles.size());
|
||||||
|
|
||||||
|
systemTable.addWALFiles(swalFiles, "backup");
|
||||||
|
String backupIdFull = BackupClient.create("full", BACKUP_ROOT_DIR, tablesetFull, null);
|
||||||
|
assertTrue(checkSucceeded(backupIdFull));
|
||||||
|
// Check one more time
|
||||||
|
deletable = cleaner.getDeletableFiles(walFiles);
|
||||||
|
// We can delete wal files because they were saved into hbase:backup table
|
||||||
|
int size = Iterables.size(deletable);
|
||||||
|
assertTrue(size == walFiles.size());
|
||||||
|
|
||||||
|
List<FileStatus> newWalFiles = getListOfWALFiles(TEST_UTIL.getConfiguration());
|
||||||
|
LOG.debug("WAL list after full backup");
|
||||||
|
convert(newWalFiles);
|
||||||
|
|
||||||
|
// New list of wal files is greater than the previous one,
|
||||||
|
// because new wal per RS have been opened after full backup
|
||||||
|
assertTrue(walFiles.size() < newWalFiles.size());
|
||||||
|
// TODO : verify that result files are not walFiles collection
|
||||||
|
Connection conn = ConnectionFactory.createConnection(conf1);
|
||||||
|
// #2 - insert some data to table
|
||||||
|
HTable t1 = (HTable) conn.getTable(table1);
|
||||||
|
Put p1;
|
||||||
|
for (int i = 0; i < NB_ROWS_IN_BATCH; i++) {
|
||||||
|
p1 = new Put(Bytes.toBytes("row-t1" + i));
|
||||||
|
p1.addColumn(famName, qualName, Bytes.toBytes("val" + i));
|
||||||
|
t1.put(p1);
|
||||||
|
}
|
||||||
|
|
||||||
|
t1.close();
|
||||||
|
|
||||||
|
HTable t2 = (HTable) conn.getTable(table2);
|
||||||
|
Put p2;
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
p2 = new Put(Bytes.toBytes("row-t2" + i));
|
||||||
|
p2.addColumn(famName, qualName, Bytes.toBytes("val" + i));
|
||||||
|
t2.put(p2);
|
||||||
|
}
|
||||||
|
|
||||||
|
t2.close();
|
||||||
|
|
||||||
|
// #3 - incremental backup for multiple tables
|
||||||
|
String tablesetIncMultiple =
|
||||||
|
table1.getNameAsString() + BackupRestoreConstants.TABLENAME_DELIMITER_IN_COMMAND
|
||||||
|
+ table2.getNameAsString() + BackupRestoreConstants.TABLENAME_DELIMITER_IN_COMMAND
|
||||||
|
+ table3.getNameAsString();
|
||||||
|
|
||||||
|
String backupIdIncMultiple =
|
||||||
|
BackupClient.create("incremental", BACKUP_ROOT_DIR, tablesetIncMultiple, null);
|
||||||
|
assertTrue(checkSucceeded(backupIdIncMultiple));
|
||||||
|
deletable = cleaner.getDeletableFiles(newWalFiles);
|
||||||
|
|
||||||
|
assertTrue(Iterables.size(deletable) == newWalFiles.size());
|
||||||
|
|
||||||
|
conn.close();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> convert(List<FileStatus> walFiles) {
|
||||||
|
List<String> result = new ArrayList<String>();
|
||||||
|
for (FileStatus fs : walFiles) {
|
||||||
|
LOG.debug("+++WAL: " + fs.getPath().toString());
|
||||||
|
result.add(fs.getPath().toString());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<FileStatus> getListOfWALFiles(Configuration c) throws IOException {
|
||||||
|
Path logRoot = new Path(FSUtils.getRootDir(c), HConstants.HREGION_LOGDIR_NAME);
|
||||||
|
FileSystem fs = FileSystem.get(c);
|
||||||
|
RemoteIterator<LocatedFileStatus> it = fs.listFiles(logRoot, true);
|
||||||
|
List<FileStatus> logFiles = new ArrayList<FileStatus>();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
LocatedFileStatus lfs = it.next();
|
||||||
|
if (lfs.isFile() && !DefaultWALProvider.isMetaFile(lfs.getPath())) {
|
||||||
|
logFiles.add(lfs);
|
||||||
|
LOG.info(lfs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return logFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,341 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 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 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 java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
import org.apache.hadoop.fs.FileSystem;
|
||||||
|
import org.apache.hadoop.hbase.HBaseTestingUtility;
|
||||||
|
import org.apache.hadoop.hbase.MiniHBaseCluster;
|
||||||
|
import org.apache.hadoop.hbase.backup.BackupHandler.BACKUPSTATUS;
|
||||||
|
import org.apache.hadoop.hbase.backup.BackupUtil.BackupCompleteData;
|
||||||
|
import org.apache.hadoop.hbase.client.Admin;
|
||||||
|
import org.apache.hadoop.hbase.testclassification.MediumTests;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.experimental.categories.Category;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test cases for hbase:backup API
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Category(MediumTests.class)
|
||||||
|
public class TestBackupSystemTable {
|
||||||
|
|
||||||
|
private static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
|
||||||
|
protected static Configuration conf = UTIL.getConfiguration();
|
||||||
|
protected static MiniHBaseCluster cluster;
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void setUp() throws Exception {
|
||||||
|
cluster = UTIL.startMiniCluster();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateReadDeleteBackupStatus() throws IOException {
|
||||||
|
BackupSystemTable table = BackupSystemTable.getTable(conf);
|
||||||
|
BackupContext ctx = createBackupContext();
|
||||||
|
table.updateBackupStatus(ctx);
|
||||||
|
BackupContext readCtx = table.readBackupStatus(ctx.getBackupId());
|
||||||
|
assertTrue(compare(ctx, readCtx));
|
||||||
|
|
||||||
|
// try fake backup id
|
||||||
|
readCtx = table.readBackupStatus("fake");
|
||||||
|
|
||||||
|
assertNull(readCtx);
|
||||||
|
// delete backup context
|
||||||
|
table.deleteBackupStatus(ctx.getBackupId());
|
||||||
|
readCtx = table.readBackupStatus(ctx.getBackupId());
|
||||||
|
assertNull(readCtx);
|
||||||
|
cleanBackupTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWriteReadBackupStartCode() throws IOException {
|
||||||
|
BackupSystemTable table = BackupSystemTable.getTable(conf);
|
||||||
|
String code = "100";
|
||||||
|
table.writeBackupStartCode(code);
|
||||||
|
String readCode = table.readBackupStartCode();
|
||||||
|
assertEquals(code, readCode);
|
||||||
|
cleanBackupTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cleanBackupTable() throws IOException {
|
||||||
|
Admin admin = UTIL.getHBaseAdmin();
|
||||||
|
admin.disableTable(BackupSystemTable.getTableName());
|
||||||
|
admin.truncateTable(BackupSystemTable.getTableName(), true);
|
||||||
|
if (admin.isTableDisabled(BackupSystemTable.getTableName())) {
|
||||||
|
admin.enableTable(BackupSystemTable.getTableName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBackupHistory() throws IOException {
|
||||||
|
BackupSystemTable table = BackupSystemTable.getTable(conf);
|
||||||
|
int n = 10;
|
||||||
|
List<BackupContext> list = createBackupContextList(n);
|
||||||
|
|
||||||
|
// Load data
|
||||||
|
for (BackupContext bc : list) {
|
||||||
|
// Make sure we set right status
|
||||||
|
bc.setFlag(BACKUPSTATUS.COMPLETE);
|
||||||
|
table.updateBackupStatus(bc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse list for comparison
|
||||||
|
Collections.reverse(list);
|
||||||
|
ArrayList<BackupCompleteData> history = table.getBackupHistory();
|
||||||
|
assertTrue(history.size() == n);
|
||||||
|
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
BackupContext ctx = list.get(i);
|
||||||
|
BackupCompleteData data = history.get(i);
|
||||||
|
assertTrue(compare(ctx, data));
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanBackupTable();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRegionServerLastLogRollResults() throws IOException {
|
||||||
|
BackupSystemTable table = BackupSystemTable.getTable(conf);
|
||||||
|
|
||||||
|
String[] servers = new String[] { "server1", "server2", "server3" };
|
||||||
|
String[] timestamps = new String[] { "100", "102", "107" };
|
||||||
|
|
||||||
|
for (int i = 0; i < servers.length; i++) {
|
||||||
|
table.writeRegionServerLastLogRollResult(servers[i], timestamps[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
HashMap<String, String> result = table.readRegionServerLastLogRollResult();
|
||||||
|
assertTrue(servers.length == result.size());
|
||||||
|
Set<String> keys = result.keySet();
|
||||||
|
String[] keysAsArray = new String[keys.size()];
|
||||||
|
keys.toArray(keysAsArray);
|
||||||
|
Arrays.sort(keysAsArray);
|
||||||
|
|
||||||
|
for (int i = 0; i < keysAsArray.length; i++) {
|
||||||
|
assertEquals(keysAsArray[i], servers[i]);
|
||||||
|
String ts1 = timestamps[i];
|
||||||
|
String ts2 = result.get(keysAsArray[i]);
|
||||||
|
assertEquals(ts1, ts2);
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanBackupTable();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIncrementalBackupTableSet() throws IOException {
|
||||||
|
BackupSystemTable table = BackupSystemTable.getTable(conf);
|
||||||
|
|
||||||
|
TreeSet<String> tables1 = new TreeSet<String>();
|
||||||
|
|
||||||
|
tables1.add("t1");
|
||||||
|
tables1.add("t2");
|
||||||
|
tables1.add("t3");
|
||||||
|
|
||||||
|
TreeSet<String> tables2 = new TreeSet<String>();
|
||||||
|
|
||||||
|
tables2.add("t3");
|
||||||
|
tables2.add("t4");
|
||||||
|
tables2.add("t5");
|
||||||
|
|
||||||
|
table.addIncrementalBackupTableSet(tables1);
|
||||||
|
TreeSet<String> res1 = (TreeSet<String>) table.getIncrementalBackupTableSet();
|
||||||
|
assertTrue(tables1.size() == res1.size());
|
||||||
|
Iterator<String> desc1 = tables1.descendingIterator();
|
||||||
|
Iterator<String> desc2 = res1.descendingIterator();
|
||||||
|
while (desc1.hasNext()) {
|
||||||
|
assertEquals(desc1.next(), desc2.next());
|
||||||
|
}
|
||||||
|
|
||||||
|
table.addIncrementalBackupTableSet(tables2);
|
||||||
|
TreeSet<String> res2 = (TreeSet<String>) table.getIncrementalBackupTableSet();
|
||||||
|
assertTrue((tables2.size() + tables1.size() - 1) == res2.size());
|
||||||
|
|
||||||
|
tables1.addAll(tables2);
|
||||||
|
|
||||||
|
desc1 = tables1.descendingIterator();
|
||||||
|
desc2 = res2.descendingIterator();
|
||||||
|
|
||||||
|
while (desc1.hasNext()) {
|
||||||
|
assertEquals(desc1.next(), desc2.next());
|
||||||
|
}
|
||||||
|
cleanBackupTable();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRegionServerLogTimestampMap() throws IOException {
|
||||||
|
BackupSystemTable table = BackupSystemTable.getTable(conf);
|
||||||
|
|
||||||
|
TreeSet<String> tables = new TreeSet<String>();
|
||||||
|
|
||||||
|
tables.add("t1");
|
||||||
|
tables.add("t2");
|
||||||
|
tables.add("t3");
|
||||||
|
|
||||||
|
HashMap<String, String> rsTimestampMap = new HashMap<String, String>();
|
||||||
|
|
||||||
|
rsTimestampMap.put("rs1", "100");
|
||||||
|
rsTimestampMap.put("rs2", "101");
|
||||||
|
rsTimestampMap.put("rs3", "103");
|
||||||
|
|
||||||
|
table.writeRegionServerLogTimestamp(tables, rsTimestampMap);
|
||||||
|
|
||||||
|
HashMap<String, HashMap<String, String>> result = table.readLogTimestampMap();
|
||||||
|
|
||||||
|
assertTrue(tables.size() == result.size());
|
||||||
|
|
||||||
|
for (String t : tables) {
|
||||||
|
HashMap<String, String> rstm = result.get(t);
|
||||||
|
assertNotNull(rstm);
|
||||||
|
assertEquals(rstm.get("rs1"), "100");
|
||||||
|
assertEquals(rstm.get("rs2"), "101");
|
||||||
|
assertEquals(rstm.get("rs3"), "103");
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> tables1 = new TreeSet<String>();
|
||||||
|
|
||||||
|
tables1.add("t3");
|
||||||
|
tables1.add("t4");
|
||||||
|
tables1.add("t5");
|
||||||
|
|
||||||
|
HashMap<String, String> rsTimestampMap1 = new HashMap<String, String>();
|
||||||
|
|
||||||
|
rsTimestampMap1.put("rs1", "200");
|
||||||
|
rsTimestampMap1.put("rs2", "201");
|
||||||
|
rsTimestampMap1.put("rs3", "203");
|
||||||
|
|
||||||
|
table.writeRegionServerLogTimestamp(tables1, rsTimestampMap1);
|
||||||
|
|
||||||
|
result = table.readLogTimestampMap();
|
||||||
|
|
||||||
|
assertTrue(5 == result.size());
|
||||||
|
|
||||||
|
for (String t : tables) {
|
||||||
|
HashMap<String, String> rstm = result.get(t);
|
||||||
|
assertNotNull(rstm);
|
||||||
|
if (t.equals("t3") == false) {
|
||||||
|
assertEquals(rstm.get("rs1"), "100");
|
||||||
|
assertEquals(rstm.get("rs2"), "101");
|
||||||
|
assertEquals(rstm.get("rs3"), "103");
|
||||||
|
} else {
|
||||||
|
assertEquals(rstm.get("rs1"), "200");
|
||||||
|
assertEquals(rstm.get("rs2"), "201");
|
||||||
|
assertEquals(rstm.get("rs3"), "203");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String t : tables1) {
|
||||||
|
HashMap<String, String> rstm = result.get(t);
|
||||||
|
assertNotNull(rstm);
|
||||||
|
assertEquals(rstm.get("rs1"), "200");
|
||||||
|
assertEquals(rstm.get("rs2"), "201");
|
||||||
|
assertEquals(rstm.get("rs3"), "203");
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanBackupTable();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddWALFiles() throws IOException {
|
||||||
|
BackupSystemTable table = BackupSystemTable.getTable(conf);
|
||||||
|
FileSystem fs = FileSystem.get(conf);
|
||||||
|
List<String> files =
|
||||||
|
Arrays.asList("hdfs://server/WALs/srv1,101,15555/srv1,101,15555.default.1",
|
||||||
|
"hdfs://server/WALs/srv2,102,16666/srv2,102,16666.default.2",
|
||||||
|
"hdfs://server/WALs/srv3,103,17777/srv3,103,17777.default.3");
|
||||||
|
String newFile = "hdfs://server/WALs/srv1,101,15555/srv1,101,15555.default.5";
|
||||||
|
|
||||||
|
table.addWALFiles(files, "backup");
|
||||||
|
|
||||||
|
assertTrue(table.checkWALFile(files.get(0)));
|
||||||
|
assertTrue(table.checkWALFile(files.get(1)));
|
||||||
|
assertTrue(table.checkWALFile(files.get(2)));
|
||||||
|
assertFalse(table.checkWALFile(newFile));
|
||||||
|
|
||||||
|
cleanBackupTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean compare(BackupContext ctx, BackupCompleteData data) {
|
||||||
|
|
||||||
|
return ctx.getBackupId().equals(data.getBackupToken())
|
||||||
|
&& ctx.getTargetRootDir().equals(data.getBackupRootPath())
|
||||||
|
&& ctx.getType().equals(data.getType())
|
||||||
|
&& ctx.getStartTs() == Long.parseLong(data.getStartTime())
|
||||||
|
&& ctx.getEndTs() == Long.parseLong(data.getEndTime());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean compare(BackupContext one, BackupContext two) {
|
||||||
|
return one.getBackupId().equals(two.getBackupId()) && one.getType().equals(two.getType())
|
||||||
|
&& one.getTargetRootDir().equals(two.getTargetRootDir())
|
||||||
|
&& one.getStartTs() == two.getStartTs() && one.getEndTs() == two.getEndTs();
|
||||||
|
}
|
||||||
|
|
||||||
|
private BackupContext createBackupContext() {
|
||||||
|
|
||||||
|
BackupContext ctxt =
|
||||||
|
new BackupContext("backup_" + System.nanoTime(), "full", new String[] { "t1", "t2", "t3" },
|
||||||
|
"/hbase/backup", null);
|
||||||
|
ctxt.setStartTs(System.currentTimeMillis());
|
||||||
|
ctxt.setEndTs(System.currentTimeMillis() + 1);
|
||||||
|
return ctxt;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<BackupContext> createBackupContextList(int size) {
|
||||||
|
List<BackupContext> list = new ArrayList<BackupContext>();
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
list.add(createBackupContext());
|
||||||
|
try {
|
||||||
|
Thread.sleep(10);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void tearDown() throws IOException {
|
||||||
|
if (cluster != null) cluster.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
/**
|
||||||
|
* 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 static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.apache.hadoop.hbase.testclassification.LargeTests;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.experimental.categories.Category;
|
||||||
|
|
||||||
|
@Category(LargeTests.class)
|
||||||
|
public class TestFullBackup extends TestBackupBase {
|
||||||
|
|
||||||
|
private static final Log LOG = LogFactory.getLog(TestFullBackup.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that full backup is created on a single table with data correctly.
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testFullBackupSingle() throws Exception {
|
||||||
|
|
||||||
|
LOG.info("test full backup on a single table with data");
|
||||||
|
String backupId =
|
||||||
|
BackupClient.create("full", BACKUP_ROOT_DIR, table1.getNameAsString(), null);
|
||||||
|
LOG.info("backup complete");
|
||||||
|
assertTrue(checkSucceeded(backupId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that full backup is created on multiple tables correctly.
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testFullBackupMultiple() throws Exception {
|
||||||
|
LOG.info("create full backup image on multiple tables with data");
|
||||||
|
String tableset =
|
||||||
|
table1.getNameAsString() + BackupRestoreConstants.TABLENAME_DELIMITER_IN_COMMAND
|
||||||
|
+ table2.getNameAsString();
|
||||||
|
String backupId = BackupClient.create("full", BACKUP_ROOT_DIR, tableset, null);
|
||||||
|
assertTrue(checkSucceeded(backupId));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that full backup is created on all tables correctly.
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testFullBackupAll() throws Exception {
|
||||||
|
LOG.info("create full backup image on all tables");
|
||||||
|
String backupId = BackupClient.create("full", BACKUP_ROOT_DIR, null, null);
|
||||||
|
assertTrue(checkSucceeded(backupId));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that full backup is created on a table correctly using a snapshot.
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
//@Test
|
||||||
|
//public void testFullBackupUsingSnapshot() throws Exception {
|
||||||
|
// HBaseAdmin hba = new HBaseAdmin(conf1);
|
||||||
|
//String snapshot = "snapshot";
|
||||||
|
//hba.snapshot(snapshot, table1);
|
||||||
|
//LOG.info("create full backup image on a table using snapshot");
|
||||||
|
//String backupId =
|
||||||
|
// BackupClient.create("full", BACKUP_ROOT_DIR, table1.getNameAsString(),
|
||||||
|
// snapshot);
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,166 @@
|
||||||
|
/**
|
||||||
|
* 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 static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.apache.hadoop.fs.Path;
|
||||||
|
import org.apache.hadoop.hbase.TableName;
|
||||||
|
import org.apache.hadoop.hbase.client.HBaseAdmin;
|
||||||
|
import org.apache.hadoop.hbase.testclassification.LargeTests;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.experimental.categories.Category;
|
||||||
|
|
||||||
|
@Category(LargeTests.class)
|
||||||
|
public class TestFullRestore extends TestBackupBase {
|
||||||
|
|
||||||
|
private static final Log LOG = LogFactory.getLog(TestFullRestore.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that a single table is restored to a new table
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testFullRestoreSingle() throws Exception {
|
||||||
|
|
||||||
|
LOG.info("test full restore on a single table empty table");
|
||||||
|
String backupId =
|
||||||
|
BackupClient.create("full", BACKUP_ROOT_DIR, table1.getNameAsString(), null);
|
||||||
|
LOG.info("backup complete");
|
||||||
|
assertTrue(checkSucceeded(backupId));
|
||||||
|
|
||||||
|
String[] tableset = new String[] { table1.getNameAsString() };
|
||||||
|
String[] tablemap = new String[] { table1_restore };
|
||||||
|
Path path = new Path(BACKUP_ROOT_DIR);
|
||||||
|
HBackupFileSystem hbfs = new HBackupFileSystem(conf1, path, backupId);
|
||||||
|
RestoreClient.restore_stage1(hbfs, BACKUP_ROOT_DIR, backupId, false, false, tableset, tablemap,
|
||||||
|
false);
|
||||||
|
HBaseAdmin hba = TEST_UTIL.getHBaseAdmin();
|
||||||
|
assertTrue(hba.tableExists(TableName.valueOf(table1_restore)));
|
||||||
|
TEST_UTIL.deleteTable(TableName.valueOf(table1_restore));
|
||||||
|
hba.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that multiple tables are restored to new tables.
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testFullRestoreMultiple() throws Exception {
|
||||||
|
LOG.info("create full backup image on multiple tables");
|
||||||
|
String tableset =
|
||||||
|
table2.getNameAsString() + BackupRestoreConstants.TABLENAME_DELIMITER_IN_COMMAND
|
||||||
|
+ table3.getNameAsString();
|
||||||
|
String backupId = BackupClient.create("full", BACKUP_ROOT_DIR, tableset, null);
|
||||||
|
assertTrue(checkSucceeded(backupId));
|
||||||
|
|
||||||
|
String[] restore_tableset = new String[] { table2.getNameAsString(), table3.getNameAsString() };
|
||||||
|
String[] tablemap = new String[] { table2_restore, table3_restore };
|
||||||
|
Path path = new Path(BACKUP_ROOT_DIR);
|
||||||
|
HBackupFileSystem hbfs = new HBackupFileSystem(conf1, path, backupId);
|
||||||
|
RestoreClient.restore_stage1(hbfs, BACKUP_ROOT_DIR, backupId, false, false,
|
||||||
|
restore_tableset, tablemap, false);
|
||||||
|
HBaseAdmin hba = TEST_UTIL.getHBaseAdmin();
|
||||||
|
assertTrue(hba.tableExists(TableName.valueOf(table2_restore)));
|
||||||
|
assertTrue(hba.tableExists(TableName.valueOf(table3_restore)));
|
||||||
|
TEST_UTIL.deleteTable(TableName.valueOf(table2_restore));
|
||||||
|
TEST_UTIL.deleteTable(TableName.valueOf(table3_restore));
|
||||||
|
hba.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that a single table is restored using overwrite
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testFullRestoreSingleOverwrite() throws Exception {
|
||||||
|
|
||||||
|
LOG.info("test full restore on a single table empty table");
|
||||||
|
String backupId =
|
||||||
|
BackupClient.create("full", BACKUP_ROOT_DIR, table1.getNameAsString(), null);
|
||||||
|
LOG.info("backup complete");
|
||||||
|
assertTrue(checkSucceeded(backupId));
|
||||||
|
|
||||||
|
String[] tableset = new String[] { table1.getNameAsString() };
|
||||||
|
Path path = new Path(BACKUP_ROOT_DIR);
|
||||||
|
HBackupFileSystem hbfs = new HBackupFileSystem(conf1, path, backupId);
|
||||||
|
RestoreClient.restore_stage1(hbfs, BACKUP_ROOT_DIR, backupId, false, false, tableset, null,
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that multiple tables are restored to new tables using overwrite.
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testFullRestoreMultipleOverwrite() throws Exception {
|
||||||
|
LOG.info("create full backup image on multiple tables");
|
||||||
|
String tableset =
|
||||||
|
table2.getNameAsString() + BackupRestoreConstants.TABLENAME_DELIMITER_IN_COMMAND
|
||||||
|
+ table3.getNameAsString();
|
||||||
|
String backupId = BackupClient.create("full", BACKUP_ROOT_DIR, tableset, null);
|
||||||
|
assertTrue(checkSucceeded(backupId));
|
||||||
|
|
||||||
|
String[] restore_tableset = new String[] { table2.getNameAsString(), table3.getNameAsString() };
|
||||||
|
Path path = new Path(BACKUP_ROOT_DIR);
|
||||||
|
HBackupFileSystem hbfs = new HBackupFileSystem(conf1, path, backupId);
|
||||||
|
RestoreClient.restore_stage1(hbfs, BACKUP_ROOT_DIR, backupId, false,
|
||||||
|
false, restore_tableset, null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that restore fails on a single table that does not exist.
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Test(expected = IOException.class)
|
||||||
|
public void testFullRestoreSingleDNE() throws Exception {
|
||||||
|
|
||||||
|
LOG.info("test restore fails on a single table that does not exist");
|
||||||
|
String backupId =
|
||||||
|
BackupClient.create("full", BACKUP_ROOT_DIR, table1.getNameAsString(), null);
|
||||||
|
LOG.info("backup complete");
|
||||||
|
assertTrue(checkSucceeded(backupId));
|
||||||
|
|
||||||
|
String[] tableset = new String[] { "faketable" };
|
||||||
|
String[] tablemap = new String[] { table1_restore };
|
||||||
|
Path path = new Path(BACKUP_ROOT_DIR);
|
||||||
|
HBackupFileSystem hbfs = new HBackupFileSystem(conf1, path, backupId);
|
||||||
|
RestoreClient.restore_stage1(hbfs, BACKUP_ROOT_DIR, backupId, false, false, tableset, tablemap,
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that restore fails on multiple tables that do not exist.
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Test(expected = IOException.class)
|
||||||
|
public void testFullRestoreMultipleDNE() throws Exception {
|
||||||
|
|
||||||
|
LOG.info("test restore fails on multiple tables that do not exist");
|
||||||
|
String tableset =
|
||||||
|
table2.getNameAsString() + BackupRestoreConstants.TABLENAME_DELIMITER_IN_COMMAND
|
||||||
|
+ table3.getNameAsString();
|
||||||
|
String backupId = BackupClient.create("full", BACKUP_ROOT_DIR, tableset, null);
|
||||||
|
assertTrue(checkSucceeded(backupId));
|
||||||
|
|
||||||
|
String[] restore_tableset = new String[] { "faketable1", "faketable2" };
|
||||||
|
String[] tablemap = new String[] { table2_restore, table3_restore };
|
||||||
|
Path path = new Path(BACKUP_ROOT_DIR);
|
||||||
|
HBackupFileSystem hbfs = new HBackupFileSystem(conf1, path, backupId);
|
||||||
|
RestoreClient.restore_stage1(hbfs, BACKUP_ROOT_DIR, backupId, false,
|
||||||
|
false, restore_tableset, tablemap, false);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,179 @@
|
||||||
|
/**
|
||||||
|
* 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 static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.apache.hadoop.hbase.TableName;
|
||||||
|
import org.apache.hadoop.hbase.client.Connection;
|
||||||
|
import org.apache.hadoop.hbase.client.ConnectionFactory;
|
||||||
|
import org.apache.hadoop.hbase.client.HBaseAdmin;
|
||||||
|
import org.apache.hadoop.hbase.client.HTable;
|
||||||
|
import org.apache.hadoop.hbase.client.Put;
|
||||||
|
import org.apache.hadoop.hbase.testclassification.LargeTests;
|
||||||
|
import org.apache.hadoop.hbase.util.Bytes;
|
||||||
|
import org.apache.hadoop.hbase.backup.HBackupFileSystem;
|
||||||
|
import org.apache.hadoop.fs.Path;
|
||||||
|
import org.hamcrest.CoreMatchers;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.experimental.categories.Category;
|
||||||
|
|
||||||
|
@Category(LargeTests.class)
|
||||||
|
public class TestIncrementalBackup extends TestBackupBase {
|
||||||
|
private static final Log LOG = LogFactory.getLog(TestIncrementalBackup.class);
|
||||||
|
//implement all testcases in 1 test since incremental backup/restore has dependencies
|
||||||
|
@Test
|
||||||
|
public void TestIncBackupRestore() throws Exception {
|
||||||
|
HBackupFileSystem hbfs;
|
||||||
|
|
||||||
|
// #1 - create full backup for all tables
|
||||||
|
LOG.info("create full backup image for all tables");
|
||||||
|
String tablesetFull =
|
||||||
|
table1.getNameAsString() + BackupRestoreConstants.TABLENAME_DELIMITER_IN_COMMAND
|
||||||
|
+ table2.getNameAsString() + BackupRestoreConstants.TABLENAME_DELIMITER_IN_COMMAND
|
||||||
|
+ table3.getNameAsString() + BackupRestoreConstants.TABLENAME_DELIMITER_IN_COMMAND
|
||||||
|
+ table4.getNameAsString();
|
||||||
|
|
||||||
|
String backupIdFull =
|
||||||
|
BackupClient.create("full", BACKUP_ROOT_DIR, tablesetFull, null);
|
||||||
|
assertTrue(checkSucceeded(backupIdFull));
|
||||||
|
|
||||||
|
Connection conn = ConnectionFactory.createConnection(conf1);
|
||||||
|
// #2 - insert some data to table
|
||||||
|
HTable t1 = (HTable) conn.getTable(table1);
|
||||||
|
Put p1;
|
||||||
|
for (int i = 0; i < NB_ROWS_IN_BATCH; i++) {
|
||||||
|
p1 = new Put(Bytes.toBytes("row-t1" + i));
|
||||||
|
p1.addColumn(famName, qualName, Bytes.toBytes("val" + i));
|
||||||
|
t1.put(p1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.assertThat(TEST_UTIL.countRows(t1), CoreMatchers.equalTo(NB_ROWS_IN_BATCH * 2));
|
||||||
|
t1.close();
|
||||||
|
|
||||||
|
HTable t2 = (HTable) conn.getTable(table2);
|
||||||
|
Put p2;
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
p2 = new Put(Bytes.toBytes("row-t2" + i));
|
||||||
|
p2.addColumn(famName, qualName, Bytes.toBytes("val" + i));
|
||||||
|
t2.put(p2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.assertThat(TEST_UTIL.countRows(t2), CoreMatchers.equalTo(NB_ROWS_IN_BATCH + 5));
|
||||||
|
t2.close();
|
||||||
|
|
||||||
|
// #3 - incremental backup for multiple tables
|
||||||
|
String tablesetIncMultiple =
|
||||||
|
table1.getNameAsString() + BackupRestoreConstants.TABLENAME_DELIMITER_IN_COMMAND
|
||||||
|
+ table2.getNameAsString() + BackupRestoreConstants.TABLENAME_DELIMITER_IN_COMMAND
|
||||||
|
+ table3.getNameAsString();
|
||||||
|
|
||||||
|
String backupIdIncMultiple = BackupClient.create("incremental", BACKUP_ROOT_DIR,
|
||||||
|
tablesetIncMultiple, null);
|
||||||
|
assertTrue(checkSucceeded(backupIdIncMultiple));
|
||||||
|
|
||||||
|
|
||||||
|
// #4 - restore full backup for all tables, without overwrite
|
||||||
|
String[] tablesRestoreFull =
|
||||||
|
new String[] { table1.getNameAsString(), table2.getNameAsString(),
|
||||||
|
table3.getNameAsString(), table4.getNameAsString() };
|
||||||
|
|
||||||
|
String[] tablesMapFull =
|
||||||
|
new String[] { table1_restore, table2_restore, table3_restore, table4_restore };
|
||||||
|
|
||||||
|
hbfs = new HBackupFileSystem(conf1, new Path(BACKUP_ROOT_DIR), backupIdFull);
|
||||||
|
RestoreClient.restore_stage1(hbfs, BACKUP_ROOT_DIR, backupIdFull, false, false,
|
||||||
|
tablesRestoreFull,
|
||||||
|
tablesMapFull, false);
|
||||||
|
|
||||||
|
// #5.1 - check tables for full restore
|
||||||
|
HBaseAdmin hAdmin = TEST_UTIL.getHBaseAdmin();
|
||||||
|
assertTrue(hAdmin.tableExists(TableName.valueOf(table1_restore)));
|
||||||
|
assertTrue(hAdmin.tableExists(TableName.valueOf(table2_restore)));
|
||||||
|
assertTrue(hAdmin.tableExists(TableName.valueOf(table3_restore)));
|
||||||
|
assertTrue(hAdmin.tableExists(TableName.valueOf(table4_restore)));
|
||||||
|
|
||||||
|
hAdmin.close();
|
||||||
|
|
||||||
|
// #5.2 - checking row count of tables for full restore
|
||||||
|
HTable hTable = (HTable) conn.getTable(TableName.valueOf(table1_restore));
|
||||||
|
Assert.assertThat(TEST_UTIL.countRows(hTable), CoreMatchers.equalTo(NB_ROWS_IN_BATCH));
|
||||||
|
hTable.close();
|
||||||
|
|
||||||
|
hTable = (HTable) conn.getTable(TableName.valueOf(table2_restore));
|
||||||
|
Assert.assertThat(TEST_UTIL.countRows(hTable), CoreMatchers.equalTo(NB_ROWS_IN_BATCH));
|
||||||
|
hTable.close();
|
||||||
|
|
||||||
|
hTable = (HTable) conn.getTable(TableName.valueOf(table3_restore));
|
||||||
|
Assert.assertThat(TEST_UTIL.countRows(hTable), CoreMatchers.equalTo(0));
|
||||||
|
hTable.close();
|
||||||
|
|
||||||
|
hTable = (HTable) conn.getTable(TableName.valueOf(table4_restore));
|
||||||
|
Assert.assertThat(TEST_UTIL.countRows(hTable), CoreMatchers.equalTo(0));
|
||||||
|
hTable.close();
|
||||||
|
|
||||||
|
// #6 - restore incremental backup for multiple tables, with overwrite
|
||||||
|
String[] tablesRestoreIncMultiple =
|
||||||
|
new String[]
|
||||||
|
{ table1.getNameAsString(), table2.getNameAsString(), table3.getNameAsString() };
|
||||||
|
String[] tablesMapIncMultiple =
|
||||||
|
new String[] { table1_restore, table2_restore, table3_restore };
|
||||||
|
hbfs = new HBackupFileSystem(conf1, new Path(BACKUP_ROOT_DIR), backupIdIncMultiple);
|
||||||
|
RestoreClient.restore_stage1(hbfs, BACKUP_ROOT_DIR, backupIdIncMultiple, false, false,
|
||||||
|
tablesRestoreIncMultiple, tablesMapIncMultiple, true);
|
||||||
|
|
||||||
|
hTable = (HTable) conn.getTable(TableName.valueOf(table1_restore));
|
||||||
|
Assert.assertThat(TEST_UTIL.countRows(hTable), CoreMatchers.equalTo(NB_ROWS_IN_BATCH * 2));
|
||||||
|
hTable.close();
|
||||||
|
|
||||||
|
hTable = (HTable) conn.getTable(TableName.valueOf(table2_restore));
|
||||||
|
Assert.assertThat(TEST_UTIL.countRows(hTable), CoreMatchers.equalTo(NB_ROWS_IN_BATCH + 5));
|
||||||
|
hTable.close();
|
||||||
|
|
||||||
|
hTable = (HTable) conn.getTable(TableName.valueOf(table3_restore));
|
||||||
|
Assert.assertThat(TEST_UTIL.countRows(hTable), CoreMatchers.equalTo(0));
|
||||||
|
hTable.close();
|
||||||
|
|
||||||
|
// #7 - incremental backup for single, empty table
|
||||||
|
|
||||||
|
String tablesetIncEmpty = table4.getNameAsString();
|
||||||
|
String backupIdIncEmpty =
|
||||||
|
BackupClient.create("incremental", BACKUP_ROOT_DIR, tablesetIncEmpty, null);
|
||||||
|
assertTrue(checkSucceeded(backupIdIncEmpty));
|
||||||
|
|
||||||
|
|
||||||
|
// #8 - restore incremental backup for single empty table, with overwrite
|
||||||
|
String[] tablesRestoreIncEmpty = new String[] { table4.getNameAsString() };
|
||||||
|
String[] tablesMapIncEmpty = new String[] { table4_restore };
|
||||||
|
hbfs = new HBackupFileSystem(conf1, new Path(BACKUP_ROOT_DIR), backupIdIncEmpty);
|
||||||
|
RestoreClient.restore_stage1(hbfs, BACKUP_ROOT_DIR, backupIdIncEmpty, false, false,
|
||||||
|
tablesRestoreIncEmpty,
|
||||||
|
tablesMapIncEmpty, true);
|
||||||
|
|
||||||
|
hTable = (HTable) conn.getTable(TableName.valueOf(table4_restore));
|
||||||
|
Assert.assertThat(TEST_UTIL.countRows(hTable), CoreMatchers.equalTo(0));
|
||||||
|
hTable.close();
|
||||||
|
conn.close();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* 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 static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.apache.hadoop.hbase.testclassification.LargeTests;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.experimental.categories.Category;
|
||||||
|
|
||||||
|
@Category(LargeTests.class)
|
||||||
|
public class TestRemoteBackup extends TestBackupBase {
|
||||||
|
|
||||||
|
private static final Log LOG = LogFactory.getLog(TestRemoteBackup.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that a remote full backup is created on a single table with data correctly.
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testFullBackupRemote() throws Exception {
|
||||||
|
|
||||||
|
LOG.info("test remote full backup on a single table");
|
||||||
|
|
||||||
|
// String rootdir = TEST_UTIL2.getDefaultRootDirPath() + BACKUP_ROOT_DIR;
|
||||||
|
// LOG.info("ROOTDIR " + rootdir);
|
||||||
|
String backupId =
|
||||||
|
BackupClient.create("full", BACKUP_REMOTE_ROOT_DIR, table1.getNameAsString(), null);
|
||||||
|
LOG.info("backup complete");
|
||||||
|
assertTrue(checkSucceeded(backupId));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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 static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.apache.hadoop.fs.Path;
|
||||||
|
import org.apache.hadoop.hbase.TableName;
|
||||||
|
import org.apache.hadoop.hbase.client.HBaseAdmin;
|
||||||
|
import org.apache.hadoop.hbase.testclassification.LargeTests;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.experimental.categories.Category;
|
||||||
|
|
||||||
|
@Category(LargeTests.class)
|
||||||
|
public class TestRemoteRestore extends TestBackupBase {
|
||||||
|
|
||||||
|
private static final Log LOG = LogFactory.getLog(TestRemoteRestore.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that a remote restore on a single table is successful.
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testFullRestoreRemote() throws Exception {
|
||||||
|
|
||||||
|
LOG.info("test remote full backup on a single table");
|
||||||
|
String backupId =
|
||||||
|
BackupClient.create("full", BACKUP_REMOTE_ROOT_DIR, table1.getNameAsString(), null);
|
||||||
|
LOG.info("backup complete");
|
||||||
|
assertTrue(checkSucceeded(backupId));
|
||||||
|
String[] tableset = new String[] { table1.getNameAsString() };
|
||||||
|
String[] tablemap = new String[] { table1_restore };
|
||||||
|
Path path = new Path(BACKUP_REMOTE_ROOT_DIR);
|
||||||
|
HBackupFileSystem hbfs = new HBackupFileSystem(conf1, path, backupId);
|
||||||
|
RestoreClient.restore_stage1(hbfs, BACKUP_REMOTE_ROOT_DIR, backupId, false, false, tableset,
|
||||||
|
tablemap, false);
|
||||||
|
HBaseAdmin hba = TEST_UTIL.getHBaseAdmin();
|
||||||
|
assertTrue(hba.tableExists(TableName.valueOf(table1_restore)));
|
||||||
|
TEST_UTIL.deleteTable(TableName.valueOf(table1_restore));
|
||||||
|
hba.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
/**
|
||||||
|
* 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 static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.apache.hadoop.fs.Path;
|
||||||
|
import org.apache.hadoop.hbase.TableName;
|
||||||
|
import org.apache.hadoop.hbase.client.HBaseAdmin;
|
||||||
|
import org.apache.hadoop.hbase.testclassification.LargeTests;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.experimental.categories.Category;
|
||||||
|
|
||||||
|
@Category(LargeTests.class)
|
||||||
|
public class TestRestoreBoundaryTests extends TestBackupBase {
|
||||||
|
|
||||||
|
private static final Log LOG = LogFactory.getLog(TestRestoreBoundaryTests.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that a single empty table is restored to a new table
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testFullRestoreSingleEmpty() throws Exception {
|
||||||
|
|
||||||
|
LOG.info("test full restore on a single table empty table");
|
||||||
|
String backupId =
|
||||||
|
BackupClient.create("full", BACKUP_ROOT_DIR, table1.getNameAsString(), null);
|
||||||
|
LOG.info("backup complete");
|
||||||
|
assertTrue(checkSucceeded(backupId));
|
||||||
|
String[] tableset = new String[] { table1.getNameAsString() };
|
||||||
|
String[] tablemap = new String[] { table1_restore };
|
||||||
|
Path path = new Path(BACKUP_ROOT_DIR);
|
||||||
|
HBackupFileSystem hbfs = new HBackupFileSystem(conf1, path, backupId);
|
||||||
|
RestoreClient.restore_stage1(hbfs, BACKUP_ROOT_DIR, backupId, false, false, tableset, tablemap,
|
||||||
|
false);
|
||||||
|
HBaseAdmin hba = TEST_UTIL.getHBaseAdmin();
|
||||||
|
assertTrue(hba.tableExists(TableName.valueOf(table1_restore)));
|
||||||
|
TEST_UTIL.deleteTable(TableName.valueOf(table1_restore));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that multiple tables are restored to new tables.
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testFullRestoreMultipleEmpty() throws Exception {
|
||||||
|
LOG.info("create full backup image on multiple tables");
|
||||||
|
String tableset =
|
||||||
|
table2.getNameAsString() + BackupRestoreConstants.TABLENAME_DELIMITER_IN_COMMAND
|
||||||
|
+ table3.getNameAsString();
|
||||||
|
String backupId = BackupClient.create("full", BACKUP_ROOT_DIR, tableset, null);
|
||||||
|
assertTrue(checkSucceeded(backupId));
|
||||||
|
String[] restore_tableset = new String[] { table2.getNameAsString(), table3.getNameAsString() };
|
||||||
|
String[] tablemap = new String[] { table2_restore, table3_restore };
|
||||||
|
Path path = new Path(BACKUP_ROOT_DIR);
|
||||||
|
HBackupFileSystem hbfs = new HBackupFileSystem(conf1, path, backupId);
|
||||||
|
RestoreClient.restore_stage1(hbfs, BACKUP_ROOT_DIR, backupId, false, false, restore_tableset,
|
||||||
|
tablemap,
|
||||||
|
false);
|
||||||
|
HBaseAdmin hba = TEST_UTIL.getHBaseAdmin();
|
||||||
|
assertTrue(hba.tableExists(TableName.valueOf(table2_restore)));
|
||||||
|
assertTrue(hba.tableExists(TableName.valueOf(table3_restore)));
|
||||||
|
TEST_UTIL.deleteTable(TableName.valueOf(table2_restore));
|
||||||
|
TEST_UTIL.deleteTable(TableName.valueOf(table3_restore));
|
||||||
|
}
|
||||||
|
}
|
|
@ -49,7 +49,7 @@ public class SimpleRSProcedureManager extends RegionServerProcedureManager {
|
||||||
private ProcedureMember member;
|
private ProcedureMember member;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize(RegionServerServices rss) throws KeeperException {
|
public void initialize(RegionServerServices rss) throws IOException {
|
||||||
this.rss = rss;
|
this.rss = rss;
|
||||||
ZooKeeperWatcher zkw = rss.getZooKeeper();
|
ZooKeeperWatcher zkw = rss.getZooKeeper();
|
||||||
this.memberRpcs = new ZKProcedureMemberRpcs(zkw, getProcedureSignature());
|
this.memberRpcs = new ZKProcedureMemberRpcs(zkw, getProcedureSignature());
|
||||||
|
|
Loading…
Reference in New Issue