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