Skip to content

Commit e04f49f

Browse files
vinayakphegdeanmolnar
authored andcommitted
HBASE-29133: Implement "pitr" Command for Point-in-Time Restore (#6717)
Signed-off-by: Andor Molnar <andor@apache.org> Signed-off-by: Tak Lon (Stephen) Wu <taklwu@apache.org>
1 parent faadf20 commit e04f49f

15 files changed

Lines changed: 1495 additions & 214 deletions

bin/hbase

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ show_usage() {
102102
echo " version Print the version"
103103
echo " backup Backup tables for recovery"
104104
echo " restore Restore tables from existing backup image"
105+
echo " pitr Restore tables to a specific point in time using backup and WAL replay"
105106
echo " completebulkload Run BulkLoadHFiles tool"
106107
echo " regionsplitter Run RegionSplitter tool"
107108
echo " rowcounter Run RowCounter tool"
@@ -639,6 +640,22 @@ elif [ "$COMMAND" = "restore" ] ; then
639640
fi
640641
done
641642
fi
643+
elif [ "$COMMAND" = "pitr" ] ; then
644+
CLASS='org.apache.hadoop.hbase.backup.PointInTimeRestoreDriver'
645+
if [ -n "${shaded_jar}" ] ; then
646+
for f in "${HBASE_HOME}"/lib/hbase-backup*.jar; do
647+
if [ -f "${f}" ]; then
648+
CLASSPATH="${CLASSPATH}:${f}"
649+
break
650+
fi
651+
done
652+
for f in "${HBASE_HOME}"/lib/commons-lang3*.jar; do
653+
if [ -f "${f}" ]; then
654+
CLASSPATH="${CLASSPATH}:${f}"
655+
break
656+
fi
657+
done
658+
fi
642659
elif [ "$COMMAND" = "upgrade" ] ; then
643660
echo "This command was used to upgrade to HBase 0.96, it was removed in HBase 2.0.0."
644661
echo "Please follow the documentation at http://hbase.apache.org/book.html#upgrading."
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.hadoop.hbase.backup;
19+
20+
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_CHECK;
21+
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_CHECK_DESC;
22+
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_DEBUG;
23+
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_DEBUG_DESC;
24+
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_OVERWRITE;
25+
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_OVERWRITE_DESC;
26+
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_SET;
27+
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_SET_RESTORE_DESC;
28+
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_TABLE;
29+
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_TABLE_LIST_DESC;
30+
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_TABLE_MAPPING;
31+
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_TABLE_MAPPING_DESC;
32+
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_YARN_QUEUE_NAME;
33+
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.OPTION_YARN_QUEUE_NAME_RESTORE_DESC;
34+
35+
import java.io.IOException;
36+
import java.util.List;
37+
import java.util.Objects;
38+
import org.apache.commons.lang3.StringUtils;
39+
import org.apache.hadoop.hbase.TableName;
40+
import org.apache.hadoop.hbase.backup.impl.BackupManager;
41+
import org.apache.hadoop.hbase.backup.impl.BackupSystemTable;
42+
import org.apache.hadoop.hbase.backup.util.BackupUtils;
43+
import org.apache.hadoop.hbase.client.Connection;
44+
import org.apache.hadoop.hbase.client.ConnectionFactory;
45+
import org.apache.hadoop.hbase.logging.Log4jUtils;
46+
import org.apache.hadoop.hbase.util.AbstractHBaseTool;
47+
import org.apache.yetus.audience.InterfaceAudience;
48+
import org.slf4j.Logger;
49+
import org.slf4j.LoggerFactory;
50+
51+
import org.apache.hbase.thirdparty.org.apache.commons.cli.CommandLine;
52+
import org.apache.hbase.thirdparty.org.apache.commons.cli.HelpFormatter;
53+
54+
@InterfaceAudience.Private
55+
public abstract class AbstractRestoreDriver extends AbstractHBaseTool {
56+
protected static final Logger LOG = LoggerFactory.getLogger(AbstractRestoreDriver.class);
57+
protected CommandLine cmd;
58+
59+
protected static final String USAGE_FOOTER = "";
60+
61+
protected AbstractRestoreDriver() {
62+
init();
63+
}
64+
65+
protected void init() {
66+
Log4jUtils.disableZkAndClientLoggers();
67+
}
68+
69+
protected abstract int executeRestore(boolean check, TableName[] fromTables, TableName[] toTables,
70+
boolean isOverwrite);
71+
72+
private int parseAndRun() throws IOException {
73+
if (!BackupManager.isBackupEnabled(getConf())) {
74+
System.err.println(BackupRestoreConstants.ENABLE_BACKUP);
75+
return -1;
76+
}
77+
78+
if (cmd.hasOption(OPTION_DEBUG)) {
79+
Log4jUtils.setLogLevel("org.apache.hadoop.hbase.backup", "DEBUG");
80+
}
81+
82+
boolean overwrite = cmd.hasOption(OPTION_OVERWRITE);
83+
if (overwrite) {
84+
LOG.debug("Found overwrite option (-{}) in restore command, "
85+
+ "will overwrite to existing table if any in the restore target", OPTION_OVERWRITE);
86+
}
87+
88+
boolean check = cmd.hasOption(OPTION_CHECK);
89+
if (check) {
90+
LOG.debug(
91+
"Found check option (-{}) in restore command, will check and verify the dependencies",
92+
OPTION_CHECK);
93+
}
94+
95+
if (cmd.hasOption(OPTION_SET) && cmd.hasOption(OPTION_TABLE)) {
96+
System.err.printf(
97+
"Set name (-%s) and table list (-%s) are mutually exclusive, you can not specify both "
98+
+ "of them.%n",
99+
OPTION_SET, OPTION_TABLE);
100+
printToolUsage();
101+
return -1;
102+
}
103+
104+
if (!cmd.hasOption(OPTION_SET) && !cmd.hasOption(OPTION_TABLE)) {
105+
System.err.printf(
106+
"You have to specify either set name (-%s) or table list (-%s) to " + "restore%n",
107+
OPTION_SET, OPTION_TABLE);
108+
printToolUsage();
109+
return -1;
110+
}
111+
112+
if (cmd.hasOption(OPTION_YARN_QUEUE_NAME)) {
113+
String queueName = cmd.getOptionValue(OPTION_YARN_QUEUE_NAME);
114+
// Set MR job queuename to configuration
115+
getConf().set("mapreduce.job.queuename", queueName);
116+
}
117+
118+
String tables;
119+
TableName[] sTableArray;
120+
TableName[] tTableArray;
121+
122+
String tableMapping = cmd.getOptionValue(OPTION_TABLE_MAPPING);
123+
124+
try (final Connection conn = ConnectionFactory.createConnection(conf)) {
125+
// Check backup set
126+
if (cmd.hasOption(OPTION_SET)) {
127+
String setName = cmd.getOptionValue(OPTION_SET);
128+
try {
129+
tables = getTablesForSet(conn, setName);
130+
} catch (IOException e) {
131+
System.out.println("ERROR: " + e.getMessage() + " for setName=" + setName);
132+
printToolUsage();
133+
return -2;
134+
}
135+
if (tables == null) {
136+
System.out
137+
.println("ERROR: Backup set '" + setName + "' is either empty or does not exist");
138+
printToolUsage();
139+
return -3;
140+
}
141+
} else {
142+
tables = cmd.getOptionValue(OPTION_TABLE);
143+
}
144+
145+
sTableArray = BackupUtils.parseTableNames(tables);
146+
tTableArray = BackupUtils.parseTableNames(tableMapping);
147+
148+
if (
149+
sTableArray != null && tTableArray != null && (sTableArray.length != tTableArray.length)
150+
) {
151+
System.out.println("ERROR: table mapping mismatch: " + tables + " : " + tableMapping);
152+
printToolUsage();
153+
return -4;
154+
}
155+
}
156+
157+
return executeRestore(check, sTableArray, tTableArray, overwrite);
158+
}
159+
160+
@Override
161+
protected void addOptions() {
162+
addOptNoArg(OPTION_OVERWRITE, OPTION_OVERWRITE_DESC);
163+
addOptNoArg(OPTION_CHECK, OPTION_CHECK_DESC);
164+
addOptNoArg(OPTION_DEBUG, OPTION_DEBUG_DESC);
165+
addOptWithArg(OPTION_SET, OPTION_SET_RESTORE_DESC);
166+
addOptWithArg(OPTION_TABLE, OPTION_TABLE_LIST_DESC);
167+
addOptWithArg(OPTION_TABLE_MAPPING, OPTION_TABLE_MAPPING_DESC);
168+
addOptWithArg(OPTION_YARN_QUEUE_NAME, OPTION_YARN_QUEUE_NAME_RESTORE_DESC);
169+
}
170+
171+
@Override
172+
protected void processOptions(CommandLine cmd) {
173+
this.cmd = cmd;
174+
}
175+
176+
@Override
177+
protected int doWork() throws Exception {
178+
return parseAndRun();
179+
}
180+
181+
@Override
182+
public int run(String[] args) {
183+
Objects.requireNonNull(conf, "Tool configuration is not initialized");
184+
185+
try {
186+
cmd = parseArgs(args);
187+
} catch (Exception e) {
188+
System.out.println("Error parsing command-line arguments: " + e.getMessage());
189+
printToolUsage();
190+
return EXIT_FAILURE;
191+
}
192+
193+
if (cmd.hasOption(SHORT_HELP_OPTION) || cmd.hasOption(LONG_HELP_OPTION)) {
194+
printToolUsage();
195+
return EXIT_FAILURE;
196+
}
197+
198+
processOptions(cmd);
199+
200+
try {
201+
return doWork();
202+
} catch (Exception e) {
203+
LOG.error("Error running restore tool", e);
204+
return EXIT_FAILURE;
205+
}
206+
}
207+
208+
protected void printToolUsage() {
209+
System.out.println(getUsageString());
210+
HelpFormatter helpFormatter = new HelpFormatter();
211+
helpFormatter.setLeftPadding(2);
212+
helpFormatter.setDescPadding(8);
213+
helpFormatter.setWidth(100);
214+
helpFormatter.setSyntaxPrefix("Options:");
215+
helpFormatter.printHelp(" ", null, options, USAGE_FOOTER);
216+
System.out.println(BackupRestoreConstants.VERIFY_BACKUP);
217+
}
218+
219+
protected abstract String getUsageString();
220+
221+
private String getTablesForSet(Connection conn, String name) throws IOException {
222+
try (final BackupSystemTable table = new BackupSystemTable(conn)) {
223+
List<TableName> tables = table.describeBackupSet(name);
224+
225+
if (tables == null) {
226+
return null;
227+
}
228+
229+
return StringUtils.join(tables, BackupRestoreConstants.TABLENAME_DELIMITER_IN_COMMAND);
230+
}
231+
}
232+
}

hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/BackupAdmin.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ public interface BackupAdmin extends Closeable {
5151
*/
5252
void restore(RestoreRequest request) throws IOException;
5353

54+
/**
55+
* Restore the tables to specific time
56+
* @param request Point in Time restore request
57+
* @throws IOException exception
58+
*/
59+
void pointInTimeRestore(PointInTimeRestoreRequest request) throws IOException;
60+
5461
/**
5562
* Describe backup image command
5663
* @param backupId backup id

hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/BackupRestoreConstants.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,16 @@ public interface BackupRestoreConstants {
109109
String OPTION_FORCE_DELETE_DESC =
110110
"Flag to forcefully delete the backup, even if it may be required for Point-in-Time Restore";
111111

112+
String OPTION_TO_DATETIME = "td";
113+
String LONG_OPTION_TO_DATETIME = "to-datetime";
114+
String OPTION_TO_DATETIME_DESC = "Target date and time up to which data should be restored";
115+
116+
String OPTION_PITR_BACKUP_PATH = "bp";
117+
String LONG_OPTION_PITR_BACKUP_PATH = "backup-path";
118+
String OPTION_PITR_BACKUP_PATH_DESC =
119+
"Specifies a custom backup location for Point-In-Time Recovery (PITR). "
120+
+ "If provided, this location will be used exclusively instead of deriving the path from the system table.";
121+
112122
String JOB_NAME_CONF_KEY = "mapreduce.job.name";
113123

114124
String BACKUP_CONFIG_STRING = BackupRestoreConstants.BACKUP_ENABLE_KEY + "=true\n"

hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/HBackupFileSystem.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,21 @@
1717
*/
1818
package org.apache.hadoop.hbase.backup;
1919

20+
import static org.apache.hadoop.hbase.backup.BackupRestoreConstants.BACKUPID_PREFIX;
21+
2022
import com.google.errorprone.annotations.RestrictedApi;
2123
import java.io.IOException;
24+
import java.util.ArrayList;
25+
import java.util.Comparator;
26+
import java.util.List;
2227
import org.apache.hadoop.conf.Configuration;
2328
import org.apache.hadoop.fs.FileSystem;
29+
import org.apache.hadoop.fs.LocatedFileStatus;
2430
import org.apache.hadoop.fs.Path;
31+
import org.apache.hadoop.fs.RemoteIterator;
2532
import org.apache.hadoop.hbase.TableName;
2633
import org.apache.hadoop.hbase.backup.impl.BackupManifest;
34+
import org.apache.hadoop.hbase.backup.impl.BackupManifest.BackupImage;
2735
import org.apache.yetus.audience.InterfaceAudience;
2836
import org.slf4j.Logger;
2937
import org.slf4j.LoggerFactory;
@@ -135,4 +143,36 @@ public static BackupManifest getManifest(Configuration conf, Path backupRootPath
135143
new BackupManifest(conf, getManifestPath(conf, backupRootPath, backupId));
136144
return manifest;
137145
}
146+
147+
public static List<BackupImage> getAllBackupImages(Configuration conf, Path backupRootPath)
148+
throws IOException {
149+
FileSystem fs = FileSystem.get(backupRootPath.toUri(), conf);
150+
RemoteIterator<LocatedFileStatus> it = fs.listLocatedStatus(backupRootPath);
151+
152+
List<BackupImage> images = new ArrayList<>();
153+
154+
while (it.hasNext()) {
155+
LocatedFileStatus lfs = it.next();
156+
if (!lfs.isDirectory()) {
157+
continue;
158+
}
159+
160+
String backupId = lfs.getPath().getName();
161+
try {
162+
BackupManifest manifest = getManifest(conf, backupRootPath, backupId);
163+
images.add(manifest.getBackupImage());
164+
} catch (IOException e) {
165+
LOG.error("Cannot load backup manifest from: " + lfs.getPath(), e);
166+
}
167+
}
168+
169+
// Sort images by timestamp in descending order
170+
images.sort(Comparator.comparingLong(m -> -getTimestamp(m.getBackupId())));
171+
172+
return images;
173+
}
174+
175+
private static long getTimestamp(String backupId) {
176+
return Long.parseLong(backupId.substring(BACKUPID_PREFIX.length()));
177+
}
138178
}

0 commit comments

Comments
 (0)