org.apache.hadoop.hbase.snapshot.RestoreSnapshotHelper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hbase-server Show documentation
Show all versions of hbase-server Show documentation
Server functionality for HBase
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hbase.snapshot;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ThreadPoolExecutor;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.MetaTableAccessor;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.backup.HFileArchiver;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.client.RegionInfo;
import org.apache.hadoop.hbase.client.RegionInfoBuilder;
import org.apache.hadoop.hbase.client.TableDescriptor;
import org.apache.hadoop.hbase.errorhandling.ForeignExceptionDispatcher;
import org.apache.hadoop.hbase.io.HFileLink;
import org.apache.hadoop.hbase.io.Reference;
import org.apache.hadoop.hbase.mob.MobUtils;
import org.apache.hadoop.hbase.monitoring.MonitoredTask;
import org.apache.hadoop.hbase.monitoring.TaskMonitor;
import org.apache.hadoop.hbase.regionserver.HRegion;
import org.apache.hadoop.hbase.regionserver.HRegionFileSystem;
import org.apache.hadoop.hbase.regionserver.StoreContext;
import org.apache.hadoop.hbase.regionserver.StoreFileInfo;
import org.apache.hadoop.hbase.regionserver.StoreUtils;
import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTracker;
import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory;
import org.apache.hadoop.hbase.security.access.AccessControlClient;
import org.apache.hadoop.hbase.security.access.Permission;
import org.apache.hadoop.hbase.security.access.ShadedAccessControlUtil;
import org.apache.hadoop.hbase.security.access.TablePermission;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.CommonFSUtils;
import org.apache.hadoop.hbase.util.FSUtils;
import org.apache.hadoop.hbase.util.ModifyRegionUtils;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.hadoop.io.IOUtils;
import org.apache.yetus.audience.InterfaceAudience;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.hbase.thirdparty.com.google.common.collect.ListMultimap;
import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotDescription;
import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotRegionManifest;
/**
* Helper to Restore/Clone a Snapshot
*
* The helper assumes that a table is already created, and by calling restore() the content present
* in the snapshot will be restored as the new content of the table.
*
* Clone from Snapshot: If the target table is empty, the restore operation is just a "clone
* operation", where the only operations are:
*
* - for each region in the snapshot create a new region (note that the region will have a
* different name, since the encoding contains the table name)
*
- for each file in the region create a new HFileLink to point to the original file.
*
- restore the logs, if any
*
*
* Restore from Snapshot:
*
* - for each region in the table verify which are available in the snapshot and which are not
*
* - if the region is not present in the snapshot, remove it.
*
- if the region is present in the snapshot
*
* - for each file in the table region verify which are available in the snapshot
*
* - if the hfile is not present in the snapshot, remove it
*
- if the hfile is present, keep it (nothing to do)
*
* - for each file in the snapshot region but not in the table
*
* - create a new HFileLink that point to the original file
*
*
*
* - for each region in the snapshot not present in the current table state
*
* - create a new region and for each file in the region create a new HFileLink (This is the same
* as the clone operation)
*
* - restore the logs, if any
*
*/
@InterfaceAudience.Private
public class RestoreSnapshotHelper {
private static final Logger LOG = LoggerFactory.getLogger(RestoreSnapshotHelper.class);
private final Map regionsMap = new TreeMap<>(Bytes.BYTES_COMPARATOR);
private final Map> parentsMap = new HashMap<>();
private final ForeignExceptionDispatcher monitor;
private final MonitoredTask status;
private final SnapshotManifest snapshotManifest;
private final SnapshotDescription snapshotDesc;
private final TableName snapshotTable;
private final TableDescriptor tableDesc;
private final Path rootDir;
private final Path tableDir;
private final Configuration conf;
private final FileSystem fs;
private final boolean createBackRefs;
public RestoreSnapshotHelper(final Configuration conf, final FileSystem fs,
final SnapshotManifest manifest, final TableDescriptor tableDescriptor, final Path rootDir,
final ForeignExceptionDispatcher monitor, final MonitoredTask status) {
this(conf, fs, manifest, tableDescriptor, rootDir, monitor, status, true);
}
public RestoreSnapshotHelper(final Configuration conf, final FileSystem fs,
final SnapshotManifest manifest, final TableDescriptor tableDescriptor, final Path rootDir,
final ForeignExceptionDispatcher monitor, final MonitoredTask status,
final boolean createBackRefs) {
this.fs = fs;
this.conf = conf;
this.snapshotManifest = manifest;
this.snapshotDesc = manifest.getSnapshotDescription();
this.snapshotTable = TableName.valueOf(snapshotDesc.getTable());
this.tableDesc = tableDescriptor;
this.rootDir = rootDir;
this.tableDir = CommonFSUtils.getTableDir(rootDir, tableDesc.getTableName());
this.monitor = monitor;
this.status = status;
this.createBackRefs = createBackRefs;
}
/**
* Restore the on-disk table to a specified snapshot state.
* @return the set of regions touched by the restore operation
*/
public RestoreMetaChanges restoreHdfsRegions() throws IOException {
ThreadPoolExecutor exec = SnapshotManifest.createExecutor(conf, "RestoreSnapshot");
try {
return restoreHdfsRegions(exec);
} finally {
exec.shutdown();
}
}
private RestoreMetaChanges restoreHdfsRegions(final ThreadPoolExecutor exec) throws IOException {
LOG.info("starting restore table regions using snapshot=" + snapshotDesc);
Map regionManifests = snapshotManifest.getRegionManifestsMap();
if (regionManifests == null) {
LOG.warn("Nothing to restore. Snapshot " + snapshotDesc + " looks empty");
return null;
}
RestoreMetaChanges metaChanges = new RestoreMetaChanges(tableDesc, parentsMap);
// Take a copy of the manifest.keySet() since we are going to modify
// this instance, by removing the regions already present in the restore dir.
Set regionNames = new HashSet<>(regionManifests.keySet());
List tableRegions = getTableRegions();
RegionInfo mobRegion =
MobUtils.getMobRegionInfo(snapshotManifest.getTableDescriptor().getTableName());
if (tableRegions != null) {
// restore the mob region in case
if (regionNames.contains(mobRegion.getEncodedName())) {
monitor.rethrowException();
status.setStatus("Restoring mob region...");
List mobRegions = new ArrayList<>(1);
mobRegions.add(mobRegion);
restoreHdfsMobRegions(exec, regionManifests, mobRegions);
regionNames.remove(mobRegion.getEncodedName());
status.setStatus("Finished restoring mob region.");
}
}
if (regionNames.contains(mobRegion.getEncodedName())) {
// add the mob region
monitor.rethrowException();
status.setStatus("Cloning mob region...");
cloneHdfsMobRegion(regionManifests, mobRegion);
regionNames.remove(mobRegion.getEncodedName());
status.setStatus("Finished cloning mob region.");
}
// Identify which region are still available and which not.
// NOTE: we rely upon the region name as: "table name, start key, end key"
if (tableRegions != null) {
monitor.rethrowException();
for (RegionInfo regionInfo : tableRegions) {
String regionName = regionInfo.getEncodedName();
if (regionNames.contains(regionName)) {
LOG.info("region to restore: " + regionName);
regionNames.remove(regionName);
metaChanges.addRegionToRestore(
ProtobufUtil.toRegionInfo(regionManifests.get(regionName).getRegionInfo()));
} else {
LOG.info("region to remove: " + regionName);
metaChanges.addRegionToRemove(regionInfo);
}
}
}
// Regions to Add: present in the snapshot but not in the current table
List regionsToAdd = new ArrayList<>(regionNames.size());
if (regionNames.size() > 0) {
monitor.rethrowException();
for (String regionName : regionNames) {
LOG.info("region to add: " + regionName);
regionsToAdd
.add(ProtobufUtil.toRegionInfo(regionManifests.get(regionName).getRegionInfo()));
}
}
// Create new regions cloning from the snapshot
// HBASE-19980: We need to call cloneHdfsRegions() before restoreHdfsRegions() because
// regionsMap is constructed in cloneHdfsRegions() and it can be used in restoreHdfsRegions().
monitor.rethrowException();
status.setStatus("Cloning regions...");
RegionInfo[] clonedRegions = cloneHdfsRegions(exec, regionManifests, regionsToAdd);
metaChanges.setNewRegions(clonedRegions);
status.setStatus("Finished cloning regions.");
// Restore regions using the snapshot data
monitor.rethrowException();
status.setStatus("Restoring table regions...");
restoreHdfsRegions(exec, regionManifests, metaChanges.getRegionsToRestore());
status.setStatus("Finished restoring all table regions.");
// Remove regions from the current table
monitor.rethrowException();
status.setStatus("Starting to delete excess regions from table");
removeHdfsRegions(exec, metaChanges.getRegionsToRemove());
status.setStatus("Finished deleting excess regions from table.");
LOG.info("finishing restore table regions using snapshot=" + snapshotDesc);
return metaChanges;
}
/**
* Describe the set of operations needed to update hbase:meta after restore.
*/
public static class RestoreMetaChanges {
private final Map> parentsMap;
private final TableDescriptor htd;
private List regionsToRestore = null;
private List regionsToRemove = null;
private List regionsToAdd = null;
public RestoreMetaChanges(TableDescriptor htd, Map> parentsMap) {
this.parentsMap = parentsMap;
this.htd = htd;
}
public TableDescriptor getTableDescriptor() {
return htd;
}
/**
* Returns the map of parent-children_pair.
* @return the map
*/
public Map> getParentToChildrenPairMap() {
return this.parentsMap;
}
/** Returns true if there're new regions */
public boolean hasRegionsToAdd() {
return this.regionsToAdd != null && this.regionsToAdd.size() > 0;
}
/**
* Returns the list of new regions added during the on-disk restore. The caller is responsible
* to add the regions to META. e.g MetaTableAccessor.addRegionsToMeta(...)
* @return the list of regions to add to META
*/
public List getRegionsToAdd() {
return this.regionsToAdd;
}
/** Returns true if there're regions to restore */
public boolean hasRegionsToRestore() {
return this.regionsToRestore != null && this.regionsToRestore.size() > 0;
}
/**
* Returns the list of 'restored regions' during the on-disk restore. The caller is responsible
* to add the regions to hbase:meta if not present.
* @return the list of regions restored
*/
public List getRegionsToRestore() {
return this.regionsToRestore;
}
/** Returns true if there're regions to remove */
public boolean hasRegionsToRemove() {
return this.regionsToRemove != null && this.regionsToRemove.size() > 0;
}
/**
* Returns the list of regions removed during the on-disk restore. The caller is responsible to
* remove the regions from META. e.g. MetaTableAccessor.deleteRegions(...)
* @return the list of regions to remove from META
*/
public List getRegionsToRemove() {
return this.regionsToRemove;
}
void setNewRegions(final RegionInfo[] hris) {
if (hris != null) {
regionsToAdd = Arrays.asList(hris);
} else {
regionsToAdd = null;
}
}
void addRegionToRemove(final RegionInfo hri) {
if (regionsToRemove == null) {
regionsToRemove = new LinkedList<>();
}
regionsToRemove.add(hri);
}
void addRegionToRestore(final RegionInfo hri) {
if (regionsToRestore == null) {
regionsToRestore = new LinkedList<>();
}
regionsToRestore.add(hri);
}
public void updateMetaParentRegions(Connection connection, final List regionInfos)
throws IOException {
if (regionInfos == null || parentsMap.isEmpty()) return;
// Extract region names and offlined regions
Map regionsByName = new HashMap<>(regionInfos.size());
List parentRegions = new LinkedList<>();
for (RegionInfo regionInfo : regionInfos) {
if (regionInfo.isSplitParent()) {
parentRegions.add(regionInfo);
} else {
regionsByName.put(regionInfo.getEncodedName(), regionInfo);
}
}
// Update Offline parents
for (RegionInfo regionInfo : parentRegions) {
Pair daughters = parentsMap.get(regionInfo.getEncodedName());
if (daughters == null) {
// The snapshot contains an unreferenced region.
// It will be removed by the CatalogJanitor.
LOG.warn("Skip update of unreferenced offline parent: " + regionInfo);
continue;
}
// One side of the split is already compacted
if (daughters.getSecond() == null) {
daughters.setSecond(daughters.getFirst());
}
LOG.debug("Update splits parent " + regionInfo.getEncodedName() + " -> " + daughters);
MetaTableAccessor.addSplitsToParent(connection, regionInfo,
regionsByName.get(daughters.getFirst()), regionsByName.get(daughters.getSecond()));
}
}
}
/**
* Remove specified regions from the file-system, using the archiver.
*/
private void removeHdfsRegions(final ThreadPoolExecutor exec, final List regions)
throws IOException {
if (regions == null || regions.isEmpty()) return;
ModifyRegionUtils.editRegions(exec, regions, new ModifyRegionUtils.RegionEditTask() {
@Override
public void editRegion(final RegionInfo hri) throws IOException {
HFileArchiver.archiveRegion(conf, fs, hri);
}
});
}
/**
* Restore specified regions by restoring content to the snapshot state.
*/
private void restoreHdfsRegions(final ThreadPoolExecutor exec,
final Map regionManifests, final List regions)
throws IOException {
if (regions == null || regions.isEmpty()) return;
ModifyRegionUtils.editRegions(exec, regions, new ModifyRegionUtils.RegionEditTask() {
@Override
public void editRegion(final RegionInfo hri) throws IOException {
restoreRegion(hri, regionManifests.get(hri.getEncodedName()));
}
});
}
/**
* Restore specified mob regions by restoring content to the snapshot state.
*/
private void restoreHdfsMobRegions(final ThreadPoolExecutor exec,
final Map regionManifests, final List regions)
throws IOException {
if (regions == null || regions.isEmpty()) return;
ModifyRegionUtils.editRegions(exec, regions, new ModifyRegionUtils.RegionEditTask() {
@Override
public void editRegion(final RegionInfo hri) throws IOException {
restoreMobRegion(hri, regionManifests.get(hri.getEncodedName()));
}
});
}
private Map>
getRegionHFileReferences(final SnapshotRegionManifest manifest) {
Map> familyMap =
new HashMap<>(manifest.getFamilyFilesCount());
for (SnapshotRegionManifest.FamilyFiles familyFiles : manifest.getFamilyFilesList()) {
familyMap.put(familyFiles.getFamilyName().toStringUtf8(),
new ArrayList<>(familyFiles.getStoreFilesList()));
}
return familyMap;
}
/**
* Restore region by removing files not in the snapshot and adding the missing ones from the
* snapshot.
*/
private void restoreRegion(final RegionInfo regionInfo,
final SnapshotRegionManifest regionManifest) throws IOException {
restoreRegion(regionInfo, regionManifest, new Path(tableDir, regionInfo.getEncodedName()));
}
/**
* Restore mob region by removing files not in the snapshot and adding the missing ones from the
* snapshot.
*/
private void restoreMobRegion(final RegionInfo regionInfo,
final SnapshotRegionManifest regionManifest) throws IOException {
if (regionManifest == null) {
return;
}
restoreRegion(regionInfo, regionManifest,
MobUtils.getMobRegionPath(conf, tableDesc.getTableName()));
}
/**
* Restore region by removing files not in the snapshot and adding the missing ones from the
* snapshot.
*/
private void restoreRegion(final RegionInfo regionInfo,
final SnapshotRegionManifest regionManifest, Path regionDir) throws IOException {
Map> snapshotFiles =
getRegionHFileReferences(regionManifest);
String tableName = tableDesc.getTableName().getNameAsString();
final String snapshotName = snapshotDesc.getName();
Path regionPath = new Path(tableDir, regionInfo.getEncodedName());
HRegionFileSystem regionFS = (fs.exists(regionPath))
? HRegionFileSystem.openRegionFromFileSystem(conf, fs, tableDir, regionInfo, false)
: HRegionFileSystem.createRegionOnFileSystem(conf, fs, tableDir, regionInfo);
// Restore families present in the table
for (Path familyDir : FSUtils.getFamilyDirs(fs, regionDir)) {
byte[] family = Bytes.toBytes(familyDir.getName());
Set familyFiles = getTableRegionFamilyFiles(familyDir);
List snapshotFamilyFiles =
snapshotFiles.remove(familyDir.getName());
List filesToTrack = new ArrayList<>();
if (snapshotFamilyFiles != null) {
List hfilesToAdd = new ArrayList<>();
for (SnapshotRegionManifest.StoreFile storeFile : snapshotFamilyFiles) {
if (familyFiles.contains(storeFile.getName())) {
// HFile already present
familyFiles.remove(storeFile.getName());
// no need to restore already present files, but we need to add those to tracker
filesToTrack
.add(new StoreFileInfo(conf, fs, new Path(familyDir, storeFile.getName()), true));
} else {
// HFile missing
hfilesToAdd.add(storeFile);
}
}
// Remove hfiles not present in the snapshot
for (String hfileName : familyFiles) {
Path hfile = new Path(familyDir, hfileName);
if (!fs.getFileStatus(hfile).isDirectory()) {
LOG.trace("Removing HFile=" + hfileName + " not present in snapshot=" + snapshotName
+ " from region=" + regionInfo.getEncodedName() + " table=" + tableName);
HFileArchiver.archiveStoreFile(conf, fs, regionInfo, tableDir, family, hfile);
}
}
// Restore Missing files
for (SnapshotRegionManifest.StoreFile storeFile : hfilesToAdd) {
LOG.debug("Restoring missing HFileLink " + storeFile.getName() + " of snapshot="
+ snapshotName + " to region=" + regionInfo.getEncodedName() + " table=" + tableName);
String fileName = restoreStoreFile(familyDir, regionInfo, storeFile, createBackRefs);
// mark the reference file to be added to tracker
filesToTrack.add(new StoreFileInfo(conf, fs, new Path(familyDir, fileName), true));
}
} else {
// Family doesn't exists in the snapshot
LOG.trace("Removing family=" + Bytes.toString(family) + " in snapshot=" + snapshotName
+ " from region=" + regionInfo.getEncodedName() + " table=" + tableName);
HFileArchiver.archiveFamilyByFamilyDir(fs, conf, regionInfo, familyDir, family);
fs.delete(familyDir, true);
}
StoreFileTracker tracker =
StoreFileTrackerFactory.create(conf, true, StoreContext.getBuilder()
.withFamilyStoreDirectoryPath(familyDir).withRegionFileSystem(regionFS).build());
// simply reset list of tracked files with the matching files
// and the extra one present in the snapshot
tracker.set(filesToTrack);
}
// Add families not present in the table
for (Map.Entry> familyEntry : snapshotFiles
.entrySet()) {
Path familyDir = new Path(regionDir, familyEntry.getKey());
StoreFileTracker tracker =
StoreFileTrackerFactory.create(conf, true, StoreContext.getBuilder()
.withFamilyStoreDirectoryPath(familyDir).withRegionFileSystem(regionFS).build());
List files = new ArrayList<>();
if (!fs.mkdirs(familyDir)) {
throw new IOException("Unable to create familyDir=" + familyDir);
}
for (SnapshotRegionManifest.StoreFile storeFile : familyEntry.getValue()) {
LOG.trace("Adding HFileLink (Not present in the table) " + storeFile.getName()
+ " of snapshot " + snapshotName + " to table=" + tableName);
String fileName = restoreStoreFile(familyDir, regionInfo, storeFile, createBackRefs);
files.add(new StoreFileInfo(conf, fs, new Path(familyDir, fileName), true));
}
tracker.set(files);
}
}
/** Returns The set of files in the specified family directory. */
private Set getTableRegionFamilyFiles(final Path familyDir) throws IOException {
FileStatus[] hfiles = CommonFSUtils.listStatus(fs, familyDir);
if (hfiles == null) {
return Collections.emptySet();
}
Set familyFiles = new HashSet<>(hfiles.length);
for (int i = 0; i < hfiles.length; ++i) {
String hfileName = hfiles[i].getPath().getName();
familyFiles.add(hfileName);
}
return familyFiles;
}
/**
* Clone specified regions. For each region create a new region and create a HFileLink for each
* hfile.
*/
private RegionInfo[] cloneHdfsRegions(final ThreadPoolExecutor exec,
final Map regionManifests, final List regions)
throws IOException {
if (regions == null || regions.isEmpty()) return null;
final Map snapshotRegions = new HashMap<>(regions.size());
final String snapshotName = snapshotDesc.getName();
// clone region info (change embedded tableName with the new one)
RegionInfo[] clonedRegionsInfo = new RegionInfo[regions.size()];
for (int i = 0; i < clonedRegionsInfo.length; ++i) {
// clone the region info from the snapshot region info
RegionInfo snapshotRegionInfo = regions.get(i);
clonedRegionsInfo[i] = cloneRegionInfo(snapshotRegionInfo);
// add the region name mapping between snapshot and cloned
String snapshotRegionName = snapshotRegionInfo.getEncodedName();
String clonedRegionName = clonedRegionsInfo[i].getEncodedName();
regionsMap.put(Bytes.toBytes(snapshotRegionName), Bytes.toBytes(clonedRegionName));
LOG.info("clone region=" + snapshotRegionName + " as " + clonedRegionName + " in snapshot "
+ snapshotName);
// Add mapping between cloned region name and snapshot region info
snapshotRegions.put(clonedRegionName, snapshotRegionInfo);
}
// create the regions on disk
ModifyRegionUtils.createRegions(exec, conf, rootDir, tableDesc, clonedRegionsInfo,
new ModifyRegionUtils.RegionFillTask() {
@Override
public void fillRegion(final HRegion region) throws IOException {
RegionInfo snapshotHri = snapshotRegions.get(region.getRegionInfo().getEncodedName());
cloneRegion(region, snapshotHri, regionManifests.get(snapshotHri.getEncodedName()));
}
});
return clonedRegionsInfo;
}
/**
* Clone the mob region. For the region create a new region and create a HFileLink for each hfile.
*/
private void cloneHdfsMobRegion(final Map regionManifests,
final RegionInfo region) throws IOException {
// clone region info (change embedded tableName with the new one)
Path clonedRegionPath = MobUtils.getMobRegionPath(rootDir, tableDesc.getTableName());
cloneRegion(MobUtils.getMobRegionInfo(tableDesc.getTableName()), clonedRegionPath, region,
regionManifests.get(region.getEncodedName()));
}
/**
* Clone region directory content from the snapshot info. Each region is encoded with the table
* name, so the cloned region will have a different region name. Instead of copying the hfiles a
* HFileLink is created.
* @param regionDir {@link Path} cloned dir
*/
private void cloneRegion(final RegionInfo newRegionInfo, final Path regionDir,
final RegionInfo snapshotRegionInfo, final SnapshotRegionManifest manifest) throws IOException {
final String tableName = tableDesc.getTableName().getNameAsString();
final String snapshotName = snapshotDesc.getName();
for (SnapshotRegionManifest.FamilyFiles familyFiles : manifest.getFamilyFilesList()) {
Path familyDir = new Path(regionDir, familyFiles.getFamilyName().toStringUtf8());
List clonedFiles = new ArrayList<>();
for (SnapshotRegionManifest.StoreFile storeFile : familyFiles.getStoreFilesList()) {
LOG.info("Adding HFileLink " + storeFile.getName() + " from cloned region " + "in snapshot "
+ snapshotName + " to table=" + tableName);
if (MobUtils.isMobRegionInfo(newRegionInfo)) {
String mobFileName =
HFileLink.createHFileLinkName(snapshotRegionInfo, storeFile.getName());
Path mobPath = new Path(familyDir, mobFileName);
if (fs.exists(mobPath)) {
fs.delete(mobPath, true);
}
restoreStoreFile(familyDir, snapshotRegionInfo, storeFile, createBackRefs);
} else {
String file = restoreStoreFile(familyDir, snapshotRegionInfo, storeFile, createBackRefs);
clonedFiles.add(new StoreFileInfo(conf, fs, new Path(familyDir, file), true));
}
}
// we don't need to track files under mobdir
if (!MobUtils.isMobRegionInfo(newRegionInfo)) {
Path regionPath = new Path(tableDir, newRegionInfo.getEncodedName());
HRegionFileSystem regionFS = (fs.exists(regionPath))
? HRegionFileSystem.openRegionFromFileSystem(conf, fs, tableDir, newRegionInfo, false)
: HRegionFileSystem.createRegionOnFileSystem(conf, fs, tableDir, newRegionInfo);
Configuration sftConf = StoreUtils.createStoreConfiguration(conf, tableDesc,
tableDesc.getColumnFamily(familyFiles.getFamilyName().toByteArray()));
StoreFileTracker tracker =
StoreFileTrackerFactory.create(sftConf, true, StoreContext.getBuilder()
.withFamilyStoreDirectoryPath(familyDir).withRegionFileSystem(regionFS).build());
tracker.set(clonedFiles);
}
}
}
/**
* Clone region directory content from the snapshot info. Each region is encoded with the table
* name, so the cloned region will have a different region name. Instead of copying the hfiles a
* HFileLink is created.
* @param region {@link HRegion} cloned
*/
private void cloneRegion(final HRegion region, final RegionInfo snapshotRegionInfo,
final SnapshotRegionManifest manifest) throws IOException {
cloneRegion(region.getRegionInfo(), new Path(tableDir, region.getRegionInfo().getEncodedName()),
snapshotRegionInfo, manifest);
}
/**
* Create a new {@link HFileLink} to reference the store file.
*
* The store file in the snapshot can be a simple hfile, an HFileLink or a reference.
*
* - hfile: abc -> table=region-abc
*
- reference: abc.1234 -> table=region-abc.1234
*
- hfilelink: table=region-hfile -> table=region-hfile
*
* @param familyDir destination directory for the store file
* @param regionInfo destination region info for the table
* @param createBackRef - Whether back reference should be created. Defaults to true.
* @param storeFile store file name (can be a Reference, HFileLink or simple HFile)
*/
private String restoreStoreFile(final Path familyDir, final RegionInfo regionInfo,
final SnapshotRegionManifest.StoreFile storeFile, final boolean createBackRef)
throws IOException {
String hfileName = storeFile.getName();
if (HFileLink.isHFileLink(hfileName)) {
return HFileLink.createFromHFileLink(conf, fs, familyDir, hfileName, createBackRef);
} else if (StoreFileInfo.isReference(hfileName)) {
return restoreReferenceFile(familyDir, regionInfo, storeFile);
} else {
return HFileLink.create(conf, fs, familyDir, regionInfo, hfileName, createBackRef);
}
}
/**
* Create a new {@link Reference} as copy of the source one.
*
*
*
*
* The source table looks like:
* 1234/abc (original file)
* 5678/abc.1234 (reference file)
*
* After the clone operation looks like:
* wxyz/table=1234-abc
* stuv/table=1234-abc.wxyz
*
* NOTE that the region name in the clone changes (md5 of regioninfo)
* and the reference should reflect that change.
*
*
*
* @param familyDir destination directory for the store file
* @param regionInfo destination region info for the table
* @param storeFile reference file name
*/
private String restoreReferenceFile(final Path familyDir, final RegionInfo regionInfo,
final SnapshotRegionManifest.StoreFile storeFile) throws IOException {
String hfileName = storeFile.getName();
// Extract the referred information (hfile name and parent region)
Path refPath =
StoreFileInfo
.getReferredToFile(
new Path(
new Path(
new Path(new Path(snapshotTable.getNamespaceAsString(),
snapshotTable.getQualifierAsString()), regionInfo.getEncodedName()),
familyDir.getName()),
hfileName));
String snapshotRegionName = refPath.getParent().getParent().getName();
String fileName = refPath.getName();
// The new reference should have the cloned region name as parent, if it is a clone.
String clonedRegionName = Bytes.toString(regionsMap.get(Bytes.toBytes(snapshotRegionName)));
if (clonedRegionName == null) clonedRegionName = snapshotRegionName;
// The output file should be a reference link table=snapshotRegion-fileName.clonedRegionName
Path linkPath = null;
String refLink = fileName;
if (!HFileLink.isHFileLink(fileName)) {
refLink = HFileLink.createHFileLinkName(snapshotTable, snapshotRegionName, fileName);
linkPath = new Path(familyDir,
HFileLink.createHFileLinkName(snapshotTable, regionInfo.getEncodedName(), hfileName));
}
Path outPath = new Path(familyDir, refLink + '.' + clonedRegionName);
// Create the new reference
if (storeFile.hasReference()) {
Reference reference = Reference.convert(storeFile.getReference());
reference.write(fs, outPath);
} else {
InputStream in;
if (linkPath != null) {
in = HFileLink.buildFromHFileLinkPattern(conf, linkPath).open(fs);
} else {
linkPath = new Path(new Path(
HRegion.getRegionDir(snapshotManifest.getSnapshotDir(), regionInfo.getEncodedName()),
familyDir.getName()), hfileName);
in = fs.open(linkPath);
}
OutputStream out = fs.create(outPath);
IOUtils.copyBytes(in, out, conf);
}
// Add the daughter region to the map
String regionName = Bytes.toString(regionsMap.get(regionInfo.getEncodedNameAsBytes()));
if (regionName == null) {
regionName = regionInfo.getEncodedName();
}
LOG.debug("Restore reference " + regionName + " to " + clonedRegionName);
synchronized (parentsMap) {
Pair daughters = parentsMap.get(clonedRegionName);
if (daughters == null) {
// In case one side of the split is already compacted, regionName is put as both first and
// second of Pair
daughters = new Pair<>(regionName, regionName);
parentsMap.put(clonedRegionName, daughters);
} else if (!regionName.equals(daughters.getFirst())) {
daughters.setSecond(regionName);
}
}
return outPath.getName();
}
/**
* Create a new {@link RegionInfo} from the snapshot region info. Keep the same startKey, endKey,
* regionId and split information but change the table name.
* @param snapshotRegionInfo Info for region to clone.
* @return the new HRegion instance
*/
public RegionInfo cloneRegionInfo(final RegionInfo snapshotRegionInfo) {
return cloneRegionInfo(tableDesc.getTableName(), snapshotRegionInfo);
}
public static RegionInfo cloneRegionInfo(TableName tableName, RegionInfo snapshotRegionInfo) {
return RegionInfoBuilder.newBuilder(tableName).setStartKey(snapshotRegionInfo.getStartKey())
.setEndKey(snapshotRegionInfo.getEndKey()).setSplit(snapshotRegionInfo.isSplit())
.setRegionId(snapshotRegionInfo.getRegionId()).setOffline(snapshotRegionInfo.isOffline())
.build();
}
/** Returns the set of the regions contained in the table */
private List getTableRegions() throws IOException {
LOG.debug("get table regions: " + tableDir);
FileStatus[] regionDirs =
CommonFSUtils.listStatus(fs, tableDir, new FSUtils.RegionDirFilter(fs));
if (regionDirs == null) {
return null;
}
List regions = new ArrayList<>(regionDirs.length);
for (int i = 0; i < regionDirs.length; ++i) {
RegionInfo hri = HRegionFileSystem.loadRegionInfoFileContent(fs, regionDirs[i].getPath());
regions.add(hri);
}
LOG.debug("found " + regions.size() + " regions for table="
+ tableDesc.getTableName().getNameAsString());
return regions;
}
/**
* Copy the snapshot files for a snapshot scanner, discards meta changes.
*/
public static RestoreMetaChanges copySnapshotForScanner(Configuration conf, FileSystem fs,
Path rootDir, Path restoreDir, String snapshotName) throws IOException {
// ensure that restore dir is not under root dir
if (!restoreDir.getFileSystem(conf).getUri().equals(rootDir.getFileSystem(conf).getUri())) {
throw new IllegalArgumentException(
"Filesystems for restore directory and HBase root " + "directory should be the same");
}
if (restoreDir.toUri().getPath().startsWith(rootDir.toUri().getPath() + "/")) {
throw new IllegalArgumentException("Restore directory cannot be a sub directory of HBase "
+ "root directory. RootDir: " + rootDir + ", restoreDir: " + restoreDir);
}
Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshotName, rootDir);
SnapshotDescription snapshotDesc = SnapshotDescriptionUtils.readSnapshotInfo(fs, snapshotDir);
SnapshotManifest manifest = SnapshotManifest.open(conf, fs, snapshotDir, snapshotDesc);
MonitoredTask status = TaskMonitor.get()
.createStatus("Restoring snapshot '" + snapshotName + "' to directory " + restoreDir);
ForeignExceptionDispatcher monitor = new ForeignExceptionDispatcher();
// we send createBackRefs=false so that restored hfiles do not create back reference links
// in the base hbase root dir.
RestoreSnapshotHelper helper = new RestoreSnapshotHelper(conf, fs, manifest,
manifest.getTableDescriptor(), restoreDir, monitor, status, false);
RestoreMetaChanges metaChanges = helper.restoreHdfsRegions(); // TODO: parallelize.
if (LOG.isDebugEnabled()) {
LOG.debug("Restored table dir:" + restoreDir);
CommonFSUtils.logFileSystemState(fs, restoreDir, LOG);
}
return metaChanges;
}
public static void restoreSnapshotAcl(SnapshotDescription snapshot, TableName newTableName,
Configuration conf) throws IOException {
if (snapshot.hasUsersAndPermissions() && snapshot.getUsersAndPermissions() != null) {
LOG.info("Restore snapshot acl to table. snapshot: " + snapshot + ", table: " + newTableName);
ListMultimap perms =
ShadedAccessControlUtil.toUserTablePermissions(snapshot.getUsersAndPermissions());
try (Connection conn = ConnectionFactory.createConnection(conf)) {
for (Entry e : perms.entries()) {
String user = e.getKey();
TablePermission tablePerm = (TablePermission) e.getValue();
AccessControlClient.grant(conn, newTableName, user, tablePerm.getFamily(),
tablePerm.getQualifier(), tablePerm.getActions());
}
} catch (Throwable e) {
throw new IOException("Grant acl into newly creatd table failed. snapshot: " + snapshot
+ ", table: " + newTableName, e);
}
}
}
}