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.IOException;
022import java.io.InterruptedIOException;
023import java.util.ArrayList;
024import java.util.Collection;
025import java.util.List;
026import java.util.concurrent.Callable;
027import java.util.concurrent.ExecutionException;
028import java.util.concurrent.Executor;
029import java.util.concurrent.ExecutorCompletionService;
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.client.RegionInfo;
035import org.apache.hadoop.hbase.regionserver.HRegionFileSystem;
036import org.apache.hadoop.hbase.regionserver.StoreFileInfo;
037import org.apache.hadoop.hbase.util.Bytes;
038import org.apache.hadoop.hbase.util.CommonFSUtils;
039import org.apache.hadoop.hbase.util.FSUtils;
040import org.apache.yetus.audience.InterfaceAudience;
041import org.slf4j.Logger;
042import org.slf4j.LoggerFactory;
043
044import org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations;
045
046import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
047import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotDescription;
048import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotRegionManifest;
049
050/**
051 * DO NOT USE DIRECTLY. USE {@link SnapshotManifest}.
052 *
053 * Snapshot v1 layout format
054 *  - Each region in the table is represented by a directory with the .hregioninfo file
055 *      /snapshotName/regionName/.hregioninfo
056 *  - Each file present in the table is represented by an empty file
057 *      /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<
069                                                          HRegionFileSystem, Path> {
070    private final Configuration conf;
071    private final Path snapshotDir;
072    private final FileSystem rootFs;
073    private final FileSystem workingDirFs;
074
075    public ManifestBuilder(final Configuration conf, final FileSystem rootFs,
076        final Path snapshotDir) throws IOException {
077      this.snapshotDir = snapshotDir;
078      this.conf = conf;
079      this.rootFs = rootFs;
080      this.workingDirFs = snapshotDir.getFileSystem(conf);
081    }
082
083    @Override
084    public HRegionFileSystem regionOpen(final RegionInfo regionInfo) throws IOException {
085      HRegionFileSystem snapshotRegionFs = HRegionFileSystem.createRegionOnFileSystem(conf,
086        workingDirFs, snapshotDir, regionInfo);
087      return snapshotRegionFs;
088    }
089
090    @Override
091    public void regionClose(final HRegionFileSystem region) {
092    }
093
094    @Override
095    public Path familyOpen(final HRegionFileSystem snapshotRegionFs, final byte[] familyName) {
096      Path familyDir = snapshotRegionFs.getStoreDir(Bytes.toString(familyName));
097      return familyDir;
098    }
099
100    @Override
101    public void familyClose(final HRegionFileSystem region, final Path family) {
102    }
103
104    @Override
105    public void storeFile(final HRegionFileSystem region, final Path familyDir,
106        final StoreFileInfo storeFile) throws IOException {
107      Path referenceFile = new Path(familyDir, storeFile.getPath().getName());
108      boolean success = true;
109      if (storeFile.isReference()) {
110        // write the Reference object to the snapshot
111        storeFile.getReference().write(workingDirFs, referenceFile);
112      } else {
113        // create "reference" to this store file.  It is intentionally an empty file -- all
114        // necessary information is captured by its fs location and filename.  This allows us to
115        // only figure out what needs to be done via a single nn operation (instead of having to
116        // open and read the files as well).
117        success = workingDirFs.createNewFile(referenceFile);
118      }
119      if (!success) {
120        throw new IOException("Failed to create reference file:" + referenceFile);
121      }
122    }
123  }
124
125  static List<SnapshotRegionManifest> loadRegionManifests(final Configuration conf,
126      final Executor executor,final FileSystem fs, final Path snapshotDir,
127      final SnapshotDescription desc) throws IOException {
128    FileStatus[] regions =
129      CommonFSUtils.listStatus(fs, snapshotDir, new FSUtils.RegionDirFilter(fs));
130    if (regions == null) {
131      LOG.debug("No regions under directory:" + snapshotDir);
132      return null;
133    }
134
135    final ExecutorCompletionService<SnapshotRegionManifest> completionService =
136      new ExecutorCompletionService<>(executor);
137    for (final FileStatus region: regions) {
138      completionService.submit(new Callable<SnapshotRegionManifest>() {
139        @Override
140        public SnapshotRegionManifest call() throws IOException {
141          RegionInfo hri = HRegionFileSystem.loadRegionInfoFileContent(fs, region.getPath());
142          return buildManifestFromDisk(conf, fs, snapshotDir, hri);
143        }
144      });
145    }
146
147    ArrayList<SnapshotRegionManifest> regionsManifest = new ArrayList<>(regions.length);
148    try {
149      for (int i = 0; i < regions.length; ++i) {
150        regionsManifest.add(completionService.take().get());
151      }
152    } catch (InterruptedException e) {
153      throw new InterruptedIOException(e.getMessage());
154    } catch (ExecutionException e) {
155      throw new IOException(e.getCause());
156    }
157    return regionsManifest;
158  }
159
160  static void deleteRegionManifest(final FileSystem fs, final Path snapshotDir,
161      final SnapshotRegionManifest manifest) throws IOException {
162    String regionName = SnapshotManifest.getRegionNameFromManifest(manifest);
163    fs.delete(new Path(snapshotDir, regionName), true);
164  }
165
166  static SnapshotRegionManifest buildManifestFromDisk(final Configuration conf,
167      final FileSystem fs, final Path tableDir, final RegionInfo regionInfo) throws IOException {
168    HRegionFileSystem regionFs = HRegionFileSystem.openRegionFromFileSystem(conf, fs,
169          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        Collection<StoreFileInfo> storeFiles = regionFs.getStoreFiles(familyName, false);
188        if (storeFiles == null) {
189          LOG.debug("No files under family: " + familyName);
190          continue;
191        }
192
193        // 2.1. build the snapshot reference for the store
194        SnapshotRegionManifest.FamilyFiles.Builder family =
195              SnapshotRegionManifest.FamilyFiles.newBuilder();
196        family.setFamilyName(UnsafeByteOperations.unsafeWrap(Bytes.toBytes(familyName)));
197
198        if (LOG.isDebugEnabled()) {
199          LOG.debug("Adding snapshot references for " + storeFiles  + " hfiles");
200        }
201
202        // 2.2. iterate through all the store's files and create "references".
203        int i = 0;
204        int sz = storeFiles.size();
205        for (StoreFileInfo storeFile: storeFiles) {
206          // create "reference" to this store file.
207          LOG.debug("Adding reference for file ("+ (++i) +"/" + sz + "): " + storeFile.getPath());
208          SnapshotRegionManifest.StoreFile.Builder sfManifest =
209                SnapshotRegionManifest.StoreFile.newBuilder();
210          sfManifest.setName(storeFile.getPath().getName());
211          family.addStoreFiles(sfManifest.build());
212        }
213        manifest.addFamilyFiles(family.build());
214      }
215    }
216    return manifest.build();
217  }
218}