001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.backup.util;
019
020import static org.junit.jupiter.api.Assertions.assertEquals;
021import static org.junit.jupiter.api.Assertions.assertNotNull;
022import static org.junit.jupiter.api.Assertions.assertThrows;
023import static org.junit.jupiter.api.Assertions.assertTrue;
024
025import java.io.IOException;
026import org.apache.hadoop.conf.Configuration;
027import org.apache.hadoop.fs.FileSystem;
028import org.apache.hadoop.fs.Path;
029import org.apache.hadoop.hbase.HBaseConfiguration;
030import org.apache.hadoop.hbase.testclassification.SmallTests;
031import org.junit.jupiter.api.Tag;
032import org.junit.jupiter.api.Test;
033import org.junit.jupiter.api.io.TempDir;
034
035/**
036 * Unit tests for {@link BackupFileSystemManager}.
037 */
038@Tag(SmallTests.TAG)
039public class TestBackupFileSystemManager {
040
041  @TempDir
042  java.nio.file.Path tmp;
043
044  /**
045   * extractWalPathInfo: happy path where WALs dir has a prefix. e.g.
046   * /some/prefix/WALs/2025-09-14/some-wal
047   */
048  @Test
049  public void testExtractWalPathInfo_withPrefix() throws Exception {
050    Path walPath =
051      new Path("/some/prefix/" + BackupFileSystemManager.WALS_DIR + "/2025-09-14/wal-000");
052    BackupFileSystemManager.WalPathInfo info = BackupFileSystemManager.extractWalPathInfo(walPath);
053
054    assertNotNull(info, "WalPathInfo should not be null");
055    // prefixBeforeWALs should be "/some/prefix"
056    assertEquals("/some/prefix", info.getPrefixBeforeWALs().toString());
057    assertEquals("2025-09-14", info.getDateSegment());
058  }
059
060  /**
061   * extractWalPathInfo: case where WALs is at root (leading slash). e.g. /WALs/2025-09-14/some-wal
062   * Expect prefixBeforeWALs to be "/" (root) or non-null; resolution should still work.
063   */
064  @Test
065  public void testExtractWalPathInfo_rootWALs() throws Exception {
066    Path walPath = new Path("/" + BackupFileSystemManager.WALS_DIR + "/2025-09-14/wal-123");
067    BackupFileSystemManager.WalPathInfo info = BackupFileSystemManager.extractWalPathInfo(walPath);
068
069    assertNotNull(info);
070    // parent of "/WALs" in Hadoop Path is "/" (root). Ensure date segment parsed.
071    assertEquals("2025-09-14", info.getDateSegment());
072    assertNotNull(info.getPrefixBeforeWALs(),
073      "prefixBeforeWALs should not be null for root-style path");
074    // prefix might be "/" (expected), be tolerant: assert it ends with "/" or equals "/"
075    assertTrue(info.getPrefixBeforeWALs().toString().equals("/")
076      || !info.getPrefixBeforeWALs().toString().isEmpty());
077  }
078
079  /**
080   * extractWalPathInfo: null input should throw IllegalArgumentException.
081   */
082  @Test
083  public void testExtractWalPathInfo_nullPath() throws Exception {
084    assertThrows(IllegalArgumentException.class,
085      () -> BackupFileSystemManager.extractWalPathInfo(null));
086  }
087
088  /**
089   * extractWalPathInfo: missing date directory -> should throw IOException. Example: path that has
090   * no parent for the wal file.
091   */
092  @Test
093  public void testExtractWalPathInfo_missingDateDir() throws Exception {
094    // A single segment path (no parents) e.g. "walfile"
095    Path walPath = new Path("walfile");
096    assertThrows(IOException.class, () -> BackupFileSystemManager.extractWalPathInfo(walPath));
097  }
098
099  /**
100   * extractWalPathInfo: WALs segment name mismatch -> should throw IOException. e.g.
101   * /prefix/NOT_WALs/2025/wal
102   */
103  @Test
104  public void testExtractWalPathInfo_wrongWALsegment() throws Exception {
105    Path walPath = new Path("/prefix/NOT_WALS/2025/wal");
106    assertThrows(IOException.class, () -> BackupFileSystemManager.extractWalPathInfo(walPath));
107  }
108
109  /**
110   * resolveBulkLoadFullPath: normal case with prefix.
111   */
112  @Test
113  public void testResolveBulkLoadFullPath_withPrefix() throws Exception {
114    Path walPath =
115      new Path("/some/prefix/" + BackupFileSystemManager.WALS_DIR + "/2025-08-30/wal-1");
116    Path relative = new Path("namespace/table/region/family/file1");
117    Path full = BackupFileSystemManager.resolveBulkLoadFullPath(walPath, relative);
118
119    // expected: /some/prefix/bulk-load-files/2025-08-30/namespace/table/region/family/file1
120    String expected = "/some/prefix/" + BackupFileSystemManager.BULKLOAD_FILES_DIR
121      + "/2025-08-30/namespace/table/region/family/file1";
122    assertEquals(expected, full.toString());
123  }
124
125  /**
126   * resolveBulkLoadFullPath: when WALs is under root (prefix is root) - ensure path resolved under
127   * /bulk-load-files/<date>/...
128   */
129  @Test
130  public void testResolveBulkLoadFullPath_rootWALs() throws Exception {
131    Path walPath = new Path("/" + BackupFileSystemManager.WALS_DIR + "/2025-08-30/wal-2");
132    Path relative = new Path("ns/tbl/r/f");
133    Path full = BackupFileSystemManager.resolveBulkLoadFullPath(walPath, relative);
134
135    String expected = "/" + BackupFileSystemManager.BULKLOAD_FILES_DIR + "/2025-08-30/ns/tbl/r/f";
136    assertEquals(expected, full.toString());
137  }
138
139  /**
140   * Integration-y test: constructor should create directories under the provided backup root. Uses
141   * a temporary folder (local fs).
142   */
143  @Test
144  public void testConstructorCreatesDirectories() throws Exception {
145    java.nio.file.Path root = tmp.resolve("backupRoot");
146    java.nio.file.Files.createDirectories(root);
147    String rootPath = root.toString();
148
149    Configuration conf = HBaseConfiguration.create();
150    BackupFileSystemManager mgr = new BackupFileSystemManager("peer-1", conf, rootPath);
151
152    FileSystem fs = mgr.getBackupFs();
153    Path wals = mgr.getWalsDir();
154    Path bulk = mgr.getBulkLoadFilesDir();
155
156    assertTrue(fs.exists(wals), "WALs dir should exist");
157    assertTrue(fs.exists(bulk), "bulk-load-files dir should exist");
158  }
159}