This is an automated email from the ASF dual-hosted git repository. andor pushed a commit to branch HBASE-28957 in repository https://gitbox.apache.org/repos/asf/hbase.git
commit 4b0eda1a9cffcc8f35fbf404269e9dbd4e32b17e Author: Kevin Geiszler <[email protected]> AuthorDate: Wed Feb 4 09:40:39 2026 -0800 HBASE-29825: Incremental backup is failing due to incorrect timezone (#7683) Change-Id: I8702eca4adc81bad2c18ea4990d09556c9506a34 --- .../hadoop/hbase/backup/util/BackupUtils.java | 2 + .../hadoop/hbase/backup/TestBackupUtils.java | 104 +++++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/util/BackupUtils.java b/hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/util/BackupUtils.java index 5db876dc538..b0665c23fa1 100644 --- a/hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/util/BackupUtils.java +++ b/hbase-backup/src/main/java/org/apache/hadoop/hbase/backup/util/BackupUtils.java @@ -29,6 +29,7 @@ import java.io.IOException; import java.net.URLDecoder; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.time.ZoneOffset; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -978,6 +979,7 @@ public final class BackupUtils { List<String> validDirs = new ArrayList<>(); SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT); + dateFormat.setTimeZone(TimeZone.getTimeZone(ZoneOffset.UTC)); for (FileStatus dayDir : dayDirs) { if (!dayDir.isDirectory()) { diff --git a/hbase-backup/src/test/java/org/apache/hadoop/hbase/backup/TestBackupUtils.java b/hbase-backup/src/test/java/org/apache/hadoop/hbase/backup/TestBackupUtils.java index cf69476270c..08f15b37f68 100644 --- a/hbase-backup/src/test/java/org/apache/hadoop/hbase/backup/TestBackupUtils.java +++ b/hbase-backup/src/test/java/org/apache/hadoop/hbase/backup/TestBackupUtils.java @@ -23,6 +23,11 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.security.PrivilegedAction; +import java.time.Instant; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.List; +import java.util.TimeZone; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; @@ -49,6 +54,15 @@ public class TestBackupUtils { protected static HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil(); protected static Configuration conf = TEST_UTIL.getConfiguration(); + private static FileSystem dummyFs; + private static Path backupRootDir; + + @BeforeClass + public static void setUp() throws IOException { + dummyFs = TEST_UTIL.getTestFileSystem(); + backupRootDir = TEST_UTIL.getDataTestDirOnTestFS("backupUT"); + } + @Test public void testGetBulkOutputDir() { // Create a user who is not the current user @@ -112,6 +126,96 @@ public class TestBackupUtils { assertEquals(host + Addressing.HOSTNAME_PORT_SEPARATOR + port, BackupUtils.parseHostFromOldLog(testOldWalWithRegionGroupingPath)); } + } + + // Ensure getValidWalDirs() uses UTC timestamps regardless of what time zone the test is run in. + @Test + public void testGetValidWalDirForAllTimeZonesSingleDay() throws IOException { + // This UTC test time is a time when it is still "yesterday" in other time zones (such as PST) + List<String> walDateDirs = List.of("2026-01-23"); + Path walDir = new Path(backupRootDir, "WALs"); + + // 10-minute window in UTC between start and end time + long startTime = Instant.parse("2026-01-23T01:00:00Z").toEpochMilli(); + long endTime = startTime + (10 * 60 * 1000); + + testGetValidWalDirs(startTime, endTime, walDir, walDateDirs, 1, walDateDirs); + } + + // Ensure getValidWalDirs() works as expected for time ranges across multiple days for all time + // zones + @Test + public void testGetValidWalDirsForAllTimeZonesMultiDay() throws IOException { + List<String> walDateDirs = List.of("2025-12-30", "2025-12-31", "2026-01-01", "2026-01-02"); + List<String> expectedValidWalDirs = List.of("2025-12-31", "2026-01-01"); + Path walDir = new Path(backupRootDir, "WALs"); + + // 10-minute window in UTC between start and end time that spans over two days + long startTime = Instant.parse("2025-12-31T23:55:00Z").toEpochMilli(); + long endTime = Instant.parse("2026-01-01T00:05:00Z").toEpochMilli(); + + testGetValidWalDirs(startTime, endTime, walDir, walDateDirs, 2, expectedValidWalDirs); + } + + @Test + public void testGetValidWalDirExactlyMidnightUTC() throws IOException { + List<String> walDateDirs = List.of("2026-01-23"); + Path walDir = new Path(backupRootDir, "WALs"); + // This instant is UTC + long startAndEndTime = Instant.parse("2026-01-23T00:00:00.000Z").toEpochMilli(); + testGetValidWalDirs(startAndEndTime, startAndEndTime, walDir, walDateDirs, 1, walDateDirs); + } + + @Test + public void testGetValidWalDirOneMsBeforeMidnightUTC() throws IOException { + List<String> walDateDirs = List.of("2026-01-23"); + Path walDir = new Path(backupRootDir, "WALs"); + // This instant is UTC + long startAndEndTime = Instant.parse("2026-01-23T23:59:59.999Z").toEpochMilli(); + + testGetValidWalDirs(startAndEndTime, startAndEndTime, walDir, walDateDirs, 1, walDateDirs); + } + + protected void testGetValidWalDirs(long startTime, long endTime, Path walDir, + List<String> availableWalDateDirs, int numExpectedValidWalDirs, + List<String> expectedValidWalDirs) throws IOException { + TimeZone defaultTimeZone = TimeZone.getDefault(); + try { + // This UTC test time is a time when it is still "yesterday" in other time zones (such as PST) + for (String dirName : availableWalDateDirs) { + dummyFs.mkdirs(new Path(walDir, dirName)); + } + + // Ensure we can get valid WAL dirs regardless of the test environment's time zone + for (String timeZone : ZoneId.getAvailableZoneIds()) { + // Force test environment to use specified time zone + TimeZone.setDefault(TimeZone.getTimeZone(timeZone)); + + List<String> validWalDirs = BackupUtils.getValidWalDirs(TEST_UTIL.getConfiguration(), + backupRootDir, startTime, endTime); + + // Verify the correct number of valid WAL dirs was found + assertEquals("The number of valid WAL dirs should be " + numExpectedValidWalDirs + + " for time zone " + timeZone, numExpectedValidWalDirs, validWalDirs.size()); + + // Verify the list of valid WAL dirs is as expected + for (String dirName : expectedValidWalDirs) { + assertTrue("Expected " + dirName + " to be a valid WAL dir", + validWalDirs.stream().anyMatch(path -> path.endsWith("/" + dirName))); + } + + // Verify the list of valid WAL dirs does not contain anything expected to be invalid + List<String> expectedInvalidWalDirs = new ArrayList<>(availableWalDateDirs); + expectedInvalidWalDirs.removeAll(expectedValidWalDirs); + for (String dirName : expectedInvalidWalDirs) { + assertFalse("Expected " + dirName + " to NOT be a valid WAL dir", + validWalDirs.contains(dirName)); + } + } + } finally { + TimeZone.setDefault(defaultTimeZone); + dummyFs.delete(walDir, true); + } } }
