This is an automated email from the ASF dual-hosted git repository.

ndimiduk pushed a commit to branch branch-2.6
in repository https://gitbox.apache.org/repos/asf/hbase.git


The following commit(s) were added to refs/heads/branch-2.6 by this push:
     new baabc1d7c4e HBASE-28882 Backup restores are broken if the backup has 
moved locations (#6294)
baabc1d7c4e is described below

commit baabc1d7c4e639a8e9a5838ff14494b75a769522
Author: Ray Mattingly <[email protected]>
AuthorDate: Tue Dec 3 09:10:35 2024 -0500

    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]>
---
 .../org/apache/hadoop/hbase/backup/BackupInfo.java |  2 +-
 .../hadoop/hbase/backup/HBackupFileSystem.java     | 22 +++++++++-
 .../hadoop/hbase/backup/impl/BackupManifest.java   | 24 ++++++++++-
 .../backup/mapreduce/MapReduceBackupMergeJob.java  |  7 ++++
 .../hadoop/hbase/backup/TestHBackupFileSystem.java | 48 ++++++++++++++++++++++
 .../hadoop/hbase/backup/TestIncrementalBackup.java | 24 ++++++++++-
 .../src/main/protobuf/Backup.proto                 |  3 +-
 7 files changed, 124 insertions(+), 6 deletions(-)

