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