diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java index e16c21eb2c33..a345474abce9 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java @@ -1547,6 +1547,20 @@ default Future modifyTableAsync(TableName tableName, TableDescriptor td) return modifyTableAsync(td); } + /** + * Truncate an individual region. + * @param regionName region to truncate + * @throws IOException if a remote or network exception occurs + */ + void truncateRegion(byte[] regionName) throws IOException; + + /** + * Truncate an individual region. Asynchronous operation. + * @param regionName region to truncate + * @throws IOException if a remote or network exception occurs + */ + Future truncateRegionAsync(byte[] regionName) throws IOException; + /** * Modify an existing table, more IRB (ruby) friendly version. Asynchronous operation. This means * that it may be a while before your schema change is updated across all of the table. You can diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java index 361e967b8bcb..bb6509936255 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java @@ -617,6 +617,12 @@ default CompletableFuture mergeRegions(byte[] nameOfRegionA, byte[] nameOf */ CompletableFuture splitRegion(byte[] regionName, byte[] splitPoint); + /** + * Truncate an individual region. + * @param regionName region to truncate + */ + CompletableFuture truncateRegion(byte[] regionName); + /** * Assign an individual region. * @param regionName Encoded or full name of region to assign. diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java index bf5e2905c3b0..d92fa86d3005 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java @@ -381,6 +381,11 @@ public CompletableFuture splitRegion(byte[] regionName, byte[] splitPoint) return wrap(rawAdmin.splitRegion(regionName, splitPoint)); } + @Override + public CompletableFuture truncateRegion(byte[] regionName) { + return wrap(rawAdmin.truncateRegion(regionName)); + } + @Override public CompletableFuture assign(byte[] regionName) { return wrap(rawAdmin.assign(regionName)); diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java index e2187f8b4e8a..71556665e6f2 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java @@ -1554,6 +1554,60 @@ private CompletableFuture split(final RegionInfo hri, byte[] splitPoint) { return future; } + @Override + public CompletableFuture truncateRegion(byte[] regionName) { + CompletableFuture future = new CompletableFuture<>(); + addListener(getRegionLocation(regionName), (location, err) -> { + if (err != null) { + future.completeExceptionally(err); + return; + } + RegionInfo regionInfo = location.getRegion(); + if (regionInfo.getReplicaId() != RegionInfo.DEFAULT_REPLICA_ID) { + future.completeExceptionally(new IllegalArgumentException( + "Can't truncate replicas directly.Replicas are auto-truncated " + + "when their primary is truncated.")); + return; + } + ServerName serverName = location.getServerName(); + if (serverName == null) { + future + .completeExceptionally(new NoServerForRegionException(Bytes.toStringBinary(regionName))); + return; + } + addListener(truncateRegion(regionInfo), (ret, err2) -> { + if (err2 != null) { + future.completeExceptionally(err2); + } else { + future.complete(ret); + } + }); + }); + return future; + } + + private CompletableFuture truncateRegion(final RegionInfo hri) { + CompletableFuture future = new CompletableFuture<>(); + TableName tableName = hri.getTable(); + final MasterProtos.TruncateRegionRequest request; + try { + request = RequestConverter.buildTruncateRegionRequest(hri, ng.getNonceGroup(), ng.newNonce()); + } catch (DeserializationException e) { + future.completeExceptionally(e); + return future; + } + addListener(this.procedureCall(tableName, request, MasterService.Interface::truncateRegion, + MasterProtos.TruncateRegionResponse::getProcId, + new TruncateRegionProcedureBiConsumer(tableName)), (ret, err2) -> { + if (err2 != null) { + future.completeExceptionally(err2); + } else { + future.complete(ret); + } + }); + return future; + } + @Override public CompletableFuture assign(byte[] regionName) { CompletableFuture future = new CompletableFuture<>(); @@ -2789,6 +2843,18 @@ String getOperationType() { } } + private static class TruncateRegionProcedureBiConsumer extends TableProcedureBiConsumer { + + TruncateRegionProcedureBiConsumer(TableName tableName) { + super(tableName); + } + + @Override + String getOperationType() { + return "TRUNCATE_REGION"; + } + } + private static class SnapshotProcedureBiConsumer extends TableProcedureBiConsumer { SnapshotProcedureBiConsumer(TableName tableName) { super(tableName); diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/RequestConverter.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/RequestConverter.java index 9bd29f37ff96..33a97c5f8ee3 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/RequestConverter.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/RequestConverter.java @@ -1186,6 +1186,17 @@ public static SplitTableRegionRequest buildSplitTableRegionRequest(final RegionI return builder.build(); } + public static MasterProtos.TruncateRegionRequest + buildTruncateRegionRequest(final RegionInfo regionInfo, final long nonceGroup, final long nonce) + throws DeserializationException { + MasterProtos.TruncateRegionRequest.Builder builder = + MasterProtos.TruncateRegionRequest.newBuilder(); + builder.setRegionInfo(ProtobufUtil.toRegionInfo(regionInfo)); + builder.setNonceGroup(nonceGroup); + builder.setNonce(nonce); + return builder.build(); + } + /** * Create a protocol buffer AssignRegionRequest * @return an AssignRegionRequest diff --git a/hbase-protocol-shaded/src/main/protobuf/Master.proto b/hbase-protocol-shaded/src/main/protobuf/Master.proto index 5e5a44621734..2bcd0618060a 100644 --- a/hbase-protocol-shaded/src/main/protobuf/Master.proto +++ b/hbase-protocol-shaded/src/main/protobuf/Master.proto @@ -136,6 +136,16 @@ message SplitTableRegionResponse { optional uint64 proc_id = 1; } +message TruncateRegionRequest { + required RegionInfo region_info = 1; + optional uint64 nonce_group = 2 [default = 0]; + optional uint64 nonce = 3 [default = 0]; +} + +message TruncateRegionResponse { + optional uint64 proc_id = 1; +} + message CreateTableRequest { required TableSchema table_schema = 1; repeated bytes split_keys = 2; @@ -852,6 +862,12 @@ service MasterService { rpc SplitRegion(SplitTableRegionRequest) returns(SplitTableRegionResponse); + /** + * Truncate region + */ + rpc TruncateRegion(TruncateRegionRequest) + returns(TruncateRegionResponse); + /** Deletes a table */ rpc DeleteTable(DeleteTableRequest) returns(DeleteTableResponse); diff --git a/hbase-protocol-shaded/src/main/protobuf/MasterProcedure.proto b/hbase-protocol-shaded/src/main/protobuf/MasterProcedure.proto index da1ed202693a..fa901642eb60 100644 --- a/hbase-protocol-shaded/src/main/protobuf/MasterProcedure.proto +++ b/hbase-protocol-shaded/src/main/protobuf/MasterProcedure.proto @@ -102,6 +102,14 @@ message TruncateTableStateData { repeated RegionInfo region_info = 5; } +enum TruncateRegionState { + TRUNCATE_REGION_PRE_OPERATION = 1; + TRUNCATE_REGION_MAKE_OFFLINE = 2; + TRUNCATE_REGION_REMOVE = 3; + TRUNCATE_REGION_MAKE_ONLINE = 4; + TRUNCATE_REGION_POST_OPERATION = 5; +} + enum DeleteTableState { DELETE_TABLE_PRE_OPERATION = 1; DELETE_TABLE_REMOVE_FROM_META = 2; diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/MasterObserver.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/MasterObserver.java index a49f57648433..5de99adf8855 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/MasterObserver.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/MasterObserver.java @@ -665,6 +665,46 @@ default void preSplitRegionAction(final ObserverContext c, + final RegionInfo regionInfo) { + } + + /** + * Called before the truncate region procedure is called. + * @param c The environment to interact with the framework and master + * @param regionInfo The Region being truncated + */ + @SuppressWarnings("unused") + default void preTruncateRegion(final ObserverContext c, + RegionInfo regionInfo) { + } + + /** + * Called after the truncate region procedure is called. + * @param c The environment to interact with the framework and master + * @param regionInfo The Region being truncated + */ + @SuppressWarnings("unused") + default void postTruncateRegion(final ObserverContext c, + RegionInfo regionInfo) { + } + + /** + * Called post the region is truncated. + * @param c The environment to interact with the framework and master + * @param regionInfo The Region To be truncated + */ + @SuppressWarnings("unused") + default void postTruncateRegionAction(final ObserverContext c, + final RegionInfo regionInfo) { + } + /** * Called after the region is split. * @param c the environment to interact with the framework and master diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java index 5a9d9fc1aef1..c582374e1460 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java @@ -160,6 +160,7 @@ import org.apache.hadoop.hbase.master.procedure.ProcedureSyncWait; import org.apache.hadoop.hbase.master.procedure.ReopenTableRegionsProcedure; import org.apache.hadoop.hbase.master.procedure.ServerCrashProcedure; +import org.apache.hadoop.hbase.master.procedure.TruncateRegionProcedure; import org.apache.hadoop.hbase.master.procedure.TruncateTableProcedure; import org.apache.hadoop.hbase.master.region.MasterRegion; import org.apache.hadoop.hbase.master.region.MasterRegionFactory; @@ -2482,6 +2483,36 @@ protected String getDescription() { }); } + @Override + public long truncateRegion(final RegionInfo regionInfo, final long nonceGroup, final long nonce) + throws IOException { + checkInitialized(); + + return MasterProcedureUtil + .submitProcedure(new MasterProcedureUtil.NonceProcedureRunnable(this, nonceGroup, nonce) { + @Override + protected void run() throws IOException { + getMaster().getMasterCoprocessorHost().preTruncateRegion(regionInfo); + + LOG.info( + getClientIdAuditPrefix() + " truncate region " + regionInfo.getRegionNameAsString()); + + // Execute the operation asynchronously + ProcedurePrepareLatch latch = ProcedurePrepareLatch.createLatch(2, 0); + submitProcedure( + new TruncateRegionProcedure(procedureExecutor.getEnvironment(), regionInfo, latch)); + latch.await(); + + getMaster().getMasterCoprocessorHost().postTruncateRegion(regionInfo); + } + + @Override + protected String getDescription() { + return "TruncateRegionProcedure"; + } + }); + } + @Override public long addColumn(final TableName tableName, final ColumnFamilyDescriptor column, final long nonceGroup, final long nonce) throws IOException { diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterCoprocessorHost.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterCoprocessorHost.java index aa20c2afea22..95c183b4a396 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterCoprocessorHost.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterCoprocessorHost.java @@ -862,6 +862,60 @@ public void call(MasterObserver observer) throws IOException { }); } + /** + * Invoked just before calling the truncate region procedure + * @param regionInfo region being truncated + */ + public void preTruncateRegion(RegionInfo regionInfo) throws IOException { + execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() { + @Override + public void call(MasterObserver observer) { + observer.preTruncateRegion(this, regionInfo); + } + }); + } + + /** + * Invoked after calling the truncate region procedure + * @param regionInfo region being truncated + */ + public void postTruncateRegion(RegionInfo regionInfo) throws IOException { + execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() { + @Override + public void call(MasterObserver observer) { + observer.postTruncateRegion(this, regionInfo); + } + }); + } + + /** + * Invoked just before calling the truncate region procedure + * @param region Region to be truncated + * @param user The user + */ + public void preTruncateRegionAction(final RegionInfo region, User user) throws IOException { + execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation(user) { + @Override + public void call(MasterObserver observer) throws IOException { + observer.preTruncateRegionAction(this, region); + } + }); + } + + /** + * Invoked after calling the truncate region procedure + * @param region Region which was truncated + * @param user The user + */ + public void postTruncateRegionAction(final RegionInfo region, User user) throws IOException { + execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation(user) { + @Override + public void call(MasterObserver observer) throws IOException { + observer.postTruncateRegionAction(this, region); + } + }); + } + /** * This will be called before update META step as part of split table region procedure. * @param user the user diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java index e6244aae026e..593f028d17d7 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java @@ -876,6 +876,18 @@ public SplitTableRegionResponse splitRegion(final RpcController controller, } } + @Override + public MasterProtos.TruncateRegionResponse truncateRegion(RpcController controller, + final MasterProtos.TruncateRegionRequest request) throws ServiceException { + try { + long procId = server.truncateRegion(ProtobufUtil.toRegionInfo(request.getRegionInfo()), + request.getNonceGroup(), request.getNonce()); + return MasterProtos.TruncateRegionResponse.newBuilder().setProcId(procId).build(); + } catch (IOException ie) { + throw new ServiceException(ie); + } + } + @Override public ClientProtos.CoprocessorServiceResponse execMasterService(final RpcController controller, final ClientProtos.CoprocessorServiceRequest request) throws ServiceException { diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterServices.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterServices.java index 43040472006f..2b42adeeeded 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterServices.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterServices.java @@ -433,4 +433,13 @@ boolean normalizeRegions(final NormalizeTableFilterParams ntfp, final boolean is * Flush master local region */ void flushMasterStore() throws IOException; + + /** + * Truncate region + * @param regionInfo region to be truncated + * @param nonceGroup the nonce group + * @param nonce the nonce + * @return procedure Id + */ + long truncateRegion(RegionInfo regionInfo, long nonceGroup, long nonce) throws IOException; } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/AssignmentManager.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/AssignmentManager.java index 6257edf04f77..8e5365c59021 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/AssignmentManager.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/AssignmentManager.java @@ -69,6 +69,7 @@ import org.apache.hadoop.hbase.master.procedure.MasterProcedureScheduler; import org.apache.hadoop.hbase.master.procedure.ProcedureSyncWait; import org.apache.hadoop.hbase.master.procedure.ServerCrashProcedure; +import org.apache.hadoop.hbase.master.procedure.TruncateRegionProcedure; import org.apache.hadoop.hbase.master.region.MasterRegion; import org.apache.hadoop.hbase.procedure2.Procedure; import org.apache.hadoop.hbase.procedure2.ProcedureEvent; @@ -1030,6 +1031,11 @@ public SplitTableRegionProcedure createSplitProcedure(final RegionInfo regionToS return new SplitTableRegionProcedure(getProcedureEnvironment(), regionToSplit, splitKey); } + public TruncateRegionProcedure createTruncateRegionProcedure(final RegionInfo regionToTruncate) + throws IOException { + return new TruncateRegionProcedure(getProcedureEnvironment(), regionToTruncate); + } + public MergeTableRegionsProcedure createMergeProcedure(RegionInfo... ris) throws IOException { return new MergeTableRegionsProcedure(getProcedureEnvironment(), ris, false); } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/AbstractStateMachineRegionProcedure.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/AbstractStateMachineRegionProcedure.java index fe98a78b4d74..cf886e9824b1 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/AbstractStateMachineRegionProcedure.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/AbstractStateMachineRegionProcedure.java @@ -41,6 +41,12 @@ protected AbstractStateMachineRegionProcedure(MasterProcedureEnv env, RegionInfo this.hri = hri; } + protected AbstractStateMachineRegionProcedure(MasterProcedureEnv env, RegionInfo hri, + ProcedurePrepareLatch latch) { + super(env, latch); + this.hri = hri; + } + protected AbstractStateMachineRegionProcedure() { // Required by the Procedure framework to create the procedure on replay super(); diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/TableProcedureInterface.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/TableProcedureInterface.java index 4b3ae5ae1387..b8dbfdc870d1 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/TableProcedureInterface.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/TableProcedureInterface.java @@ -41,7 +41,8 @@ public enum TableOperationType { REGION_ASSIGN, REGION_UNASSIGN, REGION_GC, - MERGED_REGIONS_GC/* region operations */ + MERGED_REGIONS_GC/* region operations */, + REGION_TRUNCATE }; /** Returns the name of the table the procedure is operating on */ diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/TableQueue.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/TableQueue.java index 0f580969c4f9..40db4544fad8 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/TableQueue.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/TableQueue.java @@ -68,6 +68,7 @@ private static boolean requireTableExclusiveLock(TableProcedureInterface proc) { case REGION_GC: case MERGED_REGIONS_GC: case REGION_SNAPSHOT: + case REGION_TRUNCATE: return false; default: break; diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/TruncateRegionProcedure.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/TruncateRegionProcedure.java new file mode 100644 index 000000000000..5e907c1681ac --- /dev/null +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/TruncateRegionProcedure.java @@ -0,0 +1,219 @@ +/* + * 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.master.procedure; + +import java.io.IOException; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.hbase.HBaseIOException; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.client.RegionInfo; +import org.apache.hadoop.hbase.master.MasterCoprocessorHost; +import org.apache.hadoop.hbase.master.MasterFileSystem; +import org.apache.hadoop.hbase.master.assignment.RegionStateNode; +import org.apache.hadoop.hbase.master.assignment.TransitRegionStateProcedure; +import org.apache.hadoop.hbase.regionserver.HRegionFileSystem; +import org.apache.hadoop.hbase.util.CommonFSUtils; +import org.apache.yetus.audience.InterfaceAudience; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.TruncateRegionState; + +@InterfaceAudience.Private +public class TruncateRegionProcedure + extends AbstractStateMachineRegionProcedure { + private static final Logger LOG = LoggerFactory.getLogger(TruncateRegionProcedure.class); + + @SuppressWarnings("unused") + public TruncateRegionProcedure() { + // Required by the Procedure framework to create the procedure on replay + super(); + } + + public TruncateRegionProcedure(final MasterProcedureEnv env, final RegionInfo hri) + throws HBaseIOException { + super(env, hri); + checkOnline(env, getRegion()); + } + + public TruncateRegionProcedure(final MasterProcedureEnv env, final RegionInfo region, + ProcedurePrepareLatch latch) throws HBaseIOException { + super(env, region, latch); + preflightChecks(env, true); + } + + @Override + protected Flow executeFromState(final MasterProcedureEnv env, TruncateRegionState state) + throws InterruptedException { + if (LOG.isTraceEnabled()) { + LOG.trace(this + " execute state=" + state); + } + try { + switch (state) { + case TRUNCATE_REGION_PRE_OPERATION: + if (!prepareTruncate()) { + assert isFailed() : "the truncate should have an exception here"; + return Flow.NO_MORE_STATE; + } + checkOnline(env, getRegion()); + assert getRegion().getReplicaId() == RegionInfo.DEFAULT_REPLICA_ID || isFailed() + : "Can't truncate replicas directly. " + + "Replicas are auto-truncated when their primary is truncated."; + preTruncate(env); + setNextState(TruncateRegionState.TRUNCATE_REGION_MAKE_OFFLINE); + break; + case TRUNCATE_REGION_MAKE_OFFLINE: + addChildProcedure(createUnAssignProcedures(env)); + setNextState(TruncateRegionState.TRUNCATE_REGION_REMOVE); + break; + case TRUNCATE_REGION_REMOVE: + deleteRegionFromFileSystem(env); + setNextState(TruncateRegionState.TRUNCATE_REGION_MAKE_ONLINE); + break; + case TRUNCATE_REGION_MAKE_ONLINE: + addChildProcedure(createAssignProcedures(env)); + setNextState(TruncateRegionState.TRUNCATE_REGION_POST_OPERATION); + break; + case TRUNCATE_REGION_POST_OPERATION: + postTruncate(env); + LOG.debug("truncate '" + getTableName() + "' completed"); + return Flow.NO_MORE_STATE; + default: + throw new UnsupportedOperationException("unhandled state=" + state); + } + } catch (IOException e) { + if (isRollbackSupported(state)) { + setFailure("master-truncate-region", e); + } else { + LOG.warn("Retriable error trying to truncate region=" + getRegion().getRegionNameAsString() + + " state=" + state, e); + } + } + return Flow.HAS_MORE_STATE; + } + + private void deleteRegionFromFileSystem(final MasterProcedureEnv env) throws IOException { + RegionStateNode regionNode = + env.getAssignmentManager().getRegionStates().getRegionStateNode(getRegion()); + try { + regionNode.lock(); + final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem(); + final Path tableDir = CommonFSUtils.getTableDir(mfs.getRootDir(), getTableName()); + HRegionFileSystem.deleteRegionFromFileSystem(env.getMasterConfiguration(), + mfs.getFileSystem(), tableDir, getRegion()); + } finally { + regionNode.unlock(); + } + } + + @Override + protected void rollbackState(final MasterProcedureEnv env, final TruncateRegionState state) + throws IOException { + if (state == TruncateRegionState.TRUNCATE_REGION_PRE_OPERATION) { + // Nothing to rollback, pre-truncate is just table-state checks. + return; + } + if (state == TruncateRegionState.TRUNCATE_REGION_MAKE_OFFLINE) { + RegionStateNode regionNode = + env.getAssignmentManager().getRegionStates().getRegionStateNode(getRegion()); + if (regionNode == null) { + // Region was unassigned by state TRUNCATE_REGION_MAKE_OFFLINE. + // So Assign it back + addChildProcedure(createAssignProcedures(env)); + } + return; + } + // The truncate doesn't have a rollback. The execution will succeed, at some point. + throw new UnsupportedOperationException("unhandled state=" + state); + } + + @Override + protected void completionCleanup(final MasterProcedureEnv env) { + releaseSyncLatch(); + } + + @Override + protected boolean isRollbackSupported(final TruncateRegionState state) { + switch (state) { + case TRUNCATE_REGION_PRE_OPERATION: + return true; + case TRUNCATE_REGION_MAKE_OFFLINE: + return true; + default: + return false; + } + } + + @Override + protected TruncateRegionState getState(final int stateId) { + return TruncateRegionState.forNumber(stateId); + } + + @Override + protected int getStateId(final TruncateRegionState state) { + return state.getNumber(); + } + + @Override + protected TruncateRegionState getInitialState() { + return TruncateRegionState.TRUNCATE_REGION_PRE_OPERATION; + } + + @Override + public void toStringClassDetails(StringBuilder sb) { + sb.append(getClass().getSimpleName()); + sb.append(" (region="); + sb.append(getRegion().getRegionNameAsString()); + sb.append(")"); + } + + private boolean prepareTruncate() throws IOException { + if (getTableName().equals(TableName.META_TABLE_NAME)) { + throw new IOException("Can't truncate region in catalog tables"); + } + return true; + } + + private void preTruncate(final MasterProcedureEnv env) throws IOException { + final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); + if (cpHost != null) { + cpHost.preTruncateRegionAction(getRegion(), getUser()); + } + } + + private void postTruncate(final MasterProcedureEnv env) throws IOException { + final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); + if (cpHost != null) { + cpHost.postTruncateRegionAction(getRegion(), getUser()); + } + } + + @Override + public TableOperationType getTableOperationType() { + return TableOperationType.REGION_TRUNCATE; + } + + private TransitRegionStateProcedure createUnAssignProcedures(MasterProcedureEnv env) + throws IOException { + return env.getAssignmentManager().createOneUnassignProcedure(getRegion(), true); + } + + private TransitRegionStateProcedure createAssignProcedures(MasterProcedureEnv env) { + return env.getAssignmentManager().createOneAssignProcedure(getRegion(), true); + } +} diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncRegionAdminApi2.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncRegionAdminApi2.java index 44106fb4ccf0..611b7de919ce 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncRegionAdminApi2.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncRegionAdminApi2.java @@ -22,10 +22,12 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -37,6 +39,7 @@ import org.apache.hadoop.hbase.HRegionLocation; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.master.HMaster; +import org.apache.hadoop.hbase.master.assignment.AssignmentTestingUtil; import org.apache.hadoop.hbase.master.janitor.CatalogJanitor; import org.apache.hadoop.hbase.testclassification.ClientTests; import org.apache.hadoop.hbase.testclassification.LargeTests; @@ -283,4 +286,85 @@ private void splitTest(TableName tableName, int rowCount, boolean isSplitRegion, } assertEquals(2, count); } + + @Test + public void testTruncateRegion() throws Exception { + // Arrange - Create table, insert data, identify region to truncate. + final byte[][] splitKeys = + new byte[][] { Bytes.toBytes("30"), Bytes.toBytes("60"), Bytes.toBytes("90") }; + String family1 = "f1"; + String family2 = "f2"; + + final String[] sFamilies = new String[] { family1, family2 }; + final byte[][] bFamilies = new byte[][] { Bytes.toBytes(family1), Bytes.toBytes(family2) }; + createTableWithDefaultConf(tableName, splitKeys, bFamilies); + + AsyncTable metaTable = ASYNC_CONN.getTable(META_TABLE_NAME); + List regionLocations = + ClientMetaTableAccessor.getTableHRegionLocations(metaTable, tableName).get(); + RegionInfo regionToBeTruncated = regionLocations.get(0).getRegion(); + + assertEquals(4, regionLocations.size()); + + AssignmentTestingUtil.insertData(TEST_UTIL, tableName, 2, 21, sFamilies); + AssignmentTestingUtil.insertData(TEST_UTIL, tableName, 2, 31, sFamilies); + AssignmentTestingUtil.insertData(TEST_UTIL, tableName, 2, 61, sFamilies); + AssignmentTestingUtil.insertData(TEST_UTIL, tableName, 2, 91, sFamilies); + int rowCountBeforeTruncate = TEST_UTIL.countRows(tableName); + + // Act - Truncate the first region + admin.truncateRegion(regionToBeTruncated.getRegionName()).get(); + + // Assert + int rowCountAfterTruncate = TEST_UTIL.countRows(tableName); + assertNotEquals(rowCountBeforeTruncate, rowCountAfterTruncate); + int expectedRowCount = rowCountBeforeTruncate - 2;// Since region with 2 rows was truncated. + assertEquals(expectedRowCount, rowCountAfterTruncate); + } + + @Test + public void testTruncateReplicaRegionNotAllowed() throws Exception { + // Arrange - Create table, insert data, identify region to truncate. + final byte[][] splitKeys = + new byte[][] { Bytes.toBytes("30"), Bytes.toBytes("60"), Bytes.toBytes("90") }; + String family1 = "f1"; + String family2 = "f2"; + + final byte[][] bFamilies = new byte[][] { Bytes.toBytes(family1), Bytes.toBytes(family2) }; + createTableWithDefaultConf(tableName, 2, splitKeys, bFamilies); + + AsyncTable metaTable = ASYNC_CONN.getTable(META_TABLE_NAME); + List regionLocations = + ClientMetaTableAccessor.getTableHRegionLocations(metaTable, tableName).get(); + RegionInfo primaryRegion = regionLocations.get(0).getRegion(); + + RegionInfo firstReplica = RegionReplicaUtil.getRegionInfoForReplica(primaryRegion, 1); + + // Act - Truncate the first region + try { + admin.truncateRegion(firstReplica.getRegionName()).get(); + } catch (Exception e) { + // Assert + assertEquals("Expected message is different", + "Can't truncate replicas directly.Replicas are auto-truncated " + + "when their primary is truncated.", + e.getCause().getMessage()); + } + } + + @Test + public void testTruncateRegionsMetaTableRegionsNotAllowed() throws Exception { + AsyncTableRegionLocator locator = ASYNC_CONN.getRegionLocator(META_TABLE_NAME); + List regionLocations = locator.getAllRegionLocations().get(); + HRegionLocation regionToBeTruncated = regionLocations.get(0); + // 1 + try { + admin.truncateRegion(regionToBeTruncated.getRegion().getRegionName()).get(); + fail(); + } catch (ExecutionException e) { + // expected + assertThat(e.getCause(), instanceOf(IOException.class)); + assertEquals("Can't truncate region in catalog tables", e.getCause().getMessage()); + } + } } diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/MockNoopMasterServices.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/MockNoopMasterServices.java index 1150c4eb16a9..4532b42c89d1 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/MockNoopMasterServices.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/MockNoopMasterServices.java @@ -78,6 +78,12 @@ public void checkTableModifiable(TableName tableName) throws IOException { // no-op } + @Override + public long truncateRegion(RegionInfo regionInfo, long nonceGroup, long nonce) + throws IOException { + return 0; + } + @Override public long createTable(final TableDescriptor desc, final byte[][] splitKeys, final long nonceGroup, final long nonce) throws IOException { diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/procedure/TestTruncateRegionProcedure.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/procedure/TestTruncateRegionProcedure.java new file mode 100644 index 000000000000..8ce60ee5550a --- /dev/null +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/procedure/TestTruncateRegionProcedure.java @@ -0,0 +1,202 @@ +/* + * 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.master.procedure; + +import static org.apache.hadoop.hbase.master.assignment.AssignmentTestingUtil.insertData; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HBaseClassTestRule; +import org.apache.hadoop.hbase.HConstants; +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.RegionReplicaUtil; +import org.apache.hadoop.hbase.client.TableDescriptor; +import org.apache.hadoop.hbase.client.TableDescriptorBuilder; +import org.apache.hadoop.hbase.master.assignment.TestSplitTableRegionProcedure; +import org.apache.hadoop.hbase.procedure2.Procedure; +import org.apache.hadoop.hbase.procedure2.ProcedureExecutor; +import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility; +import org.apache.hadoop.hbase.testclassification.LargeTests; +import org.apache.hadoop.hbase.testclassification.MasterTests; +import org.apache.hadoop.hbase.util.Bytes; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.rules.TestName; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.hadoop.hbase.shaded.protobuf.generated.ProcedureProtos; + +@SuppressWarnings("OptionalGetWithoutIsPresent") +@Category({ MasterTests.class, LargeTests.class }) +public class TestTruncateRegionProcedure extends TestTableDDLProcedureBase { + @ClassRule + public static final HBaseClassTestRule CLASS_RULE = + HBaseClassTestRule.forClass(TestTruncateRegionProcedure.class); + private static final Logger LOG = LoggerFactory.getLogger(TestTruncateRegionProcedure.class); + + @Rule + public TestName name = new TestName(); + + private static void setupConf(Configuration conf) { + conf.setInt(MasterProcedureConstants.MASTER_PROCEDURE_THREADS, 1); + conf.setLong(HConstants.MAJOR_COMPACTION_PERIOD, 0); + conf.set("hbase.coprocessor.region.classes", + TestSplitTableRegionProcedure.RegionServerHostingReplicaSlowOpenCopro.class.getName()); + conf.setInt("hbase.client.sync.wait.timeout.msec", 60000); + } + + @BeforeClass + public static void setupCluster() throws Exception { + setupConf(UTIL.getConfiguration()); + UTIL.startMiniCluster(3); + } + + @AfterClass + public static void cleanupTest() throws Exception { + try { + UTIL.shutdownMiniCluster(); + } catch (Exception e) { + LOG.warn("failure shutting down cluster", e); + } + } + + @Before + public void setup() throws Exception { + ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(getMasterProcedureExecutor(), false); + + // Turn off balancer, so it doesn't cut in and mess up our placements. + UTIL.getAdmin().balancerSwitch(false, true); + // Turn off the meta scanner, so it doesn't remove, parent on us. + UTIL.getHBaseCluster().getMaster().setCatalogJanitorEnabled(false); + } + + @After + public void tearDown() throws Exception { + ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(getMasterProcedureExecutor(), false); + for (TableDescriptor htd : UTIL.getAdmin().listTableDescriptors()) { + UTIL.deleteTable(htd.getTableName()); + } + } + + @Test + public void testTruncateRegionProcedure() throws Exception { + final ProcedureExecutor procExec = getMasterProcedureExecutor(); + // Arrange - Load table and prepare arguments values. + final TableName tableName = TableName.valueOf(name.getMethodName()); + final String[] families = new String[] { "f1", "f2" }; + final byte[][] splitKeys = + new byte[][] { Bytes.toBytes("30"), Bytes.toBytes("60"), Bytes.toBytes("90") }; + + MasterProcedureTestingUtility.createTable(procExec, tableName, splitKeys, families); + + insertData(UTIL, tableName, 2, 20, families); + insertData(UTIL, tableName, 2, 31, families); + insertData(UTIL, tableName, 2, 61, families); + insertData(UTIL, tableName, 2, 91, families); + + assertEquals(8, UTIL.countRows(tableName)); + + int rowsBeforeDropRegion = 8; + + MasterProcedureEnv environment = procExec.getEnvironment(); + RegionInfo regionToBeTruncated = environment.getAssignmentManager().getAssignedRegions() + .stream().filter(r -> tableName.getNameAsString().equals(r.getTable().getNameAsString())) + .min((o1, o2) -> Bytes.compareTo(o1.getStartKey(), o2.getStartKey())).get(); + + // Act - Execute Truncate region procedure + long procId = + procExec.submitProcedure(new TruncateRegionProcedure(environment, regionToBeTruncated)); + ProcedureTestingUtility.waitProcedure(procExec, procId); + assertEquals(8 - 2, UTIL.countRows(tableName)); + + int rowsAfterDropRegion = UTIL.countRows(tableName); + assertTrue("Row counts after truncate region should be less than row count before it", + rowsAfterDropRegion < rowsBeforeDropRegion); + assertEquals(rowsBeforeDropRegion, rowsAfterDropRegion + 2); + + insertData(UTIL, tableName, 2, 20, families); + assertEquals(8, UTIL.countRows(tableName)); + } + + @Test + public void testTruncateRegionProcedureErrorWhenSpecifiedReplicaRegionID() throws Exception { + final ProcedureExecutor procExec = getMasterProcedureExecutor(); + // Arrange - Load table and prepare arguments values. + final TableName tableName = TableName.valueOf(name.getMethodName()); + final String[] families = new String[] { "f1", "f2" }; + createTable(tableName, families, 2); + insertData(UTIL, tableName, 2, 20, families); + insertData(UTIL, tableName, 2, 30, families); + insertData(UTIL, tableName, 2, 60, families); + + assertEquals(6, UTIL.countRows(tableName)); + + MasterProcedureEnv environment = procExec.getEnvironment(); + RegionInfo regionToBeTruncated = environment.getAssignmentManager().getAssignedRegions() + .stream().filter(r -> tableName.getNameAsString().equals(r.getTable().getNameAsString())) + .min((o1, o2) -> Bytes.compareTo(o1.getStartKey(), o2.getStartKey())).get(); + + RegionInfo replicatedRegionId = + RegionReplicaUtil.getRegionInfoForReplica(regionToBeTruncated, 1); + + // Act - Execute Truncate region procedure + long procId = + procExec.submitProcedure(new TruncateRegionProcedure(environment, replicatedRegionId)); + + ProcedureTestingUtility.waitProcedure(procExec, procId); + Procedure result = procExec.getResult(procId); + // Asserts + + assertEquals(ProcedureProtos.ProcedureState.ROLLEDBACK, result.getState()); + assertTrue(result.getException().getMessage() + .endsWith("Can't truncate replicas directly. Replicas are auto-truncated " + + "when their primary is truncated.")); + } + + private TableDescriptor tableDescriptor(final TableName tableName, String[] families, + final int replicaCount) { + return TableDescriptorBuilder.newBuilder(tableName).setRegionReplication(replicaCount) + .setColumnFamilies(columnFamilyDescriptor(families)).build(); + } + + private List columnFamilyDescriptor(String[] families) { + return Arrays.stream(families).map(ColumnFamilyDescriptorBuilder::of) + .collect(Collectors.toList()); + } + + @SuppressWarnings("SameParameterValue") + private void createTable(final TableName tableName, String[] families, final int replicaCount) + throws IOException { + UTIL.getAdmin().createTable(tableDescriptor(tableName, families, replicaCount)); + } +} diff --git a/hbase-shell/src/main/ruby/hbase/admin.rb b/hbase-shell/src/main/ruby/hbase/admin.rb index a6dcb541c71e..2ba198a257d5 100644 --- a/hbase-shell/src/main/ruby/hbase/admin.rb +++ b/hbase-shell/src/main/ruby/hbase/admin.rb @@ -163,6 +163,16 @@ def split(table_or_region_name, split_point = nil) end end + #---------------------------------------------------------------------------------------------- + # Requests a region truncate + def truncate_region(region_name) + begin + org.apache.hadoop.hbase.util.FutureUtils.get(@admin.truncateRegionAsync(region_name.to_java_bytes)) + rescue java.lang.IllegalArgumentException, org.apache.hadoop.hbase.UnknownRegionException + @admin.truncate_region(region_name.to_java_bytes) + end + end + #---------------------------------------------------------------------------------------------- # Enable/disable one split or merge switch # Returns previous switch setting. diff --git a/hbase-shell/src/main/ruby/shell.rb b/hbase-shell/src/main/ruby/shell.rb index adb253725404..5a2f62513fdc 100644 --- a/hbase-shell/src/main/ruby/shell.rb +++ b/hbase-shell/src/main/ruby/shell.rb @@ -487,6 +487,7 @@ def self.exception_handler(hide_traceback) list_decommissioned_regionservers decommission_regionservers recommission_regionserver + truncate_region ], # TODO: remove older hlog_roll command aliases: { diff --git a/hbase-shell/src/main/ruby/shell/commands/truncate_region.rb b/hbase-shell/src/main/ruby/shell/commands/truncate_region.rb new file mode 100644 index 000000000000..b443d3210bc4 --- /dev/null +++ b/hbase-shell/src/main/ruby/shell/commands/truncate_region.rb @@ -0,0 +1,36 @@ +# +# +# 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. +# + +module Shell + module Commands + class TruncateRegion < Command + def help + <<-EOF +Truncate individual region. +Examples: + truncate_region 'REGIONNAME' + truncate_region 'ENCODED_REGIONNAME' +EOF + end + def command(region_name) + admin.truncate_region(region_name) + end + end + end +end diff --git a/hbase-shell/src/test/ruby/hbase/admin_test.rb b/hbase-shell/src/test/ruby/hbase/admin_test.rb index e84ffd6385a7..dcd3d850343e 100644 --- a/hbase-shell/src/test/ruby/hbase/admin_test.rb +++ b/hbase-shell/src/test/ruby/hbase/admin_test.rb @@ -158,6 +158,17 @@ def teardown #------------------------------------------------------------------------------- + define_test "truncate region should work" do + @t_name = 'hbase_shell_truncate_region' + drop_test_table(@t_name) + admin.create(@t_name, 'a', NUMREGIONS => 10, SPLITALGO => 'HexStringSplit') + r1 = command(:locate_region, @t_name, '1') + region1 = r1.getRegion.getRegionNameAsString + command(:truncate_region, region1) + end + + #------------------------------------------------------------------------------- + define_test "drop should fail on non-existent tables" do assert_raise(ArgumentError) do command(:drop, 'NOT.EXISTS') diff --git a/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift2/client/ThriftAdmin.java b/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift2/client/ThriftAdmin.java index 60cee0945dd8..09a9ec6bbf9d 100644 --- a/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift2/client/ThriftAdmin.java +++ b/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift2/client/ThriftAdmin.java @@ -887,6 +887,15 @@ public void splitRegion(byte[] regionName) { public void split(TableName tableName, byte[] splitPoint) { throw new NotImplementedException("split not supported in ThriftAdmin"); } + @Override + public void truncateRegion(byte[] regionName) throws IOException { + throw new NotImplementedException("Truncate Region not supported in ThriftAdmin"); + } + + @Override + public Future truncateRegionAsync(byte[] regionName) { + throw new NotImplementedException("Truncate Region Async not supported in ThriftAdmin"); + } @Override public void splitRegion(byte[] regionName, byte[] splitPoint) {