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}