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  
20  package org.apache.hadoop.hbase.regionserver.wal;
21  
22  import java.io.IOException;
23  import java.util.NavigableSet;
24  import java.util.TreeSet;
25  import java.util.regex.Matcher;
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.conf.Configuration;
31  import org.apache.hadoop.fs.FileStatus;
32  import org.apache.hadoop.fs.FileSystem;
33  import org.apache.hadoop.fs.Path;
34  import org.apache.hadoop.fs.PathFilter;
35  import org.apache.hadoop.hbase.HConstants;
36  import org.apache.hadoop.hbase.HRegionInfo;
37  import org.apache.hadoop.hbase.HTableDescriptor;
38  import org.apache.hadoop.hbase.ServerName;
39  import org.apache.hadoop.hbase.protobuf.generated.WALProtos.CompactionDescriptor;
40  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
41  import org.apache.hadoop.hbase.util.FSUtils;
42  import org.apache.hadoop.hbase.util.Bytes;
43  
44  public class HLogUtil {
45    static final Log LOG = LogFactory.getLog(HLogUtil.class);
46  
47    /**
48     * Pattern used to validate a HLog file name
49     */
50    private static final Pattern pattern =
51        Pattern.compile(".*\\.\\d*("+HLog.META_HLOG_FILE_EXTN+")*");
52  
53    /**
54     * @param filename
55     *          name of the file to validate
56     * @return <tt>true</tt> if the filename matches an HLog, <tt>false</tt>
57     *         otherwise
58     */
59    public static boolean validateHLogFilename(String filename) {
60      return pattern.matcher(filename).matches();
61    }
62  
63    /**
64     * Construct the HLog directory name
65     *
66     * @param serverName
67     *          Server name formatted as described in {@link ServerName}
68     * @return the relative HLog directory name, e.g.
69     *         <code>.logs/1.example.org,60030,12345</code> if
70     *         <code>serverName</code> passed is
71     *         <code>1.example.org,60030,12345</code>
72     */
73    public static String getHLogDirectoryName(final String serverName) {
74      StringBuilder dirName = new StringBuilder(HConstants.HREGION_LOGDIR_NAME);
75      dirName.append("/");
76      dirName.append(serverName);
77      return dirName.toString();
78    }
79  
80    /**
81     * @param regiondir
82     *          This regions directory in the filesystem.
83     * @return The directory that holds recovered edits files for the region
84     *         <code>regiondir</code>
85     */
86    public static Path getRegionDirRecoveredEditsDir(final Path regiondir) {
87      return new Path(regiondir, HConstants.RECOVERED_EDITS_DIR);
88    }
89  
90    /**
91     * Move aside a bad edits file.
92     *
93     * @param fs
94     * @param edits
95     *          Edits file to move aside.
96     * @return The name of the moved aside file.
97     * @throws IOException
98     */
99    public static Path moveAsideBadEditsFile(final FileSystem fs, final Path edits)
100       throws IOException {
101     Path moveAsideName = new Path(edits.getParent(), edits.getName() + "."
102         + System.currentTimeMillis());
103     if (!fs.rename(edits, moveAsideName)) {
104       LOG.warn("Rename failed from " + edits + " to " + moveAsideName);
105     }
106     return moveAsideName;
107   }
108 
109   /**
110    * @param path
111    *          - the path to analyze. Expected format, if it's in hlog directory:
112    *          / [base directory for hbase] / hbase / .logs / ServerName /
113    *          logfile
114    * @return null if it's not a log file. Returns the ServerName of the region
115    *         server that created this log file otherwise.
116    */
117   public static ServerName getServerNameFromHLogDirectoryName(
118       Configuration conf, String path) throws IOException {
119     if (path == null
120         || path.length() <= HConstants.HREGION_LOGDIR_NAME.length()) {
121       return null;
122     }
123 
124     if (conf == null) {
125       throw new IllegalArgumentException("parameter conf must be set");
126     }
127 
128     final String rootDir = conf.get(HConstants.HBASE_DIR);
129     if (rootDir == null || rootDir.isEmpty()) {
130       throw new IllegalArgumentException(HConstants.HBASE_DIR
131           + " key not found in conf.");
132     }
133 
134     final StringBuilder startPathSB = new StringBuilder(rootDir);
135     if (!rootDir.endsWith("/"))
136       startPathSB.append('/');
137     startPathSB.append(HConstants.HREGION_LOGDIR_NAME);
138     if (!HConstants.HREGION_LOGDIR_NAME.endsWith("/"))
139       startPathSB.append('/');
140     final String startPath = startPathSB.toString();
141 
142     String fullPath;
143     try {
144       fullPath = FileSystem.get(conf).makeQualified(new Path(path)).toString();
145     } catch (IllegalArgumentException e) {
146       LOG.info("Call to makeQualified failed on " + path + " " + e.getMessage());
147       return null;
148     }
149 
150     if (!fullPath.startsWith(startPath)) {
151       return null;
152     }
153 
154     final String serverNameAndFile = fullPath.substring(startPath.length());
155 
156     if (serverNameAndFile.indexOf('/') < "a,0,0".length()) {
157       // Either it's a file (not a directory) or it's not a ServerName format
158       return null;
159     }
160 
161     final String serverName = serverNameAndFile.substring(0,
162         serverNameAndFile.indexOf('/') - 1);
163 
164     if (!ServerName.isFullServerName(serverName)) {
165       return null;
166     }
167 
168     return ServerName.parseServerName(serverName);
169   }
170 
171   /**
172    * This function returns region server name from a log file name which is in either format:
173    * hdfs://<name node>/hbase/.logs/<server name>-splitting/... or hdfs://<name
174    * node>/hbase/.logs/<server name>/...
175    * @param logFile
176    * @return null if the passed in logFile isn't a valid HLog file path
177    */
178   public static ServerName getServerNameFromHLogDirectoryName(Path logFile) {
179     Path logDir = logFile.getParent();
180     String logDirName = logDir.getName();
181     if (logDirName.equals(HConstants.HREGION_LOGDIR_NAME)) {
182       logDir = logFile;
183       logDirName = logDir.getName();
184     }
185     ServerName serverName = null;
186     if (logDirName.endsWith(HLog.SPLITTING_EXT)) {
187       logDirName = logDirName.substring(0, logDirName.length() - HLog.SPLITTING_EXT.length());
188     }
189     try {
190       serverName = ServerName.parseServerName(logDirName);
191     } catch (IllegalArgumentException ex) {
192       serverName = null;
193       LOG.warn("Invalid log file path=" + logFile, ex);
194     }
195     if (serverName != null && serverName.getStartcode() < 0) {
196       LOG.warn("Invalid log file path=" + logFile);
197       return null;
198     }
199     return serverName;
200   }
201 
202   /**
203    * Returns sorted set of edit files made by wal-log splitter, excluding files
204    * with '.temp' suffix.
205    *
206    * @param fs
207    * @param regiondir
208    * @return Files in passed <code>regiondir</code> as a sorted set.
209    * @throws IOException
210    */
211   public static NavigableSet<Path> getSplitEditFilesSorted(final FileSystem fs,
212       final Path regiondir) throws IOException {
213     NavigableSet<Path> filesSorted = new TreeSet<Path>();
214     Path editsdir = HLogUtil.getRegionDirRecoveredEditsDir(regiondir);
215     if (!fs.exists(editsdir))
216       return filesSorted;
217     FileStatus[] files = FSUtils.listStatus(fs, editsdir, new PathFilter() {
218       @Override
219       public boolean accept(Path p) {
220         boolean result = false;
221         try {
222           // Return files and only files that match the editfile names pattern.
223           // There can be other files in this directory other than edit files.
224           // In particular, on error, we'll move aside the bad edit file giving
225           // it a timestamp suffix. See moveAsideBadEditsFile.
226           Matcher m = HLog.EDITFILES_NAME_PATTERN.matcher(p.getName());
227           result = fs.isFile(p) && m.matches();
228           // Skip the file whose name ends with RECOVERED_LOG_TMPFILE_SUFFIX,
229           // because it means splithlog thread is writting this file.
230           if (p.getName().endsWith(HLog.RECOVERED_LOG_TMPFILE_SUFFIX)) {
231             result = false;
232           }
233         } catch (IOException e) {
234           LOG.warn("Failed isFile check on " + p);
235         }
236         return result;
237       }
238     });
239     if (files == null)
240       return filesSorted;
241     for (FileStatus status : files) {
242       filesSorted.add(status.getPath());
243     }
244     return filesSorted;
245   }
246 
247   public static boolean isMetaFile(Path p) {
248     if (p.getName().endsWith(HLog.META_HLOG_FILE_EXTN)) {
249       return true;
250     }
251     return false;
252   }
253 
254   /**
255    * Write the marker that a compaction has succeeded and is about to be committed.
256    * This provides info to the HMaster to allow it to recover the compaction if
257    * this regionserver dies in the middle (This part is not yet implemented). It also prevents
258    * the compaction from finishing if this regionserver has already lost its lease on the log.
259    */
260   public static void writeCompactionMarker(HLog log, HTableDescriptor htd, HRegionInfo info,
261       final CompactionDescriptor c) throws IOException {
262     WALEdit e = WALEdit.createCompaction(c);
263     log.append(info, c.getTableName().toByteArray(), e,
264         EnvironmentEdgeManager.currentTimeMillis(), htd);
265     LOG.info("Appended compaction marker " + c);
266   }
267 }