blob: ca1597d1eb6cf0d12923ad08fc34c7218914a8c5 [file] [log] [blame]
/*
* 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;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.LineIterator;
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;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.ServerName;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.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;
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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.hbase.thirdparty.org.apache.commons.cli.CommandLine;
import org.apache.hbase.thirdparty.org.apache.commons.cli.CommandLineParser;
import org.apache.hbase.thirdparty.org.apache.commons.cli.DefaultParser;
import org.apache.hbase.thirdparty.org.apache.commons.cli.HelpFormatter;
import org.apache.hbase.thirdparty.org.apache.commons.cli.Option;
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.
*/
// TODO:
// + On assign, can we look to see if existing assign and if so fail until cancelled?
// + Doc how we just take pointer to zk ensemble... If want to do more exotic config. on client,
// then add a hbase-site.xml onto CLASSPATH for this tool to pick up.
// + Add --version
public class HBCK2 extends Configured implements org.apache.hadoop.util.Tool {
private static final Logger LOG = LoggerFactory.getLogger(HBCK2.class);
private static final int EXIT_SUCCESS = 0;
static final int EXIT_FAILURE = 1;
/** The delimiter for meta columns for replicaIds > 0 */
private static final char META_REPLICA_ID_DELIMITER = '_';
// Commands
private static final String SET_TABLE_STATE = "setTableState";
private static final String ASSIGNS = "assigns";
private static final String UNASSIGNS = "unassigns";
private static final String BYPASS = "bypass";
private static final String FILESYSTEM = "filesystem";
private static final String REPLICATION = "replication";
private static final String VERSION = "version";
private static final String SET_REGION_STATE = "setRegionState";
private static final String SCHEDULE_RECOVERIES = "scheduleRecoveries";
private static final String RECOVER_UNKNOWN = "recoverUnknown";
private static final String GENERATE_TABLE_INFO = "generateMissingTableDescriptorFile";
private static final String FIX_META = "fixMeta";
private static final String REGIONINFO_MISMATCH = "regionInfoMismatch";
// TODO update this map in case of the name of a method changes in Hbck interface
// in org.apache.hadoop.hbase.client package. Or a new command is added and the hbck command
// does not equals to the method name in Hbck interface.
private static final Map<String, List<String> > FUNCTION_NAME_MAP =
Collections.unmodifiableMap(new HashMap<String, List<String>>() {{
put(SET_TABLE_STATE, Arrays.asList("setTableStateInMeta"));
put(BYPASS, Arrays.asList("bypassProcedure"));
put(SCHEDULE_RECOVERIES, Arrays.asList("scheduleServerCrashProcedure",
"scheduleServerCrashProcedures"));
put(RECOVER_UNKNOWN, Arrays.asList("scheduleSCPsForUnknownServers"));
}});
private static final String ADD_MISSING_REGIONS_IN_META_FOR_TABLES =
"addFsRegionsMissingInMeta";
private static final String REPORT_MISSING_REGIONS_IN_META = "reportMissingRegionsInMeta";
private 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;
/**
* Wait 1ms on lock by default.
*/
private static final long DEFAULT_LOCK_WAIT = 1;
/**
* Check for HBCK support.
* Expects created connection.
* @param supportedVersions list of zero or more supported versions.
*/
void checkHBCKSupport(ClusterConnection connection, String cmd, String ... supportedVersions)
throws IOException {
if (skipCheck) {
LOG.info("Skipped {} command version check; 'skip' set", cmd);
return;
}
try (Admin admin = connection.getAdmin()) {
String serverVersion = admin.
getClusterMetrics(EnumSet.of(ClusterMetrics.Option.HBASE_VERSION)).getHBaseVersion();
String [] thresholdVersions = supportedVersions == null || supportedVersions.length == 0?
MINIMUM_HBCK2_VERSION: supportedVersions;
boolean supported = Version.check(serverVersion, thresholdVersions);
if (!supported) {
throw new UnsupportedOperationException(cmd + " not supported on server version=" +
serverVersion + "; needs at least a server that matches or exceeds " +
Arrays.toString(thresholdVersions));
}
}
}
void checkFunctionSupported(ClusterConnection connection, String cmd) throws IOException {
if (skipCheck) {
LOG.info("Skipped {} command version check; 'skip' set", cmd);
return;
}
List<Method> methods = Arrays.asList(connection.getHbck().getClass().getDeclaredMethods());
List<String> finalCmds = FUNCTION_NAME_MAP.getOrDefault(cmd, Collections.singletonList(cmd));
boolean supported = methods.stream().anyMatch(method -> finalCmds.contains(method.getName()));
if (!supported) {
throw new UnsupportedOperationException("This HBase cluster does not support command: "
+ cmd);
}
}
public static byte[] getRegionStateColumn(int replicaId) {
try {
return replicaId == 0 ? HConstants.STATE_QUALIFIER
: (HConstants.STATE_QUALIFIER_STR + META_REPLICA_ID_DELIMITER
+ String.format(RegionInfo.REPLICA_ID_FORMAT,
replicaId)).getBytes(StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
// should never happen!
throw new IllegalArgumentException("UTF8 decoding is not supported", e);
}
}
void setTableState(Hbck hbck, String[] args) throws IOException {
Options options = new Options();
Option inputFile = Option.builder("i").longOpt("inputFiles").build();
options.addOption(inputFile);
CommandLine commandLine = getCommandLine(args, options);
if (commandLine == null) {
return;
}
String[] argList = commandLine.getArgs();
if(!commandLine.hasOption(inputFile.getOpt())) {
System.out.println(setTableStateByArgs(hbck, argList));
} else {
List<String> inputList = getFromArgsOrFiles(stringArrayToList(argList), true);
for (String line : inputList) {
String[] params = line.split("\\s+");
System.out.println(setTableStateByArgs(hbck, params));
}
}
}
TableState setTableStateByArgs(Hbck hbck, String[] args) throws IOException {
if (args == null || args.length < 2) {
showErrorMessage(SET_TABLE_STATE +
" takes tablename and state arguments: e.g. user ENABLED, you entered: " +
Arrays.toString(args));
return null;
}
return setTableState(hbck, TableName.valueOf(args[0]),
TableState.State.valueOf(args[1]));
}
TableState setTableState(Hbck hbck, TableName tableName, TableState.State state)
throws IOException {
return hbck.setTableStateInMeta(new TableState(tableName, state));
}
int setRegionState(ClusterConnection connection, String region,
RegionState.State newState)
throws IOException {
return setRegionState(connection, region, 0, newState);
}
int setRegionState(ClusterConnection connection, String[] args) throws IOException {
Options options = new Options();
Option inputFile = Option.builder("i").longOpt("inputFiles").build();
options.addOption(inputFile);
CommandLine commandLine = getCommandLine(args, options);
if (commandLine == null) {
return EXIT_FAILURE;
}
String[] argList = commandLine.getArgs();
if (argList == null) {
return EXIT_FAILURE;
}
if(!commandLine.hasOption(inputFile.getOpt())) {
String[] params = formatSetRegionStateCommand(argList);
return setRegionStateByArgs(connection, params);
} else {
List<String> inputList = getFromArgsOrFiles(stringArrayToList(argList), true);
for (String line : inputList) {
String[] params = formatSetRegionStateCommand(line.split("\\s+"));
if (setRegionStateByArgs(connection, params) == EXIT_FAILURE) {
showErrorMessage("setRegionState failed to set " + Arrays.toString(args));
}
}
return EXIT_SUCCESS;
}
}
int setRegionStateByArgs(ClusterConnection connection, String[] args) throws IOException {
if (args == null || args.length < 3) {
return EXIT_FAILURE;
}
RegionState.State state = RegionState.State.valueOf(args[2]);
int replicaId = Integer.parseInt(args[1]);
return setRegionState(connection, args[0], replicaId, state);
}
int setRegionState(ClusterConnection connection, String region, int replicaId,
RegionState.State newState)
throws IOException {
if (newState == null) {
throw new IllegalArgumentException("State can't be null.");
}
RegionState.State currentState = null;
Table table = connection.getTable(TableName.valueOf("hbase:meta"));
RowFilter filter = new RowFilter(CompareOperator.EQUAL, new SubstringComparator(region));
Scan scan = new Scan();
scan.setFilter(filter);
Result result = table.getScanner(scan).next();
if (result != null) {
byte[] currentStateValue = result.getValue(HConstants.CATALOG_FAMILY,
getRegionStateColumn(replicaId));
if (currentStateValue == null) {
System.out.println("WARN: Region state info on meta was NULL");
} else {
currentState = RegionState.State.valueOf(
org.apache.hadoop.hbase.util.Bytes.toString(currentStateValue));
}
Put put = new Put(result.getRow());
put.addColumn(HConstants.CATALOG_FAMILY, getRegionStateColumn(replicaId),
org.apache.hadoop.hbase.util.Bytes.toBytes(newState.name()));
table.put(put);
if (replicaId == 0) {
System.out.println("Changed region " + region + " STATE from "
+ currentState + " to " + newState);
} else {
System.out.println("Changed STATE for replica reigon " + replicaId +
" of primary region " + region +
"from " + currentState + " to " + newState);
}
return EXIT_SUCCESS;
} else {
System.out.println("ERROR: Could not find region " + region + " in meta.");
}
return EXIT_FAILURE;
}
Map<TableName,List<Path>> reportTablesWithMissingRegionsInMeta(String... args)
throws IOException {
Map<TableName,List<Path>> report;
try (final FsRegionsMetaRecoverer fsRegionsMetaRecoverer =
new FsRegionsMetaRecoverer(this.conf)) {
report = fsRegionsMetaRecoverer.reportTablesMissingRegions(
(getInputList(args)));
} catch (IOException e) {
LOG.error("Error reporting missing regions: ", e);
throw e;
}
if(LOG.isDebugEnabled()) {
LOG.debug(formatMissingRegionsInMetaReport(report));
}
return report;
}
Map<TableName, List<String>> extraRegionsInMeta(String[] args)
throws Exception {
Options options = new Options();
Option fixOption = Option.builder("f").longOpt("fix").build();
options.addOption(fixOption);
Option inputFile = Option.builder("i").longOpt("inputFiles").build();
options.addOption(inputFile);
Map<TableName, List<String>> result = new HashMap<>();
// Parse command-line.
CommandLine commandLine = getCommandLine(args, options);
if (commandLine == null) {
return result;
}
boolean fix = commandLine.hasOption(fixOption.getOpt());
boolean inputFileFlag = commandLine.hasOption(inputFile.getOpt());
try (final FsRegionsMetaRecoverer fsRegionsMetaRecoverer =
new FsRegionsMetaRecoverer(this.conf)) {
List<String> namespacesTables =
getFromArgsOrFiles(commandLine.getArgList(), inputFileFlag);
Map<TableName, List<HBCKMetaEntry>> reportMap =
fsRegionsMetaRecoverer.reportTablesExtraRegions(namespacesTables);
final List<String> toFix = new ArrayList<>();
reportMap.entrySet().forEach(e -> {
result.put(e.getKey(),
e.getValue().stream().map(r->r.getEncodedRegionName()).collect(Collectors.toList()));
if(fix && e.getValue().size()>0){
toFix.add(e.getKey().getNameWithNamespaceInclAsString());
}
});
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 e) {
LOG.error("Error on checking extra regions: ", e);
throw e;
}
if(LOG.isDebugEnabled()) {
LOG.debug(formatExtraRegionsReport(result));
}
return result;
}
List<Future<List<String>>> addMissingRegionsInMetaForTables(String...
nameSpaceOrTable) throws IOException {
try (final FsRegionsMetaRecoverer fsRegionsMetaRecoverer =
new FsRegionsMetaRecoverer(this.conf)) {
return fsRegionsMetaRecoverer.addMissingRegionsInMetaForTables(
getInputList(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();
Option inputFile = Option.builder("i").longOpt("inputFiles").build();
options.addOption(override);
options.addOption(inputFile);
// Parse command-line.
CommandLine commandLine = getCommandLine(args, options);
if (commandLine == null) {
return null;
}
boolean overrideFlag = commandLine.hasOption(override.getOpt());
boolean inputFileFlag = commandLine.hasOption(inputFile.getOpt());
List<String> argList = commandLine.getArgList();
return hbck.assigns(getFromArgsOrFiles(argList, inputFileFlag),overrideFlag);
}
List<Long> unassigns(Hbck hbck, String [] args) throws IOException {
Options options = new Options();
Option override = Option.builder("o").longOpt("override").build();
Option inputFile = Option.builder("i").longOpt("inputFiles").build();
options.addOption(override);
options.addOption(inputFile);
// Parse command-line.
CommandLine commandLine = getCommandLine(args, options);
if (commandLine == null) {
return null;
}
boolean overrideFlag = commandLine.hasOption(override.getOpt());
boolean inputFileFlag = commandLine.hasOption(inputFile.getOpt());
List<String> argList = commandLine.getArgList();
return hbck.unassigns(getFromArgsOrFiles(argList, inputFileFlag), overrideFlag);
}
/**
* @return List of results OR null if failed to run.
*/
List<Boolean> bypass(String[] args) throws IOException {
// Bypass has two options....
Options options = new Options();
// See usage for 'help' on these options.
Option override = Option.builder("o").longOpt("override").build();
options.addOption(override);
Option recursive = Option.builder("r").longOpt("recursive").build();
options.addOption(recursive);
Option wait = Option.builder("w").longOpt("lockWait").hasArg().type(Integer.class).build();
options.addOption(wait);
Option inputFile = Option.builder("i").longOpt("inputFiles").build();
options.addOption(inputFile);
// Parse command-line.
CommandLine commandLine = getCommandLine(args, options);
if (commandLine == null) {
return null;
}
long lockWait = DEFAULT_LOCK_WAIT;
if (commandLine.hasOption(wait.getOpt())) {
lockWait = Integer.parseInt(commandLine.getOptionValue(wait.getOpt()));
}
boolean overrideFlag = commandLine.hasOption(override.getOpt());
boolean recursiveFlag = commandLine.hasOption(recursive.getOpt());
boolean inputFileFlag = commandLine.hasOption(inputFile.getOpt());
String[] pidStrs = getFromArgsOrFiles(commandLine.getArgList(), inputFileFlag)
.toArray(new String[0]);
if (pidStrs == null || pidStrs.length <= 0) {
showErrorMessage("No pids supplied.");
return null;
}
List<Long> pids = Arrays.stream(pidStrs).map(Long::valueOf).collect(Collectors.toList());
try (ClusterConnection connection = connect(); Hbck hbck = connection.getHbck()) {
checkFunctionSupported(connection, BYPASS);
return hbck.bypassProcedure(pids, lockWait, overrideFlag, recursiveFlag);
}
}
List<Long> scheduleRecoveries(Hbck hbck, String[] args) throws IOException {
List<HBaseProtos.ServerName> serverNames = new ArrayList<>();
List<String> inputList = getInputList(args);
if (inputList != null) {
for (String serverName : inputList) {
serverNames.add(parseServerName(serverName));
}
}
return hbck.scheduleServerCrashProcedure(serverNames);
}
List<Long> recoverUnknown(Hbck hbck) throws IOException {
return hbck.scheduleSCPsForUnknownServers();
}
/**
* Runs the RegionInfoMismatchTool using CLI options.
*/
void regionInfoMismatch(String[] args) throws Exception {
// CLI Options
Options options = new Options();
Option dryRunOption = Option.builder("f").longOpt("fix").hasArg(false).build();
options.addOption(dryRunOption);
// Parse command-line.
CommandLineParser parser = new DefaultParser();
CommandLine commandLine = parser.parse(options, args, false);
final boolean fix = commandLine.hasOption(dryRunOption.getOpt());
try (ClusterConnection connection = connect()) {
new RegionInfoMismatchTool(connection).run(fix);
}
}
private HBaseProtos.ServerName parseServerName(String serverName) {
ServerName sn = ServerName.parseServerName(serverName);
return HBaseProtos.ServerName.newBuilder().setHostName(sn.getHostname()).
setPort(sn.getPort()).setStartCode(sn.getStartcode()).build();
}
/**
* Read property from hbck2.properties file.
*/
private String readHBCK2BuildProperties(final String propertyKey) throws IOException {
ClassLoader classLoader = getClass().getClassLoader();
InputStream inputStream = classLoader.getResourceAsStream("hbck2.properties");
final Properties properties = new Properties();
properties.load(inputStream);
return properties.getProperty(propertyKey);
}
private static String getCommandUsage() {
// NOTE: List commands below alphabetically!
StringWriter sw = new StringWriter();
PrintWriter writer = new PrintWriter(sw);
writer.println("Command:");
usageAddFsRegionsMissingInMeta(writer);
writer.println();
usageAssigns(writer);
writer.println();
usageBypass(writer);
writer.println();
usageExtraRegionsInMeta(writer);
writer.println();
usageFilesystem(writer);
writer.println();
usageFixMeta(writer);
writer.println();
usageGenerateMissingTableInfo(writer);
writer.println();
usageReplication(writer);
writer.println();
usageReportMissingRegionsInMeta(writer);
writer.println();
usageSetRegionState(writer);
writer.println();
usageSetTableState(writer);
writer.println();
usageScheduleRecoveries(writer);
writer.println();
usageRecoverUnknown(writer);
writer.println();
usageUnassigns(writer);
writer.println();
usageRegioninfoMismatch(writer);
writer.println();
writer.close();
return sw.toString();
}
private static void usageAddFsRegionsMissingInMeta(PrintWriter writer) {
writer.println(" " + ADD_MISSING_REGIONS_IN_META_FOR_TABLES + " [<NAMESPACE|"
+ "NAMESPACE:TABLENAME>...|-i <INPUTFILES>...]");
writer.println(" Options:");
writer.println(" -i,--inputFiles take one or more files of namespace or table names");
writer.println(" To be used when regions missing from hbase:meta but directories");
writer.println(" are present still in HDFS. Can happen if user has run _hbck1_");
writer.println(" 'OfflineMetaRepair' against an hbase-2.x cluster. Needs hbase:meta");
writer.println(" to be online. For each table name passed as parameter, performs diff");
writer.println(" between regions available in hbase:meta and region dirs on HDFS.");
writer.println(" Then for dirs with no hbase:meta matches, it reads the 'regioninfo'");
writer.println(" metadata file and re-creates given region in hbase:meta. Regions are");
writer.println(" re-created in 'CLOSED' state in the hbase:meta table, but not in the");
writer.println(" Masters' cache, and they are not assigned either. To get these");
writer.println(" regions online, run the HBCK2 'assigns'command printed when this");
writer.println(" command-run completes.");
writer.println(" NOTE: If using hbase releases older than 2.3.0, a rolling restart of");
writer.println(" HMasters is needed prior to executing the set of 'assigns' output.");
writer.println(" An example adding missing regions for tables 'tbl_1' in the default");
writer.println(" namespace, 'tbl_2' in namespace 'n1' and for all tables from");
writer.println(" namespace 'n2':");
writer.println(" $ HBCK2 " + ADD_MISSING_REGIONS_IN_META_FOR_TABLES +
" default:tbl_1 n1:tbl_2 n2");
writer.println(" Returns HBCK2 an 'assigns' command with all re-inserted regions.");
writer.println(" SEE ALSO: " + REPORT_MISSING_REGIONS_IN_META);
writer.println(" SEE ALSO: " + FIX_META);
writer.println(" If -i or --inputFiles is specified, pass one or more input file names.");
writer.println(" Each file contains <NAMESPACE|NAMESPACE:TABLENAME>, one per line.");
writer.println(" For example:");
writer.println(" $ HBCK2 " + ADD_MISSING_REGIONS_IN_META_FOR_TABLES +
" -i fileName1 fileName2");
}
private static void usageAssigns(PrintWriter writer) {
writer.println(" " + ASSIGNS + " [OPTIONS] [<ENCODED_REGIONNAME>...|-i <INPUT_FILE>...]");
writer.println(" Options:");
writer.println(" -o,--override override ownership by another procedure");
writer.println(" -i,--inputFiles take one or more files of encoded region names");
writer.println(" A 'raw' assign that can be used even during Master initialization (if");
writer.println(" the -skip flag is specified). Skirts Coprocessors. Pass one or more");
writer.println(" encoded region names. 1588230740 is the hard-coded name for the");
writer.println(" hbase:meta region and de00010733901a05f5a2a3a382e27dd4 is an example of");
writer.println(" what a user-space encoded region name looks like. For example:");
writer.println(" $ HBCK2 assigns 1588230740 de00010733901a05f5a2a3a382e27dd4");
writer.println(" Returns the pid(s) of the created AssignProcedure(s) or -1 if none.");
writer.println(" If -i or --inputFiles is specified, pass one or more input file names.");
writer.println(" Each file contains encoded region names, one per line. For example:");
writer.println(" $ HBCK2 assigns -i fileName1 fileName2");
}
private static void usageBypass(PrintWriter writer) {
writer.println(" " + BYPASS + " [OPTIONS] [<PID>...|-i <INPUT_FILE>...]");
writer.println(" Options:");
writer.println(" -o,--override override if procedure is running/stuck");
writer.println(" -r,--recursive bypass parent and its children. SLOW! EXPENSIVE!");
writer.println(" -w,--lockWait milliseconds to wait before giving up; default=1");
writer.println(" -i,--inputFiles take one or more files of pids");
writer.println(" Pass one (or more) procedure 'pid's to skip to procedure finish. Parent");
writer.println(" of bypassed procedure will also be skipped to the finish. Entities will");
writer.println(" be left in an inconsistent state and will require manual fixup. May");
writer.println(" need Master restart to clear locks still held. Bypass fails if");
writer.println(" procedure has children. Add 'recursive' if all you have is a parent pid");
writer.println(" to finish parent and children. This is SLOW, and dangerous so use");
writer.println(" selectively. Does not always work.");
writer.println(" If -i or --inputFiles is specified, pass one or more input file names.");
writer.println(" Each file contains pids, one per line. For example:");
writer.println(" $ HBCK2 " + BYPASS + " -i fileName1 fileName2");
}
private static void usageFilesystem(PrintWriter writer) {
writer.println(" " + FILESYSTEM + " [OPTIONS] [<TABLENAME>...|-i <INPUT_FILE>...]");
writer.println(" Options:");
writer.println(" -f, --fix sideline corrupt hfiles, bad links, and references.");
writer.println(" -i,--inputFiles take one or more files of table names");
writer.println(" Report on corrupt hfiles, references, broken links, and integrity.");
writer.println(" Pass '--fix' to sideline corrupt files and links. '--fix' does NOT");
writer.println(" fix integrity issues; i.e. 'holes' or 'orphan' regions. Pass one or");
writer.println(" more tablenames to narrow checkup. Default checks all tables and");
writer.println(" restores 'hbase.version' if missing. Interacts with the filesystem");
writer.println(" only! Modified regions need to be reopened to pick-up changes.");
writer.println(" If -i or --inputFiles is specified, pass one or more input file names.");
writer.println(" Each file contains table names, one per line. For example:");
writer.println(" $ HBCK2 " + FILESYSTEM + " -i fileName1 fileName2");
}
private static void usageFixMeta(PrintWriter writer) {
writer.println(" " + FIX_META);
writer.println(" Do a server-side fix of bad or inconsistent state in hbase:meta.");
writer.println(" Available in hbase 2.2.1/2.1.6 or newer versions. Master UI has");
writer.println(" matching, new 'HBCK Report' tab that dumps reports generated by");
writer.println(" most recent run of _catalogjanitor_ and a new 'HBCK Chore'. It");
writer.println(" is critical that hbase:meta first be made healthy before making");
writer.println(" any other repairs. Fixes 'holes', 'overlaps', etc., creating");
writer.println(" (empty) region directories in HDFS to match regions added to");
writer.println(" hbase:meta. Command is NOT the same as the old _hbck1_ command");
writer.println(" named similarily. Works against the reports generated by the last");
writer.println(" catalog_janitor and hbck chore runs. If nothing to fix, run is a");
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 execute.");
writer.println(" SEE ALSO: " + REPORT_MISSING_REGIONS_IN_META);
}
private static void usageGenerateMissingTableInfo(PrintWriter writer) {
writer.println(" " + GENERATE_TABLE_INFO + " <TABLENAME>");
writer.println(" Trying to fix an orphan table by generating a missing table descriptor");
writer.println(" file. This command will have no effect if the table folder is missing");
writer.println(" or if the .tableinfo is present (we don't override existing table");
writer.println(" descriptors). This command will first check it the TableDescriptor is");
writer.println(" cached in HBase Master in which case it will recover the .tableinfo");
writer.println(" accordingly. If TableDescriptor is not cached in master then it will");
writer.println(" create a default .tableinfo file with the following items:");
writer.println(" - the table name");
writer.println(" - the column family list determined based on the file system");
writer.println(" - the default properties for both TableDescriptor and");
writer.println(" ColumnFamilyDescriptors");
writer.println(" If the .tableinfo file was generated using default parameters then");
writer.println(" make sure you check the table / column family properties later (and");
writer.println(" change them if needed).");
writer.println(" This method does not change anything in HBase, only writes the new");
writer.println(" .tableinfo file to the file system. Orphan tables can cause e.g.");
writer.println(" ServerCrashProcedures to stuck, you might need to fix these still");
writer.println(" after you generated the missing table info files.");
}
private static void usageReplication(PrintWriter writer) {
writer.println(" " + REPLICATION + " [OPTIONS] [<TABLENAME>...|-i <INPUT_FILE>...]");
writer.println(" Options:");
writer.println(" -f, --fix fix any replication issues found.");
writer.println(" -i,--inputFiles take one or more files of table names");
writer.println(" Looks for undeleted replication queues and deletes them if passed the");
writer.println(" '--fix' option. Pass a table name to check for replication barrier and");
writer.println(" purge if '--fix'.");
writer.println(" If -i or --inputFiles is specified, pass one or more input file names.");
writer.println(" Each file contains table names, one per line. For example:");
writer.println(" $ HBCK2 " + REPLICATION + " -i fileName1 fileName2");
}
private static void usageExtraRegionsInMeta(PrintWriter writer) {
writer.println(" " + EXTRA_REGIONS_IN_META + " [<NAMESPACE|"
+ "NAMESPACE:TABLENAME>...|-i <INPUT_FILE>...]");
writer.println(" Options:");
writer.println(" -f, --fix fix meta by removing all extra regions found.");
writer.println(" -i,--inputFiles take one or more files of namespace or table names");
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.");
writer.println(" If -i or --inputFiles is specified, pass one or more input file names.");
writer.println(" Each file contains <NAMESPACE|NAMESPACE:TABLENAME>, one per line.");
writer.println(" For example:");
writer.println(" $ HBCK2 " + EXTRA_REGIONS_IN_META + " -i fileName1 fileName2");
}
private static void usageReportMissingRegionsInMeta(PrintWriter writer) {
writer.println(" " + REPORT_MISSING_REGIONS_IN_META + " [<NAMESPACE|"
+ "NAMESPACE:TABLENAME>...|-i <INPUT_FILE>...]");
writer.println(" Options:");
writer.println(" -i,--inputFiles take one or more files of encoded region names");
writer.println(" To be used when regions missing from hbase:meta but directories");
writer.println(" are present still in HDFS. Can happen if user has run _hbck1_");
writer.println(" 'OfflineMetaRepair' against an hbase-2.x cluster. This is a CHECK only");
writer.println(" method, designed for reporting purposes and doesn't perform any");
writer.println(" fixes, providing a view of which regions (if any) would get re-added");
writer.println(" to hbase:meta, grouped by respective table/namespace. To effectively");
writer.println(" re-add regions in meta, run " + ADD_MISSING_REGIONS_IN_META_FOR_TABLES +
".");
writer.println(" This command needs hbase:meta to be online. For each namespace/table");
writer.println(" passed as parameter, it performs a diff between regions available in");
writer.println(" hbase:meta against existing regions dirs on HDFS. Region dirs with no");
writer.println(" matches are printed grouped under its related table name. Tables with");
writer.println(" no 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 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 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");
writer.println(" for each table on namespaces specified as parameter.");
writer.println(" If -i or --inputFiles is specified, pass one or more input file names.");
writer.println(" Each file contains <NAMESPACE|NAMESPACE:TABLENAME>, one per line.);" +
"For example:");
writer.println(" $ HBCK2 " + REPORT_MISSING_REGIONS_IN_META + " -i fileName1 fileName2");
}
private static void usageSetRegionState(PrintWriter writer) {
writer.println(" " + SET_REGION_STATE + " [<ENCODED_REGIONNAME> <STATE>"
+"|-i <INPUT_FILE>...]");
writer.println(" Options:");
writer.println(" -i,--inputFiles take one or more files of encoded region names " +
"and states");
writer.println(" To set the replica region's state, it needs the primary region's ");
writer.println(" encoded regionname and replica id. The command will be ");
writer.println(" " + SET_REGION_STATE + " <PRIMARY_ENCODED_REGIONNAME>,<replicaId> <STATE>");
writer.println(" Possible region states:");
writer.println(" OFFLINE, OPENING, OPEN, CLOSING, CLOSED, SPLITTING, SPLIT,");
writer.println(" FAILED_OPEN, FAILED_CLOSE, MERGING, MERGED, SPLITTING_NEW,");
writer.println(" MERGING_NEW, ABNORMALLY_CLOSED");
writer.println(" WARNING: This is a very risky option intended for use as last resort.");
writer.println(" Example scenarios include unassigns/assigns that can't move forward");
writer.println(" because region is in an inconsistent state in 'hbase:meta'. For");
writer.println(" example, the 'unassigns' command can only proceed if passed a region");
writer.println(" in one of the following states: SPLITTING|SPLIT|MERGING|OPEN|CLOSING");
writer.println(" Before manually setting a region state with this command, please");
writer.println(" certify that this region is not being handled by a running procedure,");
writer.println(" such as 'assign' or 'split'. You can get a view of running procedures");
writer.println(" in the hbase shell using the 'list_procedures' command. An example");
writer.println(" setting region 'de00010733901a05f5a2a3a382e27dd4' to CLOSING:");
writer.println(" $ HBCK2 setRegionState de00010733901a05f5a2a3a382e27dd4 CLOSING");
writer.println(" Returns \"0\" if region state changed and \"1\" otherwise.");
writer.println(" If -i or --inputFiles is specified, pass one or more input file names.");
writer.println(" Each file contains <ENCODED_REGIONNAME> <STATE>, one pair per line.);" +
"For example:");
writer.println(" $ HBCK2 " + SET_REGION_STATE + " -i fileName1 fileName2");
}
private static void usageSetTableState(PrintWriter writer) {
writer.println(" " + SET_TABLE_STATE + " [<TABLENAME> <STATE>|-i <INPUT_FILE>...]");
writer.println(" Options:");
writer.println(" -i,--inputFiles take one or more files of table names and states");
writer.println(" Possible table states: " + Arrays.stream(TableState.State.values()).
map(Enum::toString).collect(Collectors.joining(", ")));
writer.println(" To read current table state, in the hbase shell run:");
writer.println(" hbase> get 'hbase:meta', '<TABLENAME>', 'table:state'");
writer.println(" A value of \\x08\\x00 == ENABLED, \\x08\\x01 == DISABLED, etc.");
writer.println(" Can also run a 'describe \"<TABLENAME>\"' at the shell prompt.");
writer.println(" An example making table name 'user' ENABLED:");
writer.println(" $ HBCK2 setTableState users ENABLED");
writer.println(" Returns whatever the previous table state was.");
writer.println(" If -i or --inputFiles is specified, pass one or more input file names.");
writer.println(" Each file contains <TABLENAME> <STATE>, one pair per line.);" +
"For example:");
writer.println(" $ HBCK2 " + SET_TABLE_STATE + " -i fileName1 fileName2");
}
private static void usageScheduleRecoveries(PrintWriter writer) {
writer.println(" " + SCHEDULE_RECOVERIES + " [<SERVERNAME>...|-i <INPUT_FILE>...]");
writer.println(" Options:");
writer.println(" -i,--inputFiles take one or more files of server names");
writer.println(" Schedule ServerCrashProcedure(SCP) for list of RegionServers. Format");
writer.println(" server name as '<HOSTNAME>,<PORT>,<STARTCODE>' (See HBase UI/logs).");
writer.println(" Example using RegionServer 'a.example.org,29100,1540348649479':");
writer.println(" $ HBCK2 scheduleRecoveries a.example.org,29100,1540348649479");
writer.println(" Returns the pid(s) of the created ServerCrashProcedure(s) or -1 if");
writer.println(" no procedure created (see master logs for why not).");
writer.println(" Command support added in hbase versions 2.0.3, 2.1.2, 2.2.0 or newer.");
writer.println(" If -i or --inputFiles is specified, pass one or more input file names.");
writer.println(" Each file contains server names, one per line. For example:");
writer.println(" $ HBCK2 " + SCHEDULE_RECOVERIES + " -i fileName1 fileName2");
}
private static void usageRecoverUnknown(PrintWriter writer) {
writer.println(" " + RECOVER_UNKNOWN);
writer.println(" Schedule ServerCrashProcedure(SCP) for RegionServers that are reported");
writer.println(" as unknown.");
writer.println(" Returns the pid(s) of the created ServerCrashProcedure(s) or -1 if");
writer.println(" no procedure created (see master logs for why not).");
writer.println(" Command support added in hbase versions 2.2.7, 2.3.5, 2.4.3,");
writer.println(" 2.5.0 or newer.");
}
private static void usageUnassigns(PrintWriter writer) {
writer.println(" " + UNASSIGNS + " [<ENCODED_REGIONNAME>...|-i <INPUT_FILE>...]");
writer.println(" Options:");
writer.println(" -o,--override override ownership by another procedure");
writer.println(" -i,--inputFiles take one or more files of encoded region names");
writer.println(" A 'raw' unassign that can be used even during Master initialization");
writer.println(" (if the -skip flag is specified). Skirts Coprocessors. Pass one or");
writer.println(" more encoded region names. 1588230740 is the hard-coded name for the");
writer.println(" hbase:meta region and de00010733901a05f5a2a3a382e27dd4 is an example");
writer.println(" of what a userspace encoded region name looks like. For example:");
writer.println(" $ HBCK2 unassigns 1588230740 de00010733901a05f5a2a3a382e27dd4");
writer.println(" Returns the pid(s) of the created UnassignProcedure(s) or -1 if none.");
writer.println();
writer.println(" SEE ALSO, org.apache.hbase.hbck1.OfflineMetaRepair, the offline");
writer.println(" hbase:meta tool. See the HBCK2 README for how to use.");
writer.println(" If -i or --inputFiles is specified, pass one or more input file names.");
writer.println(" Each file contains encoded region names, one per line. For example:");
writer.println(" $ HBCK2 " + UNASSIGNS + " -i fileName1 fileName2");
}
private static void usageRegioninfoMismatch(PrintWriter writer) {
writer.println(" " + REGIONINFO_MISMATCH);
writer.println(" Options:");
writer.println(" -f,--fix Update hbase:meta with the corrections");
writer.println(" It is recommended to first run this utility without the fix");
writer.println(" option to ensure that the utility is generating the correct");
writer.println(" serialized RegionInfo data structures. Inspect the output to");
writer.println(" confirm that the hbase:meta rowkey matches the new RegionInfo.");
writer.println();
writer.println(" This tool will read hbase:meta and report any regions whose rowkey");
writer.println(" and cell value differ in their encoded region name. HBASE-23328 ");
writer.println(" illustrates a problem for read-replica enabled tables in which ");
writer.println(" the encoded region name (the MD5 hash) does not match between ");
writer.println(" the rowkey and the value. This problem is generally harmless ");
writer.println(" for normal operation, but can break other HBCK2 tools.");
writer.println();
writer.println(" Run this command to determine if any regions are affected by ");
writer.println(" this bug and use the -f/--fix option to then correct any");
writer.println(" affected regions.");
}
static void showErrorMessage(String error) {
if (error != null) {
System.out.println("ERROR: " + error);
System.out.println("FOR USAGE, use the -h or --help option");
}
}
static void showUsage(Options options){
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp("HBCK2 [OPTIONS] COMMAND <ARGS>",
"Options:", options, getCommandUsage());
}
@Override
public void setConf(Configuration configuration) {
this.conf = configuration;
}
@Override
public Configuration getConf() {
return this.conf;
}
/**
* Process command line general options.
*/
@Override
public int run(String[] args) throws IOException {
// Configure Options. The below article was more helpful than the commons-cli doc:
// https://dzone.com/articles/java-command-line-interfaces-part-1-apache-commons
Options options = new Options();
Option help = Option.builder("h").longOpt("help").desc("output this help message").build();
options.addOption(help);
Option debug = Option.builder("d").longOpt("debug").desc("run with debug output").build();
options.addOption(debug);
Option quorum = Option.builder("q").longOpt(HConstants.ZOOKEEPER_QUORUM).hasArg().
desc("hbase ensemble").build();
options.addOption(quorum);
Option parent = Option.builder("z").longOpt(HConstants.ZOOKEEPER_ZNODE_PARENT).hasArg()
.desc("parent znode of hbase ensemble").build();
options.addOption(parent);
Option peerPort = Option.builder("p").longOpt(HConstants.ZOOKEEPER_CLIENT_PORT).hasArg()
.desc("port of hbase ensemble").type(Integer.class).build();
options.addOption(peerPort);
Option version = Option.builder("v").longOpt(VERSION).desc("this hbck2 version").build();
options.addOption(version);
Option skip = Option.builder("s").longOpt("skip").
desc("skip hbase version check (PleaseHoldException)").build();
options.addOption(skip);
// Parse command-line.
CommandLineParser parser = new DefaultParser();
CommandLine commandLine;
try {
commandLine = parser.parse(options, args, true);
} catch (ParseException e) {
showErrorMessage(e.getMessage());
return EXIT_FAILURE;
}
// Process general options.
if (commandLine.hasOption(version.getOpt())) {
System.out.println(readHBCK2BuildProperties(VERSION));
return EXIT_SUCCESS;
}
if (commandLine.hasOption(help.getOpt()) || commandLine.getArgList().isEmpty()) {
showUsage(options);
return EXIT_SUCCESS;
}
if (commandLine.hasOption(debug.getOpt())) {
Configurator.setRootLevel(Level.DEBUG);
}
// Build up Configuration for client to use connecting to hbase zk ensemble.
if (commandLine.hasOption(quorum.getOpt())) {
getConf().set(HConstants.ZOOKEEPER_QUORUM, commandLine.getOptionValue(quorum.getOpt()));
}
if (commandLine.hasOption(peerPort.getOpt())) {
String optionValue = commandLine.getOptionValue(peerPort.getOpt());
if (optionValue.matches("[0-9]+")) {
getConf().setInt(HConstants.ZOOKEEPER_CLIENT_PORT, Integer.parseInt(optionValue));
} else {
showErrorMessage(
"Invalid client port. Please provide proper port for target hbase ensemble.");
return EXIT_FAILURE;
}
}
if (commandLine.hasOption(parent.getOpt())) {
String optionValue = commandLine.getOptionValue(parent.getOpt());
if (optionValue.startsWith("/")) {
getConf().set(HConstants.ZOOKEEPER_ZNODE_PARENT, optionValue);
} else {
showErrorMessage("Invalid parent znode. Please provide proper parent znode of target hbase."
+ " Note that valid znodes must start with \"/\".");
return EXIT_FAILURE;
}
}
if (commandLine.hasOption(skip.getOpt())) {
skipCheck = true;
}
return doCommandLine(commandLine, options);
}
/**
* Create connection.
* Needs to be called before we go against remote server.
* Be sure to close when done.
*/
ClusterConnection connect() throws IOException {
return (ClusterConnection)ConnectionFactory.createConnection(getConf());
}
/**
* Process parsed command-line. General options have already been processed by caller.
*/
@SuppressWarnings("checkstyle:methodlength")
private int doCommandLine(CommandLine commandLine, Options options) throws IOException {
// Now process command.
String[] commands = commandLine.getArgs();
String command = commands[0];
switch (command) {
// Case handlers all have same format. Check first that the server supports
// the feature FIRST, then move to process the command.
case SET_TABLE_STATE:
if (commands.length < 2) {
showErrorMessage(command +
" takes tablename and state arguments: e.g. user ENABLED, or a list of input files");
return EXIT_FAILURE;
}
try (ClusterConnection connection = connect(); Hbck hbck = connection.getHbck()) {
checkFunctionSupported(connection, command);
setTableState(hbck, purgeFirst(commands));
}
break;
case ASSIGNS:
if (commands.length < 2) {
showErrorMessage(command + " takes one or more encoded region names");
return EXIT_FAILURE;
}
try (ClusterConnection connection = connect(); Hbck hbck = connection.getHbck()) {
checkFunctionSupported(connection, command);
System.out.println(assigns(hbck, purgeFirst(commands)));
}
break;
case BYPASS:
if (commands.length < 2) {
showErrorMessage(command + " takes one or more pids");
return EXIT_FAILURE;
}
// bypass does the connection setup and the checkFunctionSupported down
// inside in the bypass method delaying connection setup until last
// moment. It does this because it has another set of command options
// to process and wants to do that before setting up connection.
// This is why it is not like the other command processings.
List<Boolean> 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) {
showErrorMessage(command + " takes one or more encoded region names");
return EXIT_FAILURE;
}
try (ClusterConnection connection = connect(); Hbck hbck = connection.getHbck()) {
checkFunctionSupported(connection, command);
System.out.println(toString(unassigns(hbck, purgeFirst(commands))));
}
break;
case SET_REGION_STATE:
if (commands.length < 2) {
showErrorMessage(command + " takes region encoded name and state arguments: e.g. "
+ "35f30b0ce922c34bf5c284eff33ba8b3 CLOSING, or a list of input files");
return EXIT_FAILURE;
}
try (ClusterConnection connection = connect()) {
checkHBCKSupport(connection, command);
return setRegionState(connection, purgeFirst(commands));
}
case FILESYSTEM:
try (ClusterConnection connection = connect()) {
checkHBCKSupport(connection, command);
try (FileSystemFsck fsfsck = new FileSystemFsck(getConf())) {
Pair<CommandLine, List<String>> pair =
parseCommandWithFixAndInputOptions(purgeFirst(commands));
return fsfsck.fsck(pair.getSecond(),
pair.getFirst().hasOption("f"))!= 0? EXIT_FAILURE : EXIT_SUCCESS;
}
}
case REPLICATION:
try (ClusterConnection connection = connect()) {
checkHBCKSupport(connection, command, "2.1.1", "2.2.0", "3.0.0");
try (ReplicationFsck replicationFsck = new ReplicationFsck(getConf())) {
Pair<CommandLine, List<String>> pair =
parseCommandWithFixAndInputOptions(purgeFirst(commands));
return replicationFsck.fsck(pair.getSecond(),
pair.getFirst().hasOption("f")) != 0? EXIT_FAILURE : EXIT_SUCCESS;
}
}
case SCHEDULE_RECOVERIES:
if (commands.length < 2) {
showErrorMessage(command + " takes one or more serverNames");
return EXIT_FAILURE;
}
try (ClusterConnection connection = connect(); Hbck hbck = connection.getHbck()) {
checkFunctionSupported(connection, command);
System.out.println(toString(scheduleRecoveries(hbck, purgeFirst(commands))));
}
break;
case RECOVER_UNKNOWN:
if (commands.length > 1) {
showErrorMessage(command + " doesn't take any arguments");
return EXIT_FAILURE;
}
try (ClusterConnection connection = connect(); Hbck hbck = connection.getHbck()) {
checkFunctionSupported(connection, command);
System.out.println(toString(recoverUnknown(hbck)));
}
break;
case FIX_META:
if (commands.length > 1) {
showErrorMessage(command + " doesn't take any arguments");
return EXIT_FAILURE;
}
try (ClusterConnection connection = connect(); Hbck hbck = connection.getHbck()) {
checkFunctionSupported(connection, command);
hbck.fixMeta();
System.out.println("Server-side processing of fixMeta triggered.");
}
break;
case ADD_MISSING_REGIONS_IN_META_FOR_TABLES:
if(commands.length < 2){
showErrorMessage(command + " takes one or more table names.");
return EXIT_FAILURE;
}
List<Future<List<String>>> addedRegions =
addMissingRegionsInMetaForTables(purgeFirst(commands));
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:
try {
Map<TableName,List<Path>> report =
reportTablesWithMissingRegionsInMeta(purgeFirst(commands));
System.out.println(formatMissingRegionsInMetaReport(report));
} catch (Exception e) {
return EXIT_FAILURE;
}
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;
case GENERATE_TABLE_INFO:
if(commands.length != 2) {
showErrorMessage(command + " takes one table name as argument.");
return EXIT_FAILURE;
}
MissingTableDescriptorGenerator tableInfoGenerator =
new MissingTableDescriptorGenerator(getConf());
tableInfoGenerator.generateTableDescriptorFileIfMissing(commands[1].trim());
break;
case REGIONINFO_MISMATCH:
// `commands` includes the `regionInfoMismatch` argument.
if (commands.length > 2) {
showErrorMessage(command + " takes one optional argument, got more than one.");
return EXIT_FAILURE;
}
try {
regionInfoMismatch(commands);
} catch (Exception e) {
e.printStackTrace();
return EXIT_FAILURE;
}
break;
default:
showErrorMessage("Unsupported command: " + command);
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
private static String toString(List<?> things) {
return things.stream().map(Object::toString).collect(Collectors.joining(", "));
}
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 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(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(resolver.apply(region))
.append(" "));
} else {
builder.append(" -> No mismatching regions. This table is good!");
}
builder.append("\n\t");
});
return builder.toString();
}
private String formatReAddedRegionsMessage(List<String> readdedRegionNames,
List<Exception> 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.forEach(e -> finalText.append(e.getMessage()).append("\n"));
}
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 ");
regions.forEach(region -> builder.append(region).append(" "));
return builder.toString();
}
/**
* @return A new array with first element dropped.
*/
private static String[] purgeFirst(String[] args) {
int size = args.length;
if (size <= 1) {
return new String [] {};
}
size--;
String [] result = new String [size];
System.arraycopy(args, 1, result, 0, size);
return result;
}
/**
* @return arguements for SET_REGION_STATE command
*/
private String[] formatSetRegionStateCommand(String[] commands) {
if (commands.length < 2) {
showErrorMessage("setRegionState takes region encoded name and state arguments: e.g. "
+ "35f30b0ce922c34bf5c284eff33ba8b3 CLOSING");
return null;
}
RegionState.State state = RegionState.State.valueOf(commands[1]);
Integer replicaId = 0;
String region = commands[0];
int separatorIndex = commands[0].indexOf(",");
if (separatorIndex > 0) {
region = commands[0].substring(0, separatorIndex);
replicaId = Integer.getInteger(commands[0].substring(separatorIndex + 1));
}
if (replicaId > 0) {
System.out.println("Change state for replica reigon " + replicaId +
" for primary region " + region);
}
return new String[]{region, replicaId.toString(), state.name()};
}
HBCK2(Configuration conf) {
super(conf);
}
public static void main(String [] args) throws Exception {
Configuration conf = HBaseConfiguration.create();
int errCode = org.apache.hadoop.util.ToolRunner.run(new HBCK2(conf), args);
if (errCode != 0) {
System.exit(errCode);
}
}
private List<String> stringArrayToList(String... nameSpaceOrTable) {
return nameSpaceOrTable != null ? Arrays.asList(nameSpaceOrTable) : null;
}
/**
* Get list of input if no other options
* @param args Array of arguments
* @return the list of input from arguments or parsed from input files
*/
private List<String> getInputList(String[] args) throws IOException {
CommandLine commandLine = parseCommandWithInputList(args, null);
if (commandLine == null) {
return null;
}
return getFromArgsOrFiles(commandLine.getArgList(),
commandLine.hasOption("i"));
}
private CommandLine parseCommandWithInputList(String[] args, Options options) {
if (args == null) {
return null;
}
if (options == null) {
options = new Options();
}
Option inputFile = Option.builder("i").longOpt("inputFiles").build();
options.addOption(inputFile);
return getCommandLine(args, options);
}
private Pair<CommandLine,List<String>> parseAndGetCommandLineWithInputOption(String[] args,
Options options) throws IOException {
CommandLine commandLine = parseCommandWithInputList(args, options);
List<String> params = getFromArgsOrFiles(commandLine.getArgList(),
commandLine.hasOption("i"));
return Pair.newPair(commandLine, params);
}
private Pair<CommandLine,List<String>> parseCommandWithFixAndInputOptions(String[] args)
throws IOException {
Options options = new Options();
Option fixOption = Option.builder("f").longOpt("fix").build();
options.addOption(fixOption);
CommandLine commandLine = parseCommandWithInputList(args, options);
List<String> params = getFromArgsOrFiles(commandLine.getArgList(),
commandLine.hasOption("i"));
return Pair.newPair(commandLine, params);
}
/**
* Get a commandLine object with options and a arg list
*/
private CommandLine getCommandLine(String[] args, Options options) {
// Parse command-line.
CommandLineParser parser = new DefaultParser();
CommandLine commandLine;
try {
commandLine = parser.parse(options, args, false);
} catch (ParseException e) {
showErrorMessage(e.getMessage());
return null;
}
return commandLine;
}
/**
* @return Read arguments from args or a list of input files
*/
private List<String> getFromArgsOrFiles(List<String> args, boolean getFromFile)
throws IOException {
if (!getFromFile || args == null) {
return args;
}
return getFromFiles(args);
}
/**
* @return Read arguments from a list of input files
*/
private List<String> getFromFiles(List<String> args) throws IOException {
List<String> argList = new ArrayList<>();
for (String filePath : args) {
try (InputStream fileStream = new FileInputStream(filePath)){
LineIterator it = IOUtils.lineIterator(fileStream, "UTF-8");
while (it.hasNext()) {
argList.add(it.nextLine().trim());
}
}
}
return argList;
}
}