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