View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.master;
20  
21  import java.io.IOException;
22  import java.io.InterruptedIOException;
23  import java.util.ArrayList;
24  import java.util.HashSet;
25  import java.util.List;
26  import java.util.Set;
27  import java.util.concurrent.locks.Lock;
28  import java.util.concurrent.locks.ReentrantLock;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.hadoop.hbase.classification.InterfaceAudience;
33  import org.apache.hadoop.conf.Configuration;
34  import org.apache.hadoop.fs.FileStatus;
35  import org.apache.hadoop.fs.FileSystem;
36  import org.apache.hadoop.fs.Path;
37  import org.apache.hadoop.fs.PathFilter;
38  import org.apache.hadoop.fs.permission.FsPermission;
39  import org.apache.hadoop.hbase.ClusterId;
40  import org.apache.hadoop.hbase.TableName;
41  import org.apache.hadoop.hbase.HColumnDescriptor;
42  import org.apache.hadoop.hbase.HConstants;
43  import org.apache.hadoop.hbase.HRegionInfo;
44  import org.apache.hadoop.hbase.HTableDescriptor;
45  import org.apache.hadoop.hbase.RemoteExceptionHandler;
46  import org.apache.hadoop.hbase.Server;
47  import org.apache.hadoop.hbase.ServerName;
48  import org.apache.hadoop.hbase.backup.HFileArchiver;
49  import org.apache.hadoop.hbase.exceptions.DeserializationException;
50  import org.apache.hadoop.hbase.fs.HFileSystem;
51  import org.apache.hadoop.hbase.protobuf.generated.ZooKeeperProtos.SplitLogTask.RecoveryMode;
52  import org.apache.hadoop.hbase.regionserver.HRegion;
53  import org.apache.hadoop.hbase.wal.DefaultWALProvider;
54  import org.apache.hadoop.hbase.wal.WALSplitter;
55  import org.apache.hadoop.hbase.util.Bytes;
56  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
57  import org.apache.hadoop.hbase.util.FSTableDescriptors;
58  import org.apache.hadoop.hbase.util.FSUtils;
59  
60  import com.google.common.annotations.VisibleForTesting;
61  
62  /**
63   * This class abstracts a bunch of operations the HMaster needs to interact with
64   * the underlying file system, including splitting log files, checking file
65   * system status, etc.
66   */
67  @InterfaceAudience.Private
68  public class MasterFileSystem {
69    private static final Log LOG = LogFactory.getLog(MasterFileSystem.class.getName());
70    // HBase configuration
71    Configuration conf;
72    // master status
73    Server master;
74    // metrics for master
75    private final MetricsMasterFileSystem metricsMasterFilesystem = new MetricsMasterFileSystem();
76    // Persisted unique cluster ID
77    private ClusterId clusterId;
78    // Keep around for convenience.
79    private final FileSystem fs;
80    // Is the fileystem ok?
81    private volatile boolean fsOk = true;
82    // The Path to the old logs dir
83    private final Path oldLogDir;
84    // root hbase directory on the FS
85    private final Path rootdir;
86    // hbase temp directory used for table construction and deletion
87    private final Path tempdir;
88    // create the split log lock
89    final Lock splitLogLock = new ReentrantLock();
90    final boolean distributedLogReplay;
91    final SplitLogManager splitLogManager;
92    private final MasterServices services;
93  
94    final static PathFilter META_FILTER = new PathFilter() {
95      @Override
96      public boolean accept(Path p) {
97        return DefaultWALProvider.isMetaFile(p);
98      }
99    };
100 
101   final static PathFilter NON_META_FILTER = new PathFilter() {
102     @Override
103     public boolean accept(Path p) {
104       return !DefaultWALProvider.isMetaFile(p);
105     }
106   };
107 
108   public MasterFileSystem(Server master, MasterServices services)
109   throws IOException {
110     this.conf = master.getConfiguration();
111     this.master = master;
112     this.services = services;
113     // Set filesystem to be that of this.rootdir else we get complaints about
114     // mismatched filesystems if hbase.rootdir is hdfs and fs.defaultFS is
115     // default localfs.  Presumption is that rootdir is fully-qualified before
116     // we get to here with appropriate fs scheme.
117     this.rootdir = FSUtils.getRootDir(conf);
118     this.tempdir = new Path(this.rootdir, HConstants.HBASE_TEMP_DIRECTORY);
119     // Cover both bases, the old way of setting default fs and the new.
120     // We're supposed to run on 0.20 and 0.21 anyways.
121     this.fs = this.rootdir.getFileSystem(conf);
122     FSUtils.setFsDefault(conf, new Path(this.fs.getUri()));
123     // make sure the fs has the same conf
124     fs.setConf(conf);
125     // setup the filesystem variable
126     // set up the archived logs path
127     this.oldLogDir = createInitialFileSystemLayout();
128     HFileSystem.addLocationsOrderInterceptor(conf);
129     this.splitLogManager =
130         new SplitLogManager(master, master.getConfiguration(), master, services,
131             master.getServerName());
132     this.distributedLogReplay = this.splitLogManager.isLogReplaying();
133   }
134 
135   @VisibleForTesting
136   SplitLogManager getSplitLogManager() {
137     return this.splitLogManager;
138   }
139 
140   /**
141    * Create initial layout in filesystem.
142    * <ol>
143    * <li>Check if the meta region exists and is readable, if not create it.
144    * Create hbase.version and the hbase:meta directory if not one.
145    * </li>
146    * <li>Create a log archive directory for RS to put archived logs</li>
147    * </ol>
148    * Idempotent.
149    */
150   private Path createInitialFileSystemLayout() throws IOException {
151     // check if the root directory exists
152     checkRootDir(this.rootdir, conf, this.fs);
153 
154     // check if temp directory exists and clean it
155     checkTempDir(this.tempdir, conf, this.fs);
156 
157     Path oldLogDir = new Path(this.rootdir, HConstants.HREGION_OLDLOGDIR_NAME);
158 
159     // Make sure the region servers can archive their old logs
160     if(!this.fs.exists(oldLogDir)) {
161       this.fs.mkdirs(oldLogDir);
162     }
163 
164     return oldLogDir;
165   }
166 
167   public FileSystem getFileSystem() {
168     return this.fs;
169   }
170 
171   /**
172    * Get the directory where old logs go
173    * @return the dir
174    */
175   public Path getOldLogDir() {
176     return this.oldLogDir;
177   }
178 
179   /**
180    * Checks to see if the file system is still accessible.
181    * If not, sets closed
182    * @return false if file system is not available
183    */
184   public boolean checkFileSystem() {
185     if (this.fsOk) {
186       try {
187         FSUtils.checkFileSystemAvailable(this.fs);
188         FSUtils.checkDfsSafeMode(this.conf);
189       } catch (IOException e) {
190         master.abort("Shutting down HBase cluster: file system not available", e);
191         this.fsOk = false;
192       }
193     }
194     return this.fsOk;
195   }
196 
197   /**
198    * @return HBase root dir.
199    */
200   public Path getRootDir() {
201     return this.rootdir;
202   }
203 
204   /**
205    * @return HBase temp dir.
206    */
207   public Path getTempDir() {
208     return this.tempdir;
209   }
210 
211   /**
212    * @return The unique identifier generated for this cluster
213    */
214   public ClusterId getClusterId() {
215     return clusterId;
216   }
217 
218   /**
219    * Inspect the log directory to find dead servers which need recovery work
220    * @return A set of ServerNames which aren't running but still have WAL files left in file system
221    */
222   Set<ServerName> getFailedServersFromLogFolders() {
223     boolean retrySplitting = !conf.getBoolean("hbase.hlog.split.skip.errors",
224         WALSplitter.SPLIT_SKIP_ERRORS_DEFAULT);
225 
226     Set<ServerName> serverNames = new HashSet<ServerName>();
227     Path logsDirPath = new Path(this.rootdir, HConstants.HREGION_LOGDIR_NAME);
228 
229     do {
230       if (master.isStopped()) {
231         LOG.warn("Master stopped while trying to get failed servers.");
232         break;
233       }
234       try {
235         if (!this.fs.exists(logsDirPath)) return serverNames;
236         FileStatus[] logFolders = FSUtils.listStatus(this.fs, logsDirPath, null);
237         // Get online servers after getting log folders to avoid log folder deletion of newly
238         // checked in region servers . see HBASE-5916
239         Set<ServerName> onlineServers = ((HMaster) master).getServerManager().getOnlineServers()
240             .keySet();
241 
242         if (logFolders == null || logFolders.length == 0) {
243           LOG.debug("No log files to split, proceeding...");
244           return serverNames;
245         }
246         for (FileStatus status : logFolders) {
247           FileStatus[] curLogFiles = FSUtils.listStatus(this.fs, status.getPath(), null);
248           if (curLogFiles == null || curLogFiles.length == 0) {
249             // Empty log folder. No recovery needed
250             continue;
251           }
252           final ServerName serverName = DefaultWALProvider.getServerNameFromWALDirectoryName(
253               status.getPath());
254           if (null == serverName) {
255             LOG.warn("Log folder " + status.getPath() + " doesn't look like its name includes a " +
256                 "region server name; leaving in place. If you see later errors about missing " +
257                 "write ahead logs they may be saved in this location.");
258           } else if (!onlineServers.contains(serverName)) {
259             LOG.info("Log folder " + status.getPath() + " doesn't belong "
260                 + "to a known region server, splitting");
261             serverNames.add(serverName);
262           } else {
263             LOG.info("Log folder " + status.getPath() + " belongs to an existing region server");
264           }
265         }
266         retrySplitting = false;
267       } catch (IOException ioe) {
268         LOG.warn("Failed getting failed servers to be recovered.", ioe);
269         if (!checkFileSystem()) {
270           LOG.warn("Bad Filesystem, exiting");
271           Runtime.getRuntime().halt(1);
272         }
273         try {
274           if (retrySplitting) {
275             Thread.sleep(conf.getInt("hbase.hlog.split.failure.retry.interval", 30 * 1000));
276           }
277         } catch (InterruptedException e) {
278           LOG.warn("Interrupted, aborting since cannot return w/o splitting");
279           Thread.currentThread().interrupt();
280           retrySplitting = false;
281           Runtime.getRuntime().halt(1);
282         }
283       }
284     } while (retrySplitting);
285 
286     return serverNames;
287   }
288 
289   public void splitLog(final ServerName serverName) throws IOException {
290     Set<ServerName> serverNames = new HashSet<ServerName>();
291     serverNames.add(serverName);
292     splitLog(serverNames);
293   }
294 
295   /**
296    * Specialized method to handle the splitting for meta WAL
297    * @param serverName
298    * @throws IOException
299    */
300   public void splitMetaLog(final ServerName serverName) throws IOException {
301     Set<ServerName> serverNames = new HashSet<ServerName>();
302     serverNames.add(serverName);
303     splitMetaLog(serverNames);
304   }
305 
306   /**
307    * Specialized method to handle the splitting for meta WAL
308    * @param serverNames
309    * @throws IOException
310    */
311   public void splitMetaLog(final Set<ServerName> serverNames) throws IOException {
312     splitLog(serverNames, META_FILTER);
313   }
314 
315   @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="UL_UNRELEASED_LOCK", justification=
316       "We only release this lock when we set it. Updates to code that uses it should verify use " +
317       "of the guard boolean.")
318   private List<Path> getLogDirs(final Set<ServerName> serverNames) throws IOException {
319     List<Path> logDirs = new ArrayList<Path>();
320     boolean needReleaseLock = false;
321     if (!this.services.isInitialized()) {
322       // during master initialization, we could have multiple places splitting a same wal
323       this.splitLogLock.lock();
324       needReleaseLock = true;
325     }
326     try {
327       for (ServerName serverName : serverNames) {
328         Path logDir = new Path(this.rootdir,
329             DefaultWALProvider.getWALDirectoryName(serverName.toString()));
330         Path splitDir = logDir.suffix(DefaultWALProvider.SPLITTING_EXT);
331         // Rename the directory so a rogue RS doesn't create more WALs
332         if (fs.exists(logDir)) {
333           if (!this.fs.rename(logDir, splitDir)) {
334             throw new IOException("Failed fs.rename for log split: " + logDir);
335           }
336           logDir = splitDir;
337           LOG.debug("Renamed region directory: " + splitDir);
338         } else if (!fs.exists(splitDir)) {
339           LOG.info("Log dir for server " + serverName + " does not exist");
340           continue;
341         }
342         logDirs.add(splitDir);
343       }
344     } catch (IOException ioe) {
345       if (!checkFileSystem()) {
346         this.services.abort("Aborting due to filesystem unavailable", ioe);
347         throw ioe;
348       }
349     } finally {
350       if (needReleaseLock) {
351         this.splitLogLock.unlock();
352       }
353     }
354     return logDirs;
355   }
356 
357   /**
358    * Mark regions in recovering state when distributedLogReplay are set true
359    * @param serverName Failed region server whose wals to be replayed
360    * @param regions Set of regions to be recovered
361    * @throws IOException
362    */
363   public void prepareLogReplay(ServerName serverName, Set<HRegionInfo> regions) throws IOException {
364     if (!this.distributedLogReplay) {
365       return;
366     }
367     // mark regions in recovering state
368     if (regions == null || regions.isEmpty()) {
369       return;
370     }
371     this.splitLogManager.markRegionsRecovering(serverName, regions);
372   }
373 
374   public void splitLog(final Set<ServerName> serverNames) throws IOException {
375     splitLog(serverNames, NON_META_FILTER);
376   }
377 
378   /**
379    * Wrapper function on {@link SplitLogManager#removeStaleRecoveringRegions(Set)}
380    * @param failedServers
381    * @throws IOException
382    */
383   void removeStaleRecoveringRegionsFromZK(final Set<ServerName> failedServers)
384       throws IOException, InterruptedIOException {
385     this.splitLogManager.removeStaleRecoveringRegions(failedServers);
386   }
387 
388   /**
389    * This method is the base split method that splits WAL files matching a filter. Callers should
390    * pass the appropriate filter for meta and non-meta WALs.
391    * @param serverNames logs belonging to these servers will be split; this will rename the log
392    *                    directory out from under a soft-failed server
393    * @param filter
394    * @throws IOException
395    */
396   public void splitLog(final Set<ServerName> serverNames, PathFilter filter) throws IOException {
397     long splitTime = 0, splitLogSize = 0;
398     List<Path> logDirs = getLogDirs(serverNames);
399 
400     splitLogManager.handleDeadWorkers(serverNames);
401     splitTime = EnvironmentEdgeManager.currentTime();
402     splitLogSize = splitLogManager.splitLogDistributed(serverNames, logDirs, filter);
403     splitTime = EnvironmentEdgeManager.currentTime() - splitTime;
404 
405     if (this.metricsMasterFilesystem != null) {
406       if (filter == META_FILTER) {
407         this.metricsMasterFilesystem.addMetaWALSplit(splitTime, splitLogSize);
408       } else {
409         this.metricsMasterFilesystem.addSplit(splitTime, splitLogSize);
410       }
411     }
412   }
413 
414   /**
415    * Get the rootdir.  Make sure its wholesome and exists before returning.
416    * @param rd
417    * @param c
418    * @param fs
419    * @return hbase.rootdir (after checks for existence and bootstrapping if
420    * needed populating the directory with necessary bootup files).
421    * @throws IOException
422    */
423   @SuppressWarnings("deprecation")
424   private Path checkRootDir(final Path rd, final Configuration c,
425     final FileSystem fs)
426   throws IOException {
427     // If FS is in safe mode wait till out of it.
428     FSUtils.waitOnSafeMode(c, c.getInt(HConstants.THREAD_WAKE_FREQUENCY, 10 * 1000));
429 
430     boolean isSecurityEnabled = "kerberos".equalsIgnoreCase(c.get("hbase.security.authentication"));
431     FsPermission rootDirPerms = new FsPermission(c.get("hbase.rootdir.perms", "700"));
432 
433     // Filesystem is good. Go ahead and check for hbase.rootdir.
434     try {
435       if (!fs.exists(rd)) {
436         if (isSecurityEnabled) {
437           fs.mkdirs(rd, rootDirPerms);
438         } else {
439           fs.mkdirs(rd);
440         }
441         // DFS leaves safe mode with 0 DNs when there are 0 blocks.
442         // We used to handle this by checking the current DN count and waiting until
443         // it is nonzero. With security, the check for datanode count doesn't work --
444         // it is a privileged op. So instead we adopt the strategy of the jobtracker
445         // and simply retry file creation during bootstrap indefinitely. As soon as
446         // there is one datanode it will succeed. Permission problems should have
447         // already been caught by mkdirs above.
448         FSUtils.setVersion(fs, rd, c.getInt(HConstants.THREAD_WAKE_FREQUENCY,
449           10 * 1000), c.getInt(HConstants.VERSION_FILE_WRITE_ATTEMPTS,
450             HConstants.DEFAULT_VERSION_FILE_WRITE_ATTEMPTS));
451       } else {
452         if (!fs.isDirectory(rd)) {
453           throw new IllegalArgumentException(rd.toString() + " is not a directory");
454         }
455         if (isSecurityEnabled && !rootDirPerms.equals(fs.getFileStatus(rd).getPermission())) {
456           // check whether the permission match
457           LOG.warn("Found rootdir permissions NOT matching expected \"hbase.rootdir.perms\" for "
458               + "rootdir=" + rd.toString() + " permissions=" + fs.getFileStatus(rd).getPermission()
459               + " and  \"hbase.rootdir.perms\" configured as "
460               + c.get("hbase.rootdir.perms", "700") + ". Automatically setting the permissions. You"
461               + " can change the permissions by setting \"hbase.rootdir.perms\" in hbase-site.xml "
462               + "and restarting the master");
463           fs.setPermission(rd, rootDirPerms);
464         }
465         // as above
466         FSUtils.checkVersion(fs, rd, true, c.getInt(HConstants.THREAD_WAKE_FREQUENCY,
467           10 * 1000), c.getInt(HConstants.VERSION_FILE_WRITE_ATTEMPTS,
468             HConstants.DEFAULT_VERSION_FILE_WRITE_ATTEMPTS));
469       }
470     } catch (DeserializationException de) {
471       LOG.fatal("Please fix invalid configuration for " + HConstants.HBASE_DIR, de);
472       IOException ioe = new IOException();
473       ioe.initCause(de);
474       throw ioe;
475     } catch (IllegalArgumentException iae) {
476       LOG.fatal("Please fix invalid configuration for "
477         + HConstants.HBASE_DIR + " " + rd.toString(), iae);
478       throw iae;
479     }
480     // Make sure cluster ID exists
481     if (!FSUtils.checkClusterIdExists(fs, rd, c.getInt(
482         HConstants.THREAD_WAKE_FREQUENCY, 10 * 1000))) {
483       FSUtils.setClusterId(fs, rd, new ClusterId(), c.getInt(HConstants.THREAD_WAKE_FREQUENCY, 10 * 1000));
484     }
485     clusterId = FSUtils.getClusterId(fs, rd);
486 
487     // Make sure the meta region directory exists!
488     if (!FSUtils.metaRegionExists(fs, rd)) {
489       bootstrap(rd, c);
490     } else {
491       // Migrate table descriptor files if necessary
492       org.apache.hadoop.hbase.util.FSTableDescriptorMigrationToSubdir
493         .migrateFSTableDescriptorsIfNecessary(fs, rd);
494     }
495 
496     // Create tableinfo-s for hbase:meta if not already there.
497 
498     // meta table is a system table, so descriptors are predefined,
499     // we should get them from registry.
500     FSTableDescriptors fsd = new FSTableDescriptors(c, fs, rd);
501     fsd.createTableDescriptor(
502       new HTableDescriptor(fsd.get(TableName.META_TABLE_NAME)));
503 
504     return rd;
505   }
506 
507   /**
508    * Make sure the hbase temp directory exists and is empty.
509    * NOTE that this method is only executed once just after the master becomes the active one.
510    */
511   private void checkTempDir(final Path tmpdir, final Configuration c, final FileSystem fs)
512       throws IOException {
513     // If the temp directory exists, clear the content (left over, from the previous run)
514     if (fs.exists(tmpdir)) {
515       // Archive table in temp, maybe left over from failed deletion,
516       // if not the cleaner will take care of them.
517       for (Path tabledir: FSUtils.getTableDirs(fs, tmpdir)) {
518         for (Path regiondir: FSUtils.getRegionDirs(fs, tabledir)) {
519           HFileArchiver.archiveRegion(fs, this.rootdir, tabledir, regiondir);
520         }
521       }
522       if (!fs.delete(tmpdir, true)) {
523         throw new IOException("Unable to clean the temp directory: " + tmpdir);
524       }
525     }
526 
527     // Create the temp directory
528     if (!fs.mkdirs(tmpdir)) {
529       throw new IOException("HBase temp directory '" + tmpdir + "' creation failure.");
530     }
531   }
532 
533   private static void bootstrap(final Path rd, final Configuration c)
534   throws IOException {
535     LOG.info("BOOTSTRAP: creating hbase:meta region");
536     try {
537       // Bootstrapping, make sure blockcache is off.  Else, one will be
538       // created here in bootstrap and it'll need to be cleaned up.  Better to
539       // not make it in first place.  Turn off block caching for bootstrap.
540       // Enable after.
541       HRegionInfo metaHRI = new HRegionInfo(HRegionInfo.FIRST_META_REGIONINFO);
542       HTableDescriptor metaDescriptor = new FSTableDescriptors(c).get(TableName.META_TABLE_NAME);
543       setInfoFamilyCachingForMeta(metaDescriptor, false);
544       HRegion meta = HRegion.createHRegion(metaHRI, rd, c, metaDescriptor, null, true, true);
545       setInfoFamilyCachingForMeta(metaDescriptor, true);
546       HRegion.closeHRegion(meta);
547     } catch (IOException e) {
548       e = RemoteExceptionHandler.checkIOException(e);
549       LOG.error("bootstrap", e);
550       throw e;
551     }
552   }
553 
554   /**
555    * Enable in memory caching for hbase:meta
556    */
557   public static void setInfoFamilyCachingForMeta(final HTableDescriptor metaDescriptor,
558       final boolean b) {
559     for (HColumnDescriptor hcd: metaDescriptor.getColumnFamilies()) {
560       if (Bytes.equals(hcd.getName(), HConstants.CATALOG_FAMILY)) {
561         hcd.setBlockCacheEnabled(b);
562         hcd.setInMemory(b);
563       }
564     }
565   }
566 
567   public void deleteFamilyFromFS(HRegionInfo region, byte[] familyName)
568       throws IOException {
569     // archive family store files
570     Path tableDir = FSUtils.getTableDir(rootdir, region.getTable());
571     HFileArchiver.archiveFamily(fs, conf, region, tableDir, familyName);
572 
573     // delete the family folder
574     Path familyDir = new Path(tableDir,
575       new Path(region.getEncodedName(), Bytes.toString(familyName)));
576     if (fs.delete(familyDir, true) == false) {
577       if (fs.exists(familyDir)) {
578         throw new IOException("Could not delete family "
579             + Bytes.toString(familyName) + " from FileSystem for region "
580             + region.getRegionNameAsString() + "(" + region.getEncodedName()
581             + ")");
582       }
583     }
584   }
585 
586   public void stop() {
587     if (splitLogManager != null) {
588       this.splitLogManager.stop();
589     }
590   }
591 
592   /**
593    * The function is used in SSH to set recovery mode based on configuration after all outstanding
594    * log split tasks drained.
595    * @throws IOException
596    */
597   public void setLogRecoveryMode() throws IOException {
598       this.splitLogManager.setRecoveryMode(false);
599   }
600 
601   public RecoveryMode getLogRecoveryMode() {
602     return this.splitLogManager.getRecoveryMode();
603   }
604 
605   public void logFileSystemState(Log log) throws IOException {
606     FSUtils.logFileSystemState(fs, rootdir, log);
607   }
608 }