001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.hadoop.hbase.snapshot;
020
021import java.io.FileNotFoundException;
022import java.io.IOException;
023import java.io.InterruptedIOException;
024import java.util.HashSet;
025import java.util.List;
026import java.util.Set;
027import java.util.concurrent.Callable;
028import java.util.concurrent.ExecutionException;
029import java.util.concurrent.ExecutorCompletionService;
030import java.util.concurrent.ExecutorService;
031
032import org.apache.hadoop.conf.Configuration;
033import org.apache.hadoop.fs.FileStatus;
034import org.apache.hadoop.fs.FileSystem;
035import org.apache.hadoop.fs.Path;
036import org.apache.hadoop.hbase.TableName;
037import org.apache.hadoop.hbase.client.RegionInfo;
038import org.apache.hadoop.hbase.io.HFileLink;
039import org.apache.hadoop.hbase.mob.MobUtils;
040import org.apache.hadoop.hbase.regionserver.StoreFileInfo;
041import org.apache.hadoop.hbase.util.HFileArchiveUtil;
042import org.apache.yetus.audience.InterfaceAudience;
043import org.slf4j.Logger;
044import org.slf4j.LoggerFactory;
045import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
046import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotDescription;
047import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotRegionManifest;
048
049/**
050 * Utility methods for interacting with the snapshot referenced files.
051 */
052@InterfaceAudience.Private
053public final class SnapshotReferenceUtil {
054  private static final Logger LOG = LoggerFactory.getLogger(SnapshotReferenceUtil.class);
055
056  public interface StoreFileVisitor {
057    void storeFile(final RegionInfo regionInfo, final String familyName,
058       final SnapshotRegionManifest.StoreFile storeFile) throws IOException;
059  }
060
061  public interface SnapshotVisitor extends StoreFileVisitor {
062  }
063
064  private SnapshotReferenceUtil() {
065    // private constructor for utility class
066  }
067
068  /**
069   * Iterate over the snapshot store files
070   *
071   * @param conf The current {@link Configuration} instance.
072   * @param fs {@link FileSystem}
073   * @param snapshotDir {@link Path} to the Snapshot directory
074   * @param visitor callback object to get the referenced files
075   * @throws IOException if an error occurred while scanning the directory
076   */
077  public static void visitReferencedFiles(final Configuration conf, final FileSystem fs,
078      final Path snapshotDir, final SnapshotVisitor visitor)
079      throws IOException {
080    SnapshotDescription desc = SnapshotDescriptionUtils.readSnapshotInfo(fs, snapshotDir);
081    visitReferencedFiles(conf, fs, snapshotDir, desc, visitor);
082  }
083
084  /**
085   * Iterate over the snapshot store files, restored.edits and logs
086   *
087   * @param conf The current {@link Configuration} instance.
088   * @param fs {@link FileSystem}
089   * @param snapshotDir {@link Path} to the Snapshot directory
090   * @param desc the {@link SnapshotDescription} of the snapshot to verify
091   * @param visitor callback object to get the referenced files
092   * @throws IOException if an error occurred while scanning the directory
093   */
094  public static void visitReferencedFiles(final Configuration conf, final FileSystem fs,
095      final Path snapshotDir, final SnapshotDescription desc, final SnapshotVisitor visitor)
096      throws IOException {
097    visitTableStoreFiles(conf, fs, snapshotDir, desc, visitor);
098  }
099
100  /**©
101   * Iterate over the snapshot store files
102   *
103   * @param conf The current {@link Configuration} instance.
104   * @param fs {@link FileSystem}
105   * @param snapshotDir {@link Path} to the Snapshot directory
106   * @param desc the {@link SnapshotDescription} of the snapshot to verify
107   * @param visitor callback object to get the store files
108   * @throws IOException if an error occurred while scanning the directory
109   */
110  static void visitTableStoreFiles(final Configuration conf, final FileSystem fs,
111      final Path snapshotDir, final SnapshotDescription desc, final StoreFileVisitor visitor)
112      throws IOException {
113    SnapshotManifest manifest = SnapshotManifest.open(conf, fs, snapshotDir, desc);
114    List<SnapshotRegionManifest> regionManifests = manifest.getRegionManifests();
115    if (regionManifests == null || regionManifests.isEmpty()) {
116      LOG.debug("No manifest files present: " + snapshotDir);
117      return;
118    }
119
120    for (SnapshotRegionManifest regionManifest: regionManifests) {
121      visitRegionStoreFiles(regionManifest, visitor);
122    }
123  }
124
125  /**
126   * Iterate over the snapshot store files in the specified region
127   *
128   * @param manifest snapshot manifest to inspect
129   * @param visitor callback object to get the store files
130   * @throws IOException if an error occurred while scanning the directory
131   */
132  static void visitRegionStoreFiles(final SnapshotRegionManifest manifest,
133      final StoreFileVisitor visitor) throws IOException {
134    RegionInfo regionInfo = ProtobufUtil.toRegionInfo(manifest.getRegionInfo());
135    for (SnapshotRegionManifest.FamilyFiles familyFiles: manifest.getFamilyFilesList()) {
136      String familyName = familyFiles.getFamilyName().toStringUtf8();
137      for (SnapshotRegionManifest.StoreFile storeFile: familyFiles.getStoreFilesList()) {
138        visitor.storeFile(regionInfo, familyName, storeFile);
139      }
140    }
141  }
142
143  /**
144   * Verify the validity of the snapshot
145   *
146   * @param conf The current {@link Configuration} instance.
147   * @param fs {@link FileSystem}
148   * @param snapshotDir {@link Path} to the Snapshot directory of the snapshot to verify
149   * @param snapshotDesc the {@link SnapshotDescription} of the snapshot to verify
150   * @throws CorruptedSnapshotException if the snapshot is corrupted
151   * @throws IOException if an error occurred while scanning the directory
152   */
153  public static void verifySnapshot(final Configuration conf, final FileSystem fs,
154      final Path snapshotDir, final SnapshotDescription snapshotDesc) throws IOException {
155    SnapshotManifest manifest = SnapshotManifest.open(conf, fs, snapshotDir, snapshotDesc);
156    verifySnapshot(conf, fs, manifest);
157  }
158
159  /**
160   * Verify the validity of the snapshot
161   *
162   * @param conf The current {@link Configuration} instance.
163   * @param fs {@link FileSystem}
164   * @param manifest snapshot manifest to inspect
165   * @throws CorruptedSnapshotException if the snapshot is corrupted
166   * @throws IOException if an error occurred while scanning the directory
167   */
168  public static void verifySnapshot(final Configuration conf, final FileSystem fs,
169      final SnapshotManifest manifest) throws IOException {
170    final SnapshotDescription snapshotDesc = manifest.getSnapshotDescription();
171    final Path snapshotDir = manifest.getSnapshotDir();
172    concurrentVisitReferencedFiles(conf, fs, manifest, "VerifySnapshot", new StoreFileVisitor() {
173      @Override
174      public void storeFile(final RegionInfo regionInfo, final String family,
175          final SnapshotRegionManifest.StoreFile storeFile) throws IOException {
176        verifyStoreFile(conf, fs, snapshotDir, snapshotDesc, regionInfo, family, storeFile);
177      }
178    });
179  }
180
181  public static void concurrentVisitReferencedFiles(final Configuration conf, final FileSystem fs,
182      final SnapshotManifest manifest, final String desc, final StoreFileVisitor visitor)
183      throws IOException {
184
185    final Path snapshotDir = manifest.getSnapshotDir();
186    List<SnapshotRegionManifest> regionManifests = manifest.getRegionManifests();
187    if (regionManifests == null || regionManifests.isEmpty()) {
188      LOG.debug("No manifest files present: " + snapshotDir);
189      return;
190    }
191
192    ExecutorService exec = SnapshotManifest.createExecutor(conf, desc);
193
194    try {
195      concurrentVisitReferencedFiles(conf, fs, manifest, exec, visitor);
196    } finally {
197      exec.shutdown();
198    }
199  }
200
201  public static void concurrentVisitReferencedFiles(final Configuration conf, final FileSystem fs,
202      final SnapshotManifest manifest, final ExecutorService exec, final StoreFileVisitor visitor)
203      throws IOException {
204    final SnapshotDescription snapshotDesc = manifest.getSnapshotDescription();
205    final Path snapshotDir = manifest.getSnapshotDir();
206
207    List<SnapshotRegionManifest> regionManifests = manifest.getRegionManifests();
208    if (regionManifests == null || regionManifests.isEmpty()) {
209      LOG.debug("No manifest files present: " + snapshotDir);
210      return;
211    }
212
213    final ExecutorCompletionService<Void> completionService = new ExecutorCompletionService<>(exec);
214
215    for (final SnapshotRegionManifest regionManifest : regionManifests) {
216      completionService.submit(new Callable<Void>() {
217        @Override public Void call() throws IOException {
218          visitRegionStoreFiles(regionManifest, visitor);
219          return null;
220        }
221      });
222    }
223    try {
224      for (int i = 0; i < regionManifests.size(); ++i) {
225        completionService.take().get();
226      }
227    } catch (InterruptedException e) {
228      throw new InterruptedIOException(e.getMessage());
229    } catch (ExecutionException e) {
230      if (e.getCause() instanceof CorruptedSnapshotException) {
231        throw new CorruptedSnapshotException(e.getCause().getMessage(),
232            ProtobufUtil.createSnapshotDesc(snapshotDesc));
233      } else {
234        IOException ex = new IOException();
235        ex.initCause(e.getCause());
236        throw ex;
237      }
238    }
239  }
240
241  /**
242   * Verify the validity of the snapshot store file
243   *
244   * @param conf The current {@link Configuration} instance.
245   * @param fs {@link FileSystem}
246   * @param snapshotDir {@link Path} to the Snapshot directory of the snapshot to verify
247   * @param snapshot the {@link SnapshotDescription} of the snapshot to verify
248   * @param regionInfo {@link RegionInfo} of the region that contains the store file
249   * @param family family that contains the store file
250   * @param storeFile the store file to verify
251   * @throws CorruptedSnapshotException if the snapshot is corrupted
252   * @throws IOException if an error occurred while scanning the directory
253   */
254  private static void verifyStoreFile(final Configuration conf, final FileSystem fs,
255      final Path snapshotDir, final SnapshotDescription snapshot, final RegionInfo regionInfo,
256      final String family, final SnapshotRegionManifest.StoreFile storeFile) throws IOException {
257    TableName table = TableName.valueOf(snapshot.getTable());
258    String fileName = storeFile.getName();
259
260    Path refPath = null;
261    if (StoreFileInfo.isReference(fileName)) {
262      // If is a reference file check if the parent file is present in the snapshot
263      refPath = new Path(new Path(regionInfo.getEncodedName(), family), fileName);
264      refPath = StoreFileInfo.getReferredToFile(refPath);
265      String refRegion = refPath.getParent().getParent().getName();
266      refPath = HFileLink.createPath(table, refRegion, family, refPath.getName());
267      if (!HFileLink.buildFromHFileLinkPattern(conf, refPath).exists(fs)) {
268        throw new CorruptedSnapshotException(
269            "Missing parent hfile for: " + fileName + " path=" + refPath,
270            ProtobufUtil.createSnapshotDesc(snapshot));
271      }
272
273      if (storeFile.hasReference()) {
274        // We don't really need to look for the file on-disk
275        // we already have the Reference information embedded here.
276        return;
277      }
278    }
279
280    Path linkPath;
281    if (refPath != null && HFileLink.isHFileLink(refPath)) {
282      linkPath = new Path(family, refPath.getName());
283    } else if (HFileLink.isHFileLink(fileName)) {
284      linkPath = new Path(family, fileName);
285    } else {
286      linkPath = new Path(family, HFileLink.createHFileLinkName(
287              table, regionInfo.getEncodedName(), fileName));
288    }
289
290    // check if the linked file exists (in the archive, or in the table dir)
291    HFileLink link = null;
292    if (MobUtils.isMobRegionInfo(regionInfo)) {
293      // for mob region
294      link = HFileLink.buildFromHFileLinkPattern(MobUtils.getQualifiedMobRootDir(conf),
295          HFileArchiveUtil.getArchivePath(conf), linkPath);
296    } else {
297      // not mob region
298      link = HFileLink.buildFromHFileLinkPattern(conf, linkPath);
299    }
300    try {
301      FileStatus fstat = link.getFileStatus(fs);
302      if (storeFile.hasFileSize() && storeFile.getFileSize() != fstat.getLen()) {
303        String msg = "hfile: " + fileName + " size does not match with the expected one. " +
304          " found=" + fstat.getLen() + " expected=" + storeFile.getFileSize();
305        LOG.error(msg);
306        throw new CorruptedSnapshotException(msg,
307          ProtobufUtil.createSnapshotDesc(snapshot));
308      }
309    } catch (FileNotFoundException e) {
310      String msg = "Can't find hfile: " + fileName + " in the real (" +
311          link.getOriginPath() + ") or archive (" + link.getArchivePath()
312          + ") directory for the primary table.";
313      LOG.error(msg);
314      throw new CorruptedSnapshotException(msg,
315        ProtobufUtil.createSnapshotDesc(snapshot));
316    }
317  }
318
319  /**
320   * Returns the store file names in the snapshot.
321   *
322   * @param conf The current {@link Configuration} instance.
323   * @param fs {@link FileSystem}
324   * @param snapshotDir {@link Path} to the Snapshot directory
325   * @throws IOException if an error occurred while scanning the directory
326   * @return the names of hfiles in the specified snaphot
327   */
328  public static Set<String> getHFileNames(final Configuration conf, final FileSystem fs,
329      final Path snapshotDir) throws IOException {
330    SnapshotDescription desc = SnapshotDescriptionUtils.readSnapshotInfo(fs, snapshotDir);
331    return getHFileNames(conf, fs, snapshotDir, desc);
332  }
333
334  /**
335   * Returns the store file names in the snapshot.
336   *
337   * @param conf The current {@link Configuration} instance.
338   * @param fs {@link FileSystem}
339   * @param snapshotDir {@link Path} to the Snapshot directory
340   * @param snapshotDesc the {@link SnapshotDescription} of the snapshot to inspect
341   * @throws IOException if an error occurred while scanning the directory
342   * @return the names of hfiles in the specified snaphot
343   */
344  private static Set<String> getHFileNames(final Configuration conf, final FileSystem fs,
345      final Path snapshotDir, final SnapshotDescription snapshotDesc)
346      throws IOException {
347    final Set<String> names = new HashSet<>();
348    visitTableStoreFiles(conf, fs, snapshotDir, snapshotDesc, new StoreFileVisitor() {
349      @Override
350      public void storeFile(final RegionInfo regionInfo, final String family,
351            final SnapshotRegionManifest.StoreFile storeFile) throws IOException {
352        String hfile = storeFile.getName();
353        if (HFileLink.isHFileLink(hfile)) {
354          names.add(HFileLink.getReferencedHFileName(hfile));
355        } else if (StoreFileInfo.isReference(hfile)) {
356          Path refPath = StoreFileInfo.getReferredToFile(new Path(new Path(
357              new Path(new Path(regionInfo.getTable().getNamespaceAsString(),
358                  regionInfo.getTable().getQualifierAsString()), regionInfo.getEncodedName()),
359              family), hfile));
360          names.add(hfile);
361          names.add(refPath.getName());
362          if (HFileLink.isHFileLink(refPath.getName())) {
363            names.add(HFileLink.getReferencedHFileName(refPath.getName()));
364          }
365        } else {
366          names.add(hfile);
367        }
368      }
369    });
370    return names;
371  }
372}