diff --git a/hbase-hbck2/README.md b/hbase-hbck2/README.md
index fd74014ece..44b3174891 100644
--- a/hbase-hbck2/README.md
+++ b/hbase-hbck2/README.md
@@ -345,3 +345,47 @@ HBASE_CLASSPATH_PREFIX=./hbase-hbck2-1.0.0-SNAPSHOT.jar hbase org.apache.hbase.H
The same may happen to the _hbase:namespace_ system table. Look for the
encoded Region name of the _hbase:namespace_ Region and do similar to
what we did for _hbase:meta_.
+
+### Missing Regions in META
+
+There's been some extra-ordinary cases where table regions are removed from META table.
+Some triage on such cases revealed those were operator-induced, after execution
+attempts of the obsolete _OfflineMetaRepair_ tool. _OfflineMetaRepair_ is a well known tool
+for fixing META table related issues on HBase 1.x versions. This tool is not compatible with
+HBase 2.x or higher versions, its usage can create the type of damage described here. That
+ultimately motivated its complete removal on HBASE-22690, and it's no longer available from
+HBase 2.3.0 onwards.
+
+For recovering from missing regions in META scenarios, HBCK2 provides _addMissingRegionsInMeta_
+command. On such cases, _namespace_ table region usually will also be among those missing in
+META. This will cause master (hbase) initialization to fail after some initial period. A META
+scan can help confirm if this condition is being reached:
+
+```
+echo "scan 'hbase:meta', {COLUMN=>'info:regioninfo'}" | hbase shell | grep namespace
+```
+
+If above doesn't return anything, it means _namespace_ region info is missing in META. The same
+command can be adjusted for checking other tables regions as well.
+
+HBCK2 _addMissingRegionsInMeta_ reads region metadata info available on the FS region dirs, in
+order to re-create regions in META. It can check for specific tables/namespaces, or all tables
+from all namespaces. An example adding missing regions for tables 'tbl_1' on default namespace,
+'tbl_2' on namespace 'n1' and for all tables from namespace 'n2':
+
+```
+$ HBCK2 addMissingRegionsInMeta default:tbl_1 n1:tbl_2 n2
+```
+
+As it operates independently from Master, once it finishes successfully, additional steps are
+required to actually have the re-added regions assigned. These are listed below:
+
+1. _addMissingRegionsInMeta_ outputs an _assigns_ command with all regions that got re-added. This
+command needs to be executed later, so copy and save it for convenience.
+
+2. After _addMissingRegionsInMeta_ finished successfully and output has been saved, restart all
+running HBase Masters.
+
+3. Once Master's are restarted and META is already online (check if Web UI is accessible), run
+_assigns_ command from _addMissingRegionsInMeta_ output saved per instructions from #1.
+
diff --git a/hbase-hbck2/pom.xml b/hbase-hbck2/pom.xml
index d6f2b7d382..9a74d247ce 100644
--- a/hbase-hbck2/pom.xml
+++ b/hbase-hbck2/pom.xml
@@ -181,6 +181,12 @@
${hbase.version}
test
+
+ org.mockito
+ mockito-core
+ 2.1.0
+ test
+
diff --git a/hbase-hbck2/src/main/java/org/apache/hbase/HBCK2.java b/hbase-hbck2/src/main/java/org/apache/hbase/HBCK2.java
index 14323125e9..6beaf82d12 100644
--- a/hbase-hbck2/src/main/java/org/apache/hbase/HBCK2.java
+++ b/hbase-hbck2/src/main/java/org/apache/hbase/HBCK2.java
@@ -26,6 +26,7 @@
import org.apache.commons.cli.ParseException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
+import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.ClusterMetrics;
import org.apache.hadoop.hbase.CompareOperator;
import org.apache.hadoop.hbase.HBaseConfiguration;
@@ -45,9 +46,11 @@
import org.apache.hadoop.hbase.filter.SubstringComparator;
import org.apache.hadoop.hbase.master.RegionState;
import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.hadoop.hbase.util.Pair;
import org.apache.hadoop.hbase.util.VersionInfo;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
+import org.apache.hbase.hbck2.meta.MetaFixer;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -59,9 +62,16 @@
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
import java.util.stream.Collectors;
/**
@@ -87,6 +97,10 @@ public class HBCK2 extends Configured implements Tool {
private static final String BYPASS = "bypass";
private static final String VERSION = "version";
private static final String SET_REGION_STATE = "setRegionState";
+ private static final String ADD_MISSING_REGIONS_IN_META_FOR_TABLES =
+ "addMissingRegionsInMetaForTables";
+ private static final String ADD_MISSING_REGIONS_IN_META = "addMissingRegionsInMeta";
+ private static final String REPORT_MISSING_REGIONS_IN_META = "reportMissingRegionsInMeta";
private Configuration conf;
private static final String TWO_POINT_ONE = "2.1.0";
private static final String MININUM_VERSION = "2.0.3";
@@ -164,6 +178,102 @@ int setRegionState(String region, RegionState.State newState)
return EXIT_FAILURE;
}
+ Map> reportTablesWithMissingRegionsInMeta(String... nameSpaceOrTable)
+ throws Exception {
+ Map> report;
+ try(final MetaFixer metaFixer = new MetaFixer(this.conf)){
+ List names = nameSpaceOrTable != null ? Arrays.asList(nameSpaceOrTable) : null;
+ report = metaFixer.reportTablesMissingRegions(names);
+ } catch (Exception e) {
+ LOG.error("Error reporting missing regions: ", e);
+ throw e;
+ }
+ if(LOG.isDebugEnabled()) {
+ LOG.debug(formatMissingRegionsInMetaReport(report));
+ }
+ return report;
+ }
+
+ List addMissingRegionsInMeta(List regionsPath) throws IOException {
+ List reAddedRegionsEncodedNames = new ArrayList<>();
+ try(final MetaFixer metaFixer = new MetaFixer(this.conf)){
+ for(Path regionPath : regionsPath){
+ metaFixer.putRegionInfoFromHdfsInMeta(regionPath);
+ reAddedRegionsEncodedNames.add(regionPath.getName());
+ }
+ }
+ return reAddedRegionsEncodedNames;
+ }
+
+ Pair, List> addMissingRegionsInMetaForTables(String...
+ nameSpaceOrTable) {
+ ExecutorService executorService = Executors.newFixedThreadPool(
+ (nameSpaceOrTable == null ||
+ nameSpaceOrTable.length > Runtime.getRuntime().availableProcessors()) ?
+ Runtime.getRuntime().availableProcessors() :
+ nameSpaceOrTable.length);
+ List>> futures = new ArrayList<>( nameSpaceOrTable == null ? 1 :
+ nameSpaceOrTable.length);
+ final List readdedRegionNames = new ArrayList<>();
+ List executionErrors = new ArrayList<>();
+ try {
+ //reducing number of retries in case disable fails due to namespace table region also missing
+ this.conf.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 1);
+ try(Connection conn = ConnectionFactory.createConnection(this.conf);
+ final Admin admin = conn.getAdmin()) {
+ Map> report = reportTablesWithMissingRegionsInMeta(nameSpaceOrTable);
+ for (TableName tableName : report.keySet()) {
+ if(admin.tableExists(tableName)) {
+ futures.add(executorService.submit(new Callable>() {
+ @Override
+ public List call() throws Exception {
+ LOG.debug("running thread for {}", tableName.getNameWithNamespaceInclAsString());
+ try {
+ admin.disableTable(tableName);
+ } catch (IOException e) {
+ LOG.debug("Failed to disable table {}, "
+ + "is namespace table also missing regions? Continue anyway...",
+ tableName.getNameWithNamespaceInclAsString(), e);
+ }
+ List reAddedRegions = addMissingRegionsInMeta(report.get(tableName));
+ try {
+ admin.enableTable(tableName);
+ } catch (IOException e) {
+ LOG.debug("Failed enabling table {}. It might be that namespace table "
+ + "region is also missing.\n"
+ + "After this command finishes, please make sure on this table state.",
+ tableName.getNameWithNamespaceInclAsString(), e);
+ }
+ return reAddedRegions;
+ }
+ }));
+ } else {
+ LOG.warn("Table {} does not exist! Skipping...",
+ tableName.getNameWithNamespaceInclAsString());
+ }
+ }
+ for(Future> f : futures){
+ try {
+ readdedRegionNames.addAll(f.get());
+ } catch (ExecutionException e){
+ //we want to allow potential running threads to finish, so we collect execution
+ //errors and show those later
+ LOG.debug("Caught execution error: ", e);
+ executionErrors.add(e);
+ }
+ }
+ }
+ } catch (Exception ie){
+ LOG.error("ERROR executing thread: ", ie);
+ } finally {
+ executorService.shutdown();
+ }
+ Pair, List> result = new Pair<>();
+ result.setFirst(readdedRegionNames);
+ result.setSecond(executionErrors);
+ return result;
+ }
+
List assigns(String [] args) throws IOException {
Options options = new Options();
Option override = Option.builder("o").longOpt("override").build();
@@ -334,6 +444,58 @@ private static final String getCommandUsage() {
writer.println(" Returns \"0\" SUCCESS code if it informed region state is changed, "
+ "\"1\" FAIL code otherwise.");
writer.println();
+ writer.println(" " + ADD_MISSING_REGIONS_IN_META_FOR_TABLES + " ...");
+ writer.println(" To be used in scenarios where some regions may be missing in META,");
+ writer.println(" but there's still a valid 'regioninfo metadata file on HDFS. ");
+ writer.println(" This is a lighter version of 'OfflineMetaRepair tool commonly used for ");
+ writer.println(" similar issues on 1.x release line. ");
+ writer.println(" This command needs META to be online. For each table name passed as");
+ writer.println(" parameter, it performs a diff between regions available in META, ");
+ writer.println(" against existing regions dirs on HDFS. Then, for region dirs with ");
+ writer.println(" no matches in META, it reads regioninfo metadata file and ");
+ writer.println(" re-creates given region in META. Regions are re-created in 'CLOSED' ");
+ writer.println(" state at META table only, but not in Masters' cache, and are not ");
+ writer.println(" assigned either. A rolling Masters restart, followed by a ");
+ writer.println(" hbck2 'assigns' command with all re-inserted regions is required. ");
+ writer.println(" This hbck2 'assigns' command is printed out after this command ");
+ writer.println(" completes for users subsequent convenience.");
+ writer.println(" WARNING: To avoid potential region overlapping problems due to ongoing ");
+ writer.println(" splits, this command attempts to disable given tables ");
+ writer.println(" (if it fails, disable is skipped) while re-inserting regions. ");
+ writer.println(" An example adding missing regions for tables 'tbl_1' on default ");
+ writer.println(" namespace, 'tbl_2' on namespace 'n1' and for all tables from ");
+ writer.println(" namespace 'n2': ");
+ writer.println(" $ HBCK2 addMissingRegionsInMeta default:tbl_1 n1:tbl_2 n2 ");
+ writer.println(" Returns hbck2 'assigns' command with all re-inserted regions.");
+ writer.println();
+ writer.println(" " + REPORT_MISSING_REGIONS_IN_META + " ...");
+ writer.println(" To be used in scenarios where some regions may be missing in META,");
+ writer.println(" but there's still a valid 'regioninfo metadata file on HDFS. ");
+ writer.println(" This is a checking only method, designed for reporting purposes and");
+ writer.println(" doesn't perform any fixes, providing a view of which regions (if any) ");
+ writer.println(" would get re-added to meta, grouped by respective table/namespace. ");
+ writer.println(" To effectively re-add regions in meta, "
+ + ADD_MISSING_REGIONS_IN_META_FOR_TABLES + " should be executed. ");
+ writer.println(" This command needs META to be online. For each namespace/table passed");
+ writer.println(" as parameter, it performs a diff between regions available in META, ");
+ writer.println(" against existing regions dirs on HDFS. Region dirs with no matches");
+ writer.println(" are printed grouped under its related table name. Tables with no");
+ writer.println(" missing regions will show a 'no missing regions' message. If no");
+ writer.println(" namespace or table is specified, it will verify all existing regions.");
+ writer.println(" It accepts a combination of multiple namespace and tables. Table names");
+ writer.println(" should include the namespace portion, even for tables in the default");
+ writer.println(" namespace, otherwise it will assume as a namespace value.");
+ writer.println(" An example triggering missing regions report for tables 'table_1'");
+ writer.println(" and 'table_2', under default namespace:");
+ writer.println(" $ HBCK2 reportMissingRegionsInMeta default:table_1 default:table_2");
+ writer.println(" An example triggering missing regions report for table 'table_1'");
+ writer.println(" under default namespace, and for all tables from namespace 'ns1':");
+ writer.println(" $ HBCK2 reportMissingRegionsInMeta default:table_1 ns1");
+ writer.println(" Returns list of missing regions for each table passed as parameter, or ");
+ writer.println(" for each table on namespaces specified as parameter.");
+ writer.println();
writer.close();
return sw.toString();
@@ -440,51 +602,68 @@ public int run(String[] args) throws IOException {
String[] commands = commandLine.getArgs();
String command = commands[0];
switch (command) {
- case SET_TABLE_STATE:
- if (commands.length < 3) {
- usage(options, command + " takes tablename and state arguments: e.g. user ENABLED");
- return EXIT_FAILURE;
- }
- System.out.println(setTableState(TableName.valueOf(commands[1]),
- TableState.State.valueOf(commands[2])));
- break;
-
- case ASSIGNS:
- if (commands.length < 2) {
- usage(options, command + " takes one or more encoded region names");
- return EXIT_FAILURE;
- }
- System.out.println(assigns(purgeFirst(commands)));
- break;
+ case SET_TABLE_STATE:
+ if (commands.length < 3) {
+ usage(options, command + " takes tablename and state arguments: e.g. user ENABLED");
+ return EXIT_FAILURE;
+ }
+ System.out.println(setTableState(TableName.valueOf(commands[1]), TableState.State.valueOf(commands[2])));
+ break;
- case BYPASS:
- if (commands.length < 2) {
- usage(options, command + " takes one or more pids");
- return EXIT_FAILURE;
- }
- List bs = bypass(purgeFirst(commands));
- if (bs == null) {
- // Something went wrong w/ the parse and command didn't run.
- return EXIT_FAILURE;
- }
- System.out.println(toString(bs));
- break;
+ case ASSIGNS:
+ if (commands.length < 2) {
+ usage(options, command + " takes one or more encoded region names");
+ return EXIT_FAILURE;
+ }
+ System.out.println(assigns(purgeFirst(commands)));
+ break;
- case UNASSIGNS:
- if (commands.length < 2) {
- usage(options, command + " takes one or more encoded region names");
- return EXIT_FAILURE;
- }
- System.out.println(toString(unassigns(purgeFirst(commands))));
- break;
-
- case SET_REGION_STATE:
- if(commands.length < 3){
- usage(options, command + " takes region encoded name and state arguments: e.g. "
- + "35f30b0ce922c34bf5c284eff33ba8b3 CLOSING");
- return EXIT_FAILURE;
- }
- return setRegionState(commands[1], RegionState.State.valueOf(commands[2]));
+ case BYPASS:
+ if (commands.length < 2) {
+ usage(options, command + " takes one or more pids");
+ return EXIT_FAILURE;
+ }
+ List bs = bypass(purgeFirst(commands));
+ if (bs == null) {
+ // Something went wrong w/ the parse and command didn't run.
+ return EXIT_FAILURE;
+ }
+ System.out.println(toString(bs));
+ break;
+
+ case UNASSIGNS:
+ if (commands.length < 2) {
+ usage(options, command + " takes one or more encoded region names");
+ return EXIT_FAILURE;
+ }
+ System.out.println(toString(unassigns(purgeFirst(commands))));
+ break;
+
+ case SET_REGION_STATE:
+ if (commands.length < 3) {
+ usage(options, command + " takes region encoded name and state arguments: e.g. "
+ + "35f30b0ce922c34bf5c284eff33ba8b3 CLOSING");
+ return EXIT_FAILURE;
+ }
+ return setRegionState(commands[1], RegionState.State.valueOf(commands[2]));
+ case ADD_MISSING_REGIONS_IN_META_FOR_TABLES:
+ if(commands.length < 2){
+ usage(options, command + " takes one or more table names.");
+ return EXIT_FAILURE;
+ }
+ Pair, List> result =
+ addMissingRegionsInMetaForTables(purgeFirst(commands));
+ System.out.println(formatReAddedRegionsMessage(result.getFirst(),result.getSecond()));
+ break;
+ case REPORT_MISSING_REGIONS_IN_META:
+ try {
+ Map> report =
+ reportTablesWithMissingRegionsInMeta(purgeFirst(commands));
+ System.out.println(formatMissingRegionsInMetaReport(report));
+ } catch (Exception e) {
+ return EXIT_FAILURE;
+ }
+ break;
default:
usage(options, "Unsupported command: " + command);
return EXIT_FAILURE;
@@ -496,6 +675,51 @@ private static String toString(List> things) {
return things.stream().map(i -> i.toString()).collect(Collectors.joining(", "));
}
+ private String formatMissingRegionsInMetaReport(Map> report) {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("Missing Regions for each table:\n\t");
+ report.keySet().stream().forEach(table -> {
+ builder.append(table);
+ if (!report.get(table).isEmpty()){
+ builder.append("->\n\t\t");
+ report.get(table).stream().forEach(region -> builder.append(region.getName())
+ .append(" "));
+ } else {
+ builder.append(" -> No missing regions");
+ }
+ builder.append("\n\t");
+ });
+ return builder.toString();
+ }
+
+ private String formatReAddedRegionsMessage(List readdedRegionNames,
+ List executionErrors) {
+ final StringBuilder finalText = new StringBuilder();
+ finalText.append("Regions re-added into Meta: ").append(readdedRegionNames.size());
+ if(!readdedRegionNames.isEmpty()){
+ finalText.append("\n")
+ .append("WARNING: \n\t")
+ .append(readdedRegionNames.size()).append(" regions were added ")
+ .append("to META, but these are not yet on Masters cache. \n")
+ .append("You need to restart Masters, then run hbck2 'assigns' command below:\n\t\t")
+ .append(buildHbck2AssignsCommand(readdedRegionNames));
+ }
+ if(!executionErrors.isEmpty()){
+ finalText.append("\n")
+ .append("ERROR: \n\t")
+ .append("There were following errors on at least one table thread:\n");
+ executionErrors.stream().forEach(e -> finalText.append(e.getMessage()).append("\n"));
+ }
+ return finalText.toString();
+ }
+
+ private String buildHbck2AssignsCommand(List regions) {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("assigns ");
+ regions.forEach(region -> builder.append(region).append(" "));
+ return builder.toString();
+ }
+
/**
* @return A new array with first element dropped.
*/
diff --git a/hbase-hbck2/src/main/java/org/apache/hbase/hbck2/meta/MetaFixer.java b/hbase-hbck2/src/main/java/org/apache/hbase/hbck2/meta/MetaFixer.java
new file mode 100644
index 0000000000..9d30291117
--- /dev/null
+++ b/hbase-hbck2/src/main/java/org/apache/hbase/hbck2/meta/MetaFixer.java
@@ -0,0 +1,130 @@
+/**
+ * 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.hbck2.meta;
+
+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.HConstants;
+import org.apache.hadoop.hbase.MetaTableAccessor;
+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.RegionInfo;
+import org.apache.hadoop.hbase.regionserver.HRegionFileSystem;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * This class implements the inner works required for check and recover regions that wrongly
+ * went missing in META. It assumes HDFS state as the source of truth, in other words,
+ * methods provided here consider meta information found on HDFS region dirs as the valid ones.
+ */
+public class MetaFixer implements Closeable {
+ private static final String TABLE_DESC_FILE = ".tabledesc";
+ private static final Logger LOG = LogManager.getLogger(MetaFixer.class);
+ private final FileSystem fs;
+ private final Connection conn;
+ private final Configuration config;
+
+ public MetaFixer(Configuration configuration) throws IOException {
+ this.config = configuration;
+ this.fs = FileSystem.get(configuration);
+ this.conn = ConnectionFactory.createConnection(configuration);
+ }
+
+ /*Initially defined for test only purposes */
+ MetaFixer(Configuration configuration, Connection connection, FileSystem fileSystem){
+ this.config = configuration;
+ this.conn = connection;
+ this.fs = fileSystem;
+ }
+
+ private FileStatus[] getTableRegionsDirs(String table) throws Exception {
+ String hbaseRoot = this.config.get("hbase.rootdir");
+ String tableRootDir = hbaseRoot + "/" + HConstants.BASE_NAMESPACE_DIR + "/" +
+ TableName.valueOf(table).getNameWithNamespaceInclAsString()
+ .replaceAll(":", "/");
+ return fs.listStatus(new Path(tableRootDir));
+ }
+
+ public Map> reportTablesMissingRegions(final List namespacesOrTables)
+ throws IOException {
+ final Map> result = new HashMap<>();
+ List tableNames = MetaTableAccessor.getTableStates(this.conn).keySet().stream()
+ .filter(tableName -> {
+ if(namespacesOrTables==null || namespacesOrTables.isEmpty()){
+ return true;
+ } else {
+ Optional findings = namespacesOrTables.stream().filter(
+ name -> (name.indexOf(":") > 0) ?
+ tableName.getNameWithNamespaceInclAsString().equals(name) :
+ tableName.getNamespaceAsString().equals(name)).findFirst();
+ return findings.isPresent();
+ }
+ }).collect(Collectors.toList());
+ tableNames.stream().forEach(tableName -> {
+ try {
+ result.put(tableName,
+ findMissingRegionsInMETA(tableName.getNameWithNamespaceInclAsString()));
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ });
+ return result;
+ }
+
+ List findMissingRegionsInMETA(String table) throws Exception {
+ final List missingRegions = new ArrayList<>();
+ final FileStatus[] regionsDirs = getTableRegionsDirs(table);
+ TableName tableName = TableName.valueOf(table);
+ List regionInfos = MetaTableAccessor.
+ getTableRegions(this.conn, tableName, false);
+ for(final FileStatus regionDir : regionsDirs){
+ if(!regionDir.getPath().getName().equals(TABLE_DESC_FILE) &&
+ !regionDir.getPath().getName().equals(HConstants.HBASE_TEMP_DIRECTORY)) {
+ boolean foundInMeta = regionInfos.stream()
+ .anyMatch(info -> info.getEncodedName().equals(regionDir.getPath().getName()));
+ if (!foundInMeta) {
+ LOG.debug(regionDir + "is not in META.");
+ missingRegions.add(regionDir.getPath());
+ }
+ }
+ }
+ return missingRegions;
+ }
+
+ public void putRegionInfoFromHdfsInMeta(Path region) throws IOException {
+ RegionInfo info = HRegionFileSystem.loadRegionInfoFileContent(fs, region);
+ MetaTableAccessor.addRegionToMeta(conn, info);
+ }
+
+ @Override public void close() throws IOException {
+ this.conn.close();
+ }
+}
diff --git a/hbase-hbck2/src/test/java/org/apache/hbase/TestHBCK2.java b/hbase-hbck2/src/test/java/org/apache/hbase/TestHBCK2.java
index 39c9c5bbd1..4d8cf548b7 100644
--- a/hbase-hbck2/src/test/java/org/apache/hbase/TestHBCK2.java
+++ b/hbase-hbck2/src/test/java/org/apache/hbase/TestHBCK2.java
@@ -18,11 +18,12 @@
package org.apache.hbase;
import junit.framework.TestCase;
+import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.HBaseTestingUtility;
import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.MetaTableAccessor;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Admin;
-import org.apache.hadoop.hbase.client.Delete;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.RegionInfo;
import org.apache.hadoop.hbase.client.Result;
@@ -37,12 +38,15 @@
import org.junit.Test;
import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
/**
* Tests commands. For command-line parsing, see adjacent test.
@@ -177,6 +181,114 @@ public void testSetRegionStateInvalidState() throws IOException {
}
}
+ @Test
+ public void testAddMissingRegionsInMetaAllRegionsMissing() throws Exception {
+ this.testAddMissingRegionsInMetaForTables(5,5);
+ }
+
+ @Test
+ public void testAddMissingRegionsInMetaTwoMissingOnly() throws Exception {
+ this.testAddMissingRegionsInMetaForTables(2,5);
+ }
+
+ @Test
+ public void testReportMissingRegionsInMetaAllNsTbls() throws Exception {
+ this.testReportMissingRegionsInMeta(5, 5,null);
+ }
+
+ @Test
+ public void testReportMissingRegionsInMetaSpecificTbl() throws Exception {
+ this.testReportMissingRegionsInMeta(5, 5,
+ TABLE_NAME.getNameWithNamespaceInclAsString());
+ }
+
+ @Test
+ public void testReportMissingRegionsInMetaSpecificTblAndNsTbl() throws Exception {
+ this.testReportMissingRegionsInMeta(5, 5,
+ TABLE_NAME.getNameWithNamespaceInclAsString(), "hbase:namespace");
+ }
+
+ @Test
+ public void testReportMissingRegionsInMetaSpecificTblAndNsTblAlsoMissing() throws Exception {
+ List regions = MetaTableAccessor
+ .getTableRegions(TEST_UTIL.getConnection(), TableName.valueOf("hbase:namespace"));
+ MetaTableAccessor.deleteRegions(TEST_UTIL.getConnection(),
+ regions.subList(0,1));
+ this.testReportMissingRegionsInMeta(5, 6,
+ TABLE_NAME.getNameWithNamespaceInclAsString(), "hbase:namespace");
+ }
+
+ @Test
+ public void testFormatReportMissingRegionsInMetaNoMissing() throws IOException {
+ final String expectedResult = "Missing Regions for each table:\n"
+ + "\tTestHBCK2 -> No missing regions\n\thbase:namespace -> No missing regions\n\t\n";
+ testFormatMissingRegionsInMetaReport(expectedResult);
+ }
+
+ @Test
+ public void testFormatReportMissingInMetaOneMissing() throws IOException {
+ List regions = MetaTableAccessor
+ .getTableRegions(TEST_UTIL.getConnection(), TABLE_NAME);
+ MetaTableAccessor.deleteRegions(TEST_UTIL.getConnection(), regions.subList(0,1));
+ final String expectedResult = "Missing Regions for each table:\n"
+ + "\tTestHBCK2->\n\t\t" + regions.get(0).getEncodedName()
+ + " \n\thbase:namespace -> No missing regions\n\t\n";
+ testFormatMissingRegionsInMetaReport(expectedResult);
+ HBCK2 hbck = new HBCK2(TEST_UTIL.getConfiguration());
+ hbck.addMissingRegionsInMetaForTables(null);
+ }
+
+ private void testFormatMissingRegionsInMetaReport(String expectedResultMessage)
+ throws IOException {
+ HBCK2 hbck = new HBCK2(TEST_UTIL.getConfiguration());
+ final StringBuilder builder = new StringBuilder();
+ PrintStream originalOS = System.out;
+ OutputStream testOS = new OutputStream() {
+ @Override public void write(int b) throws IOException {
+ builder.append((char)b);
+ }
+ };
+ System.setOut(new PrintStream(testOS));
+
+ hbck.run(new String[]{"reportMissingRegionsInMeta"});
+ assertTrue(builder.toString().contains(expectedResultMessage));
+ System.setOut(originalOS);
+ }
+
+ private void testAddMissingRegionsInMetaForTables(int missingRegions, int totalRegions)
+ throws Exception {
+ HBCK2 hbck = new HBCK2(TEST_UTIL.getConfiguration());
+ List regions = MetaTableAccessor
+ .getTableRegions(TEST_UTIL.getConnection(), TABLE_NAME);
+ MetaTableAccessor.deleteRegions(TEST_UTIL.getConnection(), regions.subList(0,missingRegions));
+ int remaining = totalRegions - missingRegions;
+ assertEquals("Table should had " + remaining + " regions in META.", remaining,
+ MetaTableAccessor.getRegionCount(TEST_UTIL.getConnection(), TABLE_NAME));
+ assertEquals(missingRegions,hbck.addMissingRegionsInMetaForTables("default:"
+ + TABLE_NAME.getNameAsString()).getFirst().size());
+ assertEquals("Table regions should had been re-added in META.", totalRegions,
+ MetaTableAccessor.getRegionCount(TEST_UTIL.getConnection(), TABLE_NAME));
+ //compare the added regions to make sure those are the same
+ List newRegions = MetaTableAccessor
+ .getTableRegions(TEST_UTIL.getConnection(), TABLE_NAME);
+ assertEquals("All re-added regions should be the same", regions, newRegions);
+ }
+
+ private void testReportMissingRegionsInMeta(int missingRegionsInTestTbl,
+ int expectedTotalMissingRegions, String... namespaceOrTable) throws Exception {
+ List regions = MetaTableAccessor
+ .getTableRegions(TEST_UTIL.getConnection(), TABLE_NAME);
+ MetaTableAccessor.deleteRegions(TEST_UTIL.getConnection(),
+ regions.subList(0,missingRegionsInTestTbl));
+ HBCK2 hbck = new HBCK2(TEST_UTIL.getConfiguration());
+ final Map> report =
+ hbck.reportTablesWithMissingRegionsInMeta(namespaceOrTable);
+ long resultingMissingRegions = report.keySet().stream().mapToLong( nsTbl ->
+ report.get(nsTbl).size()).sum();
+ assertEquals(expectedTotalMissingRegions, resultingMissingRegions);
+ hbck.addMissingRegionsInMetaForTables(null);
+ }
+
@Test (expected = IllegalArgumentException.class)
public void testSetRegionStateInvalidRegionAndInvalidState() throws IOException {
HBCK2 hbck = new HBCK2(TEST_UTIL.getConfiguration());
diff --git a/hbase-hbck2/src/test/java/org/apache/hbase/hbck2/meta/TestMetaFixer.java b/hbase-hbck2/src/test/java/org/apache/hbase/hbck2/meta/TestMetaFixer.java
new file mode 100644
index 0000000000..345c6b0f9e
--- /dev/null
+++ b/hbase-hbck2/src/test/java/org/apache/hbase/hbck2/meta/TestMetaFixer.java
@@ -0,0 +1,208 @@
+/**
+ * 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.hbck2.meta;
+
+import static org.junit.Assert.assertEquals;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FSDataInputStream;
+import org.apache.hadoop.fs.FSInputStream;
+import org.apache.hadoop.fs.FileStatus;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.hbase.Cell;
+import org.apache.hadoop.hbase.CellBuilderFactory;
+import org.apache.hadoop.hbase.CellBuilderType;
+import org.apache.hadoop.hbase.HBaseConfiguration;
+import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.client.Connection;
+import org.apache.hadoop.hbase.client.Put;
+import org.apache.hadoop.hbase.client.RegionInfo;
+import org.apache.hadoop.hbase.client.RegionInfoBuilder;
+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;
+import org.apache.hadoop.hbase.client.TableState;
+import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class TestMetaFixer {
+
+ private Connection mockedConnection;
+ private FileSystem mockedFileSystem;
+ private Table mockedTable;
+ private MetaFixer fixer;
+ private String testTblDir;
+
+ @Before
+ public void setup() throws Exception {
+ this.mockedConnection = Mockito.mock(Connection.class);
+ this.mockedFileSystem = Mockito.mock(FileSystem.class);
+ this.mockedTable = Mockito.mock(Table.class);
+ Configuration config = HBaseConfiguration.create();
+ Mockito.when(this.mockedConnection.getConfiguration()).thenReturn(config);
+ Mockito.when(this.mockedConnection.getTable(TableName.META_TABLE_NAME)).thenReturn(mockedTable);
+ this.testTblDir = config.get("hbase.rootdir") + "/data/default/test-tbl";
+ this.fixer = new MetaFixer(config, mockedConnection, mockedFileSystem);
+ }
+
+ private RegionInfo createRegionInfo(String table){
+ long regionTS = System.currentTimeMillis();
+ RegionInfo info = RegionInfoBuilder.newBuilder(TableName.valueOf(table))
+ .setRegionId(regionTS)
+ .build();
+ return info;
+ }
+
+ private Cell createCellForRegionInfo(RegionInfo info){
+ byte[] regionInfoValue = ProtobufUtil.prependPBMagic(ProtobufUtil.toRegionInfo(info)
+ .toByteArray());
+ Cell cell = CellBuilderFactory.create(CellBuilderType.SHALLOW_COPY)
+ .setRow(info.getRegionName())
+ .setFamily(Bytes.toBytes("info"))
+ .setQualifier(Bytes.toBytes("regioninfo"))
+ .setType(Cell.Type.Put)
+ .setValue(regionInfoValue)
+ .build();
+ return cell;
+ }
+
+ private Cell createCellForTableState(TableName tableName){
+ Cell cell = CellBuilderFactory.create(CellBuilderType.SHALLOW_COPY)
+ .setRow(tableName.getName())
+ .setFamily(Bytes.toBytes("table"))
+ .setQualifier(Bytes.toBytes("state"))
+ .setType(Cell.Type.Put)
+ .setValue(HBaseProtos.TableState.newBuilder()
+ .setState(TableState.State.ENABLED.convert()).build().toByteArray())
+ .build();
+ return cell;
+ }
+
+ @Test
+ public void testFindMissingRegionsInMETANoMissing() throws Exception {
+ ResultScanner mockedRS = Mockito.mock(ResultScanner.class);
+ Mockito.when(this.mockedTable.getScanner(Mockito.any(Scan.class))).thenReturn(mockedRS);
+ RegionInfo info = createRegionInfo("test-tbl");
+ List cells = new ArrayList();
+ cells.add(createCellForRegionInfo(info));
+ Result result = Result.create(cells);
+ Mockito.when(mockedRS.next()).thenReturn(result,null);
+ FileStatus status = new FileStatus();
+ status.setPath(new Path(this.testTblDir + "/" + info.getEncodedName()));
+ Mockito.when(mockedFileSystem.listStatus(new Path(this.testTblDir)))
+ .thenReturn(new FileStatus[]{status});
+ assertEquals("Should had returned 0 missing regions",
+ 0, fixer.findMissingRegionsInMETA("test-tbl").size());
+ }
+
+ @Test
+ public void testFindMissingRegionsInMETAOneMissing() throws Exception {
+ ResultScanner mockedRS = Mockito.mock(ResultScanner.class);
+ Mockito.when(this.mockedTable.getScanner(Mockito.any(Scan.class))).thenReturn(mockedRS);
+ List cells = new ArrayList();
+ Result result = Result.create(cells);
+ Mockito.when(mockedRS.next()).thenReturn(result,null);
+ FileStatus status = new FileStatus();
+ Path p = new Path(this.testTblDir+ "/182182182121");
+ status.setPath(p);
+ Mockito.when(mockedFileSystem.listStatus(new Path(this.testTblDir)))
+ .thenReturn(new FileStatus[]{status});
+ List missingRegions = fixer.findMissingRegionsInMETA("test-tbl");
+ assertEquals("Should had returned 1 missing region",
+ 1, missingRegions.size());
+ assertEquals(p,missingRegions.get(0));
+ }
+
+ @Test
+ public void testPutRegionInfoFromHdfsInMeta() throws Exception {
+ RegionInfo info = this.createRegionInfo("test-tbl");
+ Path regionPath = new Path("/hbase/data/default/test-tbl/" + info.getEncodedName());
+ FSDataInputStream fis = new FSDataInputStream(new TestInputStreamSeekable(info));
+ Mockito.when(this.mockedFileSystem.open(new Path(regionPath, ".regioninfo")))
+ .thenReturn(fis);
+ fixer.putRegionInfoFromHdfsInMeta(regionPath);
+ Mockito.verify(this.mockedConnection).getTable(TableName.META_TABLE_NAME);
+ Mockito.verify(this.mockedTable).put(Mockito.any(Put.class));
+ }
+
+ @Test
+ public void testReportTablesMissingRegionsOneMissing() throws Exception {
+ ResultScanner mockedRS = Mockito.mock(ResultScanner.class);
+ Mockito.when(this.mockedTable.getScanner(Mockito.any(Scan.class))).thenReturn(mockedRS);
+ List| cells = new ArrayList();
+ cells.add(createCellForTableState(TableName.valueOf("test-tbl")));
+ Result result = Result.create(cells);
+ Mockito.when(mockedRS.next()).thenReturn(result,null);
+ FileStatus status = new FileStatus();
+ Path p = new Path(this.testTblDir+ "/182182182121");
+ status.setPath(p);
+ Mockito.when(mockedFileSystem.listStatus(new Path(this.testTblDir)))
+ .thenReturn(new FileStatus[]{status});
+ Mockito.when(this.mockedConnection.getTable(TableName.META_TABLE_NAME))
+ .thenReturn(this.mockedTable);
+ Map> report = fixer.reportTablesMissingRegions(null);
+ assertEquals("Should had returned 1 missing region",
+ 1,report.size());
+ }
+
+ private class TestInputStreamSeekable extends FSInputStream {
+
+ private ByteArrayInputStream in;
+ private long length;
+
+ private TestInputStreamSeekable(RegionInfo info) throws Exception {
+ byte[] bytes = RegionInfo.toDelimitedByteArray(info);
+ this.length = bytes.length;
+ this.in = new ByteArrayInputStream(bytes);
+ }
+
+ @Override
+ public void seek(long l) throws IOException {
+ this.in.skip(l);
+ }
+
+ @Override
+ public long getPos() throws IOException {
+ return this.length - in.available();
+ }
+
+ @Override
+ public boolean seekToNewSource(long l) throws IOException {
+ this.in.skip(l);
+ return true;
+ }
+
+ @Override
+ public int read() throws IOException {
+ return in.read();
+ }
+ }
+
+}
+
| | |