HBASE-26286: Add support for specifying store file tracker when restoring or cloning snapshot

Closes #3851

Signed-off-by: Duo Zhang <zhangduo@apache.org>
Signed-off-by: Josh Elser <elserj@apache.org>
This commit is contained in:
BukrosSzabolcs 2021-12-15 20:09:03 -05:00 committed by Josh Elser
parent d00b5faade
commit 771e552cf4
24 changed files with 357 additions and 61 deletions

View File

@ -69,6 +69,7 @@ import org.apache.hadoop.hbase.util.Pair;
import org.apache.yetus.audience.InterfaceAudience;
import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableList;
import org.apache.yetus.audience.InterfaceStability;
/**
* The administrative API for HBase. Obtain an instance from {@link Connection#getAdmin()} and
@ -1620,7 +1621,7 @@ public interface Admin extends Abortable, Closeable {
* @throws IllegalArgumentException if the restore request is formatted incorrectly
*/
void restoreSnapshot(String snapshotName, boolean takeFailSafeSnapshot, boolean restoreAcl)
throws IOException, RestoreSnapshotException;
throws IOException, RestoreSnapshotException;
/**
* Create a new table by cloning the snapshot content.
@ -1633,7 +1634,25 @@ public interface Admin extends Abortable, Closeable {
*/
default void cloneSnapshot(String snapshotName, TableName tableName)
throws IOException, TableExistsException, RestoreSnapshotException {
cloneSnapshot(snapshotName, tableName, false);
cloneSnapshot(snapshotName, tableName, false, null);
}
/**
* Create a new table by cloning the snapshot content.
* @param snapshotName name of the snapshot to be cloned
* @param tableName name of the table where the snapshot will be restored
* @param restoreAcl <code>true</code> to clone acl into newly created table
* @param customSFT specify the StoreFileTracker used for the table
* @throws IOException if a remote or network exception occurs
* @throws TableExistsException if table to be created already exists
* @throws RestoreSnapshotException if snapshot failed to be cloned
* @throws IllegalArgumentException if the specified table has not a valid name
*/
default void cloneSnapshot(String snapshotName, TableName tableName, boolean restoreAcl,
String customSFT)
throws IOException, TableExistsException, RestoreSnapshotException {
get(cloneSnapshotAsync(snapshotName, tableName, restoreAcl, customSFT), getSyncWaitTimeout(),
TimeUnit.MILLISECONDS);
}
/**
@ -1680,8 +1699,25 @@ public interface Admin extends Abortable, Closeable {
* @throws RestoreSnapshotException if snapshot failed to be cloned
* @throws IllegalArgumentException if the specified table has not a valid name
*/
Future<Void> cloneSnapshotAsync(String snapshotName, TableName tableName, boolean restoreAcl)
throws IOException, TableExistsException, RestoreSnapshotException;
default Future<Void> cloneSnapshotAsync(String snapshotName, TableName tableName,
boolean restoreAcl)
throws IOException, TableExistsException, RestoreSnapshotException {
return cloneSnapshotAsync(snapshotName, tableName, restoreAcl, null);
}
/**
* Create a new table by cloning the snapshot content.
* @param snapshotName name of the snapshot to be cloned
* @param tableName name of the table where the snapshot will be restored
* @param restoreAcl <code>true</code> to clone acl into newly created table
* @param customSFT specify the StroreFileTracker used for the table
* @throws IOException if a remote or network exception occurs
* @throws TableExistsException if table to be created already exists
* @throws RestoreSnapshotException if snapshot failed to be cloned
* @throws IllegalArgumentException if the specified table has not a valid name
*/
Future<Void> cloneSnapshotAsync(String snapshotName, TableName tableName, boolean restoreAcl,
String customSFT) throws IOException, TableExistsException, RestoreSnapshotException;
/**
* Execute a distributed procedure on a cluster.

View File

@ -644,14 +644,15 @@ class AdminOverAsyncAdmin implements Admin {
@Override
public void restoreSnapshot(String snapshotName, boolean takeFailSafeSnapshot, boolean restoreAcl)
throws IOException, RestoreSnapshotException {
throws IOException, RestoreSnapshotException {
get(admin.restoreSnapshot(snapshotName, takeFailSafeSnapshot, restoreAcl));
}
@Override
public Future<Void> cloneSnapshotAsync(String snapshotName, TableName tableName,
boolean restoreAcl) throws IOException, TableExistsException, RestoreSnapshotException {
return admin.cloneSnapshot(snapshotName, tableName, restoreAcl);
boolean restoreAcl, String customSFT)
throws IOException, TableExistsException, RestoreSnapshotException {
return admin.cloneSnapshot(snapshotName, tableName, restoreAcl, customSFT);
}
@Override

View File

@ -895,8 +895,20 @@ public interface AsyncAdmin {
* @param tableName name of the table where the snapshot will be restored
* @param restoreAcl <code>true</code> to restore acl of snapshot
*/
default CompletableFuture<Void> cloneSnapshot(String snapshotName, TableName tableName,
boolean restoreAcl) {
return cloneSnapshot(snapshotName, tableName, restoreAcl, null);
}
/**
* Create a new table by cloning the snapshot content.
* @param snapshotName name of the snapshot to be cloned
* @param tableName name of the table where the snapshot will be restored
* @param restoreAcl <code>true</code> to restore acl of snapshot
* @param customSFT specify the StroreFileTracker used for the table
*/
CompletableFuture<Void> cloneSnapshot(String snapshotName, TableName tableName,
boolean restoreAcl);
boolean restoreAcl, String customSFT);
/**
* List completed snapshots.

View File

@ -488,14 +488,14 @@ class AsyncHBaseAdmin implements AsyncAdmin {
@Override
public CompletableFuture<Void> restoreSnapshot(String snapshotName, boolean takeFailSafeSnapshot,
boolean restoreAcl) {
boolean restoreAcl) {
return wrap(rawAdmin.restoreSnapshot(snapshotName, takeFailSafeSnapshot, restoreAcl));
}
@Override
public CompletableFuture<Void> cloneSnapshot(String snapshotName, TableName tableName,
boolean restoreAcl) {
return wrap(rawAdmin.cloneSnapshot(snapshotName, tableName, restoreAcl));
boolean restoreAcl, String customSFT) {
return wrap(rawAdmin.cloneSnapshot(snapshotName, tableName, restoreAcl, customSFT));
}
@Override

View File

@ -195,6 +195,11 @@ public interface ColumnFamilyDescriptor {
* @return A clone value. Null if no mapping for the key
*/
Bytes getValue(Bytes key);
/**
* @param key The key.
* @return A clone value. Null if no mapping for the key
*/
String getValue(String key);
/**
* @param key The key.
* @return A clone value. Null if no mapping for the key

View File

@ -671,6 +671,12 @@ public class ColumnFamilyDescriptorBuilder {
return value == null ? null : value.get();
}
@Override
public String getValue(String key) {
Bytes rval = values.get(new Bytes(Bytes.toBytes(key)));
return rval == null ? null : Bytes.toString(rval.get(), rval.getOffset(), rval.getLength());
}
@Override
public Map<Bytes, Bytes> getValues() {
return Collections.unmodifiableMap(values);

View File

@ -1967,7 +1967,7 @@ class RawAsyncHBaseAdmin implements AsyncAdmin {
} else if (!exists) {
// if table does not exist, then just clone snapshot into new table.
completeConditionalOnFuture(future,
internalRestoreSnapshot(snapshotName, finalTableName, restoreAcl));
internalRestoreSnapshot(snapshotName, finalTableName, restoreAcl, null));
} else {
addListener(isTableDisabled(finalTableName), (disabled, err4) -> {
if (err4 != null) {
@ -2003,12 +2003,13 @@ class RawAsyncHBaseAdmin implements AsyncAdmin {
future.completeExceptionally(err);
} else {
// Step.2 Restore snapshot
addListener(internalRestoreSnapshot(snapshotName, tableName, restoreAcl),
addListener(internalRestoreSnapshot(snapshotName, tableName, restoreAcl, null),
(void2, err2) -> {
if (err2 != null) {
// Step.3.a Something went wrong during the restore and try to rollback.
addListener(
internalRestoreSnapshot(failSafeSnapshotSnapshotName, tableName, restoreAcl),
internalRestoreSnapshot(failSafeSnapshotSnapshotName, tableName, restoreAcl,
null),
(void3, err3) -> {
if (err3 != null) {
future.completeExceptionally(err3);
@ -2036,7 +2037,7 @@ class RawAsyncHBaseAdmin implements AsyncAdmin {
});
return future;
} else {
return internalRestoreSnapshot(snapshotName, tableName, restoreAcl);
return internalRestoreSnapshot(snapshotName, tableName, restoreAcl, null);
}
}
@ -2053,7 +2054,7 @@ class RawAsyncHBaseAdmin implements AsyncAdmin {
@Override
public CompletableFuture<Void> cloneSnapshot(String snapshotName, TableName tableName,
boolean restoreAcl) {
boolean restoreAcl, String customSFT) {
CompletableFuture<Void> future = new CompletableFuture<>();
addListener(tableExists(tableName), (exists, err) -> {
if (err != null) {
@ -2062,14 +2063,14 @@ class RawAsyncHBaseAdmin implements AsyncAdmin {
future.completeExceptionally(new TableExistsException(tableName));
} else {
completeConditionalOnFuture(future,
internalRestoreSnapshot(snapshotName, tableName, restoreAcl));
internalRestoreSnapshot(snapshotName, tableName, restoreAcl, customSFT));
}
});
return future;
}
private CompletableFuture<Void> internalRestoreSnapshot(String snapshotName, TableName tableName,
boolean restoreAcl) {
boolean restoreAcl, String customSFT) {
SnapshotProtos.SnapshotDescription snapshot = SnapshotProtos.SnapshotDescription.newBuilder()
.setName(snapshotName).setTable(tableName.getNameAsString()).build();
try {
@ -2077,10 +2078,15 @@ class RawAsyncHBaseAdmin implements AsyncAdmin {
} catch (IllegalArgumentException e) {
return failedFuture(e);
}
RestoreSnapshotRequest.Builder builder =
RestoreSnapshotRequest.newBuilder().setSnapshot(snapshot).setNonceGroup(ng.getNonceGroup())
.setNonce(ng.newNonce()).setRestoreACL(restoreAcl);
if(customSFT != null){
builder.setCustomSFT(customSFT);
}
return waitProcedureResult(this.<Long> newMasterCaller().action((controller, stub) -> this
.<RestoreSnapshotRequest, RestoreSnapshotResponse, Long> call(controller, stub,
RestoreSnapshotRequest.newBuilder().setSnapshot(snapshot).setNonceGroup(ng.getNonceGroup())
.setNonce(ng.newNonce()).setRestoreACL(restoreAcl).build(),
builder.build(),
(s, c, req, done) -> s.restoreSnapshot(c, req, done), (resp) -> resp.getProcId()))
.call());
}

View File

@ -462,6 +462,7 @@ message RestoreSnapshotRequest {
optional uint64 nonce_group = 2 [default = 0];
optional uint64 nonce = 3 [default = 0];
optional bool restoreACL = 4 [default = false];
optional string customSFT = 5;
}
message RestoreSnapshotResponse {

View File

@ -208,6 +208,7 @@ message CloneSnapshotStateData {
repeated RegionInfo region_info = 4;
repeated RestoreParentToChildRegionsPair parent_to_child_regions_pair_list = 5;
optional bool restore_acl = 6;
optional string customSFT = 7;
}
enum RestoreSnapshotState {

View File

@ -2619,8 +2619,8 @@ public class HMaster extends HBaseServerBase<MasterRpcServices> implements Maste
}
public long restoreSnapshot(final SnapshotDescription snapshotDesc,
final long nonceGroup, final long nonce, final boolean restoreAcl) throws IOException {
public long restoreSnapshot(final SnapshotDescription snapshotDesc, final long nonceGroup,
final long nonce, final boolean restoreAcl, final String customSFT) throws IOException {
checkInitialized();
getSnapshotManager().checkSnapshotSupport();
@ -2629,18 +2629,19 @@ public class HMaster extends HBaseServerBase<MasterRpcServices> implements Maste
getClusterSchema().getNamespace(dstTable.getNamespaceAsString());
return MasterProcedureUtil.submitProcedure(
new MasterProcedureUtil.NonceProcedureRunnable(this, nonceGroup, nonce) {
@Override
protected void run() throws IOException {
new MasterProcedureUtil.NonceProcedureRunnable(this, nonceGroup, nonce) {
@Override
protected void run() throws IOException {
setProcId(
getSnapshotManager().restoreOrCloneSnapshot(snapshotDesc, getNonceKey(), restoreAcl));
}
getSnapshotManager().restoreOrCloneSnapshot(snapshotDesc, getNonceKey(), restoreAcl,
customSFT));
}
@Override
protected String getDescription() {
return "RestoreSnapshotProcedure";
}
});
@Override
protected String getDescription() {
return "RestoreSnapshotProcedure";
}
});
}
private void checkTableExists(final TableName tableName)

View File

@ -1586,7 +1586,7 @@ public class MasterRpcServices extends HBaseRpcServicesBase<HMaster>
RestoreSnapshotRequest request) throws ServiceException {
try {
long procId = server.restoreSnapshot(request.getSnapshot(), request.getNonceGroup(),
request.getNonce(), request.getRestoreACL());
request.getNonce(), request.getRestoreACL(), request.getCustomSFT());
return RestoreSnapshotResponse.newBuilder().setProcId(procId).build();
} catch (ForeignException e) {
throw new ServiceException(e.getCause());

View File

@ -30,6 +30,8 @@ import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.TableExistsException;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
import org.apache.hadoop.hbase.client.RegionInfo;
import org.apache.hadoop.hbase.client.TableDescriptor;
import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
@ -44,6 +46,8 @@ import org.apache.hadoop.hbase.master.procedure.CreateTableProcedure.CreateHdfsR
import org.apache.hadoop.hbase.monitoring.MonitoredTask;
import org.apache.hadoop.hbase.monitoring.TaskMonitor;
import org.apache.hadoop.hbase.procedure2.ProcedureStateSerializer;
import org.apache.hadoop.hbase.procedure2.util.StringUtils;
import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory;
import org.apache.hadoop.hbase.snapshot.ClientSnapshotDescriptionUtils;
import org.apache.hadoop.hbase.snapshot.RestoreSnapshotException;
import org.apache.hadoop.hbase.snapshot.RestoreSnapshotHelper;
@ -72,6 +76,7 @@ public class CloneSnapshotProcedure
private TableDescriptor tableDescriptor;
private SnapshotDescription snapshot;
private boolean restoreAcl;
private String customSFT;
private List<RegionInfo> newRegions = null;
private Map<String, Pair<String, String> > parentsToChildrenPairMap = new HashMap<>();
@ -95,13 +100,20 @@ public class CloneSnapshotProcedure
* @param tableDescriptor the table to operate on
* @param snapshot snapshot to clone from
*/
public CloneSnapshotProcedure(final MasterProcedureEnv env,
final TableDescriptor tableDescriptor, final SnapshotDescription snapshot,
final boolean restoreAcl) {
this(env, tableDescriptor, snapshot, restoreAcl, null);
}
public CloneSnapshotProcedure(final MasterProcedureEnv env,
final TableDescriptor tableDescriptor, final SnapshotDescription snapshot,
final boolean restoreAcl) {
final boolean restoreAcl, final String customSFT) {
super(env);
this.tableDescriptor = tableDescriptor;
this.snapshot = snapshot;
this.restoreAcl = restoreAcl;
this.customSFT = customSFT;
getMonitorStatus();
}
@ -139,6 +151,7 @@ public class CloneSnapshotProcedure
setNextState(CloneSnapshotState.CLONE_SNAPSHOT_WRITE_FS_LAYOUT);
break;
case CLONE_SNAPSHOT_WRITE_FS_LAYOUT:
updateTableDescriptorWithSFT();
newRegions = createFilesystemLayout(env, tableDescriptor, newRegions);
env.getMasterServices().getTableDescriptors().update(tableDescriptor, true);
setNextState(CloneSnapshotState.CLONE_SNAPSHOT_ADD_TO_META);
@ -203,6 +216,37 @@ public class CloneSnapshotProcedure
return Flow.HAS_MORE_STATE;
}
/**
* If a StoreFileTracker is specified we strip the TableDescriptor from previous SFT config
* and set the specified SFT on the table level
*/
private void updateTableDescriptorWithSFT() {
if (StringUtils.isEmpty(customSFT)){
return;
}
TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(tableDescriptor);
builder.setValue(StoreFileTrackerFactory.TRACKER_IMPL, customSFT);
for (ColumnFamilyDescriptor family : tableDescriptor.getColumnFamilies()){
ColumnFamilyDescriptorBuilder cfBuilder = ColumnFamilyDescriptorBuilder.newBuilder(family);
cfBuilder.setConfiguration(StoreFileTrackerFactory.TRACKER_IMPL, null);
cfBuilder.setValue(StoreFileTrackerFactory.TRACKER_IMPL, null);
builder.modifyColumnFamily(cfBuilder.build());
}
tableDescriptor = builder.build();
}
private void validateSFT() {
if (StringUtils.isEmpty(customSFT)){
return;
}
//if customSFT is invalid getTrackerClass will throw a RuntimeException
Configuration sftConfig = new Configuration();
sftConfig.set(StoreFileTrackerFactory.TRACKER_IMPL, customSFT);
StoreFileTrackerFactory.getTrackerClass(sftConfig);
}
@Override
protected void rollbackState(final MasterProcedureEnv env, final CloneSnapshotState state)
throws IOException {
@ -292,6 +336,9 @@ public class CloneSnapshotProcedure
cloneSnapshotMsg.addParentToChildRegionsPairList(parentToChildrenPair);
}
}
if (!StringUtils.isEmpty(customSFT)){
cloneSnapshotMsg.setCustomSFT(customSFT);
}
serializer.serialize(cloneSnapshotMsg.build());
}
@ -327,6 +374,9 @@ public class CloneSnapshotProcedure
parentToChildrenPair.getChild2RegionName()));
}
}
if (!StringUtils.isEmpty(cloneSnapshotMsg.getCustomSFT())){
customSFT = cloneSnapshotMsg.getCustomSFT();
}
// Make sure that the monitor status is set up
getMonitorStatus();
}
@ -340,6 +390,8 @@ public class CloneSnapshotProcedure
if (env.getMasterServices().getTableDescriptors().exists(tableName)) {
throw new TableExistsException(tableName);
}
validateSFT();
}
/**

View File

@ -25,6 +25,7 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.DoNotRetryIOException;
@ -90,6 +91,7 @@ public class RestoreSnapshotProcedure
throws HBaseIOException {
this(env, tableDescriptor, snapshot, false);
}
/**
* Constructor
* @param env MasterProcedureEnv
@ -387,14 +389,15 @@ public class RestoreSnapshotProcedure
FileSystem fs = fileSystemManager.getFileSystem();
Path rootDir = fileSystemManager.getRootDir();
final ForeignExceptionDispatcher monitorException = new ForeignExceptionDispatcher();
final Configuration conf = new Configuration(env.getMasterConfiguration());
LOG.info("Starting restore snapshot=" + ClientSnapshotDescriptionUtils.toString(snapshot));
try {
Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshot, rootDir);
SnapshotManifest manifest = SnapshotManifest.open(
env.getMasterServices().getConfiguration(), fs, snapshotDir, snapshot);
conf, fs, snapshotDir, snapshot);
RestoreSnapshotHelper restoreHelper = new RestoreSnapshotHelper(
env.getMasterServices().getConfiguration(),
conf,
fs,
manifest,
modifiedTableDescriptor,

View File

@ -66,6 +66,7 @@ import org.apache.hadoop.hbase.procedure.ProcedureCoordinator;
import org.apache.hadoop.hbase.procedure.ProcedureCoordinatorRpcs;
import org.apache.hadoop.hbase.procedure.ZKProcedureCoordinator;
import org.apache.hadoop.hbase.procedure2.ProcedureExecutor;
import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory;
import org.apache.hadoop.hbase.security.AccessDeniedException;
import org.apache.hadoop.hbase.security.User;
import org.apache.hadoop.hbase.security.access.AccessChecker;
@ -751,8 +752,8 @@ public class SnapshotManager extends MasterProcedureManager implements Stoppable
* @throws IOException
*/
private long cloneSnapshot(final SnapshotDescription reqSnapshot, final TableName tableName,
final SnapshotDescription snapshot, final TableDescriptor snapshotTableDesc,
final NonceKey nonceKey, final boolean restoreAcl) throws IOException {
final SnapshotDescription snapshot, final TableDescriptor snapshotTableDesc,
final NonceKey nonceKey, final boolean restoreAcl, final String customSFT) throws IOException {
MasterCoprocessorHost cpHost = master.getMasterCoprocessorHost();
TableDescriptor htd = TableDescriptorBuilder.copy(tableName, snapshotTableDesc);
org.apache.hadoop.hbase.client.SnapshotDescription snapshotPOJO = null;
@ -762,7 +763,7 @@ public class SnapshotManager extends MasterProcedureManager implements Stoppable
}
long procId;
try {
procId = cloneSnapshot(snapshot, htd, nonceKey, restoreAcl);
procId = cloneSnapshot(snapshot, htd, nonceKey, restoreAcl, customSFT);
} catch (IOException e) {
LOG.error("Exception occurred while cloning the snapshot " + snapshot.getName()
+ " as table " + tableName.getNameAsString(), e);
@ -786,7 +787,8 @@ public class SnapshotManager extends MasterProcedureManager implements Stoppable
* @return procId the ID of the clone snapshot procedure
*/
synchronized long cloneSnapshot(final SnapshotDescription snapshot,
final TableDescriptor tableDescriptor, final NonceKey nonceKey, final boolean restoreAcl)
final TableDescriptor tableDescriptor, final NonceKey nonceKey, final boolean restoreAcl,
final String customSFT)
throws HBaseSnapshotException {
TableName tableName = tableDescriptor.getTableName();
@ -803,7 +805,7 @@ public class SnapshotManager extends MasterProcedureManager implements Stoppable
try {
long procId = master.getMasterProcedureExecutor().submitProcedure(
new CloneSnapshotProcedure(master.getMasterProcedureExecutor().getEnvironment(),
tableDescriptor, snapshot, restoreAcl),
tableDescriptor, snapshot, restoreAcl, customSFT),
nonceKey);
this.restoreTableToProcIdMap.put(tableName, procId);
return procId;
@ -822,7 +824,7 @@ public class SnapshotManager extends MasterProcedureManager implements Stoppable
* @throws IOException
*/
public long restoreOrCloneSnapshot(final SnapshotDescription reqSnapshot, final NonceKey nonceKey,
final boolean restoreAcl) throws IOException {
final boolean restoreAcl, String customSFT) throws IOException {
FileSystem fs = master.getMasterFileSystem().getFileSystem();
Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(reqSnapshot, rootDir);
@ -854,11 +856,12 @@ public class SnapshotManager extends MasterProcedureManager implements Stoppable
// Execute the restore/clone operation
long procId;
if (master.getTableDescriptors().exists(tableName)) {
procId = restoreSnapshot(reqSnapshot, tableName, snapshot, snapshotTableDesc, nonceKey,
restoreAcl);
procId =
restoreSnapshot(reqSnapshot, tableName, snapshot, snapshotTableDesc, nonceKey, restoreAcl);
} else {
procId =
cloneSnapshot(reqSnapshot, tableName, snapshot, snapshotTableDesc, nonceKey, restoreAcl);
cloneSnapshot(reqSnapshot, tableName, snapshot, snapshotTableDesc, nonceKey, restoreAcl,
customSFT);
}
return procId;
}
@ -880,6 +883,10 @@ public class SnapshotManager extends MasterProcedureManager implements Stoppable
final NonceKey nonceKey, final boolean restoreAcl) throws IOException {
MasterCoprocessorHost cpHost = master.getMasterCoprocessorHost();
//have to check first if restoring the snapshot would break current SFT setup
StoreFileTrackerFactory.validatePreRestoreSnapshot(master.getTableDescriptors().get(tableName),
snapshotTableDesc, master.getConfiguration());
if (master.getTableStateManager().isTableState(
TableName.valueOf(snapshot.getTable()), TableState.State.ENABLED)) {
throw new UnsupportedOperationException("Table '" +
@ -921,7 +928,7 @@ public class SnapshotManager extends MasterProcedureManager implements Stoppable
* @return procId the ID of the restore snapshot procedure
*/
private synchronized long restoreSnapshot(final SnapshotDescription snapshot,
final TableDescriptor tableDescriptor, final NonceKey nonceKey, final boolean restoreAcl)
final TableDescriptor tableDescriptor, final NonceKey nonceKey, final boolean restoreAcl)
throws HBaseSnapshotException {
final TableName tableName = tableDescriptor.getTableName();

View File

@ -29,6 +29,7 @@ import org.apache.hadoop.hbase.regionserver.HRegionFileSystem;
import org.apache.hadoop.hbase.regionserver.StoreContext;
import org.apache.hadoop.hbase.regionserver.StoreUtils;
import org.apache.hadoop.hbase.snapshot.RestoreSnapshotException;
import org.apache.hadoop.hbase.util.ReflectionUtils;
import org.apache.yetus.audience.InterfaceAudience;
import org.slf4j.Logger;
@ -92,7 +93,7 @@ public final class StoreFileTrackerFactory {
return name != null ? name.name() : clazz.getName();
}
private static Class<? extends StoreFileTracker> getTrackerClass(Configuration conf) {
public static Class<? extends StoreFileTracker> getTrackerClass(Configuration conf) {
try {
Trackers tracker = Trackers.valueOf(getStoreFileTrackerName(conf).toUpperCase());
return tracker.clazz;
@ -311,4 +312,40 @@ public final class StoreFileTrackerFactory {
}
}
}
/**
* Makes sure restoring a snapshot does not break the current SFT setup
* follows StoreUtils.createStoreConfiguration
* @param currentTableDesc Existing Table's TableDescriptor
* @param snapshotTableDesc Snapshot's TableDescriptor
* @param baseConf Current global configuration
* @throws RestoreSnapshotException if restore would break the current SFT setup
*/
public static void validatePreRestoreSnapshot(TableDescriptor currentTableDesc,
TableDescriptor snapshotTableDesc, Configuration baseConf) throws RestoreSnapshotException {
for (ColumnFamilyDescriptor cfDesc : currentTableDesc.getColumnFamilies()) {
ColumnFamilyDescriptor snapCFDesc = snapshotTableDesc.getColumnFamily(cfDesc.getName());
// if there is no counterpart in the snapshot it will be just deleted so the config does
// not matter
if (snapCFDesc != null) {
Configuration currentCompositeConf =
StoreUtils.createStoreConfiguration(baseConf, currentTableDesc, cfDesc);
Configuration snapCompositeConf =
StoreUtils.createStoreConfiguration(baseConf, snapshotTableDesc, snapCFDesc);
Class<? extends StoreFileTracker> currentSFT =
StoreFileTrackerFactory.getTrackerClass(currentCompositeConf);
Class<? extends StoreFileTracker> snapSFT =
StoreFileTrackerFactory.getTrackerClass(snapCompositeConf);
//restoration is not possible if there is an SFT mismatch
if (currentSFT != snapSFT) {
throw new RestoreSnapshotException(
"Restoring Snapshot is not possible because " + " the config for column family "
+ cfDesc.getNameAsString() + " has incompatible configuration. Current SFT: "
+ currentSFT + " SFT from snapshot: " + snapSFT);
}
}
}
}
}

View File

@ -57,6 +57,7 @@ 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;
@ -72,9 +73,7 @@ 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;
@ -200,8 +199,8 @@ public class RestoreSnapshotHelper {
List<RegionInfo> tableRegions = getTableRegions();
RegionInfo mobRegion = MobUtils.getMobRegionInfo(snapshotManifest.getTableDescriptor()
.getTableName());
RegionInfo mobRegion =
MobUtils.getMobRegionInfo(snapshotManifest.getTableDescriptor().getTableName());
if (tableRegions != null) {
// restore the mob region in case
if (regionNames.contains(mobRegion.getEncodedName())) {
@ -707,7 +706,9 @@ public class RestoreSnapshotHelper {
HRegionFileSystem.openRegionFromFileSystem(conf, fs, tableDir, newRegionInfo, false) :
HRegionFileSystem.createRegionOnFileSystem(conf, fs, tableDir, newRegionInfo);
StoreFileTracker tracker = StoreFileTrackerFactory.create(conf, true,
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);

View File

@ -0,0 +1,71 @@
/**
* 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.client;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import java.io.IOException;
import org.apache.hadoop.hbase.HBaseClassTestRule;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory;
import org.apache.hadoop.hbase.testclassification.ClientTests;
import org.apache.hadoop.hbase.testclassification.LargeTests;
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
@Category({ LargeTests.class, ClientTests.class })
public class TestCloneSnapshotFromClientCustomSFT extends CloneSnapshotFromClientTestBase{
@ClassRule
public static final HBaseClassTestRule CLASS_RULE =
HBaseClassTestRule.forClass(TestCloneSnapshotFromClientCustomSFT.class);
public static final String CLONE_SFT = "FILE";
@Test
public void testCloneSnapshotWithCustomSFT() throws IOException, InterruptedException {
TableName clonedTableName =
TableName.valueOf(getValidMethodName() + "-" + EnvironmentEdgeManager.currentTime());
admin.cloneSnapshot(snapshotName1, clonedTableName, false, CLONE_SFT);
verifyRowCount(TEST_UTIL, clonedTableName, snapshot1Rows);
TableDescriptor td = admin.getDescriptor(clonedTableName);
assertEquals(CLONE_SFT, td.getValue(StoreFileTrackerFactory.TRACKER_IMPL));
TEST_UTIL.deleteTable(clonedTableName);
}
@Test
public void testCloneSnapshotWithIncorrectCustomSFT() throws IOException, InterruptedException {
TableName clonedTableName =
TableName.valueOf(getValidMethodName() + "-" + EnvironmentEdgeManager.currentTime());
IOException ioException = assertThrows(IOException.class, () -> {
admin.cloneSnapshot(snapshotName1, clonedTableName, false, "IncorrectSFT");
});
assertEquals(
"java.lang.RuntimeException: java.lang.RuntimeException: " +
"java.lang.ClassNotFoundException: Class IncorrectSFT not found",
ioException.getMessage());
}
}

View File

@ -20,7 +20,6 @@ package org.apache.hadoop.hbase.regionserver.storefiletracker;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -28,7 +27,6 @@ import java.util.Map;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.regionserver.StoreContext;
import org.apache.hadoop.hbase.regionserver.StoreFileInfo;
import org.apache.hbase.thirdparty.org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@ -22,9 +22,16 @@ import static org.junit.Assert.assertThrows;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseClassTestRule;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
import org.apache.hadoop.hbase.client.TableDescriptor;
import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
import org.apache.hadoop.hbase.regionserver.StoreContext;
import org.apache.hadoop.hbase.snapshot.RestoreSnapshotException;
import org.apache.hadoop.hbase.testclassification.RegionServerTests;
import org.apache.hadoop.hbase.testclassification.SmallTests;
import org.apache.hadoop.hbase.util.Bytes;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
@ -55,4 +62,49 @@ public class TestStoreFileTrackerFactory {
assertThrows(IllegalArgumentException.class, () -> StoreFileTrackerFactory
.createForMigration(conf, configName, false, StoreContext.getBuilder().build()));
}
@Test
public void testCheckSFTCompatibility() throws Exception {
//checking default value change on different configuration levels
Configuration conf = new Configuration();
conf.set(StoreFileTrackerFactory.TRACKER_IMPL, "DEFAULT");
//creating a TD with only TableDescriptor level config
TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(TableName.valueOf("TableX"));
builder.setValue(StoreFileTrackerFactory.TRACKER_IMPL, "FILE");
ColumnFamilyDescriptor cf = ColumnFamilyDescriptorBuilder.of("cf");
builder.setColumnFamily(cf);
TableDescriptor td = builder.build();
//creating a TD with matching ColumnFamilyDescriptor level setting
TableDescriptorBuilder snapBuilder =
TableDescriptorBuilder.newBuilder(TableName.valueOf("TableY"));
snapBuilder.setValue(StoreFileTrackerFactory.TRACKER_IMPL, "FILE");
ColumnFamilyDescriptorBuilder snapCFBuilder =
ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("cf"));
snapCFBuilder.setValue(StoreFileTrackerFactory.TRACKER_IMPL, "FILE");
snapBuilder.setColumnFamily(snapCFBuilder.build());
TableDescriptor snapTd = snapBuilder.build();
// adding a cf config that matches the td config is fine even when it does not match the default
StoreFileTrackerFactory.validatePreRestoreSnapshot(td, snapTd, conf);
// removing cf level config is fine when it matches the td config
StoreFileTrackerFactory.validatePreRestoreSnapshot(snapTd, td, conf);
TableDescriptorBuilder defaultBuilder =
TableDescriptorBuilder.newBuilder(TableName.valueOf("TableY"));
defaultBuilder.setValue(StoreFileTrackerFactory.TRACKER_IMPL, "FILE");
ColumnFamilyDescriptorBuilder defaultCFBuilder =
ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("cf"));
defaultCFBuilder.setValue(StoreFileTrackerFactory.TRACKER_IMPL, "DEFAULT");
defaultBuilder.setColumnFamily(defaultCFBuilder.build());
TableDescriptor defaultTd = defaultBuilder.build();
assertThrows(RestoreSnapshotException.class, () -> {
StoreFileTrackerFactory.validatePreRestoreSnapshot(td, defaultTd, conf);
});
assertThrows(RestoreSnapshotException.class, () -> {
StoreFileTrackerFactory.validatePreRestoreSnapshot(snapTd, defaultTd, conf);
});
}
}

View File

@ -546,8 +546,9 @@ public class VerifyingRSGroupAdmin implements Admin, Closeable {
}
public Future<Void> cloneSnapshotAsync(String snapshotName, TableName tableName,
boolean restoreAcl) throws IOException, TableExistsException, RestoreSnapshotException {
return admin.cloneSnapshotAsync(snapshotName, tableName, restoreAcl);
boolean restoreAcl, String customSFT)
throws IOException, TableExistsException, RestoreSnapshotException {
return admin.cloneSnapshotAsync(snapshotName, tableName, restoreAcl, customSFT);
}
public void execProcedure(String signature, String instance, Map<String, String> props)

View File

@ -1259,8 +1259,8 @@ module Hbase
#----------------------------------------------------------------------------------------------
# Create a new table by cloning the snapshot content
def clone_snapshot(snapshot_name, table, restore_acl = false)
@admin.cloneSnapshot(snapshot_name, TableName.valueOf(table), restore_acl)
def clone_snapshot(snapshot_name, table, restore_acl = false, clone_sft = nil)
@admin.cloneSnapshot(snapshot_name, TableName.valueOf(table), restore_acl, clone_sft)
end
#----------------------------------------------------------------------------------------------

View File

@ -40,6 +40,7 @@ module HBaseConstants
CACHE = 'CACHE'.freeze
CACHE_BLOCKS = 'CACHE_BLOCKS'.freeze
CLASSNAME = 'CLASSNAME'.freeze
CLONE_SFT = 'CLONE_SFT'.freeze
CLUSTER_KEY = 'CLUSTER_KEY'.freeze
COLUMN = 'COLUMN'.freeze
COLUMNS = 'COLUMNS'.freeze

View File

@ -33,13 +33,17 @@ Following command will restore all acl from origin snapshot table into the
newly created table.
hbase> clone_snapshot 'snapshotName', 'namespace:tableName', {RESTORE_ACL=>true}
StoreFileTracker implementation used after restore can be set by the following command.
hbase> clone_snapshot 'snapshotName', 'namespace:tableName', {CLONE_SFT=>'FILE'}
EOF
end
def command(snapshot_name, table, args = {})
raise(ArgumentError, 'Arguments should be a Hash') unless args.is_a?(Hash)
restore_acl = args.delete(::HBaseConstants::RESTORE_ACL) || false
admin.clone_snapshot(snapshot_name, table, restore_acl)
clone_sft = args.delete(::HBaseConstants::CLONE_SFT) || nil
admin.clone_snapshot(snapshot_name, table, restore_acl, clone_sft)
end
def handle_exceptions(cause, *args)

View File

@ -869,8 +869,8 @@ public class ThriftAdmin implements Admin {
}
@Override
public Future<Void> cloneSnapshotAsync(String snapshotName, TableName tableName, boolean cloneAcl)
throws IOException, TableExistsException, RestoreSnapshotException {
public Future<Void> cloneSnapshotAsync(String snapshotName, TableName tableName, boolean cloneAcl,
String customSFT) throws IOException, TableExistsException, RestoreSnapshotException {
throw new NotImplementedException("cloneSnapshotAsync not supported in ThriftAdmin");
}