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