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}