From 523e4f971d7c990c45574b203bdaf6d7935018d2 Mon Sep 17 00:00:00 2001 From: Vaibhav Joshi Date: Mon, 16 Oct 2023 17:30:12 +0530 Subject: [PATCH] HBASE-28064:Implement truncate_region command to truncate region directly from FS Implemented truncate_region command. --- .../org/apache/hadoop/hbase/client/Admin.java | 14 + .../hbase/client/AdminOverAsyncAdmin.java | 10 + .../hadoop/hbase/client/AsyncAdmin.java | 6 + .../hadoop/hbase/client/AsyncHBaseAdmin.java | 5 + .../hbase/client/RawAsyncHBaseAdmin.java | 66 +++++ .../shaded/protobuf/RequestConverter.java | 11 + .../main/protobuf/server/master/Master.proto | 16 ++ .../server/master/MasterProcedure.proto | 13 + .../hbase/coprocessor/MasterObserver.java | 40 +++ .../apache/hadoop/hbase/master/HMaster.java | 31 +++ .../hbase/master/MasterCoprocessorHost.java | 54 ++++ .../hbase/master/MasterRpcServices.java | 12 + .../hadoop/hbase/master/MasterServices.java | 9 + .../master/assignment/AssignmentManager.java | 6 + .../AbstractStateMachineRegionProcedure.java | 6 + .../procedure/TableProcedureInterface.java | 3 +- .../hbase/master/procedure/TableQueue.java | 1 + .../procedure/TruncateRegionProcedure.java | 239 ++++++++++++++++++ .../client/TestAsyncRegionAdminApi2.java | 67 +++++ .../hbase/master/MockNoopMasterServices.java | 6 + .../TestTruncateRegionProcedure.java | 202 +++++++++++++++ .../hbase/rsgroup/VerifyingRSGroupAdmin.java | 10 + hbase-shell/src/main/ruby/hbase/admin.rb | 10 + hbase-shell/src/main/ruby/shell.rb | 1 + .../ruby/shell/commands/truncate_region.rb | 36 +++ hbase-shell/src/test/ruby/hbase/admin_test.rb | 11 + .../hbase/thrift2/client/ThriftAdmin.java | 10 + 27 files changed, 894 insertions(+), 1 deletion(-) create mode 100644 hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/TruncateRegionProcedure.java create mode 100644 hbase-server/src/test/java/org/apache/hadoop/hbase/master/procedure/TestTruncateRegionProcedure.java create mode 100644 hbase-shell/src/main/ruby/shell/commands/truncate_region.rb 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 4d579c16af26..417e0013523a 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 @@ -1033,6 +1033,20 @@ default void modifyTable(TableDescriptor td) throws IOException { get(modifyTableAsync(td), getSyncWaitTimeout(), TimeUnit.MILLISECONDS); } + /** + * 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/AdminOverAsyncAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AdminOverAsyncAdmin.java index 690b6406fd3a..bb620aa3cdaa 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AdminOverAsyncAdmin.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AdminOverAsyncAdmin.java @@ -490,6 +490,16 @@ public Future splitRegionAsync(byte[] regionName, byte[] splitPoint) throw return admin.splitRegion(regionName, splitPoint); } + @Override + public void truncateRegion(byte[] regionName) throws IOException { + get(admin.truncateRegion(regionName)); + } + + @Override + public Future truncateRegionAsync(byte[] regionName) { + return admin.truncateRegion(regionName); + } + @Override public Future modifyTableAsync(TableDescriptor td) throws IOException { return admin.modifyTable(td); 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 960982f5e3f1..1097abbbf5e2 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 @@ -618,6 +618,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 5ee8a6ab8269..0c7fd0f7b354 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 @@ -386,6 +386,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 ee1dfac16bd3..953dd2024767 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 @@ -1623,6 +1623,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<>(); @@ -2882,6 +2936,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 33884158da48..c29aacfc5ee1 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 @@ -989,6 +989,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/server/master/Master.proto b/hbase-protocol-shaded/src/main/protobuf/server/master/Master.proto index 5d715fdcdd16..f66f3b983668 100644 --- a/hbase-protocol-shaded/src/main/protobuf/server/master/Master.proto +++ b/hbase-protocol-shaded/src/main/protobuf/server/master/Master.proto @@ -137,6 +137,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; @@ -864,6 +874,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/server/master/MasterProcedure.proto b/hbase-protocol-shaded/src/main/protobuf/server/master/MasterProcedure.proto index 3f3ecd63b002..e6b9f5ab6ae2 100644 --- a/hbase-protocol-shaded/src/main/protobuf/server/master/MasterProcedure.proto +++ b/hbase-protocol-shaded/src/main/protobuf/server/master/MasterProcedure.proto @@ -102,6 +102,19 @@ 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; +} + +message TruncateRegionStateData { + required UserInformation user_info = 1; + required RegionInfo region = 2; +} + 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 175ff25e7611..820fef71fd07 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 @@ -585,6 +585,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 b0862a090760..3c433f11a689 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 @@ -169,6 +169,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; @@ -2567,6 +2568,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 493d0e3ef864..3af69b362609 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 @@ -856,6 +856,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 b6a17d8503b2..736fbae0dea9 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 @@ -958,6 +958,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 933bf0d18150..2a244cb3aa47 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 @@ -488,4 +488,13 @@ boolean normalizeRegions(final NormalizeTableFilterParams ntfp, final boolean is */ long flushTable(final TableName tableName, final List columnFamilies, final long nonceGroup, final long nonce) 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 f2cfad87997c..cfff364bae0f 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 @@ -71,6 +71,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; @@ -1082,6 +1083,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 1ca5b17ac21f..00b9776366d5 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 @@ -49,7 +49,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 d1acd08ea21c..2f0cec77e18c 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 @@ -69,6 +69,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..15c10806464b --- /dev/null +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/TruncateRegionProcedure.java @@ -0,0 +1,239 @@ +/* + * 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.conf.Configuration; +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.procedure2.ProcedureStateSerializer; +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.ProtobufUtil; +import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos; +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); + + private RegionInfo region; + + @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); + this.region = hri; + checkOnline(env, region); + } + + public TruncateRegionProcedure(final MasterProcedureEnv env, final RegionInfo region, + ProcedurePrepareLatch latch) throws HBaseIOException { + super(env, region, latch); + this.region = region; + 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, region); + assert region.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: + deleteRegionsFromFileSystem(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-table", e); + } else { + LOG.warn("Retriable error trying to truncate table=" + getTableName() + " state=" + state, + e); + } + } + return Flow.HAS_MORE_STATE; + } + + private void deleteRegionsFromFileSystem(final MasterProcedureEnv env) throws IOException { + RegionStateNode regionNode = + env.getAssignmentManager().getRegionStates().getRegionStateNode(region); + try { + regionNode.lock(); + final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem(); + final Path tableDir = CommonFSUtils.getTableDir(mfs.getRootDir(), getTableName()); + final Configuration conf = env.getMasterConfiguration(); + HRegionFileSystem.deleteRegionFromFileSystem(conf, mfs.getFileSystem(), tableDir, region); + } 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) { + 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) { + return state == TruncateRegionState.TRUNCATE_REGION_PRE_OPERATION; + } + + @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(region.getRegionNameAsString()); + sb.append(")"); + } + + @Override + protected void serializeStateData(ProcedureStateSerializer serializer) throws IOException { + super.serializeStateData(serializer); + + MasterProcedureProtos.TruncateRegionStateData.Builder state = + MasterProcedureProtos.TruncateRegionStateData.newBuilder() + .setUserInfo(MasterProcedureUtil.toProtoUserInfo(getUser())); + if (region != null) { + state.setRegion(ProtobufUtil.toRegionInfo(region)); + } + serializer.serialize(state.build()); + } + + @Override + protected void deserializeStateData(ProcedureStateSerializer serializer) throws IOException { + super.deserializeStateData(serializer); + + MasterProcedureProtos.TruncateRegionStateData state = + serializer.deserialize(MasterProcedureProtos.TruncateRegionStateData.class); + setUser(MasterProcedureUtil.toUserInfo(state.getUserInfo())); + if (state.getRegion() != null) { + region = ProtobufUtil.toRegionInfo(state.getRegion()); + } + } + + 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(region, getUser()); + } + } + + private void postTruncate(final MasterProcedureEnv env) throws IOException { + final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); + if (cpHost != null) { + cpHost.postTruncateRegionAction(region, getUser()); + } + } + + @Override + public TableOperationType getTableOperationType() { + return TableOperationType.REGION_TRUNCATE; + } + + private TransitRegionStateProcedure createUnAssignProcedures(MasterProcedureEnv env) + throws IOException { + return env.getAssignmentManager().createOneUnassignProcedure(region, true); + } + + private TransitRegionStateProcedure createAssignProcedures(MasterProcedureEnv env) { + return env.getAssignmentManager().createOneAssignProcedure(region, 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 685cd00da52a..aee3172f6fe4 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,6 +22,7 @@ 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; @@ -37,6 +38,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 +285,69 @@ 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 testTruncateReplicaRegionSNotAllowed() 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()); + } + } } 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 a19b6ffbec64..7faa7750cdff 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 @@ -79,6 +79,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-server/src/test/java/org/apache/hadoop/hbase/rsgroup/VerifyingRSGroupAdmin.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/VerifyingRSGroupAdmin.java index 9b1d8524d003..02087fb0a661 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/VerifyingRSGroupAdmin.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/VerifyingRSGroupAdmin.java @@ -412,6 +412,16 @@ public Future splitRegionAsync(byte[] regionName, byte[] splitPoint) throw return admin.splitRegionAsync(regionName, splitPoint); } + @Override + public void truncateRegion(byte[] regionName) throws IOException { + admin.truncateRegion(regionName); + } + + @Override + public Future truncateRegionAsync(byte[] regionName) throws IOException { + return admin.truncateRegionAsync(regionName); + } + public Future modifyTableAsync(TableDescriptor td) throws IOException { return admin.modifyTableAsync(td); } diff --git a/hbase-shell/src/main/ruby/hbase/admin.rb b/hbase-shell/src/main/ruby/hbase/admin.rb index 7477b8ec164f..53a8137fc0cb 100644 --- a/hbase-shell/src/main/ruby/hbase/admin.rb +++ b/hbase-shell/src/main/ruby/hbase/admin.rb @@ -196,6 +196,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 1d579319a5ca..414ab9d2bd51 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..7c681b4e0da5 --- /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 99fb27e0f769..4efcbb112765 100644 --- a/hbase-shell/src/test/ruby/hbase/admin_test.rb +++ b/hbase-shell/src/test/ruby/hbase/admin_test.rb @@ -168,6 +168,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 1b7b6938524a..70ce37faf47e 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 @@ -725,6 +725,16 @@ 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 Future splitRegionAsync(byte[] regionName, byte[] splitPoint) { throw new NotImplementedException("splitRegionAsync not supported in ThriftAdmin");