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