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  
20  package org.apache.hadoop.hbase.regionserver;
21  
22  import java.io.FileNotFoundException;
23  import java.io.IOException;
24  import java.util.regex.Matcher;
25  import java.util.regex.Pattern;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.apache.hadoop.hbase.classification.InterfaceAudience;
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.hbase.HDFSBlocksDistribution;
35  import org.apache.hadoop.hbase.io.FSDataInputStreamWrapper;
36  import org.apache.hadoop.hbase.io.HFileLink;
37  import org.apache.hadoop.hbase.io.HalfStoreFileReader;
38  import org.apache.hadoop.hbase.io.Reference;
39  import org.apache.hadoop.hbase.io.hfile.CacheConfig;
40  import org.apache.hadoop.hbase.util.FSUtils;
41  
42  /**
43   * Describe a StoreFile (hfile, reference, link)
44   */
45  @InterfaceAudience.Private
46  public class StoreFileInfo {
47    private static final Log LOG = LogFactory.getLog(StoreFileInfo.class);
48  
49    /**
50     * A non-capture group, for hfiles, so that this can be embedded.
51     * HFiles are uuid ([0-9a-z]+). Bulk loaded hfiles has (_SeqId_[0-9]+_) has suffix.
52     */
53    public static final String HFILE_NAME_REGEX = "[0-9a-f]+(?:_SeqId_[0-9]+_)?";
54  
55    /** Regex that will work for hfiles */
56    private static final Pattern HFILE_NAME_PATTERN =
57      Pattern.compile("^(" + HFILE_NAME_REGEX + ")");
58  
59    /**
60     * Regex that will work for straight reference names (<hfile>.<parentEncRegion>)
61     * and hfilelink reference names (<table>=<region>-<hfile>.<parentEncRegion>)
62     * If reference, then the regex has more than just one group.
63     * Group 1, hfile/hfilelink pattern, is this file's id.
64     * Group 2 '(.+)' is the reference's parent region name.
65     */
66    private static final Pattern REF_NAME_PATTERN =
67      Pattern.compile(String.format("^(%s|%s)\\.(.+)$",
68        HFILE_NAME_REGEX, HFileLink.LINK_NAME_REGEX));
69  
70    // Configuration
71    private Configuration conf;
72  
73    // FileSystem handle
74    private final FileSystem fs;
75  
76    // HDFS blocks distribution information
77    private HDFSBlocksDistribution hdfsBlocksDistribution = null;
78  
79    // If this storefile references another, this is the reference instance.
80    private final Reference reference;
81  
82    // If this storefile is a link to another, this is the link instance.
83    private final HFileLink link;
84  
85    private final Path initialPath;
86  
87    private RegionCoprocessorHost coprocessorHost;
88  
89    // timestamp on when the file was created, is 0 and ignored for reference or link files
90    private long createdTimestamp;
91  
92    /**
93     * Create a Store File Info
94     * @param conf the {@link Configuration} to use
95     * @param fs The current file system to use.
96     * @param initialPath The {@link Path} of the file
97     */
98    public StoreFileInfo(final Configuration conf, final FileSystem fs, final Path initialPath)
99        throws IOException {
100     assert fs != null;
101     assert initialPath != null;
102     assert conf != null;
103 
104     this.fs = fs;
105     this.conf = conf;
106     this.initialPath = initialPath;
107     Path p = initialPath;
108     if (HFileLink.isHFileLink(p)) {
109       // HFileLink
110       this.reference = null;
111       this.link = HFileLink.buildFromHFileLinkPattern(conf, p);
112       if (LOG.isTraceEnabled()) LOG.trace(p + " is a link");
113     } else if (isReference(p)) {
114       this.reference = Reference.read(fs, p);
115       Path referencePath = getReferredToFile(p);
116       if (HFileLink.isHFileLink(referencePath)) {
117         // HFileLink Reference
118         this.link = HFileLink.buildFromHFileLinkPattern(conf, referencePath);
119       } else {
120         // Reference
121         this.link = null;
122       }
123       if (LOG.isTraceEnabled()) LOG.trace(p + " is a " + reference.getFileRegion() +
124               " reference to " + referencePath);
125     } else if (isHFile(p)) {
126       // HFile
127       this.createdTimestamp = fs.getFileStatus(initialPath).getModificationTime();
128       this.reference = null;
129       this.link = null;
130     } else {
131       throw new IOException("path=" + p + " doesn't look like a valid StoreFile");
132     }
133   }
134 
135   /**
136    * Create a Store File Info
137    * @param conf the {@link Configuration} to use
138    * @param fs The current file system to use.
139    * @param fileStatus The {@link FileStatus} of the file
140    */
141   public StoreFileInfo(final Configuration conf, final FileSystem fs, final FileStatus fileStatus)
142       throws IOException {
143     this(conf, fs, fileStatus.getPath());
144   }
145 
146   /**
147    * Create a Store File Info from an HFileLink
148    * @param conf the {@link Configuration} to use
149    * @param fs The current file system to use.
150    * @param fileStatus The {@link FileStatus} of the file
151    */
152   public StoreFileInfo(final Configuration conf, final FileSystem fs, final FileStatus fileStatus,
153       final HFileLink link)
154       throws IOException {
155     this.fs = fs;
156     this.conf = conf;
157     // initialPath can be null only if we get a link.
158     this.initialPath = (fileStatus == null) ? null : fileStatus.getPath();
159       // HFileLink
160     this.reference = null;
161     this.link = link;
162   }
163 
164   /**
165    * Create a Store File Info from an HFileLink
166    * @param conf
167    * @param fs
168    * @param fileStatus
169    * @param reference
170    * @throws IOException
171    */
172   public StoreFileInfo(final Configuration conf, final FileSystem fs, final FileStatus fileStatus,
173       final Reference reference)
174       throws IOException {
175     this.fs = fs;
176     this.conf = conf;
177     this.initialPath = fileStatus.getPath();
178     this.createdTimestamp = fileStatus.getModificationTime();
179     this.reference = reference;
180     this.link = null;
181   }
182 
183   /**
184    * Sets the region coprocessor env.
185    * @param coprocessorHost
186    */
187   public void setRegionCoprocessorHost(RegionCoprocessorHost coprocessorHost) {
188     this.coprocessorHost = coprocessorHost;
189   }
190 
191   /*
192    * @return the Reference object associated to this StoreFileInfo.
193    *         null if the StoreFile is not a reference.
194    */
195   public Reference getReference() {
196     return this.reference;
197   }
198 
199   /** @return True if the store file is a Reference */
200   public boolean isReference() {
201     return this.reference != null;
202   }
203 
204   /** @return True if the store file is a top Reference */
205   public boolean isTopReference() {
206     return this.reference != null && Reference.isTopFileRegion(this.reference.getFileRegion());
207   }
208 
209   /** @return True if the store file is a link */
210   public boolean isLink() {
211     return this.link != null && this.reference == null;
212   }
213 
214   /** @return the HDFS block distribution */
215   public HDFSBlocksDistribution getHDFSBlockDistribution() {
216     return this.hdfsBlocksDistribution;
217   }
218 
219   /**
220    * Open a Reader for the StoreFile
221    * @param fs The current file system to use.
222    * @param cacheConf The cache configuration and block cache reference.
223    * @return The StoreFile.Reader for the file
224    */
225   public StoreFile.Reader open(final FileSystem fs,
226       final CacheConfig cacheConf, final boolean canUseDropBehind) throws IOException {
227     FSDataInputStreamWrapper in;
228     FileStatus status;
229 
230     final boolean doDropBehind = canUseDropBehind && cacheConf.shouldDropBehindCompaction();
231     if (this.link != null) {
232       // HFileLink
233       in = new FSDataInputStreamWrapper(fs, this.link, doDropBehind);
234       status = this.link.getFileStatus(fs);
235     } else if (this.reference != null) {
236       // HFile Reference
237       Path referencePath = getReferredToFile(this.getPath());
238       in = new FSDataInputStreamWrapper(fs, referencePath,
239           doDropBehind);
240       status = fs.getFileStatus(referencePath);
241     } else {
242       in = new FSDataInputStreamWrapper(fs, this.getPath(),
243           doDropBehind);
244       status = fs.getFileStatus(initialPath);
245     }
246     long length = status.getLen();
247     hdfsBlocksDistribution = computeHDFSBlocksDistribution(fs);
248 
249     StoreFile.Reader reader = null;
250     if (this.coprocessorHost != null) {
251       reader = this.coprocessorHost.preStoreFileReaderOpen(fs, this.getPath(), in, length,
252         cacheConf, reference);
253     }
254     if (reader == null) {
255       if (this.reference != null) {
256         reader = new HalfStoreFileReader(fs, this.getPath(), in, length, cacheConf, reference,
257           conf);
258       } else {
259         reader = new StoreFile.Reader(fs, status.getPath(), in, length, cacheConf, conf);
260       }
261     }
262     if (this.coprocessorHost != null) {
263       reader = this.coprocessorHost.postStoreFileReaderOpen(fs, this.getPath(), in, length,
264         cacheConf, reference, reader);
265     }
266     return reader;
267   }
268 
269   /**
270    * Compute the HDFS Block Distribution for this StoreFile
271    */
272   public HDFSBlocksDistribution computeHDFSBlocksDistribution(final FileSystem fs)
273       throws IOException {
274 
275     // guard against the case where we get the FileStatus from link, but by the time we
276     // call compute the file is moved again
277     if (this.link != null) {
278       FileNotFoundException exToThrow = null;
279       for (int i = 0; i < this.link.getLocations().length; i++) {
280         try {
281           return computeHDFSBlocksDistributionInternal(fs);
282         } catch (FileNotFoundException ex) {
283           // try the other location
284           exToThrow = ex;
285         }
286       }
287       throw exToThrow;
288     } else {
289       return computeHDFSBlocksDistributionInternal(fs);
290     }
291   }
292 
293   private HDFSBlocksDistribution computeHDFSBlocksDistributionInternal(final FileSystem fs)
294       throws IOException {
295     FileStatus status = getReferencedFileStatus(fs);
296     if (this.reference != null) {
297       return computeRefFileHDFSBlockDistribution(fs, reference, status);
298     } else {
299       return FSUtils.computeHDFSBlocksDistribution(fs, status, 0, status.getLen());
300     }
301   }
302 
303   /**
304    * Get the {@link FileStatus} of the file referenced by this StoreFileInfo
305    * @param fs The current file system to use.
306    * @return The {@link FileStatus} of the file referenced by this StoreFileInfo
307    */
308   public FileStatus getReferencedFileStatus(final FileSystem fs) throws IOException {
309     FileStatus status;
310     if (this.reference != null) {
311       if (this.link != null) {
312         FileNotFoundException exToThrow = null;
313         for (int i = 0; i < this.link.getLocations().length; i++) {
314           // HFileLink Reference
315           try {
316             return link.getFileStatus(fs);
317           } catch (FileNotFoundException ex) {
318             // try the other location
319             exToThrow = ex;
320           }
321         }
322         throw exToThrow;
323       } else {
324         // HFile Reference
325         Path referencePath = getReferredToFile(this.getPath());
326         status = fs.getFileStatus(referencePath);
327       }
328     } else {
329       if (this.link != null) {
330         FileNotFoundException exToThrow = null;
331         for (int i = 0; i < this.link.getLocations().length; i++) {
332           // HFileLink
333           try {
334             return link.getFileStatus(fs);
335           } catch (FileNotFoundException ex) {
336             // try the other location
337             exToThrow = ex;
338           }
339         }
340         throw exToThrow;
341       } else {
342         status = fs.getFileStatus(initialPath);
343       }
344     }
345     return status;
346   }
347 
348   /** @return The {@link Path} of the file */
349   public Path getPath() {
350     return initialPath;
351   }
352 
353   /** @return The {@link FileStatus} of the file */
354   public FileStatus getFileStatus() throws IOException {
355     return getReferencedFileStatus(fs);
356   }
357 
358   /** @return Get the modification time of the file. */
359   public long getModificationTime() throws IOException {
360     return getFileStatus().getModificationTime();
361   }
362 
363   @Override
364   public String toString() {
365     return this.getPath() +
366       (isReference() ? "-" + getReferredToFile(this.getPath()) + "-" + reference : "");
367   }
368 
369   /**
370    * @param path Path to check.
371    * @return True if the path has format of a HFile.
372    */
373   public static boolean isHFile(final Path path) {
374     return isHFile(path.getName());
375   }
376 
377   public static boolean isHFile(final String fileName) {
378     Matcher m = HFILE_NAME_PATTERN.matcher(fileName);
379     return m.matches() && m.groupCount() > 0;
380   }
381 
382   /**
383    * @param path Path to check.
384    * @return True if the path has format of a HStoreFile reference.
385    */
386   public static boolean isReference(final Path path) {
387     return isReference(path.getName());
388   }
389 
390   /**
391    * @param name file name to check.
392    * @return True if the path has format of a HStoreFile reference.
393    */
394   public static boolean isReference(final String name) {
395     Matcher m = REF_NAME_PATTERN.matcher(name);
396     return m.matches() && m.groupCount() > 1;
397   }
398 
399   /**
400    * @return timestamp when this file was created (as returned by filesystem)
401    */
402   public long getCreatedTimestamp() {
403     return createdTimestamp;
404   }
405 
406   /*
407    * Return path to the file referred to by a Reference.  Presumes a directory
408    * hierarchy of <code>${hbase.rootdir}/data/${namespace}/tablename/regionname/familyname</code>.
409    * @param p Path to a Reference file.
410    * @return Calculated path to parent region file.
411    * @throws IllegalArgumentException when path regex fails to match.
412    */
413   public static Path getReferredToFile(final Path p) {
414     Matcher m = REF_NAME_PATTERN.matcher(p.getName());
415     if (m == null || !m.matches()) {
416       LOG.warn("Failed match of store file name " + p.toString());
417       throw new IllegalArgumentException("Failed match of store file name " +
418           p.toString());
419     }
420 
421     // Other region name is suffix on the passed Reference file name
422     String otherRegion = m.group(2);
423     // Tabledir is up two directories from where Reference was written.
424     Path tableDir = p.getParent().getParent().getParent();
425     String nameStrippedOfSuffix = m.group(1);
426     if (LOG.isDebugEnabled()) {
427       LOG.debug("reference '" + p + "' to region=" + otherRegion
428         + " hfile=" + nameStrippedOfSuffix);
429     }
430 
431     // Build up new path with the referenced region in place of our current
432     // region in the reference path.  Also strip regionname suffix from name.
433     return new Path(new Path(new Path(tableDir, otherRegion),
434       p.getParent().getName()), nameStrippedOfSuffix);
435   }
436 
437   /**
438    * Validate the store file name.
439    * @param fileName name of the file to validate
440    * @return <tt>true</tt> if the file could be a valid store file, <tt>false</tt> otherwise
441    */
442   public static boolean validateStoreFileName(final String fileName) {
443     if (HFileLink.isHFileLink(fileName) || isReference(fileName))
444       return(true);
445     return !fileName.contains("-");
446   }
447 
448   /**
449    * Return if the specified file is a valid store file or not.
450    * @param fileStatus The {@link FileStatus} of the file
451    * @return <tt>true</tt> if the file is valid
452    */
453   public static boolean isValid(final FileStatus fileStatus)
454       throws IOException {
455     final Path p = fileStatus.getPath();
456 
457     if (fileStatus.isDirectory())
458       return false;
459 
460     // Check for empty hfile. Should never be the case but can happen
461     // after data loss in hdfs for whatever reason (upgrade, etc.): HBASE-646
462     // NOTE: that the HFileLink is just a name, so it's an empty file.
463     if (!HFileLink.isHFileLink(p) && fileStatus.getLen() <= 0) {
464       LOG.warn("Skipping " + p + " because it is empty. HBASE-646 DATA LOSS?");
465       return false;
466     }
467 
468     return validateStoreFileName(p.getName());
469   }
470 
471   /**
472    * helper function to compute HDFS blocks distribution of a given reference
473    * file.For reference file, we don't compute the exact value. We use some
474    * estimate instead given it might be good enough. we assume bottom part
475    * takes the first half of reference file, top part takes the second half
476    * of the reference file. This is just estimate, given
477    * midkey ofregion != midkey of HFile, also the number and size of keys vary.
478    * If this estimate isn't good enough, we can improve it later.
479    * @param fs  The FileSystem
480    * @param reference  The reference
481    * @param status  The reference FileStatus
482    * @return HDFS blocks distribution
483    */
484   private static HDFSBlocksDistribution computeRefFileHDFSBlockDistribution(
485       final FileSystem fs, final Reference reference, final FileStatus status)
486       throws IOException {
487     if (status == null) {
488       return null;
489     }
490 
491     long start = 0;
492     long length = 0;
493 
494     if (Reference.isTopFileRegion(reference.getFileRegion())) {
495       start = status.getLen()/2;
496       length = status.getLen() - status.getLen()/2;
497     } else {
498       start = 0;
499       length = status.getLen()/2;
500     }
501     return FSUtils.computeHDFSBlocksDistribution(fs, status, start, length);
502   }
503 
504   @Override
505   public boolean equals(Object that) {
506     if (this == that) return true;
507     if (that == null) return false;
508 
509     if (!(that instanceof StoreFileInfo)) return false;
510 
511     StoreFileInfo o = (StoreFileInfo)that;
512     if (initialPath != null && o.initialPath == null) return false;
513     if (initialPath == null && o.initialPath != null) return false;
514     if (initialPath != o.initialPath && initialPath != null
515             && !initialPath.equals(o.initialPath)) return false;
516 
517     if (reference != null && o.reference == null) return false;
518     if (reference == null && o.reference != null) return false;
519     if (reference != o.reference && reference != null
520             && !reference.equals(o.reference)) return false;
521 
522     if (link != null && o.link == null) return false;
523     if (link == null && o.link != null) return false;
524     if (link != o.link && link != null && !link.equals(o.link)) return false;
525 
526     return true;
527   };
528 
529 
530   @Override
531   public int hashCode() {
532     int hash = 17;
533     hash = hash * 31 + ((reference == null) ? 0 : reference.hashCode());
534     hash = hash * 31 + ((initialPath ==  null) ? 0 : initialPath.hashCode());
535     hash = hash * 31 + ((link == null) ? 0 : link.hashCode());
536     return  hash;
537   }
538 }