This is an automated email from the ASF dual-hosted git repository.

wchevreuil pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hbase-operator-tools.git


The following commit(s) were added to refs/heads/master by this push:
     new 63938dc  HBASE-23371 [HBCK2] Provide client side method for removing 
ghost regions in meta. (#45)
63938dc is described below

commit 63938dc918bc9a7adbb83d3f2ed384c30de72c77
Author: Wellington Ramos Chevreuil <[email protected]>
AuthorDate: Mon Jan 27 11:16:51 2020 +0000

    HBASE-23371 [HBCK2] Provide client side method for removing ghost regions 
in meta. (#45)
    
    Signed-off-by: Josh Elser [email protected]
---
 hbase-hbck2/README.md                              |  35 +++
 .../org/apache/hbase/FsRegionsMetaRecoverer.java   | 222 +++++++++++++++----
 .../src/main/java/org/apache/hbase/HBCK2.java      | 236 +++++++++++++--------
 .../apache/hbase/TestFsRegionsMetaRecoverer.java   | 107 ++++++++--
 .../src/test/java/org/apache/hbase/TestHBCK2.java  | 227 +++++++++++++++++++-
 5 files changed, 669 insertions(+), 158 deletions(-)

diff --git a/hbase-hbck2/README.md b/hbase-hbck2/README.md
index 869e0ac..6e6d9ea 100644
--- a/hbase-hbck2/README.md
+++ b/hbase-hbck2/README.md
@@ -148,6 +148,29 @@ Command:
    to finish parent and children. This is SLOW, and dangerous so use
    selectively. Does not always work.
 
+ extraRegionsInMeta <NAMESPACE|NAMESPACE:TABLENAME>...
+   Options:
+    -f, --fix    fix meta by removing all extra regions found.
+   Reports regions present on hbase:meta, but with no related
+   directories on the file system. Needs hbase:meta to be online.
+   For each table name passed as parameter, performs diff
+   between regions available in hbase:meta and region dirs on the given
+   file system. Extra regions would get deleted from Meta
+   if passed the --fix option.
+   NOTE: Before deciding on use the "--fix" option, it's worth check if
+   reported extra regions are overlapping with existing valid regions.
+   If so, then "extraRegionsInMeta --fix" is indeed the optimal solution.
+   Otherwise, "assigns" command is the simpler solution, as it recreates
+   regions dirs in the filesystem, if not existing.
+   An example triggering extra regions report for tables 'table_1'
+   and 'table_2', under default namespace:
+     $ HBCK2 extraRegionsInMeta default:table_1 default:table_2
+   An example triggering extra regions report for table 'table_1'
+   under default namespace, and for all tables from namespace 'ns1':
+     $ HBCK2 extraRegionsInMeta default:table_1 ns1
+   Returns list of extra regions for each table passed as parameter, or
+   for each table on namespaces specified as parameter.
+
  filesystem [OPTIONS] [<TABLENAME>...]
    Options:
     -f, --fix    sideline corrupt hfiles, bad links, and references.
@@ -528,6 +551,18 @@ using the _addFsRegionsMissingInMeta_ command in _HBCK2_. 
This command is less d
 hbase than a full hbase:meta rebuild covered later, and it can be used even for
 recovering the _namespace_ table region.
 
+### Extra Regions in hbase:meta region/table restore/rebuild
+
+There can also be situations where table regions have been removed file 
system, but still
+have related entries on hbase:meta table. This may happen due to problems on 
splitting, manual
+operation mistakes (like deleting/moving the region dir manually), or even 
meta info data loss
+issues such as HBASE-21843.
+
+Such problem can be addressed with the Master online, using the 
_removeExtraRegionsFromMeta_
+command in _HBCK2_. This command is less disruptive to hbase than a full 
hbase:meta rebuild
+covered later. Also useful when this happens on versions that don't support 
_fixMeta_ hbck2 option
+(any prior to "2.0.6", "2.1.6", "2.2.1", "2.3.0","3.0.0").
+
 #### Online hbase:meta rebuild recipe
 
 If hbase:meta corruption is not too critical, hbase would still be able to 
bring it online. Even if namespace region
diff --git 
a/hbase-hbck2/src/main/java/org/apache/hbase/FsRegionsMetaRecoverer.java 
b/hbase-hbck2/src/main/java/org/apache/hbase/FsRegionsMetaRecoverer.java
index c3dbbb8..e3cd643 100644
--- a/hbase-hbck2/src/main/java/org/apache/hbase/FsRegionsMetaRecoverer.java
+++ b/hbase-hbck2/src/main/java/org/apache/hbase/FsRegionsMetaRecoverer.java
@@ -25,6 +25,11 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 
 import org.apache.hadoop.conf.Configuration;
@@ -33,6 +38,7 @@ 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.Admin;
 import org.apache.hadoop.hbase.client.Connection;
 import org.apache.hadoop.hbase.client.ConnectionFactory;
 import org.apache.hadoop.hbase.client.RegionInfo;
@@ -44,8 +50,8 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * This class implements the inner works required for check and recover 
regions that wrongly
- * went missing in META.
+ * This class implements the inner works required for checking and recovering 
regions that wrongly
+ * went missing in META, or are left present in META but with no equivalent FS 
dir.
  * Normally HBCK2 fix options rely on Master self-contained information to 
recover/fix
  * inconsistencies, but this an exception case where META table is in a broken 
state.
  * So, it assumes HDFS state as the source of truth, in other words, methods 
provided here consider
@@ -70,7 +76,7 @@ public class FsRegionsMetaRecoverer implements Closeable {
     this.fs = fileSystem;
   }
 
-  private List<Path> getTableRegionsDirs(String table) throws Exception {
+  private List<Path> getTableRegionsDirs(String table) throws IOException {
     String hbaseRoot = this.config.get(HConstants.HBASE_DIR);
     Path tableDir = FSUtils.getTableDir(new Path(hbaseRoot), 
TableName.valueOf(table));
     return FSUtils.getRegionDirs(fs, tableDir);
@@ -78,53 +84,183 @@ public class FsRegionsMetaRecoverer implements Closeable {
 
   public Map<TableName,List<Path>> reportTablesMissingRegions(final 
List<String> namespacesOrTables)
       throws IOException {
-    final Map<TableName,List<Path>> result = new HashMap<>();
-    List<TableName> tableNames = 
MetaTableAccessor.getTableStates(this.conn).keySet().stream()
-      .filter(tableName -> {
-        if(namespacesOrTables==null || namespacesOrTables.isEmpty()){
-          return true;
-        } else {
-          Optional<String> findings = namespacesOrTables.stream().filter(
-            name -> (name.indexOf(":") > 0) ?
-              tableName.equals(TableName.valueOf(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) {
-        LOG.warn("Can't get missing regions from meta", e);
-      }
+    InternalMetaChecker<Path> missingChecker = new InternalMetaChecker<>();
+    return missingChecker.reportTablesRegions(namespacesOrTables, 
this::findMissingRegionsInMETA);
+  }
+
+  public Map<TableName,List<RegionInfo>>
+      reportTablesExtraRegions(final List<String> namespacesOrTables) throws 
IOException {
+    InternalMetaChecker<RegionInfo> extraChecker = new InternalMetaChecker<>();
+    return extraChecker.reportTablesRegions(namespacesOrTables, 
this::findExtraRegionsInMETA);
+  }
+
+  List<Path> findMissingRegionsInMETA(String table) throws IOException {
+    InternalMetaChecker<Path> missingChecker = new InternalMetaChecker<>();
+    return missingChecker.checkRegionsInMETA(table, (regions, dirs) -> {
+      ListUtils<Path, RegionInfo> utils = new ListUtils<>();
+      return utils.complement(dirs, regions, d -> d.getName(), r -> 
r.getEncodedName());
     });
-    return result;
-  }
-
-  List<Path> findMissingRegionsInMETA(String table) throws Exception {
-    final List<Path> missingRegions = new ArrayList<>();
-    final List<Path> regionsDirs = getTableRegionsDirs(table);
-    TableName tableName = TableName.valueOf(table);
-    List<RegionInfo> regionInfos = MetaTableAccessor.
-      getTableRegions(this.conn, tableName, false);
-    HashSet<String> regionsInMeta = regionInfos.stream().map(info ->
-      info.getEncodedName()).collect(Collectors.toCollection(HashSet::new));
-    for(final Path regionDir : regionsDirs){
-      if (!regionsInMeta.contains(regionDir.getName())) {
-        LOG.debug(regionDir + "is not in META.");
-        missingRegions.add(regionDir);
-      }
-    }
-    return missingRegions;
   }
 
-  public void putRegionInfoFromHdfsInMeta(Path region) throws IOException {
+  List<RegionInfo> findExtraRegionsInMETA(String table) throws IOException {
+    InternalMetaChecker<RegionInfo> extraChecker = new InternalMetaChecker<>();
+    return extraChecker.checkRegionsInMETA(table, (regions,dirs) -> {
+      ListUtils<RegionInfo, Path> utils = new ListUtils<>();
+      return utils.complement(regions, dirs, r -> r.getEncodedName(), d -> 
d.getName());
+    });
+  }
+
+  void putRegionInfoFromHdfsInMeta(Path region) throws IOException {
     RegionInfo info = HRegionFileSystem.loadRegionInfoFileContent(fs, region);
     MetaTableAccessor.addRegionToMeta(conn, info);
   }
 
-  @Override public void close() throws IOException {
+  List<String> addMissingRegionsInMeta(List<Path> regionsPath) throws 
IOException {
+    List<String> reAddedRegionsEncodedNames = new ArrayList<>();
+    for(Path regionPath : regionsPath){
+      this.putRegionInfoFromHdfsInMeta(regionPath);
+      reAddedRegionsEncodedNames.add(regionPath.getName());
+    }
+    return reAddedRegionsEncodedNames;
+  }
+
+  public List<Future<List<String>>> addMissingRegionsInMetaForTables(
+      List<String> nameSpaceOrTable) throws IOException {
+    InternalMetaChecker<Path> missingChecker = new InternalMetaChecker<>();
+    return 
missingChecker.processRegionsMetaCleanup(this::reportTablesMissingRegions,
+      this::addMissingRegionsInMeta, nameSpaceOrTable);
+  }
+
+  public List<Future<List<String>>> removeExtraRegionsFromMetaForTables(
+    List<String> nameSpaceOrTable) throws IOException {
+    if(nameSpaceOrTable.size()>0) {
+      InternalMetaChecker<RegionInfo> extraChecker = new 
InternalMetaChecker<>();
+      return 
extraChecker.processRegionsMetaCleanup(this::reportTablesExtraRegions, regions 
-> {
+        MetaTableAccessor.deleteRegionInfos(conn, regions);
+        return regions.stream().map(r -> 
r.getEncodedName()).collect(Collectors.toList());
+      }, nameSpaceOrTable);
+    } else {
+      return null;
+    }
+  }
+
+
+  @Override
+  public void close() throws IOException {
     this.conn.close();
   }
+
+  private class InternalMetaChecker<T> {
+
+    List<T> checkRegionsInMETA(String table,
+        CheckingFunction<List<RegionInfo>, List<Path>, T> checkingFunction) 
throws IOException {
+      final List<Path> regionsDirs = getTableRegionsDirs(table);
+      TableName tableName = TableName.valueOf(table);
+      List<RegionInfo> regions = MetaTableAccessor.
+        getTableRegions(FsRegionsMetaRecoverer.this.conn, tableName, false);
+      return checkingFunction.check(regions, regionsDirs);
+    }
+
+    Map<TableName,List<T>> reportTablesRegions(final List<String> 
namespacesOrTables,
+      ExecFunction<List<T>, String> checkingFunction) throws IOException {
+      final Map<TableName,List<T>> result = new HashMap<>();
+      List<TableName> tableNames = MetaTableAccessor.
+        getTableStates(FsRegionsMetaRecoverer.this.conn).keySet().stream()
+          .filter(tableName -> {
+            if(namespacesOrTables==null || namespacesOrTables.isEmpty()){
+              return true;
+            } else {
+              Optional<String> findings = namespacesOrTables.stream().filter(
+                name -> (name.indexOf(":") > 0) ?
+                  tableName.equals(TableName.valueOf(name)) :
+                  tableName.getNamespaceAsString().equals(name)).findFirst();
+              return findings.isPresent();
+            }
+          }).collect(Collectors.toList());
+      tableNames.stream().forEach(tableName -> {
+        try {
+          result.put(tableName,
+            
checkingFunction.execute(tableName.getNameWithNamespaceInclAsString()));
+        } catch (Exception e) {
+          LOG.warn("Can't get related regions report from meta", e);
+        }
+      });
+      return result;
+    }
+
+    List<Future<List<String>>> processRegionsMetaCleanup(
+        ExecFunction<Map<TableName, List<T>>, List<String>> reportFunction,
+        ExecFunction<List<String>, List<T>> execFunction,
+        List<String> nameSpaceOrTable) throws IOException {
+      ExecutorService executorService = Executors.newFixedThreadPool(
+        (nameSpaceOrTable == null ||
+          nameSpaceOrTable.size() > 
Runtime.getRuntime().availableProcessors()) ?
+          Runtime.getRuntime().availableProcessors() :
+          nameSpaceOrTable.size());
+      List<Future<List<String>>> futures =
+        new ArrayList<>(nameSpaceOrTable == null ? 1 : 
nameSpaceOrTable.size());
+      try {
+        try(final Admin admin = conn.getAdmin()) {
+          Map<TableName,List<T>> report = 
reportFunction.execute(nameSpaceOrTable);
+          if(report.size() < 1) {
+            LOG.info("\nNo mismatches found in meta. Worth using related 
reporting function " +
+              "first.\nYou are likely passing non-existent " +
+              "namespace or table. Note that table names should include the 
namespace " +
+              "portion even for tables in the default namespace. " +
+              "See also the command usage.\n");
+          }
+          for (TableName tableName : report.keySet()) {
+            if(admin.tableExists(tableName)) {
+              futures.add(executorService.submit(new Callable<List<String>>() {
+                @Override
+                public List<String> call() throws Exception {
+                  LOG.debug("running thread for {}", 
tableName.getNameWithNamespaceInclAsString());
+                  return execFunction.execute(report.get(tableName));
+                }
+              }));
+            } else {
+              LOG.warn("Table {} does not exist! Skipping...",
+                tableName.getNameWithNamespaceInclAsString());
+            }
+          }
+          boolean allDone;
+          do {
+            allDone = true;
+            for (Future<List<String>> f : futures) {
+              allDone &= f.isDone();
+            }
+          } while(!allDone);
+        }
+      } finally {
+        executorService.shutdown();
+      }
+      return futures;
+    }
+  }
+
+  @FunctionalInterface
+  interface CheckingFunction <RegionsList, DirList, T> {
+    List<T> check(RegionsList regions, DirList dirs) throws IOException;
+  }
+
+  @FunctionalInterface
+  interface ExecFunction<T, NamespaceOrTable> {
+    T execute(NamespaceOrTable name) throws IOException;
+  }
+
+  public class ListUtils<T1, T2> {
+    public List<T1> complement(List<T1> list1, List<T2> list2,
+        Function<T1, String> convertT1, Function<T2, String> convertT2) {
+      final List<T1> extraRegions = new ArrayList<>();
+      HashSet<String> baseSet = list2.stream().map(info ->
+        convertT2.apply(info)).collect(Collectors.toCollection(HashSet::new));
+      list1.forEach(region -> {
+        if(!baseSet.contains(convertT1.apply(region))) {
+          extraRegions.add(region);
+        }
+      });
+      return extraRegions;
+    }
+  }
+
 }
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 0b1d33c..ead69c4 100644
--- a/hbase-hbck2/src/main/java/org/apache/hbase/HBCK2.java
+++ b/hbase-hbck2/src/main/java/org/apache/hbase/HBCK2.java
@@ -24,14 +24,13 @@ import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.EnumSet;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Properties;
-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.function.Function;
 import java.util.stream.Collectors;
 
 import org.apache.hadoop.conf.Configuration;
@@ -48,6 +47,7 @@ import org.apache.hadoop.hbase.client.ClusterConnection;
 import org.apache.hadoop.hbase.client.ConnectionFactory;
 import org.apache.hadoop.hbase.client.Hbck;
 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;
@@ -55,7 +55,6 @@ import org.apache.hadoop.hbase.client.TableState;
 import org.apache.hadoop.hbase.filter.RowFilter;
 import org.apache.hadoop.hbase.filter.SubstringComparator;
 import org.apache.hadoop.hbase.master.RegionState;
-import org.apache.hadoop.hbase.util.Pair;
 
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.core.config.Configurator;
@@ -71,7 +70,6 @@ import 
org.apache.hbase.thirdparty.org.apache.commons.cli.Options;
 import org.apache.hbase.thirdparty.org.apache.commons.cli.ParseException;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos;
 
-
 /**
  * HBase fixup tool version 2, for hbase-2.0.0+ clusters.
  * Supercedes hbck1.
@@ -99,8 +97,8 @@ public class HBCK2 extends Configured implements 
org.apache.hadoop.util.Tool {
 
   private static final String ADD_MISSING_REGIONS_IN_META_FOR_TABLES =
     "addFsRegionsMissingInMeta";
-  private static final String ADD_MISSING_REGIONS_IN_META = 
"addMissingRegionsInMeta";
   private static final String REPORT_MISSING_REGIONS_IN_META = 
"reportMissingRegionsInMeta";
+  static final String EXTRA_REGIONS_IN_META = "extraRegionsInMeta";
   private Configuration conf;
   static final String [] MINIMUM_HBCK2_VERSION = {"2.0.3", "2.1.1", "2.2.0", 
"3.0.0"};
   private boolean skipCheck = false;
@@ -179,8 +177,8 @@ public class HBCK2 extends Configured implements 
org.apache.hadoop.util.Tool {
     Map<TableName,List<Path>> report;
     try (final FsRegionsMetaRecoverer fsRegionsMetaRecoverer =
         new FsRegionsMetaRecoverer(this.conf)) {
-      List<String> names = nameSpaceOrTable != null ? 
Arrays.asList(nameSpaceOrTable) : null;
-      report = fsRegionsMetaRecoverer.reportTablesMissingRegions(names);
+      report = fsRegionsMetaRecoverer.reportTablesMissingRegions(
+        formatNameSpaceTableParam(nameSpaceOrTable));
     } catch (IOException e) {
       LOG.error("Error reporting missing regions: ", e);
       throw e;
@@ -191,79 +189,72 @@ public class HBCK2 extends Configured implements 
org.apache.hadoop.util.Tool {
     return report;
   }
 
-  List<String> addMissingRegionsInMeta(List<Path> regionsPath) throws 
IOException {
-    List<String> reAddedRegionsEncodedNames = new ArrayList<>();
+  Map<TableName, List<String>> extraRegionsInMeta(String[] args)
+      throws Exception {
+    Options options = new Options();
+    Option fixOption = Option.builder("f").longOpt("fix").build();
+    options.addOption(fixOption);
+    // Parse command-line.
+    CommandLineParser parser = new DefaultParser();
+    CommandLine commandLine;
+    commandLine = parser.parse(options, args, false);
+    boolean fix = commandLine.hasOption(fixOption.getOpt());
+    Map<TableName, List<String>> result = new HashMap<>();
     try (final FsRegionsMetaRecoverer fsRegionsMetaRecoverer =
-        new FsRegionsMetaRecoverer(this.conf)) {
-      for(Path regionPath : regionsPath){
-        fsRegionsMetaRecoverer.putRegionInfoFromHdfsInMeta(regionPath);
-        reAddedRegionsEncodedNames.add(regionPath.getName());
-      }
-    }
-    return reAddedRegionsEncodedNames;
-  }
-
-  Pair<List<String>, List<ExecutionException>> 
addMissingRegionsInMetaForTables(String...
-      nameSpaceOrTable) throws IOException {
-    ExecutorService executorService = Executors.newFixedThreadPool(
-      (nameSpaceOrTable == null ||
-        nameSpaceOrTable.length > Runtime.getRuntime().availableProcessors()) ?
-          Runtime.getRuntime().availableProcessors() :
-          nameSpaceOrTable.length);
-    List<Future<List<String>>> futures =
-        new ArrayList<>(nameSpaceOrTable == null ? 1 : 
nameSpaceOrTable.length);
-    final List<String> readdedRegionNames = new ArrayList<>();
-    List<ExecutionException> 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(ClusterConnection conn = connect();
-        final Admin admin = conn.getAdmin()) {
-        Map<TableName,List<Path>> report = 
reportTablesWithMissingRegionsInMeta(nameSpaceOrTable);
-        if(report.size() < 1) {
-          LOG.info("\nNo missing regions in meta are found. Worth using " +
-                  "reportMissingRegionsInMeta first.\nYou are likely passing 
non-existent " +
-                  "namespace or table. Note that table names should include 
the namespace " +
-                  "portion even for tables in the default namespace. " +
-                  "See also the command usage.\n");
+      new FsRegionsMetaRecoverer(this.conf)) {
+      List<String> namespacesTables = 
formatNameSpaceTableParam(commandLine.getArgs());
+      Map<TableName, List<RegionInfo>> reportMap =
+        fsRegionsMetaRecoverer.reportTablesExtraRegions(namespacesTables);
+      final List<String> toFix = new ArrayList<>();
+      reportMap.entrySet().forEach(e -> {
+        result.put(e.getKey(),
+          
e.getValue().stream().map(r->r.getEncodedName()).collect(Collectors.toList()));
+        if(fix && e.getValue().size()>0){
+          toFix.add(e.getKey().getNameWithNamespaceInclAsString());
         }
-        for (TableName tableName : report.keySet()) {
-          if(admin.tableExists(tableName)) {
-            futures.add(executorService.submit(new Callable<List<String>>() {
-              @Override
-              public List<String> call() throws Exception {
-                LOG.debug("running thread for {}", 
tableName.getNameWithNamespaceInclAsString());
-                return addMissingRegionsInMeta(report.get(tableName));
-              }
-            }));
-          } else {
-            LOG.warn("Table {} does not exist! Skipping...",
-              tableName.getNameWithNamespaceInclAsString());
-          }
-        }
-        for(Future<List<String>> 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);
+      });
+      if(fix) {
+        List<Future<List<String>>> removeResult =
+          fsRegionsMetaRecoverer.removeExtraRegionsFromMetaForTables(toFix);
+        if(removeResult!=null) {
+          int totalRegions = 0;
+          List<Exception> errors = new ArrayList<>();
+          for(Future<List<String>> f : removeResult){
+            try {
+              totalRegions += f.get().size();
+            } catch (ExecutionException|InterruptedException e){
+              errors.add(e);
+            }
           }
+          System.out.println(formatRemovedRegionsMessage(totalRegions, 
errors));
         }
       }
-    } catch (IOException | InterruptedException e) {
-      LOG.error("ERROR executing thread: ", e);
-      throw new IOException(e);
-    } finally {
-      executorService.shutdown();
-    }
-    Pair<List<String>, List<ExecutionException>> result = new Pair<>();
-    result.setFirst(readdedRegionNames);
-    result.setSecond(executionErrors);
+    } catch (IOException e) {
+      LOG.error("Error on checking extra regions: ", e);
+      throw e;
+    }
+    if(LOG.isDebugEnabled()) {
+      LOG.debug(formatExtraRegionsReport(result));
+    }
     return result;
   }
 
+  private List<String> formatNameSpaceTableParam(String... nameSpaceOrTable) {
+    return nameSpaceOrTable != null ? Arrays.asList(nameSpaceOrTable) : null;
+  }
+
+  List<Future<List<String>>> addMissingRegionsInMetaForTables(String...
+      nameSpaceOrTable) throws IOException {
+    try (final FsRegionsMetaRecoverer fsRegionsMetaRecoverer =
+      new FsRegionsMetaRecoverer(this.conf)) {
+      return fsRegionsMetaRecoverer.addMissingRegionsInMetaForTables(
+        formatNameSpaceTableParam(nameSpaceOrTable));
+    } catch (IOException e) {
+      LOG.error("Error adding missing regions: ", e);
+      throw e;
+    }
+  }
+
   List<Long> assigns(Hbck hbck, String [] args) throws IOException {
     Options options = new Options();
     Option override = Option.builder("o").longOpt("override").build();
@@ -374,6 +365,8 @@ public class HBCK2 extends Configured implements 
org.apache.hadoop.util.Tool {
     writer.println();
     usageBypass(writer);
     writer.println();
+    usageExtraRegionsInMeta(writer);
+    writer.println();
     usageFilesystem(writer);
     writer.println();
     usageFixMeta(writer);
@@ -477,7 +470,7 @@ public class HBCK2 extends Configured implements 
org.apache.hadoop.util.Tool {
     writer.println("   noop. Otherwise, if 'HBCK Report' UI reports problems, 
a run of");
     writer.println("   " + FIX_META +
         " will clear up hbase:meta issues. See 'HBase HBCK' UI");
-    writer.println("   for how to generate new report.");
+    writer.println("   for how to generate new execute.");
     writer.println("   SEE ALSO: " + REPORT_MISSING_REGIONS_IN_META);
   }
 
@@ -490,6 +483,33 @@ public class HBCK2 extends Configured implements 
org.apache.hadoop.util.Tool {
     writer.println("   purge if '--fix'.");
   }
 
+  private static void usageExtraRegionsInMeta(PrintWriter writer) {
+    writer.println(" " + EXTRA_REGIONS_IN_META + " <NAMESPACE|"
+      + "NAMESPACE:TABLENAME>...");
+    writer.println("   Options:");
+    writer.println("    -f, --fix    fix meta by removing all extra regions 
found.");
+    writer.println("   Reports regions present on hbase:meta, but with no 
related ");
+    writer.println("   directories on the file system. Needs hbase:meta to be 
online. ");
+    writer.println("   For each table name passed as parameter, performs 
diff");
+    writer.println("   between regions available in hbase:meta and region dirs 
on the given");
+    writer.println("   file system. Extra regions would get deleted from Meta 
");
+    writer.println("   if passed the --fix option. ");
+    writer.println("   NOTE: Before deciding on use the \"--fix\" option, it's 
worth check if");
+    writer.println("   reported extra regions are overlapping with existing 
valid regions.");
+    writer.println("   If so, then \"extraRegionsInMeta --fix\" is indeed the 
optimal solution. ");
+    writer.println("   Otherwise, \"assigns\" command is the simpler solution, 
as it recreates ");
+    writer.println("   regions dirs in the filesystem, if not existing.");
+    writer.println("   An example triggering extra regions report for tables 
'table_1'");
+    writer.println("   and 'table_2', under default namespace:");
+    writer.println("     $ HBCK2 " + EXTRA_REGIONS_IN_META +
+      " 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 " + EXTRA_REGIONS_IN_META + " default:table_1 
ns1");
+    writer.println("   Returns list of extra regions for each table passed as 
parameter, or");
+    writer.println("   for each table on namespaces specified as parameter.");
+  }
+
   private static void usageReportMissingRegionsInMeta(PrintWriter writer) {
     writer.println(" " + REPORT_MISSING_REGIONS_IN_META + " <NAMESPACE|"
         + "NAMESPACE:TABLENAME>...");
@@ -510,10 +530,10 @@ public class HBCK2 extends Configured implements 
org.apache.hadoop.util.Tool {
     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("   An example triggering missing regions execute 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("   An example triggering missing regions execute 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");
@@ -816,9 +836,19 @@ public class HBCK2 extends Configured implements 
org.apache.hadoop.util.Tool {
           showErrorMessage(command + " takes one or more table names.");
           return EXIT_FAILURE;
         }
-        Pair<List<String>, List<ExecutionException>> result =
+        List<Future<List<String>>> addedRegions =
           addMissingRegionsInMetaForTables(purgeFirst(commands));
-        
System.out.println(formatReAddedRegionsMessage(result.getFirst(),result.getSecond()));
+        List<String> regionNames = new ArrayList<>();
+        List<Exception> errors = new ArrayList<>();
+        for(Future<List<String>> f : addedRegions){
+          try {
+            regionNames.addAll(f.get());
+          } catch (InterruptedException | ExecutionException e) {
+            errors.add(e);
+          }
+        }
+        System.out.println(formatReAddedRegionsMessage(regionNames,
+          errors));
         break;
 
       case REPORT_MISSING_REGIONS_IN_META:
@@ -831,6 +861,16 @@ public class HBCK2 extends Configured implements 
org.apache.hadoop.util.Tool {
         }
         break;
 
+      case EXTRA_REGIONS_IN_META:
+        try {
+          Map<TableName,List<String>> report =
+            extraRegionsInMeta(purgeFirst(commands));
+          System.out.println(formatExtraRegionsReport(report));
+        } catch (Exception e) {
+          return EXIT_FAILURE;
+        }
+        break;
+
       default:
         showErrorMessage("Unsupported command: " + command);
         return EXIT_FAILURE;
@@ -843,22 +883,34 @@ public class HBCK2 extends Configured implements 
org.apache.hadoop.util.Tool {
   }
 
   private String formatMissingRegionsInMetaReport(Map<TableName,List<Path>> 
report) {
+    Function<Path,String> resolver = r -> r.getName();
+    String message = "Missing Regions for each table:\n\t";
+    return formatReportMessage(message, (HashMap)report, resolver);
+  }
+
+  private String formatExtraRegionsReport(Map<TableName,List<String>> report) {
+    String message = "Regions in Meta but having no equivalent dir, for each 
table:\n\t";
+    return formatReportMessage(message, (HashMap)report, s -> s);
+  }
+
+  private String formatReportMessage(String reportMessage, Map<TableName, 
List<?>> report,
+      Function resolver){
     final StringBuilder builder = new StringBuilder();
     if(report.size() < 1) {
-      builder.append("\nNo reports are found. You are likely passing 
non-existent " +
-              "namespace or table. Note that table names should include the 
namespace " +
-              "portion even for tables in the default namespace. See also the 
command usage.\n");
+      builder.append("\nNo reports were found. You are likely passing 
non-existent " +
+        "namespace or table. Note that table names should include the 
namespace " +
+        "portion even for tables in the default namespace. See also the 
command usage.\n");
       return builder.toString();
     }
-    builder.append("Missing Regions for each table:\n\t");
+    builder.append(reportMessage);
     report.keySet().forEach(table -> {
       builder.append(table);
       if (!report.get(table).isEmpty()){
         builder.append("->\n\t\t");
-        report.get(table).forEach(region -> builder.append(region.getName())
+        report.get(table).forEach(region -> 
builder.append(resolver.apply(region))
           .append(" "));
       } else {
-        builder.append(" -> No missing regions");
+        builder.append(" -> No mismatching regions. This table is good!");
       }
       builder.append("\n\t");
     });
@@ -866,7 +918,7 @@ public class HBCK2 extends Configured implements 
org.apache.hadoop.util.Tool {
   }
 
   private String formatReAddedRegionsMessage(List<String> readdedRegionNames,
-    List<ExecutionException> executionErrors) {
+    List<Exception> executionErrors) {
     final StringBuilder finalText = new StringBuilder();
     finalText.append("Regions re-added into Meta: 
").append(readdedRegionNames.size());
     if(!readdedRegionNames.isEmpty()){
@@ -886,6 +938,20 @@ public class HBCK2 extends Configured implements 
org.apache.hadoop.util.Tool {
     return finalText.toString();
   }
 
+  private String formatRemovedRegionsMessage(int totalRemoved,
+    List<Exception> executionErrors) {
+    final StringBuilder finalText = new StringBuilder();
+    finalText.append("Regions that had no dir on the FileSystem and got 
removed from Meta: ").
+      append(totalRemoved);
+    if(!executionErrors.isEmpty()){
+      finalText.append("\n")
+        .append("ERROR: \n\t")
+        .append("There were following errors on at least one table thread:\n");
+      executionErrors.forEach(e -> 
finalText.append(e.getMessage()).append("\n"));
+    }
+    return finalText.toString();
+  }
+
   private String buildHbck2AssignsCommand(List<String> regions) {
     final StringBuilder builder = new StringBuilder();
     builder.append("assigns ");
diff --git 
a/hbase-hbck2/src/test/java/org/apache/hbase/TestFsRegionsMetaRecoverer.java 
b/hbase-hbck2/src/test/java/org/apache/hbase/TestFsRegionsMetaRecoverer.java
index ee1c08c..551c4e9 100644
--- a/hbase-hbck2/src/test/java/org/apache/hbase/TestFsRegionsMetaRecoverer.java
+++ b/hbase-hbck2/src/test/java/org/apache/hbase/TestFsRegionsMetaRecoverer.java
@@ -18,6 +18,9 @@
 package org.apache.hbase;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.Mockito.when;
 
 import java.io.ByteArrayInputStream;
 import java.util.ArrayList;
@@ -36,6 +39,7 @@ import org.apache.hadoop.hbase.CellBuilderType;
 import org.apache.hadoop.hbase.HBaseConfiguration;
 import org.apache.hadoop.hbase.HConstants;
 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.Put;
 import org.apache.hadoop.hbase.client.RegionInfo;
@@ -67,8 +71,11 @@ public class TestFsRegionsMetaRecoverer {
     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);
+    when(this.mockedConnection.getConfiguration()).thenReturn(config);
+    
when(this.mockedConnection.getTable(TableName.META_TABLE_NAME)).thenReturn(mockedTable);
+    Admin mockedAdmin = Mockito.mock(Admin.class);
+    when(this.mockedConnection.getAdmin()).thenReturn(mockedAdmin);
+    
when(mockedAdmin.tableExists(TableName.valueOf("test-tbl"))).thenReturn(true);
     this.testTblDir = config.get(HConstants.HBASE_DIR) + 
"/data/default/test-tbl";
     this.fixer = new FsRegionsMetaRecoverer(config, mockedConnection, 
mockedFileSystem);
   }
@@ -108,17 +115,7 @@ public class TestFsRegionsMetaRecoverer {
 
   @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<Cell> cells = new ArrayList<>();
-    cells.add(createCellForRegionInfo(info));
-    Result result = Result.create(cells);
-    Mockito.when(mockedRS.next()).thenReturn(result,(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});
+    createRegionInMetaAndFileSystem();
     assertEquals("Should had returned 0 missing regions",
       0, fixer.findMissingRegionsInMETA("test-tbl").size());
   }
@@ -126,13 +123,13 @@ public class TestFsRegionsMetaRecoverer {
   @Test
   public void testFindMissingRegionsInMETAOneMissing() throws  Exception {
     ResultScanner mockedRS = Mockito.mock(ResultScanner.class);
-    
Mockito.when(this.mockedTable.getScanner(Mockito.any(Scan.class))).thenReturn(mockedRS);
+    when(this.mockedTable.getScanner(any(Scan.class))).thenReturn(mockedRS);
     List<Cell> cells = new ArrayList<>();
     Result result = Result.create(cells);
-    Mockito.when(mockedRS.next()).thenReturn(result,(Result)null);
+    when(mockedRS.next()).thenReturn(result,(Result)null);
     Path p = new Path(this.testTblDir+ "/182182182121");
     FileStatus status = new FileStatus(0, true, 0, 0,0, p);
-    Mockito.when(mockedFileSystem.listStatus(new Path(this.testTblDir)))
+    when(mockedFileSystem.listStatus(new Path(this.testTblDir)))
       .thenReturn(new FileStatus[]{status});
     List<Path> missingRegions = fixer.findMissingRegionsInMETA("test-tbl");
     assertEquals("Should had returned 1 missing region",
@@ -145,33 +142,97 @@ public class TestFsRegionsMetaRecoverer {
     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")))
+    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));
+    Mockito.verify(this.mockedTable).put(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);
+    when(this.mockedTable.getScanner(any(Scan.class))).thenReturn(mockedRS);
     List<Cell> cells = new ArrayList<>();
     cells.add(createCellForTableState(TableName.valueOf("test-tbl")));
     Result result = Result.create(cells);
-    Mockito.when(mockedRS.next()).thenReturn(result,(Result)null);
+    when(mockedRS.next()).thenReturn(result,(Result)null);
     FileStatus status = new FileStatus();
     Path p = new Path(this.testTblDir+ "/182182182121");
     status.setPath(p);
-    Mockito.when(mockedFileSystem.listStatus(new Path(this.testTblDir)))
+    when(mockedFileSystem.listStatus(new Path(this.testTblDir)))
       .thenReturn(new FileStatus[]{status});
-    Mockito.when(this.mockedConnection.getTable(TableName.META_TABLE_NAME))
-      .thenReturn(this.mockedTable);
     Map<TableName, List<Path>> report = fixer.reportTablesMissingRegions(null);
     assertEquals("Should had returned 1 missing region",
       1,report.size());
   }
 
+  @Test
+  public void testFindExtraRegionsInMETANoExtra() throws  Exception {
+    createRegionInMetaAndFileSystem();
+    assertEquals("Should had returned 0 extra regions",
+      0, fixer.findExtraRegionsInMETA("test-tbl").size());
+  }
+
+  private void createRegionInMetaAndFileSystem() throws Exception{
+    RegionInfo info = createRegionInMeta(Mockito.mock(ResultScanner.class));
+    FileStatus status = new FileStatus(0, true, 1,128,
+      System.currentTimeMillis(), new Path(this.testTblDir + "/" + 
info.getEncodedName()));
+    when(mockedFileSystem.listStatus(new Path(this.testTblDir)))
+      .thenReturn(new FileStatus[]{status});
+  }
+
+  private RegionInfo createRegionInMeta(ResultScanner mockedRS) throws 
Exception {
+    when(this.mockedTable.getScanner(any(Scan.class))).thenReturn(mockedRS);
+    RegionInfo info = createRegionInfo("test-tbl");
+    List<Cell> cells = new ArrayList<>();
+    cells.add(createCellForRegionInfo(info));
+    Result result = Result.create(cells);
+    when(mockedRS.next()).thenReturn(result,(Result)null);
+    return info;
+  }
+
+  @Test
+  public void testFindExtraRegionsInMETAOneExtra() throws  Exception {
+    RegionInfo info = createRegionInMeta(Mockito.mock(ResultScanner.class));
+    List<RegionInfo> missingRegions = fixer.findExtraRegionsInMETA("test-tbl");
+    assertEquals("Should had returned 1 extra region",
+      1, missingRegions.size());
+    assertEquals(info.getEncodedName(),missingRegions.get(0).getEncodedName());
+  }
+
+  @Test
+  public void testRemoveRegionInfoFromMeta() throws Exception {
+    ResultScanner mockedRsTables = Mockito.mock(ResultScanner.class);
+    List<Cell> cells = new ArrayList<>();
+    cells.add(createCellForTableState(TableName.valueOf("test-tbl")));
+    Result result = Result.create(cells);
+    ResultScanner mockedRsRegions = Mockito.mock(ResultScanner.class);
+    createRegionInMeta(mockedRsRegions);
+    when(this.mockedTable.getScanner(any(Scan.class))).
+      thenReturn(mockedRsTables).
+        thenReturn(mockedRsRegions);
+    when(mockedRsTables.next()).thenReturn(result,(Result)null);
+    List<String> tableList = new ArrayList<>();
+    tableList.add("default:test-tbl");
+    fixer.removeExtraRegionsFromMetaForTables(tableList);
+    Mockito.verify(this.mockedTable).delete(anyList());
+  }
+
+
+  @Test
+  public void testReportTablesExtraRegionsOneExtra() throws  Exception {
+    ResultScanner mockedRS = Mockito.mock(ResultScanner.class);
+    
Mockito.when(this.mockedTable.getScanner(Mockito.any(Scan.class))).thenReturn(mockedRS);
+    List<Cell> cells = new ArrayList<>();
+    cells.add(createCellForTableState(TableName.valueOf("test-tbl")));
+    Result result = Result.create(cells);
+    Mockito.when(mockedRS.next()).thenReturn(result,(Result)null);
+    Map<TableName, List<RegionInfo>> report = 
fixer.reportTablesExtraRegions(null);
+    assertEquals("Should had returned 1 extra region.",
+      1,report.size());
+  }
+
   private static final class TestInputStreamSeekable extends FSInputStream {
     private ByteArrayInputStream in;
     private long length;
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 7b9bf2a..a095040 100644
--- a/hbase-hbck2/src/test/java/org/apache/hbase/TestHBCK2.java
+++ b/hbase-hbck2/src/test/java/org/apache/hbase/TestHBCK2.java
@@ -18,6 +18,7 @@
 package org.apache.hbase;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -27,6 +28,8 @@ import java.io.PrintStream;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
 import java.util.stream.Collectors;
 
 import org.apache.hadoop.fs.Path;
@@ -45,8 +48,10 @@ import org.apache.hadoop.hbase.client.Table;
 import org.apache.hadoop.hbase.client.TableState;
 import org.apache.hadoop.hbase.master.RegionState;
 import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.hadoop.hbase.util.FSUtils;
 import org.apache.hadoop.hbase.util.Threads;
 
+import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -78,7 +83,6 @@ public class TestHBCK2 {
   @BeforeClass
   public static void beforeClass() throws Exception {
     TEST_UTIL.startMiniCluster(3);
-    TEST_UTIL.createMultiRegionTable(TABLE_NAME, Bytes.toBytes("family1"), 5);
   }
 
   @AfterClass
@@ -87,8 +91,14 @@ public class TestHBCK2 {
   }
 
   @Before
-  public void before() {
+  public void before() throws Exception {
     this.hbck2 = new HBCK2(TEST_UTIL.getConfiguration());
+    TEST_UTIL.createMultiRegionTable(TABLE_NAME, Bytes.toBytes("family1"), 5);
+  }
+
+  @After
+  public void after() throws Exception {
+    TEST_UTIL.deleteTable(TABLE_NAME);
   }
 
   @Test (expected = UnsupportedOperationException.class)
@@ -229,10 +239,13 @@ public class TestHBCK2 {
 
   @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";
+    String expectedResult = "Missing Regions for each table:\n";
     String result = testFormatMissingRegionsInMetaReport();
     assertTrue(result.contains(expectedResult));
+    expectedResult = "\thbase:namespace -> No mismatching regions. This table 
is good!\n\t";
+    assertTrue(result.contains(expectedResult));
+    expectedResult = "TestHBCK2 -> No mismatching regions. This table is 
good!\n\t";
+    assertTrue(result.contains(expectedResult));
   }
 
   @Test
@@ -250,7 +263,7 @@ public class TestHBCK2 {
       + regions.get(0).getEncodedName();
     assertTrue(result.contains(expectedResult));
     //validates namespace region is not reported missing
-    expectedResult = "\n\thbase:namespace -> No missing regions\n\t";
+    expectedResult = "\n\thbase:namespace -> No mismatching regions. This 
table is good!\n\t";
     assertTrue(result.contains(expectedResult));
   }
 
@@ -288,8 +301,18 @@ public class TestHBCK2 {
     int remaining = totalRegions - missingRegions;
     assertEquals("Table should have " + remaining + " regions in META.", 
remaining,
       MetaTableAccessor.getRegionCount(TEST_UTIL.getConnection(), tableName));
-    
assertEquals(missingRegions,hbck.addMissingRegionsInMetaForTables("default:"
-      + tableName.getNameAsString()).getFirst().size());
+    List<Future<List<String>>> result = 
hbck.addMissingRegionsInMetaForTables("default:" +
+      tableName.getNameAsString());
+
+    Integer total = result.stream().map(f -> {
+      try {
+        return f.get().size();
+      } catch (InterruptedException | ExecutionException e) {
+        e.printStackTrace();
+      }
+      return 0;
+    }).reduce(0, Integer::sum);
+    assertEquals(missingRegions, total.intValue());
     assertEquals("Table regions should had been re-added in META.", 
totalRegions,
       MetaTableAccessor.getRegionCount(TEST_UTIL.getConnection(), tableName));
     //compare the added regions to make sure those are the same
@@ -349,4 +372,194 @@ public class TestHBCK2 {
       fail(e.getMessage());
     }
   }
+
+  private void deleteRegionDir(TableName tableName, String regionEncodedName) {
+    try {
+      Path tableDir = 
FSUtils.getTableDir(this.TEST_UTIL.getDataTestDirOnTestFS(), tableName);
+      Path regionPath = new Path(tableDir, regionEncodedName);
+      this.TEST_UTIL.getTestFileSystem().delete(regionPath, true);
+    } catch (IOException e) {
+      fail(e.getMessage());
+    }
+  }
+
+  @Test
+  public void testRemoveExtraRegionsInMetaTwoExtras() throws Exception {
+    this.testRemoveExtraRegionsInMetaForTables(2,5);
+  }
+
+  @Test
+  public void testReportExtraRegionsInMetaAllNsTbls() throws Exception {
+    String[] nullArgs = null;
+    this.testReportExtraRegionsInMeta(5, 5,
+      nullArgs);
+  }
+
+  @Test
+  public void testReportExtraRegionsInMetaSpecificTbl() throws Exception {
+    this.testReportExtraRegionsInMeta(5, 5,
+      TABLE_NAME.getNameWithNamespaceInclAsString());
+  }
+
+  @Test
+  public void testReportExtraRegionsInMetaSpecificTblAndNsTbl() throws 
Exception {
+    this.testReportExtraRegionsInMeta(5, 5,
+      TABLE_NAME.getNameWithNamespaceInclAsString(), "hbase:namespace");
+  }
+
+  @Test
+  public void testReportExtraRegionsInMetaSpecificTblAndNsTblAlsoExtra() 
throws Exception {
+    TableName tableName = createTestTable(5);
+    List<RegionInfo> regions = MetaTableAccessor
+      .getTableRegions(TEST_UTIL.getConnection(), tableName);
+    deleteRegionDir(tableName, regions.get(0).getEncodedName());
+    this.testReportExtraRegionsInMeta(5, 6,
+      TABLE_NAME.getNameWithNamespaceInclAsString(),
+      tableName.getNameWithNamespaceInclAsString());
+  }
+
+  @Test
+  public void testFormatReportExtraRegionsInMetaNoExtra() throws IOException {
+    String expectedResult = "Regions in Meta but having no equivalent dir, for 
each table:\n";
+    String result = testFormatExtraRegionsInMetaReport();
+    assertTrue(result.contains(expectedResult));
+    expectedResult = "\thbase:namespace -> No mismatching regions. This table 
is good!\n\t";
+    assertTrue(result.contains(expectedResult));
+    expectedResult = "TestHBCK2 -> No mismatching regions. This table is 
good!\n\t";
+    assertTrue(result.contains(expectedResult));
+  }
+
+  @Test
+  public void testFormatReportExtraInMetaOneExtra() throws IOException {
+    TableName tableName = createTestTable(5);
+    List<RegionInfo> regions = MetaTableAccessor
+      .getTableRegions(TEST_UTIL.getConnection(), tableName);
+    deleteRegionDir(tableName, regions.get(0).getEncodedName());
+    String expectedResult = "Regions in Meta but having no equivalent dir, for 
each table:\n";
+    String result = testFormatExtraRegionsInMetaReport();
+    //validates initial report message
+    assertTrue(result.contains(expectedResult));
+    //validates our test table region is reported as extra
+    expectedResult = "\t" + tableName.getNameAsString() + "->\n\t\t"
+      + regions.get(0).getEncodedName();
+    assertTrue(result.contains(expectedResult));
+    //validates namespace region is not reported missing
+    expectedResult = "\n\thbase:namespace -> No mismatching regions. This 
table is good!\n\t";
+    assertTrue(result.contains(expectedResult));
+  }
+
+  @Test
+  public void testFormatFixExtraRegionsInMetaNoExtra() throws IOException {
+    String expectedResult = "Regions in Meta but having no equivalent dir, for 
each table:\n";
+    String result = testFormatExtraRegionsInMetaFix(null);
+    assertTrue(result.contains(expectedResult));
+    expectedResult = "\thbase:namespace -> No mismatching regions. This table 
is good!\n\t";
+    assertTrue(result.contains(expectedResult));
+    expectedResult = "TestHBCK2 -> No mismatching regions. This table is 
good!\n\t";
+    assertTrue(result.contains(expectedResult));
+  }
+
+  @Test
+  public void testFormatFixExtraRegionsInMetaNoExtraSpecifyTable() throws 
IOException {
+    final String expectedResult = "Regions in Meta but having no equivalent 
dir, for each table:\n"
+      + "\thbase:namespace -> No mismatching regions. This table is good!\n\t";
+    String result = testFormatExtraRegionsInMetaFix("hbase:namespace");
+    assertTrue(result.contains(expectedResult));
+  }
+
+  @Test
+  public void testFormatFixExtraInMetaOneExtra() throws IOException {
+    TableName tableName = createTestTable(5);
+    List<RegionInfo> regions = MetaTableAccessor
+      .getTableRegions(TEST_UTIL.getConnection(), tableName);
+    deleteRegionDir(tableName, regions.get(0).getEncodedName());
+    String expectedResult = "Regions in Meta but having no equivalent dir, for 
each table:\n";
+    String result = testFormatExtraRegionsInMetaFix(null);
+    //validates initial execute message
+    assertTrue(result.contains(expectedResult));
+    //validates our test table region is reported as extra
+    expectedResult = "\t" + tableName.getNameAsString() + "->\n\t\t"
+      + regions.get(0).getEncodedName();
+    assertTrue(result.contains(expectedResult));
+    //validates namespace region is not reported missing
+    expectedResult = "\n\thbase:namespace -> No mismatching regions. This 
table is good!\n\t";
+    assertTrue(result.contains(expectedResult));
+  }
+
+  @Test
+  public void testFormatFixExtraInMetaOneExtraSpecificTable() throws 
IOException {
+    TableName tableName = createTestTable(5);
+    List<RegionInfo> regions = MetaTableAccessor
+      .getTableRegions(TEST_UTIL.getConnection(), tableName);
+    deleteRegionDir(tableName, regions.get(0).getEncodedName());
+    String expectedResult = "Regions in Meta but having no equivalent dir, for 
each table:\n";
+    String result = 
testFormatExtraRegionsInMetaFix(tableName.getNameWithNamespaceInclAsString());
+    //validates initial execute message
+    assertTrue(result.contains(expectedResult));
+    //validates our test table region is reported as extra
+    expectedResult = "\t" + tableName.getNameAsString() + "->\n\t\t"
+      + regions.get(0).getEncodedName();
+    assertTrue(result.contains(expectedResult));
+    //validates namespace region is not reported missing
+    expectedResult = "\n\thbase:namespace -> No mismatching regions. This 
table is good!\n\t";
+    assertFalse("Should not contain: " + expectedResult, 
result.contains(expectedResult));
+  }
+
+  private String testFormatExtraRegionsInMetaReport() throws IOException {
+    return testFormatExtraRegionsInMeta(new 
String[]{HBCK2.EXTRA_REGIONS_IN_META });
+  }
+
+  private String testFormatExtraRegionsInMetaFix(String table) throws 
IOException {
+    if(table!=null) {
+      return testFormatExtraRegionsInMeta(new String[] 
{HBCK2.EXTRA_REGIONS_IN_META, "-f", table});
+    } else {
+      return testFormatExtraRegionsInMeta(new String[] 
{HBCK2.EXTRA_REGIONS_IN_META, "-f"});
+    }
+  }
+
+  private String testFormatExtraRegionsInMeta(String[] args) 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(args);
+    System.setOut(originalOS);
+    return builder.toString();
+  }
+
+  private void testRemoveExtraRegionsInMetaForTables(int extraRegions, int 
totalRegions)
+    throws Exception {
+    TableName tableName = createTestTable(totalRegions);
+    HBCK2 hbck = new HBCK2(TEST_UTIL.getConfiguration());
+    List<RegionInfo> regions = MetaTableAccessor
+      .getTableRegions(TEST_UTIL.getConnection(), tableName);
+    regions.subList(0, extraRegions).forEach(r -> deleteRegionDir(tableName, 
r.getEncodedName()));
+    int remaining = totalRegions - extraRegions;
+    assertEquals(extraRegions, hbck.extraRegionsInMeta(new String[]
+      { "-f",
+        "default:" + tableName.getNameAsString()
+      }).get(tableName).size());
+    assertEquals("Table regions should had been removed from META.", remaining,
+      MetaTableAccessor.getRegionCount(TEST_UTIL.getConnection(), tableName));
+  }
+
+  private void testReportExtraRegionsInMeta(int extraRegionsInTestTbl,
+    int expectedTotalExtraRegions, String... namespaceOrTable) throws 
Exception {
+    List<RegionInfo> regions = MetaTableAccessor
+      .getTableRegions(TEST_UTIL.getConnection(), TABLE_NAME);
+    regions.subList(0, extraRegionsInTestTbl).forEach(r -> 
deleteRegionDir(TABLE_NAME,
+      r.getEncodedName()));
+    HBCK2 hbck = new HBCK2(TEST_UTIL.getConfiguration());
+    final Map<TableName,List<String>> report =
+      hbck.extraRegionsInMeta(namespaceOrTable);
+    long resultingExtraRegions = report.keySet().stream().mapToLong(nsTbl ->
+      report.get(nsTbl).size()).sum();
+    assertEquals(expectedTotalExtraRegions, resultingExtraRegions);
+  }
+
 }

Reply via email to