View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.hadoop.hbase.io;
20  
21  import java.io.IOException;
22  import java.util.regex.Matcher;
23  import java.util.regex.Pattern;
24  
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  import org.apache.hadoop.hbase.classification.InterfaceAudience;
28  import org.apache.hadoop.conf.Configuration;
29  import org.apache.hadoop.fs.FileSystem;
30  import org.apache.hadoop.fs.Path;
31  import org.apache.hadoop.hbase.TableName;
32  import org.apache.hadoop.hbase.HConstants;
33  import org.apache.hadoop.hbase.HRegionInfo;
34  import org.apache.hadoop.hbase.mob.MobConstants;
35  import org.apache.hadoop.hbase.regionserver.HRegion;
36  import org.apache.hadoop.hbase.regionserver.StoreFileInfo;
37  import org.apache.hadoop.hbase.util.FSUtils;
38  import org.apache.hadoop.hbase.util.HFileArchiveUtil;
39  import org.apache.hadoop.hbase.util.Pair;
40  
41  /**
42   * HFileLink describes a link to an hfile.
43   *
44   * An hfile can be served from a region or from the hfile archive directory (/hbase/.archive)
45   * HFileLink allows to access the referenced hfile regardless of the location where it is.
46   *
47   * <p>Searches for hfiles in the following order and locations:
48   * <ul>
49   *  <li>/hbase/table/region/cf/hfile</li>
50   *  <li>/hbase/.archive/table/region/cf/hfile</li>
51   * </ul>
52   *
53   * The link checks first in the original path if it is not present
54   * it fallbacks to the archived path.
55   */
56  @InterfaceAudience.Private
57  @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="EQ_DOESNT_OVERRIDE_EQUALS",
58    justification="To be fixed but warning suppressed for now")
59  public class HFileLink extends FileLink {
60    private static final Log LOG = LogFactory.getLog(HFileLink.class);
61  
62    /**
63     * A non-capture group, for HFileLink, so that this can be embedded.
64     * The HFileLink describe a link to an hfile in a different table/region
65     * and the name is in the form: table=region-hfile.
66     * <p>
67     * Table name is ([a-zA-Z_0-9][a-zA-Z_0-9.-]*), so '=' is an invalid character for the table name.
68     * Region name is ([a-f0-9]+), so '-' is an invalid character for the region name.
69     * HFile is ([0-9a-f]+(?:_SeqId_[0-9]+_)?) covering the plain hfiles (uuid)
70     * and the bulk loaded (_SeqId_[0-9]+_) hfiles.
71     */
72    public static final String LINK_NAME_REGEX =
73      String.format("(?:(?:%s=)?)%s=%s-%s",
74        TableName.VALID_NAMESPACE_REGEX, TableName.VALID_TABLE_QUALIFIER_REGEX,
75        HRegionInfo.ENCODED_REGION_NAME_REGEX, StoreFileInfo.HFILE_NAME_REGEX);
76  
77    /** Define the HFile Link name parser in the form of: table=region-hfile */
78    //made package private for testing
79    static final Pattern LINK_NAME_PATTERN =
80      Pattern.compile(String.format("^(?:(%s)(?:\\=))?(%s)=(%s)-(%s)$",
81        TableName.VALID_NAMESPACE_REGEX, TableName.VALID_TABLE_QUALIFIER_REGEX,
82        HRegionInfo.ENCODED_REGION_NAME_REGEX, StoreFileInfo.HFILE_NAME_REGEX));
83  
84    /**
85     * The pattern should be used for hfile and reference links
86     * that can be found in /hbase/table/region/family/
87     */
88    private static final Pattern REF_OR_HFILE_LINK_PATTERN =
89      Pattern.compile(String.format("^(?:(%s)(?:=))?(%s)=(%s)-(.+)$",
90        TableName.VALID_NAMESPACE_REGEX, TableName.VALID_TABLE_QUALIFIER_REGEX,
91        HRegionInfo.ENCODED_REGION_NAME_REGEX));
92  
93    private final Path archivePath;
94    private final Path originPath;
95    private final Path mobPath;
96    private final Path tempPath;
97  
98    /**
99     * Dead simple hfile link constructor
100    */
101   public HFileLink(final Path originPath, final Path tempPath, final Path mobPath,
102                    final Path archivePath) {
103     this.tempPath  = tempPath;
104     this.originPath = originPath;
105     this.mobPath = mobPath;
106     this.archivePath = archivePath;
107     setLocations(originPath, tempPath, mobPath, archivePath);
108   }
109 
110   /**
111    * @param conf {@link Configuration} from which to extract specific archive locations
112    * @param hFileLinkPattern The path ending with a HFileLink pattern. (table=region-hfile)
113    * @throws IOException on unexpected error.
114    */
115   public static final HFileLink buildFromHFileLinkPattern(Configuration conf, Path hFileLinkPattern)
116           throws IOException {
117     return buildFromHFileLinkPattern(FSUtils.getRootDir(conf),
118             HFileArchiveUtil.getArchivePath(conf), hFileLinkPattern);
119   }
120 
121   /**
122    * @param rootDir Path to the root directory where hbase files are stored
123    * @param archiveDir Path to the hbase archive directory
124    * @param hFileLinkPattern The path of the HFile Link.
125    */
126   public final static HFileLink buildFromHFileLinkPattern(final Path rootDir,
127                                                           final Path archiveDir,
128                                                           final Path hFileLinkPattern) {
129     Path hfilePath = getHFileLinkPatternRelativePath(hFileLinkPattern);
130     Path tempPath = new Path(new Path(rootDir, HConstants.HBASE_TEMP_DIRECTORY), hfilePath);
131     Path originPath = new Path(rootDir, hfilePath);
132     Path mobPath = new Path(new Path(rootDir, MobConstants.MOB_DIR_NAME), hfilePath);
133     Path archivePath = new Path(archiveDir, hfilePath);
134     return new HFileLink(originPath, tempPath, mobPath, archivePath);
135   }
136 
137   /**
138    * Create an HFileLink relative path for the table/region/family/hfile location
139    * @param table Table name
140    * @param region Region Name
141    * @param family Family Name
142    * @param hfile HFile Name
143    * @return the relative Path to open the specified table/region/family/hfile link
144    */
145   public static Path createPath(final TableName table, final String region,
146                                 final String family, final String hfile) {
147     if (HFileLink.isHFileLink(hfile)) {
148       return new Path(family, hfile);
149     }
150     return new Path(family, HFileLink.createHFileLinkName(table, region, hfile));
151   }
152 
153   /**
154    * Create an HFileLink instance from table/region/family/hfile location
155    * @param conf {@link Configuration} from which to extract specific archive locations
156    * @param table Table name
157    * @param region Region Name
158    * @param family Family Name
159    * @param hfile HFile Name
160    * @return Link to the file with the specified table/region/family/hfile location
161    * @throws IOException on unexpected error.
162    */
163   public static HFileLink build(final Configuration conf, final TableName table,
164                                  final String region, final String family, final String hfile)
165           throws IOException {
166     return HFileLink.buildFromHFileLinkPattern(conf, createPath(table, region, family, hfile));
167   }
168 
169   /**
170    * Create an HFileLink instance from table/region/family/hfile location
171    * @param conf {@link Configuration} from which to extract specific archive locations
172    * @param table Table name
173    * @param region Region Name
174    * @param family Family Name
175    * @param hfile HFile Name
176    * @return Link to the file with the specified table/region/family/hfile location
177    * @throws IOException on unexpected error.
178    * @deprecated use {@link #build()} instead.
179    */
180   @Deprecated
181   public static HFileLink create(final Configuration conf, final TableName table,
182                                  final String region, final String family, final String hfile)
183           throws IOException {
184     return build(conf, table, region, family, hfile);
185   }
186 
187   /**
188    * @return the origin path of the hfile.
189    */
190   public Path getOriginPath() {
191     return this.originPath;
192   }
193 
194   /**
195    * @return the path of the archived hfile.
196    */
197   public Path getArchivePath() {
198     return this.archivePath;
199   }
200 
201   /**
202    * @return the path of the mob hfiles.
203    */
204   public Path getMobPath() { return this.mobPath; }
205 
206     /**
207    * @param path Path to check.
208    * @return True if the path is a HFileLink.
209    */
210   public static boolean isHFileLink(final Path path) {
211     return isHFileLink(path.getName());
212   }
213 
214 
215   /**
216    * @param fileName File name to check.
217    * @return True if the path is a HFileLink.
218    */
219   public static boolean isHFileLink(String fileName) {
220     Matcher m = LINK_NAME_PATTERN.matcher(fileName);
221     if (!m.matches()) return false;
222     return m.groupCount() > 2 && m.group(4) != null && m.group(3) != null && m.group(2) != null;
223   }
224 
225   /**
226    * Convert a HFileLink path to a table relative path.
227    * e.g. the link: /hbase/test/0123/cf/testtb=4567-abcd
228    *      becomes: /hbase/testtb/4567/cf/abcd
229    *
230    * @param path HFileLink path
231    * @return Relative table path
232    * @throws IOException on unexpected error.
233    */
234   private static Path getHFileLinkPatternRelativePath(final Path path) {
235     // table=region-hfile
236     Matcher m = REF_OR_HFILE_LINK_PATTERN.matcher(path.getName());
237     if (!m.matches()) {
238       throw new IllegalArgumentException(path.getName() + " is not a valid HFileLink pattern!");
239     }
240 
241     // Convert the HFileLink name into a real table/region/cf/hfile path.
242     TableName tableName = TableName.valueOf(m.group(1), m.group(2));
243     String regionName = m.group(3);
244     String hfileName = m.group(4);
245     String familyName = path.getParent().getName();
246     Path tableDir = FSUtils.getTableDir(new Path("./"), tableName);
247     return new Path(tableDir, new Path(regionName, new Path(familyName,
248         hfileName)));
249   }
250 
251   /**
252    * Get the HFile name of the referenced link
253    *
254    * @param fileName HFileLink file name
255    * @return the name of the referenced HFile
256    */
257   public static String getReferencedHFileName(final String fileName) {
258     Matcher m = REF_OR_HFILE_LINK_PATTERN.matcher(fileName);
259     if (!m.matches()) {
260       throw new IllegalArgumentException(fileName + " is not a valid HFileLink name!");
261     }
262     return(m.group(4));
263   }
264 
265   /**
266    * Get the Region name of the referenced link
267    *
268    * @param fileName HFileLink file name
269    * @return the name of the referenced Region
270    */
271   public static String getReferencedRegionName(final String fileName) {
272     Matcher m = REF_OR_HFILE_LINK_PATTERN.matcher(fileName);
273     if (!m.matches()) {
274       throw new IllegalArgumentException(fileName + " is not a valid HFileLink name!");
275     }
276     return(m.group(3));
277   }
278 
279   /**
280    * Get the Table name of the referenced link
281    *
282    * @param fileName HFileLink file name
283    * @return the name of the referenced Table
284    */
285   public static TableName getReferencedTableName(final String fileName) {
286     Matcher m = REF_OR_HFILE_LINK_PATTERN.matcher(fileName);
287     if (!m.matches()) {
288       throw new IllegalArgumentException(fileName + " is not a valid HFileLink name!");
289     }
290     return(TableName.valueOf(m.group(1), m.group(2)));
291   }
292 
293   /**
294    * Create a new HFileLink name
295    *
296    * @param hfileRegionInfo - Linked HFile Region Info
297    * @param hfileName - Linked HFile name
298    * @return file name of the HFile Link
299    */
300   public static String createHFileLinkName(final HRegionInfo hfileRegionInfo,
301       final String hfileName) {
302     return createHFileLinkName(hfileRegionInfo.getTable(),
303             hfileRegionInfo.getEncodedName(), hfileName);
304   }
305 
306   /**
307    * Create a new HFileLink name
308    *
309    * @param tableName - Linked HFile table name
310    * @param regionName - Linked HFile region name
311    * @param hfileName - Linked HFile name
312    * @return file name of the HFile Link
313    */
314   public static String createHFileLinkName(final TableName tableName,
315       final String regionName, final String hfileName) {
316     String s = String.format("%s=%s-%s",
317         tableName.getNameAsString().replace(TableName.NAMESPACE_DELIM, '='),
318         regionName, hfileName);
319     return s;
320   }
321 
322   /**
323    * Create a new HFileLink
324    *
325    * <p>It also adds a back-reference to the hfile back-reference directory
326    * to simplify the reference-count and the cleaning process.
327    *
328    * @param conf {@link Configuration} to read for the archive directory name
329    * @param fs {@link FileSystem} on which to write the HFileLink
330    * @param dstFamilyPath - Destination path (table/region/cf/)
331    * @param hfileRegionInfo - Linked HFile Region Info
332    * @param hfileName - Linked HFile name
333    * @return true if the file is created, otherwise the file exists.
334    * @throws IOException on file or parent directory creation failure
335    */
336   public static boolean create(final Configuration conf, final FileSystem fs,
337       final Path dstFamilyPath, final HRegionInfo hfileRegionInfo,
338       final String hfileName) throws IOException {
339     return create(conf, fs, dstFamilyPath, hfileRegionInfo, hfileName, true);
340   }
341 
342   /**
343    * Create a new HFileLink
344    *
345    * <p>It also adds a back-reference to the hfile back-reference directory
346    * to simplify the reference-count and the cleaning process.
347    *
348    * @param conf {@link Configuration} to read for the archive directory name
349    * @param fs {@link FileSystem} on which to write the HFileLink
350    * @param dstFamilyPath - Destination path (table/region/cf/)
351    * @param hfileRegionInfo - Linked HFile Region Info
352    * @param hfileName - Linked HFile name
353    * @param createBackRef - Whether back reference should be created. Defaults to true.
354    * @return true if the file is created, otherwise the file exists.
355    * @throws IOException on file or parent directory creation failure
356    */
357   public static boolean create(final Configuration conf, final FileSystem fs,
358       final Path dstFamilyPath, final HRegionInfo hfileRegionInfo,
359       final String hfileName, final boolean createBackRef) throws IOException {
360     TableName linkedTable = hfileRegionInfo.getTable();
361     String linkedRegion = hfileRegionInfo.getEncodedName();
362     return create(conf, fs, dstFamilyPath, linkedTable, linkedRegion, hfileName, createBackRef);
363   }
364 
365   /**
366    * Create a new HFileLink
367    *
368    * <p>It also adds a back-reference to the hfile back-reference directory
369    * to simplify the reference-count and the cleaning process.
370    *
371    * @param conf {@link Configuration} to read for the archive directory name
372    * @param fs {@link FileSystem} on which to write the HFileLink
373    * @param dstFamilyPath - Destination path (table/region/cf/)
374    * @param linkedTable - Linked Table Name
375    * @param linkedRegion - Linked Region Name
376    * @param hfileName - Linked HFile name
377    * @return true if the file is created, otherwise the file exists.
378    * @throws IOException on file or parent directory creation failure
379    */
380   public static boolean create(final Configuration conf, final FileSystem fs,
381       final Path dstFamilyPath, final TableName linkedTable, final String linkedRegion,
382       final String hfileName) throws IOException {
383     return create(conf, fs, dstFamilyPath, linkedTable, linkedRegion, hfileName, true);
384   }
385 
386   /**
387    * Create a new HFileLink
388    *
389    * <p>It also adds a back-reference to the hfile back-reference directory
390    * to simplify the reference-count and the cleaning process.
391    *
392    * @param conf {@link Configuration} to read for the archive directory name
393    * @param fs {@link FileSystem} on which to write the HFileLink
394    * @param dstFamilyPath - Destination path (table/region/cf/)
395    * @param linkedTable - Linked Table Name
396    * @param linkedRegion - Linked Region Name
397    * @param hfileName - Linked HFile name
398    * @param createBackRef - Whether back reference should be created. Defaults to true.
399    * @return true if the file is created, otherwise the file exists.
400    * @throws IOException on file or parent directory creation failure
401    */
402   public static boolean create(final Configuration conf, final FileSystem fs,
403       final Path dstFamilyPath, final TableName linkedTable, final String linkedRegion,
404       final String hfileName, final boolean createBackRef) throws IOException {
405     String familyName = dstFamilyPath.getName();
406     String regionName = dstFamilyPath.getParent().getName();
407     String tableName = FSUtils.getTableName(dstFamilyPath.getParent().getParent())
408         .getNameAsString();
409 
410     String name = createHFileLinkName(linkedTable, linkedRegion, hfileName);
411     String refName = createBackReferenceName(tableName, regionName);
412 
413     // Make sure the destination directory exists
414     fs.mkdirs(dstFamilyPath);
415 
416     // Make sure the FileLink reference directory exists
417     Path archiveStoreDir = HFileArchiveUtil.getStoreArchivePath(conf,
418           linkedTable, linkedRegion, familyName);
419     Path backRefPath = null;
420     if (createBackRef) {
421       Path backRefssDir = getBackReferencesDir(archiveStoreDir, hfileName);
422       fs.mkdirs(backRefssDir);
423 
424       // Create the reference for the link
425       backRefPath = new Path(backRefssDir, refName);
426       fs.createNewFile(backRefPath);
427     }
428     try {
429       // Create the link
430       return fs.createNewFile(new Path(dstFamilyPath, name));
431     } catch (IOException e) {
432       LOG.error("couldn't create the link=" + name + " for " + dstFamilyPath, e);
433       // Revert the reference if the link creation failed
434       if (createBackRef) {
435         fs.delete(backRefPath, false);
436       }
437       throw e;
438     }
439   }
440 
441   /**
442    * Create a new HFileLink starting from a hfileLink name
443    *
444    * <p>It also adds a back-reference to the hfile back-reference directory
445    * to simplify the reference-count and the cleaning process.
446    *
447    * @param conf {@link Configuration} to read for the archive directory name
448    * @param fs {@link FileSystem} on which to write the HFileLink
449    * @param dstFamilyPath - Destination path (table/region/cf/)
450    * @param hfileLinkName - HFileLink name (it contains hfile-region-table)
451    * @return true if the file is created, otherwise the file exists.
452    * @throws IOException on file or parent directory creation failure
453    */
454   public static boolean createFromHFileLink(final Configuration conf, final FileSystem fs,
455       final Path dstFamilyPath, final String hfileLinkName)
456           throws IOException {
457     return createFromHFileLink(conf, fs, dstFamilyPath, hfileLinkName, true);
458   }
459 
460   /**
461    * Create a new HFileLink starting from a hfileLink name
462    *
463    * <p>It also adds a back-reference to the hfile back-reference directory
464    * to simplify the reference-count and the cleaning process.
465    *
466    * @param conf {@link Configuration} to read for the archive directory name
467    * @param fs {@link FileSystem} on which to write the HFileLink
468    * @param dstFamilyPath - Destination path (table/region/cf/)
469    * @param hfileLinkName - HFileLink name (it contains hfile-region-table)
470    * @param createBackRef - Whether back reference should be created. Defaults to true.
471    * @return true if the file is created, otherwise the file exists.
472    * @throws IOException on file or parent directory creation failure
473    */
474   public static boolean createFromHFileLink(final Configuration conf, final FileSystem fs,
475       final Path dstFamilyPath, final String hfileLinkName, final boolean createBackRef)
476           throws IOException {
477     Matcher m = LINK_NAME_PATTERN.matcher(hfileLinkName);
478     if (!m.matches()) {
479       throw new IllegalArgumentException(hfileLinkName + " is not a valid HFileLink name!");
480     }
481     return create(conf, fs, dstFamilyPath, TableName.valueOf(m.group(1), m.group(2)),
482         m.group(3), m.group(4), createBackRef);
483   }
484 
485   /**
486    * Create the back reference name
487    */
488   //package-private for testing
489   static String createBackReferenceName(final String tableNameStr,
490                                         final String regionName) {
491 
492     return regionName + "." + tableNameStr.replace(TableName.NAMESPACE_DELIM, '=');
493   }
494 
495   /**
496    * Get the full path of the HFile referenced by the back reference
497    *
498    * @param rootDir root hbase directory
499    * @param linkRefPath Link Back Reference path
500    * @return full path of the referenced hfile
501    */
502   public static Path getHFileFromBackReference(final Path rootDir, final Path linkRefPath) {
503     Pair<TableName, String> p = parseBackReferenceName(linkRefPath.getName());
504     TableName linkTableName = p.getFirst();
505     String linkRegionName = p.getSecond();
506 
507     String hfileName = getBackReferenceFileName(linkRefPath.getParent());
508     Path familyPath = linkRefPath.getParent().getParent();
509     Path regionPath = familyPath.getParent();
510     Path tablePath = regionPath.getParent();
511 
512     String linkName = createHFileLinkName(FSUtils.getTableName(tablePath),
513             regionPath.getName(), hfileName);
514     Path linkTableDir = FSUtils.getTableDir(rootDir, linkTableName);
515     Path regionDir = HRegion.getRegionDir(linkTableDir, linkRegionName);
516     return new Path(new Path(regionDir, familyPath.getName()), linkName);
517   }
518 
519   static Pair<TableName, String> parseBackReferenceName(String name) {
520     int separatorIndex = name.indexOf('.');
521     String linkRegionName = name.substring(0, separatorIndex);
522     String tableSubstr = name.substring(separatorIndex + 1)
523         .replace('=', TableName.NAMESPACE_DELIM);
524     TableName linkTableName = TableName.valueOf(tableSubstr);
525     return new Pair<TableName, String>(linkTableName, linkRegionName);
526   }
527 
528   /**
529    * Get the full path of the HFile referenced by the back reference
530    *
531    * @param conf {@link Configuration} to read for the archive directory name
532    * @param linkRefPath Link Back Reference path
533    * @return full path of the referenced hfile
534    * @throws IOException on unexpected error.
535    */
536   public static Path getHFileFromBackReference(final Configuration conf, final Path linkRefPath)
537       throws IOException {
538     return getHFileFromBackReference(FSUtils.getRootDir(conf), linkRefPath);
539   }
540 
541 }