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