diff --git 
a/hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/BackupInfo.java 
b/hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/BackupInfo.java
index 5a0094740d6..f0dc10b8361 100644
--- a/hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/BackupInfo.java
+++ b/hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/BackupInfo.java
@@ -267,7 +267,7 @@ public class BackupInfo implements Comparable<BackupInfo> {
   }
 
   public void setFailedMsg(String failedMsg) {
-    if (failedMsg.length() > MAX_FAILED_MESSAGE_LENGTH) {
+    if (failedMsg != null && failedMsg.length() > MAX_FAILED_MESSAGE_LENGTH) {
       failedMsg = failedMsg.substring(0, MAX_FAILED_MESSAGE_LENGTH);
     }
     this.failedMsg = failedMsg;
diff --git 
a/hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/HBackupFileSystem.java
 
b/hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/HBackupFileSystem.java
index 2b27e752747..d5fd9aaf4c3 100644
--- 
a/hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/HBackupFileSystem.java
+++ 
b/hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/HBackupFileSystem.java
@@ -17,6 +17,7 @@
  */
 package org.apache.hadoop.hbase.backup;
 
+import com.google.errorprone.annotations.RestrictedApi;
 import java.io.IOException;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.FileSystem;
@@ -27,6 +28,8 @@ import org.apache.yetus.audience.InterfaceAudience;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import org.apache.hbase.thirdparty.com.google.common.base.Preconditions;
+
 /**
  * View to an on-disk Backup Image FileSytem Provides the set of methods 
necessary to interact with
  * the on-disk Backup Image data.
@@ -97,10 +100,18 @@ public final class HBackupFileSystem {
 
   private static Path getManifestPath(Configuration conf, Path backupRootPath, 
String backupId)
     throws IOException {
+    return getManifestPath(conf, backupRootPath, backupId, true);
+  }
+
+  /* Visible for testing only */
+  @RestrictedApi(explanation = "Should only be called internally or in tests", 
link = "",
+      allowedOnPath = 
"(.*/src/test/.*|.*/org/apache/hadoop/hbase/backup/HBackupFileSystem.java)")
+  static Path getManifestPath(Configuration conf, Path backupRootPath, String 
backupId,
+    boolean throwIfNotFound) throws IOException {
     FileSystem fs = backupRootPath.getFileSystem(conf);
     Path manifestPath = new Path(getBackupPath(backupRootPath.toString(), 
backupId) + Path.SEPARATOR
       + BackupManifest.MANIFEST_FILE_NAME);
-    if (!fs.exists(manifestPath)) {
+    if (throwIfNotFound && !fs.exists(manifestPath)) {
       String errorMsg = "Could not find backup manifest " + 
BackupManifest.MANIFEST_FILE_NAME
         + " for " + backupId + ". File " + manifestPath + " does not exists. 
Did " + backupId
         + " correspond to previously taken backup ?";
@@ -109,6 +120,15 @@ public final class HBackupFileSystem {
     return manifestPath;
   }
 
+  public static Path getRootDirFromBackupPath(Path backupPath, String 
backupId) {
+    if (backupPath.getName().equals(BackupManifest.MANIFEST_FILE_NAME)) {
+      backupPath = backupPath.getParent();
+    }
+    Preconditions.checkArgument(backupPath.getName().equals(backupId),
+      String.format("Backup path %s must end in backupId %s", backupPath, 
backupId));
+    return backupPath.getParent();
+  }
+
   public static BackupManifest getManifest(Configuration conf, Path 
backupRootPath, String backupId)
     throws IOException {
     BackupManifest manifest =
diff --git 
a/hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/impl/BackupManifest.java
 
b/hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/impl/BackupManifest.java
index d66b5886794..59ae3857f2e 100644
--- 
a/hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/impl/BackupManifest.java
+++ 
b/hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/impl/BackupManifest.java
@@ -17,6 +17,7 @@
  */
 package org.apache.hadoop.hbase.backup.impl;
 
+import com.google.errorprone.annotations.RestrictedApi;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -158,12 +159,17 @@ public class BackupManifest {
       return image;
     }
 
+    /**
+     * This method deliberately does not include the backup root dir on the 
produced proto. This is
+     * because we don't want to persist the root dir on the backup itself, so 
that backups can still
+     * be used after they have moved locations. A restore's operator will 
always provide the root
+     * dir.
+     */
     BackupProtos.BackupImage toProto() {
       BackupProtos.BackupImage.Builder builder = 
BackupProtos.BackupImage.newBuilder();
       builder.setBackupId(backupId);
       builder.setCompleteTs(completeTs);
       builder.setStartTs(startTs);
-      builder.setBackupRootDir(rootDir);
       if (type == BackupType.FULL) {
         builder.setBackupType(BackupProtos.BackupType.FULL);
       } else {
@@ -439,7 +445,7 @@ public class BackupManifest {
           } catch (Exception e) {
             throw new BackupException(e);
           }
-          this.backupImage = BackupImage.fromProto(proto);
+          this.backupImage = hydrateRootDir(BackupImage.fromProto(proto), 
backupPath);
           LOG.debug("Loaded manifest instance from manifest file: "
             + BackupUtils.getPath(subFile.getPath()));
           return;
@@ -452,6 +458,20 @@ public class BackupManifest {
     }
   }
 
+  /* Visible for testing only */
+  @RestrictedApi(explanation = "Should only be called internally or in tests", 
link = "",
+      allowedOnPath = 
"(.*/src/test/.*|.*/org/apache/hadoop/hbase/backup/impl/BackupManifest.java)")
+  public static BackupImage hydrateRootDir(BackupImage backupImage, Path 
backupPath)
+    throws IOException {
+    String providedRootDir =
+      HBackupFileSystem.getRootDirFromBackupPath(backupPath, 
backupImage.backupId).toString();
+    backupImage.setRootDir(providedRootDir);
+    for (BackupImage ancestor : backupImage.getAncestors()) {
+      ancestor.setRootDir(providedRootDir);
+    }
+    return backupImage;
+  }
+
   public BackupType getType() {
     return backupImage.getType();
   }
diff --git 
a/hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/mapreduce/MapReduceBackupMergeJob.java
 
b/hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/mapreduce/MapReduceBackupMergeJob.java
index 1f9ae16c1df..0549a3371cf 100644
--- 
a/hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/mapreduce/MapReduceBackupMergeJob.java
+++ 
b/hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/mapreduce/MapReduceBackupMergeJob.java
@@ -228,6 +228,7 @@ public class MapReduceBackupMergeJob implements 
BackupMergeJob {
       if (
         fileName.indexOf(FSTableDescriptors.TABLEINFO_DIR) > 0
           || fileName.indexOf(HRegionFileSystem.REGION_INFO_FILE) > 0
+          || fileName.indexOf(BackupManifest.MANIFEST_FILE_NAME) > 0
       ) {
         toKeep.add(p);
       }
@@ -235,6 +236,7 @@ public class MapReduceBackupMergeJob implements 
BackupMergeJob {
     // Copy meta to destination
     for (Path p : toKeep) {
       Path newPath = convertToDest(p, backupDirPath);
+      LOG.info("Copying tmp metadata from {} to {}", p, newPath);
       copyFile(fs, p, newPath);
     }
   }
@@ -310,8 +312,11 @@ public class MapReduceBackupMergeJob implements 
BackupMergeJob {
     List<String> backupsToDelete) throws IllegalArgumentException, IOException 
{
     BackupManifest manifest =
       HBackupFileSystem.getManifest(conf, new Path(backupRoot), 
mergedBackupId);
+    LOG.info("Removing ancestors from merged backup {} : {}", mergedBackupId, 
backupsToDelete);
     manifest.getBackupImage().removeAncestors(backupsToDelete);
     // save back
+    LOG.info("Creating new manifest file for merged backup {} at root {}", 
mergedBackupId,
+      backupRoot);
     manifest.store(conf);
   }
 
@@ -320,12 +325,14 @@ public class MapReduceBackupMergeJob implements 
BackupMergeJob {
     // Delete from backup system table
     try (BackupSystemTable table = new BackupSystemTable(conn)) {
       for (String backupId : backupIds) {
+        LOG.info("Removing metadata for backup {}", backupId);
         table.deleteBackupInfo(backupId);
       }
     }
 
     // Delete from file system
     for (String backupId : backupIds) {
+      LOG.info("Purging backup {} from FileSystem", backupId);
       Path backupDirPath = HBackupFileSystem.getBackupPath(backupRoot, 
backupId);
 
       if (!fs.delete(backupDirPath, true)) {
diff --git 
a/hbase-backup/src/test/java/org/apache/hadoop/hbase/backup/TestHBackupFileSystem.java
 
b/hbase-backup/src/test/java/org/apache/hadoop/hbase/backup/TestHBackupFileSystem.java
new file mode 100644
index 00000000000..3bdc2f62079
--- /dev/null
+++ 
b/hbase-backup/src/test/java/org/apache/hadoop/hbase/backup/TestHBackupFileSystem.java
@@ -0,0 +1,48 @@
+/*
+ * 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.hadoop.hbase.backup;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.hbase.HBaseClassTestRule;
+import org.apache.hadoop.hbase.testclassification.SmallTests;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+@Category(SmallTests.class)
+public class TestHBackupFileSystem {
+
+  @ClassRule
+  public static final HBaseClassTestRule CLASS_RULE =
+    HBaseClassTestRule.forClass(TestHBackupFileSystem.class);
+
+  private static final Path ROOT_DIR = new Path("/backup/root");
+  private static final String BACKUP_ID = "123";
+
+  @Test
+  public void testRootDirManifestPathConversion() throws IOException {
+    Path manifestPath =
+      HBackupFileSystem.getManifestPath(new Configuration(), ROOT_DIR, 
BACKUP_ID, false);
+    Path convertedRootDir = 
HBackupFileSystem.getRootDirFromBackupPath(manifestPath, BACKUP_ID);
+    assertEquals(ROOT_DIR, convertedRootDir);
+  }
+}
diff --git 
a/hbase-backup/src/test/java/org/apache/hadoop/hbase/backup/TestIncrementalBackup.java
 
b/hbase-backup/src/test/java/org/apache/hadoop/hbase/backup/TestIncrementalBackup.java
index 1e43b2aeb72..fce64c276e6 100644
--- 
a/hbase-backup/src/test/java/org/apache/hadoop/hbase/backup/TestIncrementalBackup.java
+++ 
b/hbase-backup/src/test/java/org/apache/hadoop/hbase/backup/TestIncrementalBackup.java
@@ -106,7 +106,10 @@ public class TestIncrementalBackup extends TestBackupBase {
       insertIntoTable(conn, table1, mobName, 3, NB_ROWS_FAM3).close();
       Admin admin = conn.getAdmin();
       BackupAdminImpl client = new BackupAdminImpl(conn);
+      BackupRequest request = createBackupRequest(BackupType.FULL, tables, 
BACKUP_ROOT_DIR);
       String backupIdFull = takeFullBackup(tables, client);
+      validateRootPathCanBeOverridden(BACKUP_ROOT_DIR, backupIdFull);
+      assertTrue(checkSucceeded(backupIdFull));
 
       // #2 - insert some data to table
       Table t1 = insertIntoTable(conn, table1, famName, 1, ADD_ROWS);
@@ -148,12 +151,13 @@ public class TestIncrementalBackup extends TestBackupBase 
{
 
       // #3 - incremental backup for multiple tables
       tables = Lists.newArrayList(table1, table2);
-      BackupRequest request = createBackupRequest(BackupType.INCREMENTAL, 
tables, BACKUP_ROOT_DIR);
+      request = createBackupRequest(BackupType.INCREMENTAL, tables, 
BACKUP_ROOT_DIR);
       String backupIdIncMultiple = client.backupTables(request);
       assertTrue(checkSucceeded(backupIdIncMultiple));
       BackupManifest manifest =
         HBackupFileSystem.getManifest(conf1, new Path(BACKUP_ROOT_DIR), 
backupIdIncMultiple);
       assertEquals(Sets.newHashSet(table1, table2), new 
HashSet<>(manifest.getTableList()));
+      validateRootPathCanBeOverridden(BACKUP_ROOT_DIR, backupIdIncMultiple);
 
       // add column family f2 to table1
       // drop column family f3
@@ -181,6 +185,7 @@ public class TestIncrementalBackup extends TestBackupBase {
       request = createBackupRequest(BackupType.INCREMENTAL, tables, 
BACKUP_ROOT_DIR);
       String backupIdIncMultiple2 = client.backupTables(request);
       assertTrue(checkSucceeded(backupIdIncMultiple2));
+      validateRootPathCanBeOverridden(BACKUP_ROOT_DIR, backupIdIncMultiple2);
 
       // #5 - restore full backup for all tables
       TableName[] tablesRestoreFull = new TableName[] { table1, table2 };
@@ -247,4 +252,21 @@ public class TestIncrementalBackup extends TestBackupBase {
     checkSucceeded(backupId);
     return backupId;
   }
+
+  /**
+   * Check that backup manifest can be produced for a different root. Users 
may want to move
+   * existing backups to a different location.
+   */
+  private void validateRootPathCanBeOverridden(String originalPath, String 
backupId)
+    throws IOException {
+    String anotherRootDir = "/some/other/root/dir";
+    Path anotherPath = new Path(anotherRootDir, backupId);
+    BackupManifest.BackupImage differentLocationImage = 
BackupManifest.hydrateRootDir(
+      HBackupFileSystem.getManifest(conf1, new Path(originalPath), 
backupId).getBackupImage(),
+      anotherPath);
+    assertEquals(differentLocationImage.getRootDir(), anotherRootDir);
+    for (BackupManifest.BackupImage ancestor : 
differentLocationImage.getAncestors()) {
+      assertEquals(anotherRootDir, ancestor.getRootDir());
+    }
+  }
 }
diff --git a/hbase-protocol-shaded/src/main/protobuf/Backup.proto 
b/hbase-protocol-shaded/src/main/protobuf/Backup.proto
index afe43122f84..a114001ba50 100644
--- a/hbase-protocol-shaded/src/main/protobuf/Backup.proto
+++ b/hbase-protocol-shaded/src/main/protobuf/Backup.proto
@@ -54,11 +54,12 @@ message TableServerTimestamp {
 
 /**
  * Structure keeps relevant info for backup restore session
+ * backup_root_dir was marked as deprecated in HBase 2.6.2, will be removed in 
4.0.0.
  */
 message BackupImage {
   optional string backup_id = 1;
   optional BackupType backup_type = 2;
-  optional string backup_root_dir = 3;
+  optional string backup_root_dir = 3 [deprecated = true];
   repeated TableName table_list = 4;
   optional uint64 start_ts = 5;
   optional uint64 complete_ts = 6;

Reply via email to