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.master; 019 020import java.io.FileNotFoundException; 021import java.io.IOException; 022import org.apache.hadoop.conf.Configuration; 023import org.apache.hadoop.fs.FileStatus; 024import org.apache.hadoop.fs.FileSystem; 025import org.apache.hadoop.fs.Path; 026import org.apache.hadoop.fs.permission.FsAction; 027import org.apache.hadoop.fs.permission.FsPermission; 028import org.apache.hadoop.hbase.ActiveClusterSuffix; 029import org.apache.hadoop.hbase.ClusterId; 030import org.apache.hadoop.hbase.HConstants; 031import org.apache.hadoop.hbase.backup.HFileArchiver; 032import org.apache.hadoop.hbase.client.RegionInfo; 033import org.apache.hadoop.hbase.exceptions.DeserializationException; 034import org.apache.hadoop.hbase.fs.HFileSystem; 035import org.apache.hadoop.hbase.log.HBaseMarkers; 036import org.apache.hadoop.hbase.mob.MobConstants; 037import org.apache.hadoop.hbase.replication.ReplicationUtils; 038import org.apache.hadoop.hbase.security.access.SnapshotScannerHDFSAclHelper; 039import org.apache.hadoop.hbase.util.Bytes; 040import org.apache.hadoop.hbase.util.CommonFSUtils; 041import org.apache.hadoop.hbase.util.ConfigurationUtil; 042import org.apache.hadoop.hbase.util.FSUtils; 043import org.apache.yetus.audience.InterfaceAudience; 044import org.slf4j.Logger; 045import org.slf4j.LoggerFactory; 046 047/** 048 * This class abstracts a bunch of operations the HMaster needs to interact with the underlying file 049 * system like creating the initial layout, checking file system status, etc. 050 */ 051@InterfaceAudience.Private 052public class MasterFileSystem { 053 private static final Logger LOG = LoggerFactory.getLogger(MasterFileSystem.class); 054 055 /** Parameter name for HBase instance root directory permission */ 056 public static final String HBASE_DIR_PERMS = "hbase.rootdir.perms"; 057 058 /** Parameter name for HBase WAL directory permission */ 059 public static final String HBASE_WAL_DIR_PERMS = "hbase.wal.dir.perms"; 060 061 // HBase configuration 062 private final Configuration conf; 063 // Persisted unique cluster ID 064 private ClusterId clusterId; 065 // Persisted unique Active Cluster Suffix 066 private ActiveClusterSuffix activeClusterSuffix; 067 // Keep around for convenience. 068 private final FileSystem fs; 069 // Keep around for convenience. 070 private final FileSystem walFs; 071 // root log directory on the FS 072 private final Path rootdir; 073 // hbase temp directory used for table construction and deletion 074 private final Path tempdir; 075 // root hbase directory on the FS 076 private final Path walRootDir; 077 078 /* 079 * In a secure env, the protected sub-directories and files under the HBase rootDir would be 080 * restricted. The sub-directory will have '700' except the bulk load staging dir, which will have 081 * '711'. The default '700' can be overwritten by setting the property 'hbase.rootdir.perms'. The 082 * protected files (version file, clusterId file) will have '600'. The rootDir itself will be 083 * created with HDFS default permissions if it does not exist. We will check the rootDir 084 * permissions to make sure it has 'x' for all to ensure access to the staging dir. If it does 085 * not, we will add it. 086 */ 087 // Permissions for the directories under rootDir that need protection 088 private final FsPermission secureRootSubDirPerms; 089 // Permissions for the files under rootDir that need protection 090 private final FsPermission secureRootFilePerms = new FsPermission("600"); 091 // Permissions for bulk load staging directory under rootDir 092 private final FsPermission HiddenDirPerms = FsPermission.valueOf("-rwx--x--x"); 093 094 private boolean isSecurityEnabled; 095 096 public MasterFileSystem(Configuration conf) throws IOException { 097 this.conf = conf; 098 // Set filesystem to be that of this.rootdir else we get complaints about 099 // mismatched filesystems if hbase.rootdir is hdfs and fs.defaultFS is 100 // default localfs. Presumption is that rootdir is fully-qualified before 101 // we get to here with appropriate fs scheme. 102 this.rootdir = CommonFSUtils.getRootDir(conf); 103 this.tempdir = new Path(this.rootdir, HConstants.HBASE_TEMP_DIRECTORY); 104 // Cover both bases, the old way of setting default fs and the new. 105 // We're supposed to run on 0.20 and 0.21 anyways. 106 this.fs = this.rootdir.getFileSystem(conf); 107 this.walRootDir = CommonFSUtils.getWALRootDir(conf); 108 this.walFs = CommonFSUtils.getWALFileSystem(conf); 109 CommonFSUtils.setFsDefault(conf, new Path(this.walFs.getUri())); 110 walFs.setConf(conf); 111 CommonFSUtils.setFsDefault(conf, new Path(this.fs.getUri())); 112 // make sure the fs has the same conf 113 fs.setConf(conf); 114 this.secureRootSubDirPerms = new FsPermission(conf.get("hbase.rootdir.perms", "700")); 115 this.isSecurityEnabled = "kerberos".equalsIgnoreCase(conf.get("hbase.security.authentication")); 116 // setup the filesystem variable 117 createInitialFileSystemLayout(); 118 HFileSystem.addLocationsOrderInterceptor(conf); 119 } 120 121 /** 122 * Create initial layout in filesystem. 123 * <ol> 124 * <li>Check if the meta region exists and is readable, if not create it. Create hbase.version and 125 * the hbase:meta directory if not one.</li> 126 * </ol> 127 * Idempotent. 128 */ 129 private void createInitialFileSystemLayout() throws IOException { 130 final String[] protectedSubDirs = 131 new String[] { HConstants.BASE_NAMESPACE_DIR, HConstants.HFILE_ARCHIVE_DIRECTORY, 132 HConstants.HBCK_SIDELINEDIR_NAME, MobConstants.MOB_DIR_NAME }; 133 134 // With the introduction of RegionProcedureStore, 135 // there's no need to create MasterProcWAL dir here anymore. See HBASE-23715 136 final String[] protectedSubLogDirs = 137 new String[] { HConstants.HREGION_LOGDIR_NAME, HConstants.HREGION_OLDLOGDIR_NAME, 138 HConstants.CORRUPT_DIR_NAME, ReplicationUtils.REMOTE_WAL_DIR_NAME }; 139 // check if the root directory exists 140 checkRootDir(this.rootdir, conf, this.fs); 141 142 // Check the directories under rootdir. 143 checkTempDir(this.tempdir, conf, this.fs); 144 for (String subDir : protectedSubDirs) { 145 checkSubDir(new Path(this.rootdir, subDir), HBASE_DIR_PERMS); 146 } 147 148 final String perms; 149 if (!this.walRootDir.equals(this.rootdir)) { 150 perms = HBASE_WAL_DIR_PERMS; 151 } else { 152 perms = HBASE_DIR_PERMS; 153 } 154 for (String subDir : protectedSubLogDirs) { 155 checkSubDir(new Path(this.walRootDir, subDir), perms); 156 } 157 158 checkStagingDir(); 159 160 // Handle the last few special files and set the final rootDir permissions 161 // rootDir needs 'x' for all to support bulk load staging dir 162 if (isSecurityEnabled) { 163 fs.setPermission(new Path(rootdir, HConstants.VERSION_FILE_NAME), secureRootFilePerms); 164 fs.setPermission(new Path(rootdir, HConstants.CLUSTER_ID_FILE_NAME), secureRootFilePerms); 165 fs.setPermission(new Path(rootdir, HConstants.ACTIVE_CLUSTER_SUFFIX_FILE_NAME), 166 secureRootFilePerms); 167 } 168 FsPermission currentRootPerms = fs.getFileStatus(this.rootdir).getPermission(); 169 if ( 170 !currentRootPerms.getUserAction().implies(FsAction.EXECUTE) 171 || !currentRootPerms.getGroupAction().implies(FsAction.EXECUTE) 172 || !currentRootPerms.getOtherAction().implies(FsAction.EXECUTE) 173 ) { 174 LOG.warn("rootdir permissions do not contain 'excute' for user, group or other. " 175 + "Automatically adding 'excute' permission for all"); 176 fs.setPermission(this.rootdir, 177 new FsPermission(currentRootPerms.getUserAction().or(FsAction.EXECUTE), 178 currentRootPerms.getGroupAction().or(FsAction.EXECUTE), 179 currentRootPerms.getOtherAction().or(FsAction.EXECUTE))); 180 } 181 } 182 183 public FileSystem getFileSystem() { 184 return this.fs; 185 } 186 187 public FileSystem getWALFileSystem() { 188 return this.walFs; 189 } 190 191 public Configuration getConfiguration() { 192 return this.conf; 193 } 194 195 /** Returns HBase root dir. */ 196 public Path getRootDir() { 197 return this.rootdir; 198 } 199 200 /** Returns HBase root log dir. */ 201 public Path getWALRootDir() { 202 return this.walRootDir; 203 } 204 205 /** Returns the directory for a give {@code region}. */ 206 public Path getRegionDir(RegionInfo region) { 207 return FSUtils.getRegionDirFromRootDir(getRootDir(), region); 208 } 209 210 /** Returns HBase temp dir. */ 211 public Path getTempDir() { 212 return this.tempdir; 213 } 214 215 /** Returns The unique identifier generated for this cluster */ 216 public ClusterId getClusterId() { 217 return clusterId; 218 } 219 220 /** 221 * Get the rootdir. Make sure its wholesome and exists before returning. 222 * @return hbase.rootdir (after checks for existence and bootstrapping if needed populating the 223 * directory with necessary bootup files). 224 */ 225 private void checkRootDir(final Path rd, final Configuration c, final FileSystem fs) 226 throws IOException { 227 int threadWakeFrequency = c.getInt(HConstants.THREAD_WAKE_FREQUENCY, 10 * 1000); 228 // If FS is in safe mode wait till out of it. 229 FSUtils.waitOnSafeMode(c, threadWakeFrequency); 230 231 // Filesystem is good. Go ahead and check for hbase.rootdir. 232 FileStatus status; 233 try { 234 status = fs.getFileStatus(rd); 235 } catch (FileNotFoundException e) { 236 status = null; 237 } 238 int versionFileWriteAttempts = c.getInt(HConstants.VERSION_FILE_WRITE_ATTEMPTS, 239 HConstants.DEFAULT_VERSION_FILE_WRITE_ATTEMPTS); 240 try { 241 if (status == null) { 242 if (!fs.mkdirs(rd)) { 243 throw new IOException("Can not create configured '" + HConstants.HBASE_DIR + "' " + rd); 244 } 245 // DFS leaves safe mode with 0 DNs when there are 0 blocks. 246 // We used to handle this by checking the current DN count and waiting until 247 // it is nonzero. With security, the check for datanode count doesn't work -- 248 // it is a privileged op. So instead we adopt the strategy of the jobtracker 249 // and simply retry file creation during bootstrap indefinitely. As soon as 250 // there is one datanode it will succeed. Permission problems should have 251 // already been caught by mkdirs above. 252 FSUtils.setVersion(fs, rd, threadWakeFrequency, versionFileWriteAttempts); 253 } else { 254 if (!status.isDirectory()) { 255 throw new IllegalArgumentException( 256 "Configured '" + HConstants.HBASE_DIR + "' " + rd + " is not a directory."); 257 } 258 // as above 259 FSUtils.checkVersion(fs, rd, true, threadWakeFrequency, versionFileWriteAttempts); 260 } 261 } catch (DeserializationException de) { 262 LOG.error(HBaseMarkers.FATAL, "Please fix invalid configuration for '{}' {}", 263 HConstants.HBASE_DIR, rd, de); 264 throw new IOException(de); 265 } catch (IllegalArgumentException iae) { 266 LOG.error(HBaseMarkers.FATAL, "Please fix invalid configuration for '{}' {}", 267 HConstants.HBASE_DIR, rd, iae); 268 throw iae; 269 } 270 // Make sure cluster ID exists 271 if ( 272 !FSUtils.checkFileExistsInHbaseRootDir(fs, rootdir, HConstants.CLUSTER_ID_FILE_NAME, 273 threadWakeFrequency) 274 ) { 275 FSUtils.setClusterIdFile(fs, rootdir, HConstants.CLUSTER_ID_FILE_NAME, new ClusterId(), 276 threadWakeFrequency); 277 } 278 clusterId = FSUtils.getClusterIdFile(fs, rootdir, new ClusterId.Parser()); 279 negotiateActiveClusterSuffixFile(threadWakeFrequency); 280 } 281 282 /** 283 * Make sure the hbase temp directory exists and is empty. NOTE that this method is only executed 284 * once just after the master becomes the active one. 285 */ 286 void checkTempDir(final Path tmpdir, final Configuration c, final FileSystem fs) 287 throws IOException { 288 // If the temp directory exists, clear the content (left over, from the previous run) 289 if (fs.exists(tmpdir)) { 290 // Archive table in temp, maybe left over from failed deletion, 291 // if not the cleaner will take care of them. 292 for (Path tableDir : FSUtils.getTableDirs(fs, tmpdir)) { 293 HFileArchiver.archiveRegions(c, fs, this.rootdir, tableDir, 294 FSUtils.getRegionDirs(fs, tableDir)); 295 if (!FSUtils.getRegionDirs(fs, tableDir).isEmpty()) { 296 LOG.warn("Found regions in tmp dir after archiving table regions, {}", tableDir); 297 } 298 } 299 // if acl sync to hdfs is enabled, then skip delete tmp dir because ACLs are set 300 if (!SnapshotScannerHDFSAclHelper.isAclSyncToHdfsEnabled(c) && !fs.delete(tmpdir, true)) { 301 throw new IOException("Unable to clean the temp directory: " + tmpdir); 302 } 303 } 304 305 // Create the temp directory 306 if (!fs.exists(tmpdir)) { 307 if (isSecurityEnabled) { 308 if (!fs.mkdirs(tmpdir, secureRootSubDirPerms)) { 309 throw new IOException("HBase temp directory '" + tmpdir + "' creation failure."); 310 } 311 } else { 312 if (!fs.mkdirs(tmpdir)) { 313 throw new IOException("HBase temp directory '" + tmpdir + "' creation failure."); 314 } 315 } 316 } 317 } 318 319 /** 320 * Make sure the directories under rootDir have good permissions. Create if necessary. 321 */ 322 private void checkSubDir(final Path p, final String dirPermsConfName) throws IOException { 323 FileSystem fs = p.getFileSystem(conf); 324 FsPermission dirPerms = new FsPermission(conf.get(dirPermsConfName, "700")); 325 if (!fs.exists(p)) { 326 if (isSecurityEnabled) { 327 if (!fs.mkdirs(p, secureRootSubDirPerms)) { 328 throw new IOException("HBase directory '" + p + "' creation failure."); 329 } 330 } else { 331 if (!fs.mkdirs(p)) { 332 throw new IOException("HBase directory '" + p + "' creation failure."); 333 } 334 } 335 } 336 if (isSecurityEnabled && !dirPerms.equals(fs.getFileStatus(p).getPermission())) { 337 // check whether the permission match 338 LOG.warn("Found HBase directory permissions NOT matching expected permissions for " 339 + p.toString() + " permissions=" + fs.getFileStatus(p).getPermission() + ", expecting " 340 + dirPerms + ". Automatically setting the permissions. " 341 + "You can change the permissions by setting \"" + dirPermsConfName 342 + "\" in hbase-site.xml " + "and restarting the master"); 343 fs.setPermission(p, dirPerms); 344 } 345 } 346 347 /** 348 * Check permissions for bulk load staging directory. This directory has special hidden 349 * permissions. Create it if necessary. 350 */ 351 private void checkStagingDir() throws IOException { 352 Path p = new Path(this.rootdir, HConstants.BULKLOAD_STAGING_DIR_NAME); 353 try { 354 if (!this.fs.exists(p)) { 355 if (!this.fs.mkdirs(p, HiddenDirPerms)) { 356 throw new IOException("Failed to create staging directory " + p.toString()); 357 } 358 } 359 this.fs.setPermission(p, HiddenDirPerms); 360 361 } catch (IOException e) { 362 LOG.error("Failed to create or set permission on staging directory " + p.toString()); 363 throw new IOException( 364 "Failed to create or set permission on staging directory " + p.toString(), e); 365 } 366 } 367 368 public void deleteFamilyFromFS(RegionInfo region, byte[] familyName) throws IOException { 369 deleteFamilyFromFS(rootdir, region, familyName); 370 } 371 372 public void deleteFamilyFromFS(Path rootDir, RegionInfo region, byte[] familyName) 373 throws IOException { 374 // archive family store files 375 Path tableDir = CommonFSUtils.getTableDir(rootDir, region.getTable()); 376 HFileArchiver.archiveFamily(fs, conf, region, tableDir, familyName); 377 378 // delete the family folder 379 Path familyDir = 380 new Path(tableDir, new Path(region.getEncodedName(), Bytes.toString(familyName))); 381 if (fs.delete(familyDir, true) == false) { 382 if (fs.exists(familyDir)) { 383 throw new IOException( 384 "Could not delete family " + Bytes.toString(familyName) + " from FileSystem for region " 385 + region.getRegionNameAsString() + "(" + region.getEncodedName() + ")"); 386 } 387 } 388 } 389 390 public void stop() { 391 } 392 393 public void logFileSystemState(Logger log) throws IOException { 394 CommonFSUtils.logFileSystemState(fs, rootdir, log); 395 } 396 397 private void negotiateActiveClusterSuffixFile(long wait) throws IOException { 398 this.activeClusterSuffix = ActiveClusterSuffix.fromConfig(conf, getClusterId()); 399 if (!ConfigurationUtil.isReadOnlyModeEnabledInConf(conf)) { 400 try { 401 // verify the contents against the config set 402 ActiveClusterSuffix acs = 403 FSUtils.getClusterIdFile(fs, rootdir, new ActiveClusterSuffix.Parser()); 404 if (acs == null) { 405 throw new FileNotFoundException("[Read-replica feature] Active Cluster Suffix File " 406 + new Path(rootdir, HConstants.ACTIVE_CLUSTER_SUFFIX_FILE_NAME) + " not found"); 407 } 408 LOG.debug( 409 "Negotiating active cluster suffix file. File {} : File Suffix {} : Configured suffix {} : Cluster ID : {}", 410 new Path(rootdir, HConstants.ACTIVE_CLUSTER_SUFFIX_FILE_NAME), acs, activeClusterSuffix, 411 getClusterId()); 412 // Suffix file exists and we're in read/write mode. Content should match. 413 if (!this.activeClusterSuffix.equals(acs)) { 414 // throw error 415 LOG.error( 416 "[Read-replica feature] Another cluster is running in active (read-write) mode on this " 417 + "storage location. Active cluster ID: {}, This cluster ID {}. Rootdir location {} ", 418 acs, activeClusterSuffix, rootdir); 419 throw new IOException("Cannot start master, because another cluster is running in active " 420 + "(read-write) mode on this storage location. Active Cluster Id: " + acs 421 + ", This cluster Id: " + activeClusterSuffix); 422 } 423 LOG.info( 424 "[Read-replica feature] This is the active cluster on this storage location with cluster id: {}", 425 activeClusterSuffix); 426 } catch (FileNotFoundException fnfe) { 427 // We're in read/write mode, but suffix file missing, let's create it 428 FSUtils.setClusterIdFile(fs, rootdir, HConstants.ACTIVE_CLUSTER_SUFFIX_FILE_NAME, 429 activeClusterSuffix, wait); 430 LOG.info("[Read-replica feature] Created Active cluster suffix file: {}, with content: {}", 431 HConstants.ACTIVE_CLUSTER_SUFFIX_FILE_NAME, activeClusterSuffix); 432 } 433 } else { 434 // This is a read-only cluster, don't care about suffix file 435 LOG.info("[Read-replica feature] Replica cluster is being started in Read Only Mode"); 436 } 437 } 438 439 public ActiveClusterSuffix getActiveClusterSuffix() { 440 return activeClusterSuffix; 441 } 442}