diff --git a/README.md b/README.md
index 2ff66db39e..ff110179d4 100644
--- a/README.md
+++ b/README.md
@@ -23,3 +23,4 @@ operator tools including:
* [HBCK2](https://github.com/apache/hbase-operator-tools/tree/master/hbase-hbck2), the hbase-2.x fix-it tool, the successor to hbase-1's _hbck_ (A.K.A _hbck1_).
* [TableReporter](https://github.com/apache/hbase-operator-tools/tree/master/hbase-table-reporter), a tool to generate a basic report on Table column counts and row sizes; use when no distributed execution available.
+ * [MetadataRepair](https://github.com/apache/hbase-operator-tools/tree/master/hbase-meta-repair), a tool to repair hbase metadata for versions before 2.0.3 and 2.1.1 (without hbck2).
diff --git a/hbase-meta-repair/README.md b/hbase-meta-repair/README.md
new file mode 100644
index 0000000000..d57fa519a4
--- /dev/null
+++ b/hbase-meta-repair/README.md
@@ -0,0 +1,66 @@
+
+
+# Apache HBase Tool for repair metadata
+
+_MetaRepair_ is an utility tool for repairing hbase metadata table from hdfs regioninfo file.
+When using the [Apache HBase™](https://hbase.apache.org)
+versions before 2.0.3 and 2.1.1 (**Without HBCK2**), You can use it to fix the HBase metadata correctly.
+
+## Build
+
+Run:
+```
+$ mvn install
+```
+The built _hbase-meta-repair_ jar will be in the `target` sub-directory.
+
+## Setup
+Make sure HBase tools jar is added to HBase classpath:
+
+```
+export HBASE_CLASSPATH=$HBASE_CLASSPATH:./hbase-meta-repair-1.1.0-SNAPSHOT.jar
+```
+
+## Usage
+
+_MetaRepair_ requires an arguments as parameters: The name of the table
+to have metadata repaired.
+
+For example, to repair metadata of table `my-table` , assuming the _setup_ step above has been performed:
+
+```
+$ hbase org.apache.hbase.repair.MetaRepair my-table
+```
+> The table with _namespace_ use `namespace:tablename` form of parameter.
+
+## Implementation Details
+
+HBase uses table `hbase:meta` to store metadata. The table structure is as follows:
+
+| Column | Description |
+| ------ | ------ |
+| info:state | region state |
+| info:sn | region server node, It consists of server and serverstartcode, such as `slave1,16020,1557998852385` |
+| info:serverstartcode | region server start timestamp |
+| info:server | region server address and port,such as `slave1:16020` |
+| info:seqnumDuringOpen | a binary string representing the online duration of the region |
+| info:regioninfo | same as the `.regioninfo` file |
+
+> `info:seqnumDuringOpen` and `info:serverstartcode` will be automatically generated after the region server is restarted.
+
diff --git a/hbase-meta-repair/pom.xml b/hbase-meta-repair/pom.xml
new file mode 100644
index 0000000000..16b5eb8e23
--- /dev/null
+++ b/hbase-meta-repair/pom.xml
@@ -0,0 +1,202 @@
+
+
+
+ 4.0.0
+
+ hbase-operator-tools
+ org.apache.hbase.operator.tools
+ 1.1.0-SNAPSHOT
+ ..
+
+
+ hbase-meta-repair
+ Apache HBase - HBase Meta Repair
+ Repair hbase metadata table from hdfs
+
+
+ 2.2.1
+ 2.11.1
+
+
+
+
+ junit
+ junit
+ 4.12
+ test
+
+
+ org.apache.logging.log4j
+ log4j-slf4j-impl
+ ${log4j2.version}
+
+
+
+
+
+ org.apache.hbase
+ hbase-server
+ ${hbase.version}
+ provided
+
+
+ org.apache.hbase
+ hbase-shaded-testing-util
+ ${hbase.version}
+ test
+
+
+ org.apache.hbase
+ hbase-zookeeper
+ ${hbase.version}
+ provided
+ test-jar
+
+
+ org.apache.hbase
+ hbase-common
+ ${hbase.version}
+ provided
+ test-jar
+
+
+ org.apache.hbase
+ hbase-testing-util
+ ${hbase.version}
+ test
+
+
+ org.mockito
+ mockito-core
+ 2.1.0
+ test
+
+
+
+
+
+
+ src/main/resources
+ true
+
+
+
+
+ src/test/resources/META-INF/
+ META-INF/
+
+ NOTICE
+
+ true
+
+
+
+
+ org.apache.maven.plugins
+ maven-remote-resources-plugin
+
+
+ maven-surefire-plugin
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.2.0
+
+
+ package
+
+ shade
+
+
+
+
+ classworlds:classworlds
+ junit:junit
+ jmock:*
+ *:xml-apis
+ org.apache.maven:lib:tests
+ log4j:log4j:jar:
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+
+ true
+
+
+
+
+
+
+
+ apache-release
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+
+
+ license-javadocs
+ prepare-package
+
+ copy-resources
+
+
+ ${project.build.directory}/apidocs
+
+
+ src/main/javadoc/META-INF/
+ META-INF/
+
+ NOTICE
+
+ true
+
+
+
+
+
+
+
+
+
+
+
diff --git a/hbase-meta-repair/src/main/java/org/apache/hbase/repair/MetaRepair.java b/hbase-meta-repair/src/main/java/org/apache/hbase/repair/MetaRepair.java
new file mode 100644
index 0000000000..eaba9b3f51
--- /dev/null
+++ b/hbase-meta-repair/src/main/java/org/apache/hbase/repair/MetaRepair.java
@@ -0,0 +1,196 @@
+/*
+ * 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.hbase.repair;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.conf.Configured;
+import org.apache.hadoop.fs.FileStatus;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.hbase.HBaseConfiguration;
+import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.MetaTableAccessor;
+import org.apache.hadoop.hbase.ServerName;
+import org.apache.hadoop.hbase.TableName;
+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.Put;
+import org.apache.hadoop.hbase.client.RegionInfo;
+import org.apache.hadoop.hbase.client.Result;
+import org.apache.hadoop.hbase.client.Scan;
+import org.apache.hadoop.hbase.client.Table;
+import org.apache.hadoop.hbase.filter.PrefixFilter;
+import org.apache.hadoop.hbase.regionserver.HRegionFileSystem;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
+import org.apache.hadoop.util.ToolRunner;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Repair HBase MetaData for [Apache HBase™](https://hbase.apache.org)
+ * versions before 2.0.3 and 2.1.1 (HBase versions without HBCK2).
+ */
+
+public class MetaRepair extends Configured implements org.apache.hadoop.util.Tool {
+
+ private static final Logger LOG = LoggerFactory.getLogger(MetaRepair.class.getName());
+
+ public static final String HBASE_META_TABLE = "hbase:meta";
+ public static final String HBASE_META_FAMILY = "info";
+ public static final String HBASE_META_QUALIFIER_SN = "sn";
+ public static final String HBASE_META_QUALIFIER_SERVER = "server";
+ public static final String HBASE_META_QUALIFIER_STATE = "state";
+
+ private final Configuration conf;
+
+ public MetaRepair(Configuration conf) {
+ this.conf = conf;
+ }
+
+ public static void main(String[] args) throws Exception {
+ Configuration conf = HBaseConfiguration.create();
+ int errCode = ToolRunner.run(new MetaRepair(conf), args);
+ if (errCode != 0) {
+ System.exit(errCode);
+ }
+ }
+
+ private Path getTablePath(TableName table) {
+ Path basePath = new Path(conf.get(HConstants.HBASE_DIR));
+ basePath = new Path(basePath, "data");
+ Path tablePath = new Path(basePath, table.getNamespaceAsString());
+ return new Path(tablePath, table.getQualifierAsString());
+ }
+
+ /**
+ * Get HBase region info from the table name.
+ */
+ public Map getMetaRegions(String tableName) throws IOException {
+ Connection conn = ConnectionFactory.createConnection(conf);
+ Table table = conn.getTable(TableName.valueOf(HBASE_META_TABLE));
+ Scan scan = new Scan().setFilter(new PrefixFilter(Bytes.toBytes(tableName + ",")));
+ Map metaRegions = new HashMap<>();
+ Iterator iterator = table.getScanner(scan).iterator();
+ while (iterator.hasNext()) {
+ Result result = iterator.next();
+ // Usage Bytes.toStringBinary(), Be consistent with HDFS RegionInfo encoding.
+ metaRegions.put(Bytes.toStringBinary(result.getRow()), result.getRow());
+ }
+ conn.close();
+ return metaRegions;
+ }
+
+ /**
+ * Get HDFS region info from the table name.
+ */
+ public Map getHdfsRegions(String tableName) throws IOException {
+ FileSystem fs = FileSystem.get(conf);
+ TableName table = TableName.valueOf(tableName);
+ Path path = this.getTablePath(table);
+ Map hdfsRegions = new HashMap<>();
+ FileStatus[] list = fs.listStatus(path);
+ for (FileStatus status : list) {
+ if (status.isDirectory()) {
+ boolean isRegion = false;
+ FileStatus[] regions = fs.listStatus(status.getPath());
+ for (FileStatus regionStatus : regions) {
+ // Search the .regioninfo file.
+ if (regionStatus.toString().contains(HRegionFileSystem.REGION_INFO_FILE)) {
+ isRegion = true;
+ break;
+ }
+ }
+ if (isRegion) {
+ // Load regioninfo file content.
+ RegionInfo regionInfo = HRegionFileSystem.loadRegionInfoFileContent(fs, status.getPath());
+ hdfsRegions.put(regionInfo.getRegionNameAsString(), regionInfo);
+ }
+ }
+ }
+ return hdfsRegions;
+ }
+
+ public void repairMetadata(String tableName) throws Exception {
+ Map metaRegions = getMetaRegions(tableName);
+ LOG.debug("HBase meta regions: {}", metaRegions.keySet());
+ Map hdfsRegions = getHdfsRegions(tableName);
+ LOG.debug("HDFS region infos: {}", hdfsRegions.keySet());
+ Set hdfsRegionNames = hdfsRegions.keySet();
+ for (String hdfsRegionName : hdfsRegionNames) {
+ if (metaRegions.containsKey(hdfsRegionName)) {
+ // remove meta not in hdfs region info.
+ metaRegions.remove(hdfsRegionName);
+ }
+ }
+ Connection conn = ConnectionFactory.createConnection(conf);
+ Admin admin = conn.getAdmin();
+ ServerName[] regionServers = admin.getRegionServers().toArray(new ServerName[0]);
+ LOG.info("HBase Region Servers: {}", Arrays.asList(regionServers));
+ Table table = conn.getTable(TableName.valueOf(HBASE_META_TABLE));
+ if (!metaRegions.isEmpty()) {
+ for (String regionName : metaRegions.keySet()) {
+ table.delete(new Delete(metaRegions.get(regionName)));
+ }
+ LOG.warn("Delete HBase Metadata: {}", metaRegions.keySet());
+ }
+ int rsLength = regionServers.length, i = 0;
+ for (String regionName : hdfsRegionNames) {
+ String sn = regionServers[i % rsLength].getServerName();
+ String[] snSig = sn.split(",");
+ RegionInfo regionInfo = hdfsRegions.get(regionName);
+ Put info = MetaTableAccessor.makePutFromRegionInfo(regionInfo,
+ EnvironmentEdgeManager.currentTime());
+ info.addColumn(Bytes.toBytes(HBASE_META_FAMILY),
+ Bytes.toBytes(HBASE_META_QUALIFIER_SN), Bytes.toBytes(sn));
+ info.addColumn(Bytes.toBytes(HBASE_META_FAMILY),
+ Bytes.toBytes(HBASE_META_QUALIFIER_SERVER), Bytes.toBytes(snSig[0] + ":" + snSig[1]));
+ info.addColumn(Bytes.toBytes(HBASE_META_FAMILY),
+ Bytes.toBytes(HBASE_META_QUALIFIER_STATE), Bytes.toBytes("OPEN"));
+ table.put(info);
+ i++;
+ }
+ LOG.info("Repair HBase Metadata: {}", hdfsRegionNames);
+ conn.close();
+ }
+
+ @Override
+ public int run(String[] args) {
+ if (args.length != 1) {
+ LOG.error("Wrong number of arguments. "
+ + "Arguments are: ");
+ return 1;
+ }
+ try {
+ this.repairMetadata(args[0]);
+ } catch (Exception e) {
+ LOG.error("Repairing metadata failed:", e);
+ return 2;
+ }
+ return 0;
+ }
+}
diff --git a/hbase-meta-repair/src/main/resources/log4j2.xml b/hbase-meta-repair/src/main/resources/log4j2.xml
new file mode 100644
index 0000000000..9763014d23
--- /dev/null
+++ b/hbase-meta-repair/src/main/resources/log4j2.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/hbase-meta-repair/src/test/java/org/apache/hbase/repair/TestMetaRepair.java b/hbase-meta-repair/src/test/java/org/apache/hbase/repair/TestMetaRepair.java
new file mode 100644
index 0000000000..0a2f7753e1
--- /dev/null
+++ b/hbase-meta-repair/src/test/java/org/apache/hbase/repair/TestMetaRepair.java
@@ -0,0 +1,131 @@
+/*
+ * 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.hbase.repair;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.hadoop.hbase.HBaseTestingUtility;
+import org.apache.hadoop.hbase.NamespaceDescriptor;
+import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.client.Put;
+import org.apache.hadoop.hbase.client.RegionInfo;
+import org.apache.hadoop.hbase.client.Table;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class TestMetaRepair {
+
+ private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
+ private static final String NAMESPACE = "TEST";
+ private static final TableName TABLE_NAME_WITH_NAMESPACE =
+ TableName.valueOf(NAMESPACE, TestMetaRepair.class.getSimpleName());
+ private static final TableName TABLE_NAME =
+ TableName.valueOf(TestMetaRepair.class.getSimpleName());
+ private static final byte[] family = Bytes.toBytes("test");
+ private Table table;
+
+ @BeforeClass
+ public static void beforeClass() throws Exception {
+ TEST_UTIL.startMiniCluster(3);
+ }
+
+ @AfterClass
+ public static void afterClass() throws Exception {
+ TEST_UTIL.shutdownMiniCluster();
+ }
+
+ @Before
+ public void setup() throws Exception {
+ table = TEST_UTIL.createMultiRegionTable(TABLE_NAME, family, 5);
+ TEST_UTIL.waitUntilAllRegionsAssigned(TABLE_NAME);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ TEST_UTIL.deleteTable(TABLE_NAME);
+ }
+
+ @Test
+ public void testHbaseAndHdfsRegions() throws Exception {
+ MetaRepair metaRepair = new MetaRepair(TEST_UTIL.getConfiguration());
+ Map hbaseRegions = metaRepair.getMetaRegions(TABLE_NAME.getNameAsString());
+ Map hdfsRegions = metaRepair.getHdfsRegions(TABLE_NAME.getNameAsString());
+ assertEquals(5, hbaseRegions.size());
+ assertEquals(5, hdfsRegions.size());
+ assertTrue(hbaseRegions.keySet().containsAll(hdfsRegions.keySet()));
+ }
+
+ @Test
+ public void testRepairMetadata() throws Exception {
+ MetaRepair metaRepair = new MetaRepair(TEST_UTIL.getConfiguration());
+ generateTableData(TABLE_NAME);
+ final int originalCount = TEST_UTIL.countRows(table);
+ metaRepair.repairMetadata(TABLE_NAME.getNameAsString());
+ assertEquals("Row count before and after repair should be equal",
+ originalCount, TEST_UTIL.countRows(table));
+ }
+
+ @Test
+ public void testRepairMetadataWithNameSpace() throws Exception {
+ try {
+ TEST_UTIL.getAdmin().createNamespace(NamespaceDescriptor.create(NAMESPACE).build());
+ Table tableWithNamespace = TEST_UTIL.createMultiRegionTable(TABLE_NAME_WITH_NAMESPACE,
+ family, 6);
+ MetaRepair metaRepair = new MetaRepair(TEST_UTIL.getConfiguration());
+ final int originalCount = TEST_UTIL.countRows(tableWithNamespace);
+ metaRepair.repairMetadata(TABLE_NAME_WITH_NAMESPACE.getNameAsString());
+ assertEquals("Row count before and after repair should be equal",
+ originalCount, TEST_UTIL.countRows(tableWithNamespace));
+ } finally {
+ TEST_UTIL.deleteTable(TABLE_NAME_WITH_NAMESPACE);
+ TEST_UTIL.getAdmin().deleteNamespace(NAMESPACE);
+ }
+ }
+
+ @Test
+ public void testRepairMetadataInvalidParams() throws Exception {
+ final int originalCount = TEST_UTIL.countRows(table);
+ MetaRepair metaRepair = new MetaRepair(TEST_UTIL.getConfiguration());
+ assertEquals(0, metaRepair.run(new String[]{TABLE_NAME.getNameAsString()}));
+ assertEquals(1, metaRepair.run(new String[]{}));
+ assertEquals(2, metaRepair.run(new String[]{"XXX"}));
+ assertEquals("Row count before and after repair should be equal",
+ originalCount, TEST_UTIL.countRows(table));
+ }
+
+ private void generateTableData(TableName tableName) throws Exception {
+ TEST_UTIL.getAdmin().getRegions(tableName).forEach(r -> {
+ byte[] key = r.getStartKey().length == 0 ? new byte[]{0} : r.getStartKey();
+ Put put = new Put(key);
+ put.addColumn(family, Bytes.toBytes("c"), new byte[1024 * 1024]);
+ try {
+ table.put(put);
+ } catch (IOException e) {
+ throw new Error("Failed to put row");
+ }
+ });
+ }
+}
diff --git a/pom.xml b/pom.xml
index 1c31a01304..4f9c1ef30c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -59,7 +59,8 @@
hbase-operator-tools-assembly
- hbase-tools
+ hbase-tools
+ hbase-meta-repair
scm:git:git://gitbox.apache.org/repos/asf/hbase-operator-tools.git