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.IOException;
021import java.io.InterruptedIOException;
022import java.util.ArrayList;
023import java.util.Collection;
024import java.util.List;
025import java.util.concurrent.Callable;
026import java.util.concurrent.ExecutionException;
027import java.util.concurrent.Executor;
028import java.util.concurrent.ExecutorCompletionService;
029import org.apache.hadoop.conf.Configuration;
030import org.apache.hadoop.fs.FileStatus;
031import org.apache.hadoop.fs.FileSystem;
032import org.apache.hadoop.fs.Path;
033import org.apache.hadoop.hbase.HConstants;
034import org.apache.hadoop.hbase.client.RegionInfo;
035import org.apache.hadoop.hbase.client.TableDescriptor;
036import org.apache.hadoop.hbase.regionserver.HRegionFileSystem;
037import org.apache.hadoop.hbase.regionserver.StoreFileInfo;
038import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTracker;
039import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory;
040import org.apache.hadoop.hbase.util.Bytes;
041import org.apache.hadoop.hbase.util.CommonFSUtils;
042import org.apache.hadoop.hbase.util.FSUtils;
043import org.apache.yetus.audience.InterfaceAudience;
044import org.slf4j.Logger;
045import org.slf4j.LoggerFactory;
046
047import org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations;
048
049import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
050import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotDescription;
051import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotRegionManifest;
052
053/**
054 * DO NOT USE DIRECTLY. USE {@link SnapshotManifest}. Snapshot v1 layout format - Each region in the
055 * table is represented by a directory with the .hregioninfo file
056 * /snapshotName/regionName/.hregioninfo - Each file present in the table is represented by an empty
057 * file /snapshotName/regionName/familyName/fileName
058 */
059@InterfaceAudience.Private
060public final class SnapshotManifestV1 {
061  private static final Logger LOG = LoggerFactory.getLogger(SnapshotManifestV1.class);
062
063  public static final int DESCRIPTOR_VERSION = 0;
064
065  private SnapshotManifestV1() {
066  }
067
068  static class ManifestBuilder implements SnapshotManifest.RegionVisitor<HRegionFileSystem, Path> {
069    private final Configuration conf;
070    private final Path snapshotDir;
071    private final FileSystem rootFs;
072    private final FileSystem workingDirFs;
073
074    public ManifestBuilder(final Configuration conf, final FileSystem rootFs,
075      final Path snapshotDir) throws IOException {
076      this.snapshotDir = snapshotDir;
077      this.conf = conf;
078      this.rootFs = rootFs;
079      this.workingDirFs = snapshotDir.getFileSystem(conf);
080    }
081
082    @Override
083    public HRegionFileSystem regionOpen(final RegionInfo regionInfo) throws IOException {
084      HRegionFileSystem snapshotRegionFs =
085        HRegionFileSystem.createRegionOnFileSystem(conf, workingDirFs, snapshotDir, regionInfo);
086      return snapshotRegionFs;
087    }
088
089    @Override
090    public void regionClose(final HRegionFileSystem region) {
091    }
092
093    @Override
094    public Path familyOpen(final HRegionFileSystem snapshotRegionFs, final byte[] familyName) {
095      Path familyDir = snapshotRegionFs.getStoreDir(Bytes.toString(familyName));
096      return familyDir;
097    }
098
099    @Override
100    public void familyClose(final HRegionFileSystem region, final Path family) {
101    }
102
103    @Override
104    public void storeFile(final HRegionFileSystem region, final Path familyDir,
105      final StoreFileInfo storeFile) throws IOException {
106      Path referenceFile = new Path(familyDir, storeFile.getPath().getName());
107      boolean success = true;
108      if (storeFile.isReference()) {
109        // write the Reference object to the snapshot
110        storeFile.getReference().write(workingDirFs, referenceFile);
111      } else {
112        // create "reference" to this store file. It is intentionally an empty file -- all
113        // necessary information is captured by its fs location and filename. This allows us to
114        // only figure out what needs to be done via a single nn operation (instead of having to
115        // open and read the files as well).
116        success = workingDirFs.createNewFile(referenceFile);
117      }
118      if (!success) {
119        throw new IOException("Failed to create reference file:" + referenceFile);
120      }
121    }
122  }
123
124  static List<SnapshotRegionManifest> loadRegionManifests(final Configuration conf,
125    final Executor executor, final FileSystem fs, final Path snapshotDir,
126    final SnapshotDescription desc, final TableDescriptor htd) throws IOException {
127    FileStatus[] regions =
128      CommonFSUtils.listStatus(fs, snapshotDir, new FSUtils.RegionDirFilter(fs));
129    if (regions == null) {
130      LOG.debug("No regions under directory:" + snapshotDir);
131      return null;
132    }
133
134    final ExecutorCompletionService<SnapshotRegionManifest> completionService =
135      new ExecutorCompletionService<>(executor);
136    for (final FileStatus region : regions) {
137      completionService.submit(new Callable<SnapshotRegionManifest>() {
138        @Override
139        public SnapshotRegionManifest call() throws IOException {
140          RegionInfo hri = HRegionFileSystem.loadRegionInfoFileContent(fs, region.getPath());
141          return buildManifestFromDisk(conf, fs, snapshotDir, hri, htd);
142        }
143      });
144    }
145
146    ArrayList<SnapshotRegionManifest> regionsManifest = new ArrayList<>(regions.length);
147    try {
148      for (int i = 0; i < regions.length; ++i) {
149        regionsManifest.add(completionService.take().get());
150      }
151    } catch (InterruptedException e) {
152      throw new InterruptedIOException(e.getMessage());
153    } catch (ExecutionException e) {
154      throw new IOException(e.getCause());
155    }
156    return regionsManifest;
157  }
158
159  static void deleteRegionManifest(final FileSystem fs, final Path snapshotDir,
160    final SnapshotRegionManifest manifest) throws IOException {
161    String regionName = SnapshotManifest.getRegionNameFromManifest(manifest);
162    fs.delete(new Path(snapshotDir, regionName), true);
163  }
164
165  static SnapshotRegionManifest buildManifestFromDisk(final Configuration conf, final FileSystem fs,
166    final Path tableDir, final RegionInfo regionInfo, final TableDescriptor htd)
167    throws IOException {
168    HRegionFileSystem regionFs =
169      HRegionFileSystem.openRegionFromFileSystem(conf, fs, tableDir, regionInfo, true);
170    SnapshotRegionManifest.Builder manifest = SnapshotRegionManifest.newBuilder();
171
172    // 1. dump region meta info into the snapshot directory
173    LOG.debug("Storing region-info for snapshot.");
174    manifest.setRegionInfo(ProtobufUtil.toRegionInfo(regionInfo));
175
176    // 2. iterate through all the stores in the region
177    LOG.debug("Creating references for hfiles");
178
179    // This ensures that we have an atomic view of the directory as long as we have < ls limit
180    // (batch size of the files in a directory) on the namenode. Otherwise, we get back the files in
181    // batches and may miss files being added/deleted. This could be more robust (iteratively
182    // checking to see if we have all the files until we are sure), but the limit is currently 1000
183    // files/batch, far more than the number of store files under a single column family.
184    Collection<String> familyNames = regionFs.getFamilies();
185    if (familyNames != null) {
186      for (String familyName : familyNames) {
187        StoreFileTracker sft = StoreFileTrackerFactory.create(conf, htd,
188          htd.getColumnFamily(familyName.getBytes()), regionFs, false);
189        List<StoreFileInfo> storeFiles = getStoreFiles(sft, regionFs, familyName, false);
190        if (storeFiles == null) {
191          LOG.debug("No files under family: " + familyName);
192          continue;
193        }
194
195        // 2.1. build the snapshot reference for the store
196        SnapshotRegionManifest.FamilyFiles.Builder family =
197          SnapshotRegionManifest.FamilyFiles.newBuilder();
198        family.setFamilyName(UnsafeByteOperations.unsafeWrap(Bytes.toBytes(familyName)));
199
200        if (LOG.isDebugEnabled()) {
201          LOG.debug("Adding snapshot references for " + storeFiles + " hfiles");
202        }
203
204        // 2.2. iterate through all the store's files and create "references".
205        int i = 0;
206        int sz = storeFiles.size();
207        for (StoreFileInfo storeFile : storeFiles) {
208          // create "reference" to this store file.
209          LOG.debug("Adding reference for file (" + (++i) + "/" + sz + "): " + storeFile.getPath());
210          SnapshotRegionManifest.StoreFile.Builder sfManifest =
211            SnapshotRegionManifest.StoreFile.newBuilder();
212          sfManifest.setName(storeFile.getPath().getName());
213          family.addStoreFiles(sfManifest.build());
214        }
215        manifest.addFamilyFiles(family.build());
216      }
217    }
218    return manifest.build();
219  }
220
221  public static List<StoreFileInfo> getStoreFiles(StoreFileTracker sft, HRegionFileSystem regionFS,
222    String familyName, boolean validate) throws IOException {
223    Path familyDir = new Path(regionFS.getRegionDir(), familyName);
224    FileStatus[] files = CommonFSUtils.listStatus(regionFS.getFileSystem(), familyDir);
225    if (files == null) {
226      if (LOG.isTraceEnabled()) {
227        LOG.trace("No StoreFiles for: " + familyDir);
228      }
229      return null;
230    }
231
232    ArrayList<StoreFileInfo> storeFiles = new ArrayList<>(files.length);
233    for (FileStatus status : files) {
234      if (validate && !StoreFileInfo.isValid(status)) {
235        // recovered.hfiles directory is expected inside CF path when hbase.wal.split.to.hfile to
236        // true, refer HBASE-23740
237        if (!HConstants.RECOVERED_HFILES_DIR.equals(status.getPath().getName())) {
238          LOG.warn("Invalid StoreFile: {}", status.getPath());
239        }
240        continue;
241      }
242      StoreFileInfo info = sft.getStoreFileInfo(status.getPath(), false);
243      storeFiles.add(info);
244
245    }
246    return storeFiles;
247  }
248}