HBASE-27541 Backups should be able to be restored to a separate filesystem (#4933)
Signed-off-by: Bryan Beaudreault <bbeaudreault@apache.org>
This commit is contained in:
parent
71d0862d84
commit
d799c3c3c3
|
@ -34,10 +34,11 @@ public interface RestoreJob extends Configurable {
|
||||||
* Run restore operation
|
* Run restore operation
|
||||||
* @param dirPaths path array of WAL log directories
|
* @param dirPaths path array of WAL log directories
|
||||||
* @param fromTables from tables
|
* @param fromTables from tables
|
||||||
|
* @param restoreRootDir output file system
|
||||||
* @param toTables to tables
|
* @param toTables to tables
|
||||||
* @param fullBackupRestore full backup restore
|
* @param fullBackupRestore full backup restore
|
||||||
* @throws IOException if running the job fails
|
* @throws IOException if running the job fails
|
||||||
*/
|
*/
|
||||||
void run(Path[] dirPaths, TableName[] fromTables, TableName[] toTables, boolean fullBackupRestore)
|
void run(Path[] dirPaths, TableName[] fromTables, Path restoreRootDir, TableName[] toTables,
|
||||||
throws IOException;
|
boolean fullBackupRestore) throws IOException;
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,11 @@ public final class RestoreRequest {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder withRestoreRootDir(String restoreRootDir) {
|
||||||
|
request.setRestoreRootDir(restoreRootDir);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Builder withBackupId(String backupId) {
|
public Builder withBackupId(String backupId) {
|
||||||
request.setBackupId(backupId);
|
request.setBackupId(backupId);
|
||||||
return this;
|
return this;
|
||||||
|
@ -68,6 +73,7 @@ public final class RestoreRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private String backupRootDir;
|
private String backupRootDir;
|
||||||
|
private String restoreRootDir;
|
||||||
private String backupId;
|
private String backupId;
|
||||||
private boolean check = false;
|
private boolean check = false;
|
||||||
private TableName[] fromTables;
|
private TableName[] fromTables;
|
||||||
|
@ -86,6 +92,15 @@ public final class RestoreRequest {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getRestoreRootDir() {
|
||||||
|
return restoreRootDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RestoreRequest setRestoreRootDir(String restoreRootDir) {
|
||||||
|
this.restoreRootDir = restoreRootDir;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public String getBackupId() {
|
public String getBackupId() {
|
||||||
return backupId;
|
return backupId;
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ import org.apache.hadoop.hbase.backup.BackupType;
|
||||||
import org.apache.hadoop.hbase.backup.HBackupFileSystem;
|
import org.apache.hadoop.hbase.backup.HBackupFileSystem;
|
||||||
import org.apache.hadoop.hbase.backup.RestoreRequest;
|
import org.apache.hadoop.hbase.backup.RestoreRequest;
|
||||||
import org.apache.hadoop.hbase.backup.impl.BackupManifest.BackupImage;
|
import org.apache.hadoop.hbase.backup.impl.BackupManifest.BackupImage;
|
||||||
|
import org.apache.hadoop.hbase.backup.util.BackupUtils;
|
||||||
import org.apache.hadoop.hbase.backup.util.RestoreTool;
|
import org.apache.hadoop.hbase.backup.util.RestoreTool;
|
||||||
import org.apache.hadoop.hbase.client.Admin;
|
import org.apache.hadoop.hbase.client.Admin;
|
||||||
import org.apache.hadoop.hbase.client.Connection;
|
import org.apache.hadoop.hbase.client.Connection;
|
||||||
|
@ -55,11 +56,12 @@ public class RestoreTablesClient {
|
||||||
private String backupId;
|
private String backupId;
|
||||||
private TableName[] sTableArray;
|
private TableName[] sTableArray;
|
||||||
private TableName[] tTableArray;
|
private TableName[] tTableArray;
|
||||||
private String targetRootDir;
|
private String backupRootDir;
|
||||||
|
private Path restoreRootDir;
|
||||||
private boolean isOverwrite;
|
private boolean isOverwrite;
|
||||||
|
|
||||||
public RestoreTablesClient(Connection conn, RestoreRequest request) {
|
public RestoreTablesClient(Connection conn, RestoreRequest request) throws IOException {
|
||||||
this.targetRootDir = request.getBackupRootDir();
|
this.backupRootDir = request.getBackupRootDir();
|
||||||
this.backupId = request.getBackupId();
|
this.backupId = request.getBackupId();
|
||||||
this.sTableArray = request.getFromTables();
|
this.sTableArray = request.getFromTables();
|
||||||
this.tTableArray = request.getToTables();
|
this.tTableArray = request.getToTables();
|
||||||
|
@ -69,6 +71,12 @@ public class RestoreTablesClient {
|
||||||
this.isOverwrite = request.isOverwrite();
|
this.isOverwrite = request.isOverwrite();
|
||||||
this.conn = conn;
|
this.conn = conn;
|
||||||
this.conf = conn.getConfiguration();
|
this.conf = conn.getConfiguration();
|
||||||
|
if (request.getRestoreRootDir() != null) {
|
||||||
|
restoreRootDir = new Path(request.getRestoreRootDir());
|
||||||
|
} else {
|
||||||
|
FileSystem fs = FileSystem.get(conf);
|
||||||
|
this.restoreRootDir = BackupUtils.getTmpRestoreOutputDir(fs, conf);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -131,7 +139,7 @@ public class RestoreTablesClient {
|
||||||
String rootDir = image.getRootDir();
|
String rootDir = image.getRootDir();
|
||||||
String backupId = image.getBackupId();
|
String backupId = image.getBackupId();
|
||||||
Path backupRoot = new Path(rootDir);
|
Path backupRoot = new Path(rootDir);
|
||||||
RestoreTool restoreTool = new RestoreTool(conf, backupRoot, backupId);
|
RestoreTool restoreTool = new RestoreTool(conf, backupRoot, restoreRootDir, backupId);
|
||||||
Path tableBackupPath = HBackupFileSystem.getTableBackupPath(sTable, backupRoot, backupId);
|
Path tableBackupPath = HBackupFileSystem.getTableBackupPath(sTable, backupRoot, backupId);
|
||||||
String lastIncrBackupId = images.length == 1 ? null : images[images.length - 1].getBackupId();
|
String lastIncrBackupId = images.length == 1 ? null : images[images.length - 1].getBackupId();
|
||||||
// We need hFS only for full restore (see the code)
|
// We need hFS only for full restore (see the code)
|
||||||
|
@ -249,7 +257,7 @@ public class RestoreTablesClient {
|
||||||
// case RESTORE_IMAGES:
|
// case RESTORE_IMAGES:
|
||||||
HashMap<TableName, BackupManifest> backupManifestMap = new HashMap<>();
|
HashMap<TableName, BackupManifest> backupManifestMap = new HashMap<>();
|
||||||
// check and load backup image manifest for the tables
|
// check and load backup image manifest for the tables
|
||||||
Path rootPath = new Path(targetRootDir);
|
Path rootPath = new Path(backupRootDir);
|
||||||
HBackupFileSystem.checkImageManifestExist(backupManifestMap, sTableArray, conf, rootPath,
|
HBackupFileSystem.checkImageManifestExist(backupManifestMap, sTableArray, conf, rootPath,
|
||||||
backupId);
|
backupId);
|
||||||
|
|
||||||
|
|
|
@ -50,8 +50,8 @@ public class MapReduceRestoreJob implements RestoreJob {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run(Path[] dirPaths, TableName[] tableNames, TableName[] newTableNames,
|
public void run(Path[] dirPaths, TableName[] tableNames, Path restoreRootDir,
|
||||||
boolean fullBackupRestore) throws IOException {
|
TableName[] newTableNames, boolean fullBackupRestore) throws IOException {
|
||||||
String bulkOutputConfKey;
|
String bulkOutputConfKey;
|
||||||
|
|
||||||
player = new MapReduceHFileSplitterJob();
|
player = new MapReduceHFileSplitterJob();
|
||||||
|
@ -70,9 +70,8 @@ public class MapReduceRestoreJob implements RestoreJob {
|
||||||
|
|
||||||
for (int i = 0; i < tableNames.length; i++) {
|
for (int i = 0; i < tableNames.length; i++) {
|
||||||
LOG.info("Restore " + tableNames[i] + " into " + newTableNames[i]);
|
LOG.info("Restore " + tableNames[i] + " into " + newTableNames[i]);
|
||||||
|
Path bulkOutputPath = BackupUtils.getBulkOutputDir(restoreRootDir,
|
||||||
Path bulkOutputPath = BackupUtils
|
BackupUtils.getFileNameCompatibleString(newTableNames[i]), getConf());
|
||||||
.getBulkOutputDir(BackupUtils.getFileNameCompatibleString(newTableNames[i]), getConf());
|
|
||||||
Configuration conf = getConf();
|
Configuration conf = getConf();
|
||||||
conf.set(bulkOutputConfKey, bulkOutputPath.toString());
|
conf.set(bulkOutputConfKey, bulkOutputPath.toString());
|
||||||
String[] playerArgs = { dirs,
|
String[] playerArgs = { dirs,
|
||||||
|
|
|
@ -690,21 +690,38 @@ public final class BackupUtils {
|
||||||
return isValid;
|
return isValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Path getBulkOutputDir(String tableName, Configuration conf, boolean deleteOnExit)
|
public static Path getBulkOutputDir(Path restoreRootDir, String tableName, Configuration conf,
|
||||||
throws IOException {
|
boolean deleteOnExit) throws IOException {
|
||||||
FileSystem fs = FileSystem.get(conf);
|
FileSystem fs = restoreRootDir.getFileSystem(conf);
|
||||||
String tmp =
|
Path path = new Path(restoreRootDir,
|
||||||
conf.get(HConstants.TEMPORARY_FS_DIRECTORY_KEY, fs.getHomeDirectory() + "/hbase-staging");
|
"bulk_output-" + tableName + "-" + EnvironmentEdgeManager.currentTime());
|
||||||
Path path = new Path(tmp + Path.SEPARATOR + "bulk_output-" + tableName + "-"
|
|
||||||
+ EnvironmentEdgeManager.currentTime());
|
|
||||||
if (deleteOnExit) {
|
if (deleteOnExit) {
|
||||||
fs.deleteOnExit(path);
|
fs.deleteOnExit(path);
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Path getBulkOutputDir(String tableName, Configuration conf) throws IOException {
|
public static Path getBulkOutputDir(Path restoreRootDir, String tableName, Configuration conf)
|
||||||
return getBulkOutputDir(tableName, conf, true);
|
throws IOException {
|
||||||
|
return getBulkOutputDir(restoreRootDir, tableName, conf, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Path getBulkOutputDir(String tableName, Configuration conf, boolean deleteOnExit)
|
||||||
|
throws IOException {
|
||||||
|
FileSystem fs = FileSystem.get(conf);
|
||||||
|
return getBulkOutputDir(getTmpRestoreOutputDir(fs, conf), tableName, conf, deleteOnExit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build temporary output path
|
||||||
|
* @param fs filesystem for default output dir
|
||||||
|
* @param conf configuration
|
||||||
|
* @return output path
|
||||||
|
*/
|
||||||
|
public static Path getTmpRestoreOutputDir(FileSystem fs, Configuration conf) {
|
||||||
|
String tmp =
|
||||||
|
conf.get(HConstants.TEMPORARY_FS_DIRECTORY_KEY, fs.getHomeDirectory() + "/hbase-staging");
|
||||||
|
return new Path(tmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getFileNameCompatibleString(TableName table) {
|
public static String getFileNameCompatibleString(TableName table) {
|
||||||
|
|
|
@ -67,18 +67,20 @@ public class RestoreTool {
|
||||||
private final String[] ignoreDirs = { HConstants.RECOVERED_EDITS_DIR };
|
private final String[] ignoreDirs = { HConstants.RECOVERED_EDITS_DIR };
|
||||||
protected Configuration conf;
|
protected Configuration conf;
|
||||||
protected Path backupRootPath;
|
protected Path backupRootPath;
|
||||||
|
protected Path restoreRootDir;
|
||||||
protected String backupId;
|
protected String backupId;
|
||||||
protected FileSystem fs;
|
protected FileSystem fs;
|
||||||
|
|
||||||
// store table name and snapshot dir mapping
|
// store table name and snapshot dir mapping
|
||||||
private final HashMap<TableName, Path> snapshotMap = new HashMap<>();
|
private final HashMap<TableName, Path> snapshotMap = new HashMap<>();
|
||||||
|
|
||||||
public RestoreTool(Configuration conf, final Path backupRootPath, final String backupId)
|
public RestoreTool(Configuration conf, final Path backupRootPath, final Path restoreRootDir,
|
||||||
throws IOException {
|
final String backupId) throws IOException {
|
||||||
this.conf = conf;
|
this.conf = conf;
|
||||||
this.backupRootPath = backupRootPath;
|
this.backupRootPath = backupRootPath;
|
||||||
this.backupId = backupId;
|
this.backupId = backupId;
|
||||||
this.fs = backupRootPath.getFileSystem(conf);
|
this.fs = backupRootPath.getFileSystem(conf);
|
||||||
|
this.restoreRootDir = restoreRootDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -200,7 +202,7 @@ public class RestoreTool {
|
||||||
}
|
}
|
||||||
RestoreJob restoreService = BackupRestoreFactory.getRestoreJob(conf);
|
RestoreJob restoreService = BackupRestoreFactory.getRestoreJob(conf);
|
||||||
|
|
||||||
restoreService.run(logDirs, tableNames, newTableNames, false);
|
restoreService.run(logDirs, tableNames, restoreRootDir, newTableNames, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -350,8 +352,8 @@ public class RestoreTool {
|
||||||
RestoreJob restoreService = BackupRestoreFactory.getRestoreJob(conf);
|
RestoreJob restoreService = BackupRestoreFactory.getRestoreJob(conf);
|
||||||
Path[] paths = new Path[regionPathList.size()];
|
Path[] paths = new Path[regionPathList.size()];
|
||||||
regionPathList.toArray(paths);
|
regionPathList.toArray(paths);
|
||||||
restoreService.run(paths, new TableName[] { tableName }, new TableName[] { newTableName },
|
restoreService.run(paths, new TableName[] { tableName }, restoreRootDir,
|
||||||
true);
|
new TableName[] { newTableName }, true);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error(e.toString(), e);
|
LOG.error(e.toString(), e);
|
||||||
|
|
|
@ -17,13 +17,21 @@
|
||||||
*/
|
*/
|
||||||
package org.apache.hadoop.hbase.backup;
|
package org.apache.hadoop.hbase.backup;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
import org.apache.hadoop.fs.FileSystem;
|
||||||
|
import org.apache.hadoop.fs.Path;
|
||||||
import org.apache.hadoop.hbase.HBaseClassTestRule;
|
import org.apache.hadoop.hbase.HBaseClassTestRule;
|
||||||
import org.apache.hadoop.hbase.HBaseTestingUtil;
|
import org.apache.hadoop.hbase.HBaseTestingUtil;
|
||||||
|
import org.apache.hadoop.hbase.HConstants;
|
||||||
import org.apache.hadoop.hbase.TableName;
|
import org.apache.hadoop.hbase.TableName;
|
||||||
|
import org.apache.hadoop.hbase.backup.impl.BackupAdminImpl;
|
||||||
|
import org.apache.hadoop.hbase.backup.mapreduce.MapReduceHFileSplitterJob;
|
||||||
import org.apache.hadoop.hbase.backup.util.BackupUtils;
|
import org.apache.hadoop.hbase.backup.util.BackupUtils;
|
||||||
import org.apache.hadoop.hbase.client.Admin;
|
import org.apache.hadoop.hbase.client.Admin;
|
||||||
|
import org.apache.hadoop.hbase.client.ConnectionFactory;
|
||||||
import org.apache.hadoop.hbase.testclassification.LargeTests;
|
import org.apache.hadoop.hbase.testclassification.LargeTests;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.junit.ClassRule;
|
import org.junit.ClassRule;
|
||||||
|
@ -72,4 +80,51 @@ public class TestRemoteRestore extends TestBackupBase {
|
||||||
TEST_UTIL.deleteTable(table1_restore);
|
TEST_UTIL.deleteTable(table1_restore);
|
||||||
hba.close();
|
hba.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that restore jobs can be run on a standalone mapreduce cluster. Ensures hfiles output
|
||||||
|
* via {@link MapReduceHFileSplitterJob} exist on correct filesystem.
|
||||||
|
* @throws Exception if doing the backup or an operation on the tables fails
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testFullRestoreRemoteWithAlternateRestoreOutputDir() throws Exception {
|
||||||
|
LOG.info("test remote full backup on a single table with alternate restore output dir");
|
||||||
|
String backupId =
|
||||||
|
backupTables(BackupType.FULL, toList(table1.getNameAsString()), BACKUP_REMOTE_ROOT_DIR);
|
||||||
|
LOG.info("backup complete");
|
||||||
|
TableName[] tableset = new TableName[] { table1 };
|
||||||
|
TableName[] tablemap = new TableName[] { table1_restore };
|
||||||
|
|
||||||
|
HBaseTestingUtil mrTestUtil = new HBaseTestingUtil();
|
||||||
|
mrTestUtil.setZkCluster(TEST_UTIL.getZkCluster());
|
||||||
|
mrTestUtil.startMiniDFSCluster(3);
|
||||||
|
mrTestUtil.startMiniMapReduceCluster();
|
||||||
|
|
||||||
|
Configuration testUtilConf = TEST_UTIL.getConnection().getConfiguration();
|
||||||
|
Configuration conf = new Configuration(mrTestUtil.getConfiguration());
|
||||||
|
conf.set(HConstants.ZOOKEEPER_ZNODE_PARENT,
|
||||||
|
testUtilConf.get(HConstants.ZOOKEEPER_ZNODE_PARENT));
|
||||||
|
conf.set(HConstants.MASTER_ADDRS_KEY, testUtilConf.get(HConstants.MASTER_ADDRS_KEY));
|
||||||
|
|
||||||
|
new BackupAdminImpl(ConnectionFactory.createConnection(conf))
|
||||||
|
.restore(new RestoreRequest.Builder().withBackupRootDir(BACKUP_REMOTE_ROOT_DIR)
|
||||||
|
.withRestoreRootDir(BACKUP_ROOT_DIR).withBackupId(backupId).withCheck(false)
|
||||||
|
.withFromTables(tableset).withToTables(tablemap).withOvewrite(false).build());
|
||||||
|
|
||||||
|
Path hfileOutputPath = new Path(
|
||||||
|
new Path(conf.get(MapReduceHFileSplitterJob.BULK_OUTPUT_CONF_KEY)).toUri().getPath());
|
||||||
|
|
||||||
|
// files exist on hbase cluster
|
||||||
|
FileSystem fileSystem = FileSystem.get(TEST_UTIL.getConfiguration());
|
||||||
|
assertTrue(fileSystem.exists(hfileOutputPath));
|
||||||
|
|
||||||
|
// files don't exist on MR cluster
|
||||||
|
fileSystem = FileSystem.get(conf);
|
||||||
|
assertFalse(fileSystem.exists(hfileOutputPath));
|
||||||
|
|
||||||
|
Admin hba = TEST_UTIL.getAdmin();
|
||||||
|
assertTrue(hba.tableExists(table1_restore));
|
||||||
|
TEST_UTIL.deleteTable(table1_restore);
|
||||||
|
hba.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue