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 java.io.IOException;
021import org.apache.hadoop.conf.Configuration;
022import org.apache.hadoop.fs.FileSystem;
023import org.apache.hadoop.fs.Path;
024import org.apache.hadoop.hbase.backup.replication.Utils;
025import org.apache.yetus.audience.InterfaceAudience;
026import org.slf4j.Logger;
027import org.slf4j.LoggerFactory;
028
029/**
030 * Initializes and organizes backup directories for continuous Write-Ahead Logs (WALs) and
031 * bulk-loaded files within the specified backup root directory.
032 */
033@InterfaceAudience.Private
034public class BackupFileSystemManager {
035  private static final Logger LOG = LoggerFactory.getLogger(BackupFileSystemManager.class);
036
037  public static final String WALS_DIR = "WALs";
038  public static final String BULKLOAD_FILES_DIR = "bulk-load-files";
039  private final String peerId;
040  private final FileSystem backupFs;
041  private final Path backupRootDir;
042  private final Path walsDir;
043  private final Path bulkLoadFilesDir;
044
045  public BackupFileSystemManager(String peerId, Configuration conf, String backupRootDirStr)
046    throws IOException {
047    this.peerId = peerId;
048    this.backupRootDir = new Path(backupRootDirStr);
049    this.backupFs = FileSystem.get(backupRootDir.toUri(), conf);
050    this.walsDir = createDirectory(WALS_DIR);
051    this.bulkLoadFilesDir = createDirectory(BULKLOAD_FILES_DIR);
052  }
053
054  private Path createDirectory(String dirName) throws IOException {
055    Path dirPath = new Path(backupRootDir, dirName);
056    backupFs.mkdirs(dirPath);
057    LOG.info("{} Initialized directory: {}", Utils.logPeerId(peerId), dirPath);
058    return dirPath;
059  }
060
061  public Path getWalsDir() {
062    return walsDir;
063  }
064
065  public Path getBulkLoadFilesDir() {
066    return bulkLoadFilesDir;
067  }
068
069  public FileSystem getBackupFs() {
070    return backupFs;
071  }
072
073  public static final class WalPathInfo {
074    private final Path prefixBeforeWALs;
075    private final String dateSegment;
076
077    public WalPathInfo(Path prefixBeforeWALs, String dateSegment) {
078      this.prefixBeforeWALs = prefixBeforeWALs;
079      this.dateSegment = dateSegment;
080    }
081
082    public Path getPrefixBeforeWALs() {
083      return prefixBeforeWALs;
084    }
085
086    public String getDateSegment() {
087      return dateSegment;
088    }
089  }
090
091  /**
092   * Validate the walPath has the expected structure: .../WALs/<date>/<wal-file> and return
093   * WalPathInfo(prefixBeforeWALs, dateSegment).
094   * @throws IOException if the path is not in expected format
095   */
096  public static WalPathInfo extractWalPathInfo(Path walPath) throws IOException {
097    if (walPath == null) {
098      throw new IllegalArgumentException("walPath must not be null");
099    }
100
101    Path dateDir = walPath.getParent(); // .../WALs/<date>
102    if (dateDir == null) {
103      throw new IOException("Invalid WAL path: missing date directory. Path: " + walPath);
104    }
105
106    Path walsDir = dateDir.getParent(); // .../WALs
107    if (walsDir == null) {
108      throw new IOException("Invalid WAL path: missing WALs directory. Path: " + walPath);
109    }
110
111    String walsDirName = walsDir.getName();
112    if (!WALS_DIR.equals(walsDirName)) {
113      throw new IOException("Invalid WAL path: expected '" + WALS_DIR + "' segment but found '"
114        + walsDirName + "'. Path: " + walPath);
115    }
116
117    String dateSegment = dateDir.getName();
118    if (dateSegment == null || dateSegment.isEmpty()) {
119      throw new IOException("Invalid WAL path: date segment is empty. Path: " + walPath);
120    }
121
122    Path prefixBeforeWALs = walsDir.getParent(); // might be null if path is like "/WALs/..."
123    return new WalPathInfo(prefixBeforeWALs, dateSegment);
124  }
125
126  /**
127   * Resolve the full bulk-load file path corresponding to a relative bulk-load path referenced from
128   * a WAL file path. For a WAL path like: /some/prefix/.../WALs/23-08-2025/some-wal-file and a
129   * relative bulk path like: namespace/table/region/family/file, this returns:
130   * /some/prefix/.../bulk-load-files/23-08-2025/namespace/table/region/family/file
131   * @param walPath          the Path to the WAL file (must contain the {@link #WALS_DIR} segment
132   *                         followed by date)
133   * @param relativeBulkPath the relative bulk-load file Path
134   * @return resolved full Path for the bulk-load file
135   * @throws IOException if the WAL path does not contain the expected segments
136   */
137  public static Path resolveBulkLoadFullPath(Path walPath, Path relativeBulkPath)
138    throws IOException {
139    WalPathInfo info = extractWalPathInfo(walPath);
140
141    Path prefixBeforeWALs = info.getPrefixBeforeWALs();
142    String dateSegment = info.getDateSegment();
143
144    Path full; // Build final path:
145               // <prefixBeforeWALs>/bulk-load-files/<dateSegment>/<relativeBulkPath>
146    if (prefixBeforeWALs == null || prefixBeforeWALs.toString().isEmpty()) {
147      full = new Path(BULKLOAD_FILES_DIR, new Path(dateSegment, relativeBulkPath));
148    } else {
149      full = new Path(new Path(prefixBeforeWALs, BULKLOAD_FILES_DIR),
150        new Path(dateSegment, relativeBulkPath));
151    }
152    return full;
153  }
154}