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 eda1fc44557 HBASE-29744: Data loss scenario for WAL files belonging to
RS added between backups (#7523)
eda1fc44557 is described below
commit eda1fc4455726a838902adf2ceca770b1e4516b2
Author: Hernan Romer <[email protected]>
AuthorDate: Wed Dec 17 04:42:33 2025 -0500
HBASE-29744: Data loss scenario for WAL files belonging to RS added between
backups (#7523)
Co-authored-by: Hernan Gelaf-Romer <[email protected]>
Signed-off-by: Nick Dimiduk <[email protected]>
---
.../hbase/backup/master/BackupLogCleaner.java | 82 ++++--------
.../hadoop/hbase/backup/util/BackupBoundaries.java | 149 +++++++++++++++++++++
.../hbase/backup/master/TestBackupLogCleaner.java | 147 ++++++++++++++++++--
3 files changed, 307 insertions(+), 71 deletions(-)
diff --git
a/hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/master/BackupLogCleaner.java
b/hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/master/BackupLogCleaner.java
index 6bf1edc7bf0..ea644b98818 100644
---
a/hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/master/BackupLogCleaner.java
+++
b/hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/master/BackupLogCleaner.java
@@ -18,6 +18,7 @@
package org.apache.hadoop.hbase.backup.master;
import java.io.IOException;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -32,7 +33,8 @@ import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.backup.BackupInfo;
import org.apache.hadoop.hbase.backup.BackupRestoreConstants;
import org.apache.hadoop.hbase.backup.impl.BackupManager;
-import org.apache.hadoop.hbase.backup.util.BackupUtils;
+import org.apache.hadoop.hbase.backup.impl.BackupSystemTable;
+import org.apache.hadoop.hbase.backup.util.BackupBoundaries;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.master.HMaster;
@@ -41,7 +43,6 @@ import
org.apache.hadoop.hbase.master.cleaner.BaseLogCleanerDelegate;
import org.apache.hadoop.hbase.master.region.MasterRegionFactory;
import org.apache.hadoop.hbase.net.Address;
import org.apache.hadoop.hbase.procedure2.store.wal.WALProcedureStore;
-import org.apache.hadoop.hbase.wal.WAL;
import org.apache.yetus.audience.InterfaceAudience;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -56,6 +57,8 @@ import
org.apache.hbase.thirdparty.org.apache.commons.collections4.MapUtils;
@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG)
public class BackupLogCleaner extends BaseLogCleanerDelegate {
private static final Logger LOG =
LoggerFactory.getLogger(BackupLogCleaner.class);
+ private static final long TS_BUFFER_DEFAULT = Duration.ofHours(1).toMillis();
+ static final String TS_BUFFER_KEY =
"hbase.backup.log.cleaner.timestamp.buffer.ms";
private boolean stopped = false;
private Connection conn;
@@ -86,8 +89,9 @@ public class BackupLogCleaner extends BaseLogCleanerDelegate {
* I.e. WALs with a lower (= older) or equal timestamp are no longer needed
for future incremental
* backups.
*/
- private Map<Address, Long> serverToPreservationBoundaryTs(List<BackupInfo>
backups)
+ private BackupBoundaries serverToPreservationBoundaryTs(BackupSystemTable
sysTable)
throws IOException {
+ List<BackupInfo> backups = sysTable.getBackupHistory(true);
if (LOG.isDebugEnabled()) {
LOG.debug(
"Cleaning WALs if they are older than the WAL cleanup time-boundary. "
@@ -112,27 +116,25 @@ public class BackupLogCleaner extends
BaseLogCleanerDelegate {
.collect(Collectors.joining(", ")));
}
- // This map tracks, for every RegionServer, the least recent (= oldest /
lowest timestamp)
- // inclusion in any backup. In other words, it is the timestamp boundary
up to which all backup
- // roots have included the WAL in their backup.
- Map<Address, Long> boundaries = new HashMap<>();
+ BackupBoundaries.BackupBoundariesBuilder builder =
+ BackupBoundaries.builder(getConf().getLong(TS_BUFFER_KEY,
TS_BUFFER_DEFAULT));
for (BackupInfo backupInfo : newestBackupPerRootDir.values()) {
+ long startCode =
Long.parseLong(sysTable.readBackupStartCode(backupInfo.getBackupRootDir()));
// Iterate over all tables in the timestamp map, which contains all
tables covered in the
// backup root, not just the tables included in that specific backup
(which could be a subset)
for (TableName table : backupInfo.getTableSetTimestampMap().keySet()) {
for (Map.Entry<String, Long> entry :
backupInfo.getTableSetTimestampMap().get(table)
.entrySet()) {
- Address address = Address.fromString(entry.getKey());
- Long storedTs = boundaries.get(address);
- if (storedTs == null || entry.getValue() < storedTs) {
- boundaries.put(address, entry.getValue());
- }
+ builder.addBackupTimestamps(entry.getKey(), entry.getValue(),
startCode);
}
}
}
+ BackupBoundaries boundaries = builder.build();
+
if (LOG.isDebugEnabled()) {
- for (Map.Entry<Address, Long> entry : boundaries.entrySet()) {
+ LOG.debug("Boundaries oldestStartCode: {}",
boundaries.getOldestStartCode());
+ for (Map.Entry<Address, Long> entry :
boundaries.getBoundaries().entrySet()) {
LOG.debug("Server: {}, WAL cleanup boundary: {}",
entry.getKey().getHostName(),
entry.getValue());
}
@@ -153,11 +155,10 @@ public class BackupLogCleaner extends
BaseLogCleanerDelegate {
return files;
}
- Map<Address, Long> serverToPreservationBoundaryTs;
+ BackupBoundaries boundaries;
try {
- try (BackupManager backupManager = new BackupManager(conn, getConf())) {
- serverToPreservationBoundaryTs =
- serverToPreservationBoundaryTs(backupManager.getBackupHistory(true));
+ try (BackupSystemTable sysTable = new BackupSystemTable(conn)) {
+ boundaries = serverToPreservationBoundaryTs(sysTable);
}
} catch (IOException ex) {
LOG.error("Failed to analyse backup history with exception: {}.
Retaining all logs",
@@ -165,7 +166,7 @@ public class BackupLogCleaner extends
BaseLogCleanerDelegate {
return Collections.emptyList();
}
for (FileStatus file : files) {
- if (canDeleteFile(serverToPreservationBoundaryTs, file.getPath())) {
+ if (canDeleteFile(boundaries, file.getPath())) {
filteredFiles.add(file);
}
}
@@ -200,54 +201,17 @@ public class BackupLogCleaner extends
BaseLogCleanerDelegate {
return this.stopped;
}
- protected static boolean canDeleteFile(Map<Address, Long>
addressToBoundaryTs, Path path) {
+ protected static boolean canDeleteFile(BackupBoundaries boundaries, Path
path) {
if (isHMasterWAL(path)) {
return true;
}
-
- try {
- String hostname = BackupUtils.parseHostNameFromLogFile(path);
- if (hostname == null) {
- LOG.warn(
- "Cannot parse hostname from RegionServer WAL file: {}. Ignoring
cleanup of this log",
- path);
- return false;
- }
- Address walServerAddress = Address.fromString(hostname);
- long walTimestamp = WAL.getTimestamp(path.getName());
-
- if (!addressToBoundaryTs.containsKey(walServerAddress)) {
- if (LOG.isDebugEnabled()) {
- LOG.debug("No cleanup WAL time-boundary found for server: {}. Ok to
delete file: {}",
- walServerAddress.getHostName(), path);
- }
- return true;
- }
-
- Long backupBoundary = addressToBoundaryTs.get(walServerAddress);
- if (backupBoundary >= walTimestamp) {
- if (LOG.isDebugEnabled()) {
- LOG.debug(
- "WAL cleanup time-boundary found for server {}: {}. Ok to delete
older file: {}",
- walServerAddress.getHostName(), backupBoundary, path);
- }
- return true;
- }
-
- if (LOG.isDebugEnabled()) {
- LOG.debug("WAL cleanup time-boundary found for server {}: {}. Keeping
younger file: {}",
- walServerAddress.getHostName(), backupBoundary, path);
- }
- } catch (Exception ex) {
- LOG.warn("Error occurred while filtering file: {}. Ignoring cleanup of
this log", path, ex);
- return false;
- }
- return false;
+ return boundaries.isDeletable(path);
}
private static boolean isHMasterWAL(Path path) {
String fn = path.getName();
return fn.startsWith(WALProcedureStore.LOG_PREFIX)
- || fn.endsWith(MasterRegionFactory.ARCHIVED_WAL_SUFFIX);
+ || fn.endsWith(MasterRegionFactory.ARCHIVED_WAL_SUFFIX)
+ || path.toString().contains(String.format("/%s/",
MasterRegionFactory.MASTER_STORE_DIR));
}
}
diff --git
a/hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/util/BackupBoundaries.java
b/hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/util/BackupBoundaries.java
new file mode 100644
index 00000000000..b38c1bdb68d
--- /dev/null
+++
b/hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/util/BackupBoundaries.java
@@ -0,0 +1,149 @@
+/*
+ * 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.util;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.hbase.net.Address;
+import org.apache.hadoop.hbase.wal.WAL;
+import org.apache.yetus.audience.InterfaceAudience;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Tracks time boundaries for WAL file cleanup during backup operations.
Maintains the oldest
+ * timestamp per RegionServer included in any backup, enabling safe
determination of which WAL files
+ * can be deleted without compromising backup integrity.
+ */
[email protected]
+public class BackupBoundaries {
+ private static final Logger LOG =
LoggerFactory.getLogger(BackupBoundaries.class);
+ private static final BackupBoundaries EMPTY_BOUNDARIES =
+ new BackupBoundaries(Collections.emptyMap(), Long.MAX_VALUE);
+
+ // This map tracks, for every RegionServer, the least recent (= oldest /
lowest timestamp)
+ // inclusion in any backup. In other words, it is the timestamp boundary up
to which all backup
+ // roots have included the WAL in their backup.
+ private final Map<Address, Long> boundaries;
+
+ // The minimum WAL roll timestamp from the most recent backup of each backup
root, used as a
+ // fallback cleanup boundary for RegionServers without explicit backup
boundaries (e.g., servers
+ // that joined after backups began)
+ private final long oldestStartCode;
+
+ private BackupBoundaries(Map<Address, Long> boundaries, long
oldestStartCode) {
+ this.boundaries = boundaries;
+ this.oldestStartCode = oldestStartCode;
+ }
+
+ public boolean isDeletable(Path walLogPath) {
+ try {
+ String hostname = BackupUtils.parseHostNameFromLogFile(walLogPath);
+
+ if (hostname == null) {
+ LOG.warn(
+ "Cannot parse hostname from RegionServer WAL file: {}. Ignoring
cleanup of this log",
+ walLogPath);
+ return false;
+ }
+
+ Address address = Address.fromString(hostname);
+ long pathTs = WAL.getTimestamp(walLogPath.getName());
+
+ if (!boundaries.containsKey(address)) {
+ boolean isDeletable = pathTs <= oldestStartCode;
+ if (LOG.isDebugEnabled()) {
+ LOG.debug(
+ "Boundary for {} not found. isDeletable = {} based on
oldestStartCode = {} and WAL ts of {}",
+ walLogPath, isDeletable, oldestStartCode, pathTs);
+ }
+ return isDeletable;
+ }
+
+ long backupTs = boundaries.get(address);
+ if (pathTs <= backupTs) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug(
+ "WAL cleanup time-boundary found for server {}: {}. Ok to delete
older file: {}",
+ address.getHostName(), pathTs, walLogPath);
+ }
+ return true;
+ }
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("WAL cleanup time-boundary found for server {}: {}. Keeping
younger file: {}",
+ address.getHostName(), backupTs, walLogPath);
+ }
+
+ return false;
+ } catch (Exception e) {
+ LOG.warn("Error occurred while filtering file: {}. Ignoring cleanup of
this log", walLogPath,
+ e);
+ return false;
+ }
+ }
+
+ public Map<Address, Long> getBoundaries() {
+ return boundaries;
+ }
+
+ public long getOldestStartCode() {
+ return oldestStartCode;
+ }
+
+ public static BackupBoundariesBuilder builder(long tsCleanupBuffer) {
+ return new BackupBoundariesBuilder(tsCleanupBuffer);
+ }
+
+ public static class BackupBoundariesBuilder {
+ private final Map<Address, Long> boundaries = new HashMap<>();
+ private final long tsCleanupBuffer;
+
+ private long oldestStartCode = Long.MAX_VALUE;
+
+ private BackupBoundariesBuilder(long tsCleanupBuffer) {
+ this.tsCleanupBuffer = tsCleanupBuffer;
+ }
+
+ public BackupBoundariesBuilder addBackupTimestamps(String host, long
hostLogRollTs,
+ long backupStartCode) {
+ Address address = Address.fromString(host);
+ Long storedTs = boundaries.get(address);
+ if (storedTs == null || hostLogRollTs < storedTs) {
+ boundaries.put(address, hostLogRollTs);
+ }
+
+ if (oldestStartCode > backupStartCode) {
+ oldestStartCode = backupStartCode;
+ }
+
+ return this;
+ }
+
+ public BackupBoundaries build() {
+ if (boundaries.isEmpty()) {
+ return EMPTY_BOUNDARIES;
+ }
+
+ oldestStartCode -= tsCleanupBuffer;
+ return new BackupBoundaries(boundaries, oldestStartCode);
+ }
+ }
+}
diff --git
a/hbase-backup/src/test/java/org/apache/hadoop/hbase/backup/master/TestBackupLogCleaner.java
b/hbase-backup/src/test/java/org/apache/hadoop/hbase/backup/master/TestBackupLogCleaner.java
index 0602327f9bd..2020a75de31 100644
---
a/hbase-backup/src/test/java/org/apache/hadoop/hbase/backup/master/TestBackupLogCleaner.java
+++
b/hbase-backup/src/test/java/org/apache/hadoop/hbase/backup/master/TestBackupLogCleaner.java
@@ -21,9 +21,10 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import java.io.IOException;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
@@ -32,16 +33,23 @@ import java.util.Set;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.HBaseClassTestRule;
+import org.apache.hadoop.hbase.HRegionLocation;
+import org.apache.hadoop.hbase.ServerName;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.backup.BackupType;
import org.apache.hadoop.hbase.backup.TestBackupBase;
import org.apache.hadoop.hbase.backup.impl.BackupSystemTable;
+import org.apache.hadoop.hbase.backup.util.BackupBoundaries;
+import org.apache.hadoop.hbase.backup.util.BackupUtils;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.Put;
+import org.apache.hadoop.hbase.client.RegionInfo;
import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.master.HMaster;
import org.apache.hadoop.hbase.testclassification.LargeTests;
import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.hadoop.hbase.util.JVMClusterUtil;
+import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
@@ -60,6 +68,11 @@ public class TestBackupLogCleaner extends TestBackupBase {
// implements all test cases in 1 test since incremental full backup/
// incremental backup has dependencies
+ @BeforeClass
+ public static void before() {
+ TEST_UTIL.getConfiguration().setLong(BackupLogCleaner.TS_BUFFER_KEY, 0);
+ }
+
@Test
public void testBackupLogCleaner() throws Exception {
Path backupRoot1 = new Path(BACKUP_ROOT_DIR, "root1");
@@ -197,35 +210,145 @@ public class TestBackupLogCleaner extends TestBackupBase
{
// Taking the minimum timestamp (= 2), this means all WALs preceding B3
can be deleted.
deletable = cleaner.getDeletableFiles(walFilesAfterB5);
assertEquals(toSet(walFilesAfterB2), toSet(deletable));
+ } finally {
+
TEST_UTIL.truncateTable(BackupSystemTable.getTableName(TEST_UTIL.getConfiguration())).close();
}
}
- private Set<FileStatus> mergeAsSet(Collection<FileStatus> toCopy,
Collection<FileStatus> toAdd) {
- Set<FileStatus> result = new LinkedHashSet<>(toCopy);
- result.addAll(toAdd);
- return result;
+ @Test
+ public void testDoesNotDeleteWALsFromNewServers() throws Exception {
+ Path backupRoot1 = new Path(BACKUP_ROOT_DIR, "backup1");
+ List<TableName> tableSetFull = Arrays.asList(table1, table2, table3,
table4);
+
+ JVMClusterUtil.RegionServerThread rsThread = null;
+ try (BackupSystemTable systemTable = new
BackupSystemTable(TEST_UTIL.getConnection())) {
+ LOG.info("Creating initial backup B1");
+ String backupIdB1 = backupTables(BackupType.FULL, tableSetFull,
backupRoot1.toString());
+ assertTrue(checkSucceeded(backupIdB1));
+
+ List<FileStatus> walsAfterB1 =
getListOfWALFiles(TEST_UTIL.getConfiguration());
+ LOG.info("WALs after B1: {}", walsAfterB1.size());
+
+ String startCodeStr =
systemTable.readBackupStartCode(backupRoot1.toString());
+ long b1StartCode = Long.parseLong(startCodeStr);
+ LOG.info("B1 startCode: {}", b1StartCode);
+
+ // Add a new RegionServer to the cluster
+ LOG.info("Adding new RegionServer to cluster");
+ rsThread = TEST_UTIL.getMiniHBaseCluster().startRegionServer();
+ ServerName newServerName = rsThread.getRegionServer().getServerName();
+ LOG.info("New RegionServer started: {}", newServerName);
+
+ // Move a region to the new server to ensure it creates a WAL
+ List<RegionInfo> regions = TEST_UTIL.getAdmin().getRegions(table1);
+ RegionInfo regionToMove = regions.get(0);
+
+ LOG.info("Moving region {} to new server {}",
regionToMove.getEncodedName(), newServerName);
+ TEST_UTIL.getAdmin().move(regionToMove.getEncodedNameAsBytes(),
newServerName);
+
+ TEST_UTIL.waitFor(30000, () -> {
+ try {
+ HRegionLocation location =
TEST_UTIL.getConnection().getRegionLocator(table1)
+ .getRegionLocation(regionToMove.getStartKey());
+ return location.getServerName().equals(newServerName);
+ } catch (IOException e) {
+ return false;
+ }
+ });
+
+ // Write some data to trigger WAL creation on the new server
+ try (Table t1 = TEST_UTIL.getConnection().getTable(table1)) {
+ for (int i = 0; i < 100; i++) {
+ Put p = new Put(Bytes.toBytes("newserver-row-" + i));
+ p.addColumn(famName, qualName, Bytes.toBytes("val" + i));
+ t1.put(p);
+ }
+ }
+ TEST_UTIL.getAdmin().flushRegion(regionToMove.getEncodedNameAsBytes());
+
+ List<FileStatus> walsAfterNewServer =
getListOfWALFiles(TEST_UTIL.getConfiguration());
+ LOG.info("WALs after adding new server: {}", walsAfterNewServer.size());
+ assertTrue("Should have more WALs after new server",
+ walsAfterNewServer.size() > walsAfterB1.size());
+
+ List<FileStatus> newServerWALs = new ArrayList<>(walsAfterNewServer);
+ newServerWALs.removeAll(walsAfterB1);
+ assertFalse("Should have WALs from new server", newServerWALs.isEmpty());
+
+ BackupLogCleaner cleaner = new BackupLogCleaner();
+ cleaner.setConf(TEST_UTIL.getConfiguration());
+ Map<String, Object> params = new HashMap<>(1);
+ params.put(HMaster.MASTER, TEST_UTIL.getHBaseCluster().getMaster());
+ cleaner.init(params);
+
+ Set<FileStatus> deletable =
toSet(cleaner.getDeletableFiles(walsAfterNewServer));
+ for (FileStatus newWAL : newServerWALs) {
+ assertFalse("WAL from new server should NOT be deletable: " +
newWAL.getPath(),
+ deletable.contains(newWAL));
+ }
+ } finally {
+
TEST_UTIL.truncateTable(BackupSystemTable.getTableName(TEST_UTIL.getConfiguration())).close();
+ // Clean up the RegionServer we added
+ if (rsThread != null) {
+ LOG.info("Stopping the RegionServer added for test");
+ TEST_UTIL.getMiniHBaseCluster()
+ .stopRegionServer(rsThread.getRegionServer().getServerName());
+ TEST_UTIL.getMiniHBaseCluster()
+
.waitForRegionServerToStop(rsThread.getRegionServer().getServerName(), 30000);
+ }
+ }
}
- private <T> Set<T> toSet(Iterable<T> iterable) {
- Set<T> result = new LinkedHashSet<>();
- iterable.forEach(result::add);
- return result;
+ @Test
+ public void testCanDeleteFileWithNewServerWALs() {
+ long backupStartCode = 1000000L;
+ // Old WAL from before the backup
+ Path oldWAL = new Path("/hbase/oldWALs/server1%2C60020%2C12345.500000");
+ String host = BackupUtils.parseHostNameFromLogFile(oldWAL);
+ BackupBoundaries boundaries = BackupBoundaries.builder(0L)
+ .addBackupTimestamps(host, backupStartCode, backupStartCode).build();
+
+ assertTrue("WAL older than backup should be deletable",
+ BackupLogCleaner.canDeleteFile(boundaries, oldWAL));
+
+ // WAL from exactly at the backup boundary
+ Path boundaryWAL = new
Path("/hbase/oldWALs/server1%2C60020%2C12345.1000000");
+ assertTrue("WAL at boundary should be deletable",
+ BackupLogCleaner.canDeleteFile(boundaries, boundaryWAL));
+
+ // WAL from a server that joined AFTER the backup
+ Path newServerWAL = new
Path("/hbase/oldWALs/newserver%2C60020%2C99999.1500000");
+ assertFalse("WAL from new server (after backup) should NOT be deletable",
+ BackupLogCleaner.canDeleteFile(boundaries, newServerWAL));
}
@Test
public void testCleansUpHMasterWal() {
Path path = new Path("/hbase/MasterData/WALs/hmaster,60000,1718808578163");
- assertTrue(BackupLogCleaner.canDeleteFile(Collections.emptyMap(), path));
+
assertTrue(BackupLogCleaner.canDeleteFile(BackupBoundaries.builder(0L).build(),
path));
}
@Test
public void testCleansUpArchivedHMasterWal() {
+ BackupBoundaries empty = BackupBoundaries.builder(0L).build();
Path normalPath =
new
Path("/hbase/oldWALs/hmaster%2C60000%2C1716224062663.1716247552189$masterlocalwal$");
- assertTrue(BackupLogCleaner.canDeleteFile(Collections.emptyMap(),
normalPath));
+ assertTrue(BackupLogCleaner.canDeleteFile(empty, normalPath));
Path masterPath = new Path(
"/hbase/MasterData/oldWALs/hmaster%2C60000%2C1716224062663.1716247552189$masterlocalwal$");
- assertTrue(BackupLogCleaner.canDeleteFile(Collections.emptyMap(),
masterPath));
+ assertTrue(BackupLogCleaner.canDeleteFile(empty, masterPath));
+ }
+
+ private Set<FileStatus> mergeAsSet(Collection<FileStatus> toCopy,
Collection<FileStatus> toAdd) {
+ Set<FileStatus> result = new LinkedHashSet<>(toCopy);
+ result.addAll(toAdd);
+ return result;
+ }
+
+ private <T> Set<T> toSet(Iterable<T> iterable) {
+ Set<T> result = new LinkedHashSet<>();
+ iterable.forEach(result::add);
+ return result;
}
}