001/**
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *     http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019package org.apache.hadoop.hbase.master;
020
021import java.io.IOException;
022
023import org.apache.hadoop.conf.Configuration;
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.ClusterId;
029import org.apache.hadoop.hbase.HConstants;
030import org.apache.hadoop.hbase.TableName;
031import org.apache.hadoop.hbase.backup.HFileArchiver;
032import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
033import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
034import org.apache.hadoop.hbase.client.RegionInfo;
035import org.apache.hadoop.hbase.client.RegionInfoBuilder;
036import org.apache.hadoop.hbase.client.TableDescriptor;
037import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
038import org.apache.hadoop.hbase.exceptions.DeserializationException;
039import org.apache.hadoop.hbase.fs.HFileSystem;
040import org.apache.hadoop.hbase.log.HBaseMarkers;
041import org.apache.hadoop.hbase.mob.MobConstants;
042import org.apache.hadoop.hbase.procedure2.store.wal.WALProcedureStore;
043import org.apache.hadoop.hbase.regionserver.HRegion;
044import org.apache.hadoop.hbase.util.Bytes;
045import org.apache.hadoop.hbase.util.FSTableDescriptors;
046import org.apache.hadoop.hbase.util.FSUtils;
047import org.apache.hadoop.ipc.RemoteException;
048import org.apache.yetus.audience.InterfaceAudience;
049import org.slf4j.Logger;
050import org.slf4j.LoggerFactory;
051
052/**
053 * This class abstracts a bunch of operations the HMaster needs to interact with
054 * the underlying file system like creating the initial layout, checking file
055 * system status, etc.
056 */
057@InterfaceAudience.Private
058public class MasterFileSystem {
059  private static final Logger LOG = LoggerFactory.getLogger(MasterFileSystem.class);
060
061  /** Parameter name for HBase instance root directory permission*/
062  public static final String HBASE_DIR_PERMS = "hbase.rootdir.perms";
063
064  /** Parameter name for HBase WAL directory permission*/
065  public static final String HBASE_WAL_DIR_PERMS = "hbase.wal.dir.perms";
066
067  // HBase configuration
068  private final Configuration conf;
069  // Persisted unique cluster ID
070  private ClusterId clusterId;
071  // Keep around for convenience.
072  private final FileSystem fs;
073  // Keep around for convenience.
074  private final FileSystem walFs;
075  // root log directory on the FS
076  private final Path rootdir;
077  // hbase temp directory used for table construction and deletion
078  private final Path tempdir;
079  // root hbase directory on the FS
080  private final Path walRootDir;
081
082
083  /*
084   * In a secure env, the protected sub-directories and files under the HBase rootDir
085   * would be restricted. The sub-directory will have '700' except the bulk load staging dir,
086   * which will have '711'.  The default '700' can be overwritten by setting the property
087   * 'hbase.rootdir.perms'. The protected files (version file, clusterId file) will have '600'.
088   * The rootDir itself will be created with HDFS default permissions if it does not exist.
089   * We will check the rootDir permissions to make sure it has 'x' for all to ensure access
090   * to the staging dir. If it does not, we will add it.
091   */
092  // Permissions for the directories under rootDir that need protection
093  private final FsPermission secureRootSubDirPerms;
094  // Permissions for the files under rootDir that need protection
095  private final FsPermission secureRootFilePerms = new FsPermission("600");
096  // Permissions for bulk load staging directory under rootDir
097  private final FsPermission HiddenDirPerms = FsPermission.valueOf("-rwx--x--x");
098
099  private boolean isSecurityEnabled;
100
101  public MasterFileSystem(Configuration conf) throws IOException {
102    this.conf = conf;
103    // Set filesystem to be that of this.rootdir else we get complaints about
104    // mismatched filesystems if hbase.rootdir is hdfs and fs.defaultFS is
105    // default localfs.  Presumption is that rootdir is fully-qualified before
106    // we get to here with appropriate fs scheme.
107    this.rootdir = FSUtils.getRootDir(conf);
108    this.tempdir = new Path(this.rootdir, HConstants.HBASE_TEMP_DIRECTORY);
109    // Cover both bases, the old way of setting default fs and the new.
110    // We're supposed to run on 0.20 and 0.21 anyways.
111    this.fs = this.rootdir.getFileSystem(conf);
112    this.walRootDir = FSUtils.getWALRootDir(conf);
113    this.walFs = FSUtils.getWALFileSystem(conf);
114    FSUtils.setFsDefault(conf, new Path(this.walFs.getUri()));
115    walFs.setConf(conf);
116    FSUtils.setFsDefault(conf, new Path(this.fs.getUri()));
117    // make sure the fs has the same conf
118    fs.setConf(conf);
119    this.secureRootSubDirPerms = new FsPermission(conf.get("hbase.rootdir.perms", "700"));
120    this.isSecurityEnabled = "kerberos".equalsIgnoreCase(conf.get("hbase.security.authentication"));
121    // setup the filesystem variable
122    createInitialFileSystemLayout();
123    HFileSystem.addLocationsOrderInterceptor(conf);
124  }
125
126  /**
127   * Create initial layout in filesystem.
128   * <ol>
129   * <li>Check if the meta region exists and is readable, if not create it.
130   * Create hbase.version and the hbase:meta directory if not one.
131   * </li>
132   * </ol>
133   * Idempotent.
134   */
135  private void createInitialFileSystemLayout() throws IOException {
136
137    final String[] protectedSubDirs = new String[] {
138        HConstants.BASE_NAMESPACE_DIR,
139        HConstants.HFILE_ARCHIVE_DIRECTORY,
140        HConstants.HBCK_SIDELINEDIR_NAME,
141        MobConstants.MOB_DIR_NAME
142    };
143
144    final String[] protectedSubLogDirs = new String[] {
145      HConstants.HREGION_LOGDIR_NAME,
146      HConstants.HREGION_OLDLOGDIR_NAME,
147      HConstants.CORRUPT_DIR_NAME,
148      WALProcedureStore.MASTER_PROCEDURE_LOGDIR
149    };
150    // check if the root directory exists
151    checkRootDir(this.rootdir, conf, this.fs);
152
153    // Check the directories under rootdir.
154    checkTempDir(this.tempdir, conf, this.fs);
155    for (String subDir : protectedSubDirs) {
156      checkSubDir(new Path(this.rootdir, subDir), HBASE_DIR_PERMS);
157    }
158
159    final String perms;
160    if (!this.walRootDir.equals(this.rootdir)) {
161      perms = HBASE_WAL_DIR_PERMS;
162    } else {
163      perms = HBASE_DIR_PERMS;
164    }
165    for (String subDir : protectedSubLogDirs) {
166      checkSubDir(new Path(this.walRootDir, subDir), perms);
167    }
168
169    checkStagingDir();
170
171    // Handle the last few special files and set the final rootDir permissions
172    // rootDir needs 'x' for all to support bulk load staging dir
173    if (isSecurityEnabled) {
174      fs.setPermission(new Path(rootdir, HConstants.VERSION_FILE_NAME), secureRootFilePerms);
175      fs.setPermission(new Path(rootdir, HConstants.CLUSTER_ID_FILE_NAME), secureRootFilePerms);
176    }
177    FsPermission currentRootPerms = fs.getFileStatus(this.rootdir).getPermission();
178    if (!currentRootPerms.getUserAction().implies(FsAction.EXECUTE)
179        || !currentRootPerms.getGroupAction().implies(FsAction.EXECUTE)
180        || !currentRootPerms.getOtherAction().implies(FsAction.EXECUTE)) {
181      LOG.warn("rootdir permissions do not contain 'excute' for user, group or other. "
182        + "Automatically adding 'excute' permission for all");
183      fs.setPermission(
184        this.rootdir,
185        new FsPermission(currentRootPerms.getUserAction().or(FsAction.EXECUTE), currentRootPerms
186            .getGroupAction().or(FsAction.EXECUTE), currentRootPerms.getOtherAction().or(
187          FsAction.EXECUTE)));
188    }
189  }
190
191  public FileSystem getFileSystem() {
192    return this.fs;
193  }
194
195  protected FileSystem getWALFileSystem() { return this.walFs; }
196
197  public Configuration getConfiguration() {
198    return this.conf;
199  }
200
201  /**
202   * @return HBase root dir.
203   */
204  public Path getRootDir() {
205    return this.rootdir;
206  }
207
208  /**
209   * @return HBase root log dir.
210   */
211  public Path getWALRootDir() { return this.walRootDir; }
212
213  /**
214   * @return HBase temp dir.
215   */
216  public Path getTempDir() {
217    return this.tempdir;
218  }
219
220  /**
221   * @return The unique identifier generated for this cluster
222   */
223  public ClusterId getClusterId() {
224    return clusterId;
225  }
226
227  /**
228   * Get the rootdir.  Make sure its wholesome and exists before returning.
229   * @param rd
230   * @param c
231   * @param fs
232   * @return hbase.rootdir (after checks for existence and bootstrapping if
233   * needed populating the directory with necessary bootup files).
234   * @throws IOException
235   */
236  private Path checkRootDir(final Path rd, final Configuration c, final FileSystem fs)
237      throws IOException {
238    // If FS is in safe mode wait till out of it.
239    FSUtils.waitOnSafeMode(c, c.getInt(HConstants.THREAD_WAKE_FREQUENCY, 10 * 1000));
240
241    // Filesystem is good. Go ahead and check for hbase.rootdir.
242    try {
243      if (!fs.exists(rd)) {
244        fs.mkdirs(rd);
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, c.getInt(HConstants.THREAD_WAKE_FREQUENCY,
253          10 * 1000), c.getInt(HConstants.VERSION_FILE_WRITE_ATTEMPTS,
254            HConstants.DEFAULT_VERSION_FILE_WRITE_ATTEMPTS));
255      } else {
256        if (!fs.isDirectory(rd)) {
257          throw new IllegalArgumentException(rd.toString() + " is not a directory");
258        }
259        // as above
260        FSUtils.checkVersion(fs, rd, true, c.getInt(HConstants.THREAD_WAKE_FREQUENCY,
261          10 * 1000), c.getInt(HConstants.VERSION_FILE_WRITE_ATTEMPTS,
262            HConstants.DEFAULT_VERSION_FILE_WRITE_ATTEMPTS));
263      }
264    } catch (DeserializationException de) {
265      LOG.error(HBaseMarkers.FATAL, "Please fix invalid configuration for "
266        + HConstants.HBASE_DIR, de);
267      IOException ioe = new IOException();
268      ioe.initCause(de);
269      throw ioe;
270    } catch (IllegalArgumentException iae) {
271      LOG.error(HBaseMarkers.FATAL, "Please fix invalid configuration for "
272        + HConstants.HBASE_DIR + " " + rd.toString(), iae);
273      throw iae;
274    }
275    // Make sure cluster ID exists
276    if (!FSUtils.checkClusterIdExists(fs, rd, c.getInt(
277        HConstants.THREAD_WAKE_FREQUENCY, 10 * 1000))) {
278      FSUtils.setClusterId(fs, rd, new ClusterId(), c.getInt(HConstants.THREAD_WAKE_FREQUENCY, 10 * 1000));
279    }
280    clusterId = FSUtils.getClusterId(fs, rd);
281
282    // Make sure the meta region directory exists!
283    if (!FSUtils.metaRegionExists(fs, rd)) {
284      bootstrap(rd, c);
285    }
286
287    // Create tableinfo-s for hbase:meta if not already there.
288    // assume, created table descriptor is for enabling table
289    // meta table is a system table, so descriptors are predefined,
290    // we should get them from registry.
291    FSTableDescriptors fsd = new FSTableDescriptors(c, fs, rd);
292    fsd.createTableDescriptor(fsd.get(TableName.META_TABLE_NAME));
293
294    return rd;
295  }
296
297  /**
298   * Make sure the hbase temp directory exists and is empty.
299   * NOTE that this method is only executed once just after the master becomes the active one.
300   */
301  private void checkTempDir(final Path tmpdir, final Configuration c, final FileSystem fs)
302      throws IOException {
303    // If the temp directory exists, clear the content (left over, from the previous run)
304    if (fs.exists(tmpdir)) {
305      // Archive table in temp, maybe left over from failed deletion,
306      // if not the cleaner will take care of them.
307      for (Path tabledir: FSUtils.getTableDirs(fs, tmpdir)) {
308        for (Path regiondir: FSUtils.getRegionDirs(fs, tabledir)) {
309          HFileArchiver.archiveRegion(fs, this.rootdir, tabledir, regiondir);
310        }
311      }
312      if (!fs.delete(tmpdir, true)) {
313        throw new IOException("Unable to clean the temp directory: " + tmpdir);
314      }
315    }
316
317    // Create the temp directory
318    if (isSecurityEnabled) {
319      if (!fs.mkdirs(tmpdir, secureRootSubDirPerms)) {
320        throw new IOException("HBase temp directory '" + tmpdir + "' creation failure.");
321      }
322    } else {
323      if (!fs.mkdirs(tmpdir)) {
324        throw new IOException("HBase temp directory '" + tmpdir + "' creation failure.");
325      }
326    }
327  }
328
329  /**
330   * Make sure the directories under rootDir have good permissions. Create if necessary.
331   * @param p
332   * @throws IOException
333   */
334  private void checkSubDir(final Path p, final String dirPermsConfName) throws IOException {
335    FileSystem fs = p.getFileSystem(conf);
336    FsPermission dirPerms = new FsPermission(conf.get(dirPermsConfName, "700"));
337    if (!fs.exists(p)) {
338      if (isSecurityEnabled) {
339        if (!fs.mkdirs(p, secureRootSubDirPerms)) {
340          throw new IOException("HBase directory '" + p + "' creation failure.");
341        }
342      } else {
343        if (!fs.mkdirs(p)) {
344          throw new IOException("HBase directory '" + p + "' creation failure.");
345        }
346      }
347    }
348    else {
349      if (isSecurityEnabled && !dirPerms.equals(fs.getFileStatus(p).getPermission())) {
350        // check whether the permission match
351        LOG.warn("Found HBase directory permissions NOT matching expected permissions for "
352            + p.toString() + " permissions=" + fs.getFileStatus(p).getPermission()
353            + ", expecting " + dirPerms + ". Automatically setting the permissions. "
354            + "You can change the permissions by setting \"" + dirPermsConfName + "\" in hbase-site.xml "
355            + "and restarting the master");
356        fs.setPermission(p, dirPerms);
357      }
358    }
359  }
360
361  /**
362   * Check permissions for bulk load staging directory. This directory has special hidden
363   * permissions. Create it if necessary.
364   * @throws IOException
365   */
366  private void checkStagingDir() throws IOException {
367    Path p = new Path(this.rootdir, HConstants.BULKLOAD_STAGING_DIR_NAME);
368    try {
369      if (!this.fs.exists(p)) {
370        if (!this.fs.mkdirs(p, HiddenDirPerms)) {
371          throw new IOException("Failed to create staging directory " + p.toString());
372        }
373      } else {
374        this.fs.setPermission(p, HiddenDirPerms);
375      }
376    } catch (IOException e) {
377      LOG.error("Failed to create or set permission on staging directory " + p.toString());
378      throw new IOException("Failed to create or set permission on staging directory "
379          + p.toString(), e);
380    }
381  }
382
383  private static void bootstrap(final Path rd, final Configuration c)
384  throws IOException {
385    LOG.info("BOOTSTRAP: creating hbase:meta region");
386    try {
387      // Bootstrapping, make sure blockcache is off.  Else, one will be
388      // created here in bootstrap and it'll need to be cleaned up.  Better to
389      // not make it in first place.  Turn off block caching for bootstrap.
390      // Enable after.
391      TableDescriptor metaDescriptor = new FSTableDescriptors(c).get(TableName.META_TABLE_NAME);
392      HRegion meta = HRegion.createHRegion(RegionInfoBuilder.FIRST_META_REGIONINFO, rd,
393          c, setInfoFamilyCachingForMeta(metaDescriptor, false), null);
394      meta.close();
395    } catch (IOException e) {
396        e = e instanceof RemoteException ?
397                ((RemoteException)e).unwrapRemoteException() : e;
398      LOG.error("bootstrap", e);
399      throw e;
400    }
401  }
402
403  /**
404   * Enable in memory caching for hbase:meta
405   */
406  public static TableDescriptor setInfoFamilyCachingForMeta(TableDescriptor metaDescriptor, final boolean b) {
407    TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(metaDescriptor);
408    for (ColumnFamilyDescriptor hcd: metaDescriptor.getColumnFamilies()) {
409      if (Bytes.equals(hcd.getName(), HConstants.CATALOG_FAMILY)) {
410        builder.modifyColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(hcd)
411                .setBlockCacheEnabled(b)
412                .setInMemory(b)
413                .build());
414      }
415    }
416    return builder.build();
417  }
418
419  public void deleteFamilyFromFS(RegionInfo region, byte[] familyName)
420      throws IOException {
421    deleteFamilyFromFS(rootdir, region, familyName);
422  }
423
424  public void deleteFamilyFromFS(Path rootDir, RegionInfo region, byte[] familyName)
425      throws IOException {
426    // archive family store files
427    Path tableDir = FSUtils.getTableDir(rootDir, region.getTable());
428    HFileArchiver.archiveFamily(fs, conf, region, tableDir, familyName);
429
430    // delete the family folder
431    Path familyDir = new Path(tableDir,
432      new Path(region.getEncodedName(), Bytes.toString(familyName)));
433    if (fs.delete(familyDir, true) == false) {
434      if (fs.exists(familyDir)) {
435        throw new IOException("Could not delete family "
436            + Bytes.toString(familyName) + " from FileSystem for region "
437            + region.getRegionNameAsString() + "(" + region.getEncodedName()
438            + ")");
439      }
440    }
441  }
442
443  public void stop() {
444  }
445
446  public void logFileSystemState(Logger log) throws IOException {
447    FSUtils.logFileSystemState(fs, rootdir, log);
448  }
449}