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.wal;
20  
21  import java.io.Closeable;
22  import java.io.DataInput;
23  import java.io.DataOutput;
24  import java.io.IOException;
25  import java.util.List;
26  import java.util.concurrent.atomic.AtomicBoolean;
27  import java.util.regex.Pattern;
28  
29  import com.google.common.annotations.VisibleForTesting;
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.hbase.classification.InterfaceStability;
34  import org.apache.hadoop.conf.Configuration;
35  import org.apache.hadoop.fs.FSDataInputStream;
36  import org.apache.hadoop.fs.FileSystem;
37  import org.apache.hadoop.fs.Path;
38  import org.apache.hadoop.hbase.HConstants;
39  import org.apache.hadoop.hbase.ServerName;
40  import org.apache.hadoop.hbase.util.FSUtils;
41  
42  // imports for things that haven't moved from regionserver.wal yet.
43  import org.apache.hadoop.hbase.regionserver.wal.FSHLog;
44  import org.apache.hadoop.hbase.regionserver.wal.ProtobufLogWriter;
45  import org.apache.hadoop.hbase.regionserver.wal.WALActionsListener;
46  
47  /**
48   * A WAL Provider that returns a single thread safe WAL that writes to HDFS.
49   * By default, this implementation picks a directory in HDFS based on a combination of
50   * <ul>
51   *   <li>the HBase root directory
52   *   <li>HConstants.HREGION_LOGDIR_NAME
53   *   <li>the given factory's factoryId (usually identifying the regionserver by host:port)
54   * </ul>
55   * It also uses the providerId to diffentiate among files.
56   *
57   */
58  @InterfaceAudience.Private
59  @InterfaceStability.Evolving
60  public class DefaultWALProvider implements WALProvider {
61    private static final Log LOG = LogFactory.getLog(DefaultWALProvider.class);
62  
63    // Only public so classes back in regionserver.wal can access
64    public interface Reader extends WAL.Reader {
65      /**
66       * @param fs File system.
67       * @param path Path.
68       * @param c Configuration.
69       * @param s Input stream that may have been pre-opened by the caller; may be null.
70       */
71      void init(FileSystem fs, Path path, Configuration c, FSDataInputStream s) throws IOException;
72    }
73  
74    // Only public so classes back in regionserver.wal can access
75    public interface Writer extends WALProvider.Writer {
76      void init(FileSystem fs, Path path, Configuration c, boolean overwritable) throws IOException;
77    }
78  
79    protected volatile FSHLog log = null;
80    private WALFactory factory = null;
81    private Configuration conf = null;
82    private List<WALActionsListener> listeners = null;
83    private String providerId = null;
84    private AtomicBoolean initialized = new AtomicBoolean(false);
85    // for default wal provider, logPrefix won't change
86    private String logPrefix = null;
87  
88    /**
89     * we synchronized on walCreateLock to prevent wal recreation in different threads
90     */
91    private final Object walCreateLock = new Object();
92  
93    /**
94     * @param factory factory that made us, identity used for FS layout. may not be null
95     * @param conf may not be null
96     * @param listeners may be null
97     * @param providerId differentiate between providers from one facotry, used for FS layout. may be
98     *                   null
99     */
100   @Override
101   public void init(final WALFactory factory, final Configuration conf,
102       final List<WALActionsListener> listeners, String providerId) throws IOException {
103     if (!initialized.compareAndSet(false, true)) {
104       throw new IllegalStateException("WALProvider.init should only be called once.");
105     }
106     this.factory = factory;
107     this.conf = conf;
108     this.listeners = listeners;
109     this.providerId = providerId;
110     // get log prefix
111     StringBuilder sb = new StringBuilder().append(factory.factoryId);
112     if (providerId != null) {
113       if (providerId.startsWith(WAL_FILE_NAME_DELIMITER)) {
114         sb.append(providerId);
115       } else {
116         sb.append(WAL_FILE_NAME_DELIMITER).append(providerId);
117       }
118     }
119     logPrefix = sb.toString();
120   }
121 
122   @Override
123   public WAL getWAL(final byte[] identifier, byte[] namespace) throws IOException {
124     if (log == null) {
125       // only lock when need to create wal, and need to lock since
126       // creating hlog on fs is time consuming
127       synchronized (walCreateLock) {
128         if (log == null) {
129           log = new FSHLog(FileSystem.get(conf), FSUtils.getRootDir(conf),
130               getWALDirectoryName(factory.factoryId), HConstants.HREGION_OLDLOGDIR_NAME, conf,
131               listeners, true, logPrefix,
132               META_WAL_PROVIDER_ID.equals(providerId) ? META_WAL_PROVIDER_ID : null);
133         }
134       }
135     }
136     return log;
137   }
138 
139   @Override
140   public void close() throws IOException {
141     if (log != null) log.close();
142   }
143 
144   @Override
145   public void shutdown() throws IOException {
146     if (log != null) log.shutdown();
147   }
148 
149   // should be package private; more visible for use in FSHLog
150   public static final String WAL_FILE_NAME_DELIMITER = ".";
151   /** The hbase:meta region's WAL filename extension */
152   @VisibleForTesting
153   public static final String META_WAL_PROVIDER_ID = ".meta";
154   static final String DEFAULT_PROVIDER_ID = "default";
155 
156   // Implementation details that currently leak in tests or elsewhere follow
157   /** File Extension used while splitting an WAL into regions (HBASE-2312) */
158   public static final String SPLITTING_EXT = "-splitting";
159 
160   /**
161    * iff the given WALFactory is using the DefaultWALProvider for meta and/or non-meta,
162    * count the number of files (rolled and active). if either of them aren't, count 0
163    * for that provider.
164    */
165   @Override
166   public long getNumLogFiles() {
167     return log == null ? 0 : this.log.getNumLogFiles();
168   }
169 
170   /**
171    * iff the given WALFactory is using the DefaultWALProvider for meta and/or non-meta,
172    * count the size of files (rolled and active). if either of them aren't, count 0
173    * for that provider.
174    */
175   @Override
176   public long getLogFileSize() {
177     return log == null ? 0 : this.log.getLogFileSize();
178   }
179 
180   /**
181    * returns the number of rolled WAL files.
182    */
183   @VisibleForTesting
184   public static int getNumRolledLogFiles(WAL wal) {
185     return ((FSHLog)wal).getNumRolledLogFiles();
186   }
187 
188   /**
189    * return the current filename from the current wal.
190    */
191   @VisibleForTesting
192   public static Path getCurrentFileName(final WAL wal) {
193     return ((FSHLog)wal).getCurrentFileName();
194   }
195 
196   /**
197    * request a log roll, but don't actually do it.
198    */
199   @VisibleForTesting
200   static void requestLogRoll(final WAL wal) {
201     ((FSHLog)wal).requestLogRoll();
202   }
203 
204   /**
205    * It returns the file create timestamp from the file name.
206    * For name format see {@link #validateWALFilename(String)}
207    * public until remaining tests move to o.a.h.h.wal
208    * @param wal must not be null
209    * @return the file number that is part of the WAL file name
210    */
211   @VisibleForTesting
212   public static long extractFileNumFromWAL(final WAL wal) {
213     final Path walName = ((FSHLog)wal).getCurrentFileName();
214     if (walName == null) {
215       throw new IllegalArgumentException("The WAL path couldn't be null");
216     }
217     final String[] walPathStrs = walName.toString().split("\\" + WAL_FILE_NAME_DELIMITER);
218     return Long.parseLong(walPathStrs[walPathStrs.length - (isMetaFile(walName) ? 2:1)]);
219   }
220 
221   /**
222    * Pattern used to validate a WAL file name
223    * see {@link #validateWALFilename(String)} for description.
224    */
225   private static final Pattern pattern = Pattern.compile(".*\\.\\d*("+META_WAL_PROVIDER_ID+")*");
226 
227   /**
228    * A WAL file name is of the format:
229    * &lt;wal-name&gt;{@link #WAL_FILE_NAME_DELIMITER}&lt;file-creation-timestamp&gt;[.meta].
230    *
231    * provider-name is usually made up of a server-name and a provider-id
232    *
233    * @param filename name of the file to validate
234    * @return <tt>true</tt> if the filename matches an WAL, <tt>false</tt>
235    *         otherwise
236    */
237   public static boolean validateWALFilename(String filename) {
238     return pattern.matcher(filename).matches();
239   }
240 
241   /**
242    * Construct the directory name for all WALs on a given server.
243    *
244    * @param serverName
245    *          Server name formatted as described in {@link ServerName}
246    * @return the relative WAL directory name, e.g.
247    *         <code>.logs/1.example.org,60030,12345</code> if
248    *         <code>serverName</code> passed is
249    *         <code>1.example.org,60030,12345</code>
250    */
251   public static String getWALDirectoryName(final String serverName) {
252     StringBuilder dirName = new StringBuilder(HConstants.HREGION_LOGDIR_NAME);
253     dirName.append("/");
254     dirName.append(serverName);
255     return dirName.toString();
256   }
257 
258   /**
259    * Pulls a ServerName out of a Path generated according to our layout rules.
260    *
261    * In the below layouts, this method ignores the format of the logfile component.
262    *
263    * Current format:
264    *
265    * [base directory for hbase]/hbase/.logs/ServerName/logfile
266    *      or
267    * [base directory for hbase]/hbase/.logs/ServerName-splitting/logfile
268    *
269    * Expected to work for individual log files and server-specific directories.
270    *
271    * @return null if it's not a log file. Returns the ServerName of the region
272    *         server that created this log file otherwise.
273    */
274   public static ServerName getServerNameFromWALDirectoryName(Configuration conf, String path)
275       throws IOException {
276     if (path == null
277         || path.length() <= HConstants.HREGION_LOGDIR_NAME.length()) {
278       return null;
279     }
280 
281     if (conf == null) {
282       throw new IllegalArgumentException("parameter conf must be set");
283     }
284 
285     final String rootDir = conf.get(HConstants.HBASE_DIR);
286     if (rootDir == null || rootDir.isEmpty()) {
287       throw new IllegalArgumentException(HConstants.HBASE_DIR
288           + " key not found in conf.");
289     }
290 
291     final StringBuilder startPathSB = new StringBuilder(rootDir);
292     if (!rootDir.endsWith("/"))
293       startPathSB.append('/');
294     startPathSB.append(HConstants.HREGION_LOGDIR_NAME);
295     if (!HConstants.HREGION_LOGDIR_NAME.endsWith("/"))
296       startPathSB.append('/');
297     final String startPath = startPathSB.toString();
298 
299     String fullPath;
300     try {
301       fullPath = FileSystem.get(conf).makeQualified(new Path(path)).toString();
302     } catch (IllegalArgumentException e) {
303       LOG.info("Call to makeQualified failed on " + path + " " + e.getMessage());
304       return null;
305     }
306 
307     if (!fullPath.startsWith(startPath)) {
308       return null;
309     }
310 
311     final String serverNameAndFile = fullPath.substring(startPath.length());
312 
313     if (serverNameAndFile.indexOf('/') < "a,0,0".length()) {
314       // Either it's a file (not a directory) or it's not a ServerName format
315       return null;
316     }
317 
318     Path p = new Path(path);
319     return getServerNameFromWALDirectoryName(p);
320   }
321 
322   /**
323    * This function returns region server name from a log file name which is in one of the following
324    * formats:
325    * <ul>
326    *   <li>hdfs://&lt;name node&gt;/hbase/.logs/&lt;server name&gt;-splitting/...</li>
327    *   <li>hdfs://&lt;name node&gt;/hbase/.logs/&lt;server name&gt;/...</li>
328    * </ul>
329    * @param logFile
330    * @return null if the passed in logFile isn't a valid WAL file path
331    */
332   public static ServerName getServerNameFromWALDirectoryName(Path logFile) {
333     String logDirName = logFile.getParent().getName();
334     // We were passed the directory and not a file in it.
335     if (logDirName.equals(HConstants.HREGION_LOGDIR_NAME)) {
336       logDirName = logFile.getName();
337     }
338     ServerName serverName = null;
339     if (logDirName.endsWith(SPLITTING_EXT)) {
340       logDirName = logDirName.substring(0, logDirName.length() - SPLITTING_EXT.length());
341     }
342     try {
343       serverName = ServerName.parseServerName(logDirName);
344     } catch (IllegalArgumentException ex) {
345       serverName = null;
346       LOG.warn("Cannot parse a server name from path=" + logFile + "; " + ex.getMessage());
347     }
348     if (serverName != null && serverName.getStartcode() < 0) {
349       LOG.warn("Invalid log file path=" + logFile);
350       serverName = null;
351     }
352     return serverName;
353   }
354 
355   public static boolean isMetaFile(Path p) {
356     return isMetaFile(p.getName());
357   }
358 
359   public static boolean isMetaFile(String p) {
360     if (p != null && p.endsWith(META_WAL_PROVIDER_ID)) {
361       return true;
362     }
363     return false;
364   }
365 
366   /**
367    * public because of FSHLog. Should be package-private
368    */
369   public static Writer createWriter(final Configuration conf, final FileSystem fs, final Path path,
370       final boolean overwritable)
371       throws IOException {
372     // Configuration already does caching for the Class lookup.
373     Class<? extends Writer> logWriterClass = conf.getClass("hbase.regionserver.hlog.writer.impl",
374         ProtobufLogWriter.class, Writer.class);
375     Writer writer = null;
376     try {
377       writer = logWriterClass.newInstance();
378       writer.init(fs, path, conf, overwritable);
379       return writer;
380     } catch (Exception e) {
381       LOG.debug("Error instantiating log writer.", e);
382       if (writer != null) {
383         try{
384           writer.close();
385         } catch(IOException ee){
386           LOG.error("cannot close log writer", ee);
387         }
388       }
389       throw new IOException("cannot get log writer", e);
390     }
391   }
392 
393   /**
394    * Get prefix of the log from its name, assuming WAL name in format of
395    * log_prefix.filenumber.log_suffix @see {@link FSHLog#getCurrentFileName()}
396    * @param name Name of the WAL to parse
397    * @return prefix of the log
398    */
399   public static String getWALPrefixFromWALName(String name) {
400     int endIndex = name.replaceAll(META_WAL_PROVIDER_ID, "").lastIndexOf(".");
401     return name.substring(0, endIndex);
402   }
403 
404 }