Skip to content

Commit c556dde

Browse files
rmdmattinglyRay Mattingly
andauthored
HBASE-28882 Backup restores are broken if the backup has moved locations (#6294)
Co-authored-by: Ray Mattingly <[email protected]> Signed-off-by: Nick Dimiduk <[email protected]> Reviewed-by: Dieter De Paepe <[email protected]>
1 parent 240bc3f commit c556dde

File tree

7 files changed

+124
-6
lines changed

7 files changed

+124
-6
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ public String getFailedMsg() {
267267
}
268268

269269
public void setFailedMsg(String failedMsg) {
270-
if (failedMsg.length() > MAX_FAILED_MESSAGE_LENGTH) {
270+
if (failedMsg != null && failedMsg.length() > MAX_FAILED_MESSAGE_LENGTH) {
271271
failedMsg = failedMsg.substring(0, MAX_FAILED_MESSAGE_LENGTH);
272272
}
273273
this.failedMsg = failedMsg;

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

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
*/
1818
package org.apache.hadoop.hbase.backup;
1919

20+
import com.google.errorprone.annotations.RestrictedApi;
2021
import java.io.IOException;
2122
import org.apache.hadoop.conf.Configuration;
2223
import org.apache.hadoop.fs.FileSystem;
@@ -27,6 +28,8 @@
2728
import org.slf4j.Logger;
2829
import org.slf4j.LoggerFactory;
2930

31+
import org.apache.hbase.thirdparty.com.google.common.base.Preconditions;
32+
3033
/**
3134
* View to an on-disk Backup Image FileSytem Provides the set of methods necessary to interact with
3235
* the on-disk Backup Image data.
@@ -97,10 +100,18 @@ public static Path getTableBackupPath(TableName tableName, Path backupRootPath,
97100

98101
private static Path getManifestPath(Configuration conf, Path backupRootPath, String backupId)
99102
throws IOException {
103+
return getManifestPath(conf, backupRootPath, backupId, true);
104+
}
105+
106+
/* Visible for testing only */
107+
@RestrictedApi(explanation = "Should only be called internally or in tests", link = "",
108+
allowedOnPath = "(.*/src/test/.*|.*/org/apache/hadoop/hbase/backup/HBackupFileSystem.java)")
109+
static Path getManifestPath(Configuration conf, Path backupRootPath, String backupId,
110+
boolean throwIfNotFound) throws IOException {
100111
FileSystem fs = backupRootPath.getFileSystem(conf);
101112
Path manifestPath = new Path(getBackupPath(backupRootPath.toString(), backupId) + Path.SEPARATOR
102113
+ BackupManifest.MANIFEST_FILE_NAME);
103-
if (!fs.exists(manifestPath)) {
114+
if (throwIfNotFound && !fs.exists(manifestPath)) {
104115
String errorMsg = "Could not find backup manifest " + BackupManifest.MANIFEST_FILE_NAME
105116
+ " for " + backupId + ". File " + manifestPath + " does not exists. Did " + backupId
106117
+ " correspond to previously taken backup ?";
@@ -109,6 +120,15 @@ private static Path getManifestPath(Configuration conf, Path backupRootPath, Str
109120
return manifestPath;
110121
}
111122

123+
public static Path getRootDirFromBackupPath(Path backupPath, String backupId) {
124+
if (backupPath.getName().equals(BackupManifest.MANIFEST_FILE_NAME)) {
125+
backupPath = backupPath.getParent();
126+
}
127+
Preconditions.checkArgument(backupPath.getName().equals(backupId),
128+
String.format("Backup path %s must end in backupId %s", backupPath, backupId));
129+
return backupPath.getParent();
130+
}
131+
112132
public static BackupManifest getManifest(Configuration conf, Path backupRootPath, String backupId)
113133
throws IOException {
114134
BackupManifest manifest =

hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/impl/BackupManifest.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
*/
1818
package org.apache.hadoop.hbase.backup.impl;
1919

20+
import com.google.errorprone.annotations.RestrictedApi;
2021
import java.io.IOException;
2122
import java.util.ArrayList;
2223
import java.util.Collections;
@@ -158,12 +159,17 @@ static BackupImage fromProto(BackupProtos.BackupImage im) {
158159
return image;
159160
}
160161

162+
/**
163+
* This method deliberately does not include the backup root dir on the produced proto. This is
164+
* because we don't want to persist the root dir on the backup itself, so that backups can still
165+
* be used after they have moved locations. A restore's operator will always provide the root
166+
* dir.
167+
*/
161168
BackupProtos.BackupImage toProto() {
162169
BackupProtos.BackupImage.Builder builder = BackupProtos.BackupImage.newBuilder();
163170
builder.setBackupId(backupId);
164171
builder.setCompleteTs(completeTs);
165172
builder.setStartTs(startTs);
166-
builder.setBackupRootDir(rootDir);
167173
if (type == BackupType.FULL) {
168174
builder.setBackupType(BackupProtos.BackupType.FULL);
169175
} else {
@@ -439,7 +445,7 @@ public BackupManifest(FileSystem fs, Path backupPath) throws BackupException {
439445
} catch (Exception e) {
440446
throw new BackupException(e);
441447
}
442-
this.backupImage = BackupImage.fromProto(proto);
448+
this.backupImage = hydrateRootDir(BackupImage.fromProto(proto), backupPath);
443449
LOG.debug("Loaded manifest instance from manifest file: "
444450
+ BackupUtils.getPath(subFile.getPath()));
445451
return;
@@ -452,6 +458,20 @@ public BackupManifest(FileSystem fs, Path backupPath) throws BackupException {
452458
}
453459
}
454460

461+
/* Visible for testing only */
462+
@RestrictedApi(explanation = "Should only be called internally or in tests", link = "",
463+
allowedOnPath = "(.*/src/test/.*|.*/org/apache/hadoop/hbase/backup/impl/BackupManifest.java)")
464+
public static BackupImage hydrateRootDir(BackupImage backupImage, Path backupPath)
465+
throws IOException {
466+
String providedRootDir =
467+
HBackupFileSystem.getRootDirFromBackupPath(backupPath, backupImage.backupId).toString();
468+
backupImage.setRootDir(providedRootDir);
469+
for (BackupImage ancestor : backupImage.getAncestors()) {
470+
ancestor.setRootDir(providedRootDir);
471+
}
472+
return backupImage;
473+
}
474+
455475
public BackupType getType() {
456476
return backupImage.getType();
457477
}

hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/mapreduce/MapReduceBackupMergeJob.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,13 +228,15 @@ protected void copyMetaData(FileSystem fs, Path tmpBackupDir, Path backupDirPath
228228
if (
229229
fileName.indexOf(FSTableDescriptors.TABLEINFO_DIR) > 0
230230
|| fileName.indexOf(HRegionFileSystem.REGION_INFO_FILE) > 0
231+
|| fileName.indexOf(BackupManifest.MANIFEST_FILE_NAME) > 0
231232
) {
232233
toKeep.add(p);
233234
}
234235
}
235236
// Copy meta to destination
236237
for (Path p : toKeep) {
237238
Path newPath = convertToDest(p, backupDirPath);
239+
LOG.info("Copying tmp metadata from {} to {}", p, newPath);
238240
copyFile(fs, p, newPath);
239241
}
240242
}
@@ -310,8 +312,11 @@ protected void updateBackupManifest(String backupRoot, String mergedBackupId,
310312
List<String> backupsToDelete) throws IllegalArgumentException, IOException {
311313
BackupManifest manifest =
312314
HBackupFileSystem.getManifest(conf, new Path(backupRoot), mergedBackupId);
315+
LOG.info("Removing ancestors from merged backup {} : {}", mergedBackupId, backupsToDelete);
313316
manifest.getBackupImage().removeAncestors(backupsToDelete);
314317
// save back
318+
LOG.info("Creating new manifest file for merged backup {} at root {}", mergedBackupId,
319+
backupRoot);
315320
manifest.store(conf);
316321
}
317322

@@ -320,12 +325,14 @@ protected void deleteBackupImages(List<String> backupIds, Connection conn, FileS
320325
// Delete from backup system table
321326
try (BackupSystemTable table = new BackupSystemTable(conn)) {
322327
for (String backupId : backupIds) {
328+
LOG.info("Removing metadata for backup {}", backupId);
323329
table.deleteBackupInfo(backupId);
324330
}
325331
}
326332

327333
// Delete from file system
328334
for (String backupId : backupIds) {
335+
LOG.info("Purging backup {} from FileSystem", backupId);
329336
Path backupDirPath = HBackupFileSystem.getBackupPath(backupRoot, backupId);
330337

331338
if (!fs.delete(backupDirPath, true)) {
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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.junit.Assert.assertEquals;
21+
22+
import java.io.IOException;
23+
import org.apache.hadoop.conf.Configuration;
24+
import org.apache.hadoop.fs.Path;
25+
import org.apache.hadoop.hbase.HBaseClassTestRule;
26+
import org.apache.hadoop.hbase.testclassification.SmallTests;
27+
import org.junit.ClassRule;
28+
import org.junit.Test;
29+
import org.junit.experimental.categories.Category;
30+
31+
@Category(SmallTests.class)
32+
public class TestHBackupFileSystem {
33+
34+
@ClassRule
35+
public static final HBaseClassTestRule CLASS_RULE =
36+
HBaseClassTestRule.forClass(TestHBackupFileSystem.class);
37+
38+
private static final Path ROOT_DIR = new Path("/backup/root");
39+
private static final String BACKUP_ID = "123";
40+
41+
@Test
42+
public void testRootDirManifestPathConversion() throws IOException {
43+
Path manifestPath =
44+
HBackupFileSystem.getManifestPath(new Configuration(), ROOT_DIR, BACKUP_ID, false);
45+
Path convertedRootDir = HBackupFileSystem.getRootDirFromBackupPath(manifestPath, BACKUP_ID);
46+
assertEquals(ROOT_DIR, convertedRootDir);
47+
}
48+
}

hbase-backup/src/test/java/org/apache/hadoop/hbase/backup/TestIncrementalBackup.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,10 @@ public void TestIncBackupRestore() throws Exception {
106106
insertIntoTable(conn, table1, mobName, 3, NB_ROWS_FAM3).close();
107107
Admin admin = conn.getAdmin();
108108
BackupAdminImpl client = new BackupAdminImpl(conn);
109+
BackupRequest request = createBackupRequest(BackupType.FULL, tables, BACKUP_ROOT_DIR);
109110
String backupIdFull = takeFullBackup(tables, client);
111+
validateRootPathCanBeOverridden(BACKUP_ROOT_DIR, backupIdFull);
112+
assertTrue(checkSucceeded(backupIdFull));
110113

111114
// #2 - insert some data to table
112115
Table t1 = insertIntoTable(conn, table1, famName, 1, ADD_ROWS);
@@ -150,12 +153,13 @@ public void TestIncBackupRestore() throws Exception {
150153

151154
// #3 - incremental backup for multiple tables
152155
tables = Lists.newArrayList(table1, table2);
153-
BackupRequest request = createBackupRequest(BackupType.INCREMENTAL, tables, BACKUP_ROOT_DIR);
156+
request = createBackupRequest(BackupType.INCREMENTAL, tables, BACKUP_ROOT_DIR);
154157
String backupIdIncMultiple = client.backupTables(request);
155158
assertTrue(checkSucceeded(backupIdIncMultiple));
156159
BackupManifest manifest =
157160
HBackupFileSystem.getManifest(conf1, new Path(BACKUP_ROOT_DIR), backupIdIncMultiple);
158161
assertEquals(Sets.newHashSet(table1, table2), new HashSet<>(manifest.getTableList()));
162+
validateRootPathCanBeOverridden(BACKUP_ROOT_DIR, backupIdIncMultiple);
159163

160164
// add column family f2 to table1
161165
// drop column family f3
@@ -183,6 +187,7 @@ public void TestIncBackupRestore() throws Exception {
183187
request = createBackupRequest(BackupType.INCREMENTAL, tables, BACKUP_ROOT_DIR);
184188
String backupIdIncMultiple2 = client.backupTables(request);
185189
assertTrue(checkSucceeded(backupIdIncMultiple2));
190+
validateRootPathCanBeOverridden(BACKUP_ROOT_DIR, backupIdIncMultiple2);
186191

187192
// #5 - restore full backup for all tables
188193
TableName[] tablesRestoreFull = new TableName[] { table1, table2 };
@@ -249,4 +254,21 @@ private String takeFullBackup(List<TableName> tables, BackupAdminImpl backupAdmi
249254
checkSucceeded(backupId);
250255
return backupId;
251256
}
257+
258+
/**
259+
* Check that backup manifest can be produced for a different root. Users may want to move
260+
* existing backups to a different location.
261+
*/
262+
private void validateRootPathCanBeOverridden(String originalPath, String backupId)
263+
throws IOException {
264+
String anotherRootDir = "/some/other/root/dir";
265+
Path anotherPath = new Path(anotherRootDir, backupId);
266+
BackupManifest.BackupImage differentLocationImage = BackupManifest.hydrateRootDir(
267+
HBackupFileSystem.getManifest(conf1, new Path(originalPath), backupId).getBackupImage(),
268+
anotherPath);
269+
assertEquals(differentLocationImage.getRootDir(), anotherRootDir);
270+
for (BackupManifest.BackupImage ancestor : differentLocationImage.getAncestors()) {
271+
assertEquals(anotherRootDir, ancestor.getRootDir());
272+
}
273+
}
252274
}

hbase-protocol-shaded/src/main/protobuf/Backup.proto

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,12 @@ message TableServerTimestamp {
5454

5555
/**
5656
* Structure keeps relevant info for backup restore session
57+
* backup_root_dir was marked as deprecated in HBase 2.6.2, will be removed in 4.0.0.
5758
*/
5859
message BackupImage {
5960
optional string backup_id = 1;
6061
optional BackupType backup_type = 2;
61-
optional string backup_root_dir = 3;
62+
optional string backup_root_dir = 3 [deprecated = true];
6263
repeated TableName table_list = 4;
6364
optional uint64 start_ts = 5;
6465
optional uint64 complete_ts = 6;

0 commit comments

Comments
 (0)