Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions hbase-hbck2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,26 @@ Command:
for how to generate new report.
SEE ALSO: reportMissingRegionsInMeta

generateMissingTableDescriptorFile <TABLENAME>
Trying to fix an orphan table by generating a missing table descriptor
file. This command will have no affect if the table folder is missing

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: "effect", not "affect"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks

or if the .tableinfo is present (we don't override existing table
descriptors). This command will first check it the TableDescriptor is
cached in HBase Master in which case it will recover the .tableinfo
accordingly. If TableDescriptor is not cached in master then it will
create a default .tableinfo file with the following items:
- the table name
- the column family list determined based on the file system
- the default properties for both TableDescriptor and
ColumnFamilyDescriptors
If the .tableinfo file was generated using default parameters then
make sure you check the table / column family properties later (and
change them if needed).
This method does not change anything in HBase, only writes the new
.tableinfo file to the file system. Orphan tables can cause e.g.
ServerCrashProcedures to stuck, you might need to fix these still
after you generated the missing table info files.

replication [OPTIONS] [<TABLENAME>...]
Options:
-f, --fix fix any replication issues found.
Expand Down
35 changes: 35 additions & 0 deletions hbase-hbck2/src/main/java/org/apache/hbase/HBCK2.java
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ public class HBCK2 extends Configured implements org.apache.hadoop.util.Tool {
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 GENERATE_TABLE_INFO = "generateMissingTableDescriptorFile";
private static final String FIX_META = "fixMeta";
// 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
Expand Down Expand Up @@ -413,6 +414,8 @@ private static String getCommandUsage() {
writer.println();
usageFixMeta(writer);
writer.println();
usageGenerateMissingTableInfo(writer);
writer.println();
usageReplication(writer);
writer.println();
usageReportMissingRegionsInMeta(writer);
Expand Down Expand Up @@ -520,6 +523,28 @@ private static void usageFixMeta(PrintWriter writer) {
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 affect 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>...]");
writer.println(" Options:");
Expand Down Expand Up @@ -916,6 +941,16 @@ private int doCommandLine(CommandLine commandLine, Options options) throws IOExc
}
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;

default:
showErrorMessage("Unsupported command: " + command);
return EXIT_FAILURE;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
/*
* 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.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.TableNotFoundException;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.client.TableDescriptor;
import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
import org.apache.hadoop.hbase.util.FSTableDescriptors;
import org.apache.hadoop.hbase.util.FSUtils;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FSTableDescriptors and FSUtils are IA Private. This has caused frequently problems to maintain operators tools compiling, or even compatible at runtime. To solve that, we have been duplicating these utility classes in operator-tools project. See HBCKFsUtils and HBCKMetaTableAccessor for reference.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, I'll duplicate these as well

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* This class can be used to generate missing table descriptor file based on the in-memory cache
* of the active master or based on the file system.
*/
public class MissingTableDescriptorGenerator {

private static final Logger LOG = LoggerFactory.getLogger(MissingTableDescriptorGenerator.class);

private final Configuration configuration;
private FileSystem fs;
private Path rootDir;

public MissingTableDescriptorGenerator(Configuration configuration) throws IOException {
this.configuration = configuration;
this.rootDir = HBCKFsUtils.getRootDir(this.configuration);
this.fs = rootDir.getFileSystem(this.configuration);
}

/**
* Trying to generate missing table descriptor. If anything goes wrong, then the method throws
* IllegalStateException without changing anything. The method follows these steps:
*
* - if the table folder is missing, then we return
* - if the .tableinfo file is not missing, then we return (we don't overwrite it)
* - if TableDescriptor is cached in master then recover the .tableinfo accordingly
* - if TableDescriptor is not cached in master, then we create a default .tableinfo file
* with the following items:
* - the table name
* - the column family list (determined based on the file system)
* - the default properties for both {@link TableDescriptor} and
* {@link ColumnFamilyDescriptor}
*
* This method does not change anything in HBase, only writes the new .tableinfo file
* to the file system.
*
* @param tableNameAsString the table name in standard 'table' or 'ns:table' format
*/
public void generateTableDescriptorFileIfMissing(String tableNameAsString) {
TableName tableName = TableName.valueOf(tableNameAsString);
assertTableFolderIsPresent(tableName);
if (checkIfTableInfoPresent(tableName)) {
LOG.info("Table descriptor already exists, exiting without changing anything.");
return;
}

FSTableDescriptors fstd;
try {
fstd = new FSTableDescriptors(configuration);
} catch (IOException e) {
LOG.error("Unable to initialize FSTableDescriptors, exiting without changing anything.", e);
return;
}

Optional<TableDescriptor> tableDescriptorFromMaster = getTableDescriptorFromMaster(tableName);
try {
if (tableDescriptorFromMaster.isPresent()) {
LOG.info("Table descriptor found in the cache of HBase Master, " +
"writing it to the file system.");
fstd.createTableDescriptor(tableDescriptorFromMaster.get(), false);
LOG.info("Table descriptor written successfully. Orphan table {} fixed.", tableName);
} else {
generateDefaultTableInfo(fstd, tableName);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to refresh master's cache with the table descriptor? Had quick checked master rpc interface, didn't find any available method, maybe something we could add on a next jira.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a good idea, I'll create a follow-up Jira. Also I'll mention in the usage that currently a master restart might be required to force the cache to reinitialize.

This definitely can be improved further, but it might need some more investigation. During my manual tests I saw the table to reappear (shown by the list command) quickly after the missing tableinfo file got generated. So something must have been trying to open the table periodically. However, the scan operations failed on the table until I did a rolling restart. (I haven't checked the procedures before restarting the cluster, I guess something got stucked in the Region Server still)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

During my manual tests I saw the table to reappear (shown by the list command) quickly after the missing tableinfo file got generated.

Interesting. Have you tried disable/enable the table after re-creating the table info?

LOG.info("Table descriptor written successfully.");
LOG.warn("Orphan table {} fixed with a default .tableinfo file. It is strongly " +
"recommended to review the TableDescriptor and modify if necessary.", tableName);
}
} catch (IOException e) {
LOG.error("Exception while writing the table descriptor to the file system for table {}",
tableName, e);
}

}

private void assertTableFolderIsPresent(TableName tableName) {
final Path tableDir = HBCKFsUtils.getTableDir(rootDir, tableName);
try {
if (!fs.exists(tableDir)) {
throw new IllegalStateException("Exiting without changing anything. " +
"Table folder not exists: " + tableDir);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: "Table folder does not exist"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks

}
if (!fs.getFileStatus(tableDir).isDirectory()) {
throw new IllegalStateException("Exiting without changing anything. " +
"Table folder is not a directory: " + tableDir);
}
} catch (IOException e) {
LOG.error("Exception while trying to find table folder for table {}", tableName, e);
throw new IllegalStateException("Exiting without changing anything. " +
"Can not validate if table folder exists.");
}
}

private boolean checkIfTableInfoPresent(TableName tableName) {
final Path tableDir = HBCKFsUtils.getTableDir(rootDir, tableName);
try {
FileStatus tableInfoFile = FSTableDescriptors.getTableInfoPath(fs, tableDir);
if (tableInfoFile != null) {
LOG.info("Table descriptor found for table {} in: {}", tableName, tableInfoFile.getPath());
return true;
}
} catch (IOException e) {
LOG.error("Exception while trying to find the table descriptor for table {}", tableName, e);
throw new IllegalStateException("Can not validate if table descriptor exists. " +
"Exiting without changing anything.");
}
return false;
}

private Optional<TableDescriptor> getTableDescriptorFromMaster(TableName tableName) {
LOG.info("Trying to fetch table descriptor for orphan table: {}", tableName);
try (Connection conn = ConnectionFactory.createConnection(configuration);
Admin admin = conn.getAdmin()) {
TableDescriptor tds = admin.getDescriptor(tableName);
return Optional.of(tds);
} catch (TableNotFoundException e) {
LOG.info("Table Descriptor not found in HBase Master: {}", tableName);
} catch (IOException e) {
LOG.warn("Exception while fetching table descriptor. Is master offline?", e);
}
return Optional.empty();
}

private void generateDefaultTableInfo(FSTableDescriptors fstd, TableName tableName)
throws IOException {
Set<String> columnFamilies = getColumnFamilies(tableName);
if(columnFamilies.isEmpty()) {
LOG.warn("No column family found in HDFS for table {}.", tableName);
} else {
LOG.info("Column families to be listed in the new table info: {}", columnFamilies);
}

TableDescriptorBuilder tableBuilder = TableDescriptorBuilder.newBuilder(tableName);
for (String columnFamily : columnFamilies) {
final ColumnFamilyDescriptor family = ColumnFamilyDescriptorBuilder.of(columnFamily);
tableBuilder.setColumnFamily(family);
}
fstd.createTableDescriptor(tableBuilder.build(), false);
}

private Set<String> getColumnFamilies(TableName tableName) {
try {
final Path tableDir = HBCKFsUtils.getTableDir(rootDir, tableName);
final List<Path> regionDirs = FSUtils.getRegionDirs(fs, tableDir);
Set<String> columnFamilies = new HashSet<>();
for (Path regionDir : regionDirs) {
FileStatus[] familyDirs = fs.listStatus(regionDir, new FSUtils.FamilyDirFilter(fs));
for (FileStatus familyDir : familyDirs) {
String columnFamily = familyDir.getPath().getName();
columnFamilies.add(columnFamily);
}
}
return columnFamilies;
} catch (IOException e) {
LOG.error("Exception while trying to find in HDFS the column families for table {}",
tableName, e);
throw new IllegalStateException("Unable to determine the list of column families. " +
"Exiting without changing anything.");
}
}
}
Loading