View Javadoc

1   /**
2    * Copyright 2010 The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  package org.apache.hadoop.hbase.master;
21  
22  import java.io.IOException;
23  import java.util.Map;
24  import java.util.concurrent.locks.Lock;
25  import java.util.concurrent.locks.ReentrantLock;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.apache.hadoop.conf.Configuration;
30  import org.apache.hadoop.fs.FileStatus;
31  import org.apache.hadoop.fs.FileSystem;
32  import org.apache.hadoop.fs.Path;
33  import org.apache.hadoop.hbase.HColumnDescriptor;
34  import org.apache.hadoop.hbase.HConstants;
35  import org.apache.hadoop.hbase.HRegionInfo;
36  import org.apache.hadoop.hbase.HServerInfo;
37  import org.apache.hadoop.hbase.RemoteExceptionHandler;
38  import org.apache.hadoop.hbase.Server;
39  import org.apache.hadoop.hbase.master.metrics.MasterMetrics;
40  import org.apache.hadoop.hbase.regionserver.HRegion;
41  import org.apache.hadoop.hbase.regionserver.Store;
42  import org.apache.hadoop.hbase.regionserver.wal.HLog;
43  import org.apache.hadoop.hbase.regionserver.wal.HLogSplitter;
44  import org.apache.hadoop.hbase.regionserver.wal.OrphanHLogAfterSplitException;
45  import org.apache.hadoop.hbase.util.Bytes;
46  import org.apache.hadoop.hbase.util.FSUtils;
47  
48  /**
49   * This class abstracts a bunch of operations the HMaster needs to interact with
50   * the underlying file system, including splitting log files, checking file
51   * system status, etc.
52   */
53  public class MasterFileSystem {
54    private static final Log LOG = LogFactory.getLog(MasterFileSystem.class.getName());
55    // HBase configuration
56    Configuration conf;
57    // master status
58    Server master;
59    // metrics for master
60    MasterMetrics metrics;
61    // Keep around for convenience.
62    private final FileSystem fs;
63    // Is the fileystem ok?
64    private volatile boolean fsOk = true;
65    // The Path to the old logs dir
66    private final Path oldLogDir;
67    // root hbase directory on the FS
68    private final Path rootdir;
69    // create the split log lock
70    final Lock splitLogLock = new ReentrantLock();
71  
72    public MasterFileSystem(Server master, MasterMetrics metrics)
73    throws IOException {
74      this.conf = master.getConfiguration();
75      this.master = master;
76      this.metrics = metrics;
77      // Set filesystem to be that of this.rootdir else we get complaints about
78      // mismatched filesystems if hbase.rootdir is hdfs and fs.defaultFS is
79      // default localfs.  Presumption is that rootdir is fully-qualified before
80      // we get to here with appropriate fs scheme.
81      this.rootdir = FSUtils.getRootDir(conf);
82      // Cover both bases, the old way of setting default fs and the new.
83      // We're supposed to run on 0.20 and 0.21 anyways.
84      this.fs = this.rootdir.getFileSystem(conf);
85      String fsUri = this.fs.getUri().toString();
86      conf.set("fs.default.name", fsUri);
87      conf.set("fs.defaultFS", fsUri);
88      // setup the filesystem variable
89      // set up the archived logs path
90      this.oldLogDir = new Path(this.rootdir, HConstants.HREGION_OLDLOGDIR_NAME);
91      createInitialFileSystemLayout();
92    }
93  
94    /**
95     * Create initial layout in filesystem.
96     * <ol>
97     * <li>Check if the root region exists and is readable, if not create it.
98     * Create hbase.version and the -ROOT- directory if not one.
99     * </li>
100    * <li>Create a log archive directory for RS to put archived logs</li>
101    * </ol>
102    * Idempotent.
103    */
104   private void createInitialFileSystemLayout() throws IOException {
105     // check if the root directory exists
106     checkRootDir(this.rootdir, conf, this.fs);
107 
108     // Make sure the region servers can archive their old logs
109     if(!this.fs.exists(this.oldLogDir)) {
110       this.fs.mkdirs(this.oldLogDir);
111     }
112   }
113 
114   public FileSystem getFileSystem() {
115     return this.fs;
116   }
117 
118   /**
119    * Get the directory where old logs go
120    * @return the dir
121    */
122   public Path getOldLogDir() {
123     return this.oldLogDir;
124   }
125 
126   /**
127    * Checks to see if the file system is still accessible.
128    * If not, sets closed
129    * @return false if file system is not available
130    */
131   public boolean checkFileSystem() {
132     if (this.fsOk) {
133       try {
134         FSUtils.checkFileSystemAvailable(this.fs);
135         FSUtils.checkDfsSafeMode(this.conf);
136       } catch (IOException e) {
137         master.abort("Shutting down HBase cluster: file system not available", e);
138         this.fsOk = false;
139       }
140     }
141     return this.fsOk;
142   }
143 
144   /**
145    * @return HBase root dir.
146    * @throws IOException
147    */
148   public Path getRootDir() {
149     return this.rootdir;
150   }
151 
152   /**
153    * Inspect the log directory to recover any log file without
154    * an active region server.
155    * @param onlineServers Map of online servers keyed by
156    * {@link HServerInfo#getServerName()}
157    */
158   void splitLogAfterStartup(final Map<String, HServerInfo> onlineServers) {
159     Path logsDirPath = new Path(this.rootdir, HConstants.HREGION_LOGDIR_NAME);
160     try {
161       if (!this.fs.exists(logsDirPath)) {
162         return;
163       }
164     } catch (IOException e) {
165       throw new RuntimeException("Failed exists test on " + logsDirPath, e);
166     }
167     FileStatus[] logFolders;
168     try {
169       logFolders = this.fs.listStatus(logsDirPath);
170     } catch (IOException e) {
171       throw new RuntimeException("Failed listing " + logsDirPath.toString(), e);
172     }
173     if (logFolders == null || logFolders.length == 0) {
174       LOG.debug("No log files to split, proceeding...");
175       return;
176     }
177     for (FileStatus status : logFolders) {
178       String serverName = status.getPath().getName();
179       if (onlineServers.get(serverName) == null) {
180         LOG.info("Log folder " + status.getPath() + " doesn't belong " +
181           "to a known region server, splitting");
182         splitLog(serverName);
183       } else {
184         LOG.info("Log folder " + status.getPath() +
185           " belongs to an existing region server");
186       }
187     }
188   }
189 
190   public void splitLog(final String serverName) {
191     this.splitLogLock.lock();
192     long splitTime = 0, splitLogSize = 0;
193     Path logDir = new Path(this.rootdir, HLog.getHLogDirectoryName(serverName));
194     try {
195       HLogSplitter splitter = HLogSplitter.createLogSplitter(
196         conf, rootdir, logDir, oldLogDir, this.fs);
197       try {
198         // If FS is in safe mode, just wait till out of it.
199         FSUtils.waitOnSafeMode(conf,
200           conf.getInt(HConstants.THREAD_WAKE_FREQUENCY, 1000));  
201         splitter.splitLog();
202       } catch (OrphanHLogAfterSplitException e) {
203         LOG.warn("Retrying splitting because of:", e);
204         // An HLogSplitter instance can only be used once.  Get new instance.
205         splitter = HLogSplitter.createLogSplitter(conf, rootdir, logDir,
206           oldLogDir, this.fs);
207         splitter.splitLog();
208       }
209       splitTime = splitter.getTime();
210       splitLogSize = splitter.getSize();
211     } catch (IOException e) {
212       checkFileSystem();
213       LOG.error("Failed splitting " + logDir.toString(), e);
214     } finally {
215       this.splitLogLock.unlock();
216     }
217     if (this.metrics != null) {
218       this.metrics.addSplit(splitTime, splitLogSize);
219     }
220   }
221 
222   /**
223    * Get the rootdir.  Make sure its wholesome and exists before returning.
224    * @param rd
225    * @param conf
226    * @param fs
227    * @return hbase.rootdir (after checks for existence and bootstrapping if
228    * needed populating the directory with necessary bootup files).
229    * @throws IOException
230    */
231   private static Path checkRootDir(final Path rd, final Configuration c,
232     final FileSystem fs)
233   throws IOException {
234     // If FS is in safe mode wait till out of it.
235     FSUtils.waitOnSafeMode(c, c.getInt(HConstants.THREAD_WAKE_FREQUENCY,
236         10 * 1000));
237     // Filesystem is good. Go ahead and check for hbase.rootdir.
238     if (!fs.exists(rd)) {
239       fs.mkdirs(rd);
240       // DFS leaves safe mode with 0 DNs when there are 0 blocks.
241       // We used to handle this by checking the current DN count and waiting until
242       // it is nonzero. With security, the check for datanode count doesn't work --
243       // it is a privileged op. So instead we adopt the strategy of the jobtracker
244       // and simply retry file creation during bootstrap indefinitely. As soon as
245       // there is one datanode it will succeed. Permission problems should have
246       // already been caught by mkdirs above.
247       FSUtils.setVersion(fs, rd, c.getInt(HConstants.THREAD_WAKE_FREQUENCY,
248         10 * 1000));
249     } else {
250       // as above
251       FSUtils.checkVersion(fs, rd, true, c.getInt(HConstants.THREAD_WAKE_FREQUENCY,
252         10 * 1000));
253     }
254     // Make sure the root region directory exists!
255     if (!FSUtils.rootRegionExists(fs, rd)) {
256       bootstrap(rd, c);
257     }
258     return rd;
259   }
260 
261   private static void bootstrap(final Path rd, final Configuration c)
262   throws IOException {
263     LOG.info("BOOTSTRAP: creating ROOT and first META regions");
264     try {
265       // Bootstrapping, make sure blockcache is off.  Else, one will be
266       // created here in bootstap and it'll need to be cleaned up.  Better to
267       // not make it in first place.  Turn off block caching for bootstrap.
268       // Enable after.
269       HRegionInfo rootHRI = new HRegionInfo(HRegionInfo.ROOT_REGIONINFO);
270       setInfoFamilyCaching(rootHRI, false);
271       HRegionInfo metaHRI = new HRegionInfo(HRegionInfo.FIRST_META_REGIONINFO);
272       setInfoFamilyCaching(metaHRI, false);
273       HRegion root = HRegion.createHRegion(rootHRI, rd, c);
274       HRegion meta = HRegion.createHRegion(metaHRI, rd, c);
275       setInfoFamilyCaching(rootHRI, true);
276       setInfoFamilyCaching(metaHRI, true);
277       // Add first region from the META table to the ROOT region.
278       HRegion.addRegionToMETA(root, meta);
279       root.close();
280       root.getLog().closeAndDelete();
281       meta.close();
282       meta.getLog().closeAndDelete();
283     } catch (IOException e) {
284       e = RemoteExceptionHandler.checkIOException(e);
285       LOG.error("bootstrap", e);
286       throw e;
287     }
288   }
289 
290   /**
291    * @param hri Set all family block caching to <code>b</code>
292    * @param b
293    */
294   private static void setInfoFamilyCaching(final HRegionInfo hri, final boolean b) {
295     for (HColumnDescriptor hcd: hri.getTableDesc().families.values()) {
296       if (Bytes.equals(hcd.getName(), HConstants.CATALOG_FAMILY)) {
297         hcd.setBlockCacheEnabled(b);
298         hcd.setInMemory(b);
299       }
300     }
301   }
302 
303   public void deleteRegion(HRegionInfo region) throws IOException {
304     fs.delete(HRegion.getRegionDir(rootdir, region), true);
305   }
306 
307   public void deleteTable(byte[] tableName) throws IOException {
308     fs.delete(new Path(rootdir, Bytes.toString(tableName)), true);
309   }
310 
311   public void updateRegionInfo(HRegionInfo region) {
312     // TODO implement this.  i think this is currently broken in trunk i don't
313     //      see this getting updated.
314     //      @see HRegion.checkRegioninfoOnFilesystem()
315   }
316 
317   public void deleteFamily(HRegionInfo region, byte[] familyName)
318   throws IOException {
319     fs.delete(Store.getStoreHomedir(
320         new Path(rootdir, region.getTableDesc().getNameAsString()),
321         region.getEncodedName(), familyName), true);
322   }
323 }