/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hadoop.hbase.client; import static org.apache.hadoop.hbase.TableName.META_TABLE_NAME; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiConsumer; import java.util.regex.Pattern; import java.util.stream.Collectors; import com.google.common.annotations.VisibleForTesting; import io.netty.util.Timeout; import io.netty.util.TimerTask; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.hbase.HColumnDescriptor; import org.apache.hadoop.hbase.HRegionInfo; import org.apache.hadoop.hbase.HRegionLocation; import org.apache.hadoop.hbase.MetaTableAccessor; import org.apache.hadoop.hbase.MetaTableAccessor.QueryType; import org.apache.hadoop.hbase.NotServingRegionException; import org.apache.hadoop.hbase.ProcedureInfo; import org.apache.hadoop.hbase.RegionLocations; import org.apache.hadoop.hbase.ServerName; import org.apache.hadoop.hbase.NamespaceDescriptor; import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.TableExistsException; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.AsyncMetaTableAccessor; import org.apache.hadoop.hbase.TableNotDisabledException; import org.apache.hadoop.hbase.TableNotEnabledException; import org.apache.hadoop.hbase.TableNotFoundException; import org.apache.hadoop.hbase.UnknownRegionException; import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.classification.InterfaceStability; import org.apache.hadoop.hbase.client.AsyncRpcRetryingCallerFactory.AdminRequestCallerBuilder; import org.apache.hadoop.hbase.client.AsyncRpcRetryingCallerFactory.MasterRequestCallerBuilder; import org.apache.hadoop.hbase.client.Scan.ReadType; import org.apache.hadoop.hbase.client.replication.ReplicationSerDeHelper; import org.apache.hadoop.hbase.client.replication.TableCFs; import org.apache.hadoop.hbase.exceptions.DeserializationException; import org.apache.hadoop.hbase.ipc.HBaseRpcController; import org.apache.hadoop.hbase.quotas.QuotaFilter; import org.apache.hadoop.hbase.quotas.QuotaSettings; import org.apache.hadoop.hbase.quotas.QuotaTableUtil; import org.apache.hadoop.hbase.replication.ReplicationException; import org.apache.hadoop.hbase.replication.ReplicationPeerConfig; import org.apache.hadoop.hbase.replication.ReplicationPeerDescription; import org.apache.hadoop.hbase.shaded.com.google.protobuf.RpcCallback; import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; import org.apache.hadoop.hbase.shaded.protobuf.RequestConverter; import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.AdminService; import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.CloseRegionRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.CloseRegionResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.CompactRegionRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.CompactRegionResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.FlushRegionRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.FlushRegionResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.GetOnlineRegionRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.GetOnlineRegionResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.SplitRegionRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.SplitRegionResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos; import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos.ProcedureDescription; import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos.TableSchema; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.AbortProcedureRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.AbortProcedureResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.AddColumnRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.AddColumnResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.AssignRegionRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.AssignRegionResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.BalanceRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.BalanceResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.CreateNamespaceRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.CreateNamespaceResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.DeleteNamespaceRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.DeleteNamespaceResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.DeleteSnapshotRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.DeleteSnapshotResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.DisableTableRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.DisableTableResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.EnableTableRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.EnableTableResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.DeleteColumnRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.DeleteColumnResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.ExecProcedureRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.ExecProcedureResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.GetCompletedSnapshotsRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.GetCompletedSnapshotsResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.GetNamespaceDescriptorRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.GetNamespaceDescriptorResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.GetProcedureResultRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.GetProcedureResultResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.GetSchemaAlterStatusRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.GetSchemaAlterStatusResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.GetTableDescriptorsRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.GetTableDescriptorsResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.GetTableNamesRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.GetTableNamesResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.CreateTableRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.CreateTableResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.DeleteTableRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.DeleteTableResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsBalancerEnabledRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsBalancerEnabledResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsProcedureDoneRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsProcedureDoneResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsSnapshotDoneRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsSnapshotDoneResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.ListNamespaceDescriptorsRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.ListNamespaceDescriptorsResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.ListProceduresRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.ListProceduresResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.MasterService; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.MergeTableRegionsRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.MergeTableRegionsResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.ModifyColumnRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.ModifyColumnResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.ModifyNamespaceRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.ModifyNamespaceResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.MoveRegionRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.MoveRegionResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.OfflineRegionRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.OfflineRegionResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.RestoreSnapshotRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.RestoreSnapshotResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetBalancerRunningRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetBalancerRunningResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetQuotaRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetQuotaResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SnapshotRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SnapshotResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.TruncateTableRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.TruncateTableResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.UnassignRegionRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.UnassignRegionResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.ReplicationProtos.AddReplicationPeerRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.ReplicationProtos.AddReplicationPeerResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.ReplicationProtos.DisableReplicationPeerRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.ReplicationProtos.DisableReplicationPeerResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.ReplicationProtos.EnableReplicationPeerRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.ReplicationProtos.EnableReplicationPeerResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.ReplicationProtos.GetReplicationPeerConfigRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.ReplicationProtos.GetReplicationPeerConfigResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.ReplicationProtos.ListReplicationPeersRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.ReplicationProtos.ListReplicationPeersResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.ReplicationProtos.RemoveReplicationPeerRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.ReplicationProtos.RemoveReplicationPeerResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.ReplicationProtos.UpdateReplicationPeerConfigRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.ReplicationProtos.UpdateReplicationPeerConfigResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos; import org.apache.hadoop.hbase.snapshot.ClientSnapshotDescriptionUtils; import org.apache.hadoop.hbase.snapshot.RestoreSnapshotException; import org.apache.hadoop.hbase.snapshot.SnapshotCreationException; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; import org.apache.hadoop.hbase.util.ForeignExceptionUtil; import org.apache.hadoop.hbase.util.Pair; /** * The implementation of AsyncAdmin. */ @InterfaceAudience.Private @InterfaceStability.Evolving public class AsyncHBaseAdmin implements AsyncAdmin { public static final String FLUSH_TABLE_PROCEDURE_SIGNATURE = "flush-table-proc"; private static final Log LOG = LogFactory.getLog(AsyncHBaseAdmin.class); private final AsyncConnectionImpl connection; private final RawAsyncTable metaTable; private final long rpcTimeoutNs; private final long operationTimeoutNs; private final long pauseNs; private final int maxAttempts; private final int startLogErrorsCnt; private final NonceGenerator ng; AsyncHBaseAdmin(AsyncConnectionImpl connection) { this.connection = connection; this.metaTable = connection.getRawTable(META_TABLE_NAME); this.rpcTimeoutNs = connection.connConf.getRpcTimeoutNs(); this.operationTimeoutNs = connection.connConf.getOperationTimeoutNs(); this.pauseNs = connection.connConf.getPauseNs(); this.maxAttempts = connection.connConf.getMaxRetries(); this.startLogErrorsCnt = connection.connConf.getStartLogErrorsCnt(); this.ng = connection.getNonceGenerator(); } private <T> MasterRequestCallerBuilder<T> newMasterCaller() { return this.connection.callerFactory.<T> masterRequest() .rpcTimeout(rpcTimeoutNs, TimeUnit.NANOSECONDS) .operationTimeout(operationTimeoutNs, TimeUnit.NANOSECONDS) .pause(pauseNs, TimeUnit.NANOSECONDS).maxAttempts(maxAttempts) .startLogErrorsCnt(startLogErrorsCnt); } private <T> AdminRequestCallerBuilder<T> newAdminCaller() { return this.connection.callerFactory.<T> adminRequest() .rpcTimeout(rpcTimeoutNs, TimeUnit.NANOSECONDS) .operationTimeout(operationTimeoutNs, TimeUnit.NANOSECONDS) .pause(pauseNs, TimeUnit.NANOSECONDS).maxAttempts(maxAttempts) .startLogErrorsCnt(startLogErrorsCnt); } @FunctionalInterface private interface MasterRpcCall<RESP, REQ> { void call(MasterService.Interface stub, HBaseRpcController controller, REQ req, RpcCallback<RESP> done); } @FunctionalInterface private interface AdminRpcCall<RESP, REQ> { void call(AdminService.Interface stub, HBaseRpcController controller, REQ req, RpcCallback<RESP> done); } @FunctionalInterface private interface Converter<D, S> { D convert(S src) throws IOException; } private <PREQ, PRESP, RESP> CompletableFuture<RESP> call(HBaseRpcController controller, MasterService.Interface stub, PREQ preq, MasterRpcCall<PRESP, PREQ> rpcCall, Converter<RESP, PRESP> respConverter) { CompletableFuture<RESP> future = new CompletableFuture<>(); rpcCall.call(stub, controller, preq, new RpcCallback<PRESP>() { @Override public void run(PRESP resp) { if (controller.failed()) { future.completeExceptionally(controller.getFailed()); } else { try { future.complete(respConverter.convert(resp)); } catch (IOException e) { future.completeExceptionally(e); } } } }); return future; } //TODO abstract call and adminCall into a single method. private <PREQ, PRESP, RESP> CompletableFuture<RESP> adminCall(HBaseRpcController controller, AdminService.Interface stub, PREQ preq, AdminRpcCall<PRESP, PREQ> rpcCall, Converter<RESP, PRESP> respConverter) { CompletableFuture<RESP> future = new CompletableFuture<>(); rpcCall.call(stub, controller, preq, new RpcCallback<PRESP>() { @Override public void run(PRESP resp) { if (controller.failed()) { future.completeExceptionally(new IOException(controller.errorText())); } else { try { future.complete(respConverter.convert(resp)); } catch (IOException e) { future.completeExceptionally(e); } } } }); return future; } private <PREQ, PRESP> CompletableFuture<Void> procedureCall(PREQ preq, MasterRpcCall<PRESP, PREQ> rpcCall, Converter<Long, PRESP> respConverter, ProcedureBiConsumer consumer) { CompletableFuture<Long> procFuture = this .<Long> newMasterCaller() .action( (controller, stub) -> this.<PREQ, PRESP, Long> call(controller, stub, preq, rpcCall, respConverter)).call(); return waitProcedureResult(procFuture).whenComplete(consumer); } @FunctionalInterface private interface TableOperator { CompletableFuture<Void> operate(TableName table); } private CompletableFuture<TableDescriptor[]> batchTableOperations(Pattern pattern, TableOperator operator, String operationType) { CompletableFuture<TableDescriptor[]> future = new CompletableFuture<>(); List<TableDescriptor> failed = new LinkedList<>(); listTables(pattern, false).whenComplete( (tables, error) -> { if (error != null) { future.completeExceptionally(error); return; } CompletableFuture[] futures = Arrays.stream(tables) .map((table) -> operator.operate(table.getTableName()).whenComplete((v, ex) -> { if (ex != null) { LOG.info("Failed to " + operationType + " table " + table.getTableName(), ex); failed.add(table); } })).<CompletableFuture> toArray(size -> new CompletableFuture[size]); CompletableFuture.allOf(futures).thenAccept((v) -> { future.complete(failed.toArray(new TableDescriptor[failed.size()])); }); }); return future; } @Override public AsyncConnectionImpl getConnection() { return this.connection; } @Override public CompletableFuture<Boolean> tableExists(TableName tableName) { return AsyncMetaTableAccessor.tableExists(metaTable, tableName); } @Override public CompletableFuture<TableDescriptor[]> listTables() { return listTables((Pattern) null, false); } @Override public CompletableFuture<TableDescriptor[]> listTables(String regex, boolean includeSysTables) { return listTables(Pattern.compile(regex), false); } @Override public CompletableFuture<TableDescriptor[]> listTables(Pattern pattern, boolean includeSysTables) { return this .<TableDescriptor[]>newMasterCaller() .action( (controller, stub) -> this .<GetTableDescriptorsRequest, GetTableDescriptorsResponse, TableDescriptor[]> call( controller, stub, RequestConverter.buildGetTableDescriptorsRequest(pattern, includeSysTables), (s, c, req, done) -> s.getTableDescriptors(c, req, done), ( resp) -> ProtobufUtil.getTableDescriptorArray(resp))).call(); } @Override public CompletableFuture<TableName[]> listTableNames() { return listTableNames((Pattern) null, false); } @Override public CompletableFuture<TableName[]> listTableNames(String regex, boolean includeSysTables) { return listTableNames(Pattern.compile(regex), false); } @Override public CompletableFuture<TableName[]> listTableNames(Pattern pattern, boolean includeSysTables) { return this .<TableName[]>newMasterCaller() .action( (controller, stub) -> this .<GetTableNamesRequest, GetTableNamesResponse, TableName[]> call(controller, stub, RequestConverter.buildGetTableNamesRequest(pattern, includeSysTables), (s, c, req, done) -> s.getTableNames(c, req, done), (resp) -> ProtobufUtil .getTableNameArray(resp.getTableNamesList()))).call(); } @Override public CompletableFuture<TableDescriptor> getTableDescriptor(TableName tableName) { CompletableFuture<TableDescriptor> future = new CompletableFuture<>(); this.<List<TableSchema>> newMasterCaller() .action( (controller, stub) -> this .<GetTableDescriptorsRequest, GetTableDescriptorsResponse, List<TableSchema>> call( controller, stub, RequestConverter.buildGetTableDescriptorsRequest(tableName), (s, c, req, done) -> s.getTableDescriptors(c, req, done), (resp) -> resp .getTableSchemaList())).call().whenComplete((tableSchemas, error) -> { if (error != null) { future.completeExceptionally(error); return; } if (!tableSchemas.isEmpty()) { future.complete(ProtobufUtil.convertToTableDesc(tableSchemas.get(0))); } else { future.completeExceptionally(new TableNotFoundException(tableName.getNameAsString())); } }); return future; } @Override public CompletableFuture<Void> createTable(TableDescriptor desc) { return createTable(desc, null); } @Override public CompletableFuture<Void> createTable(TableDescriptor desc, byte[] startKey, byte[] endKey, int numRegions) { try { return createTable(desc, getSplitKeys(startKey, endKey, numRegions)); } catch (IllegalArgumentException e) { return failedFuture(e); } } @Override public CompletableFuture<Void> createTable(TableDescriptor desc, byte[][] splitKeys) { if (desc.getTableName() == null) { return failedFuture(new IllegalArgumentException("TableName cannot be null")); } if (splitKeys != null && splitKeys.length > 0) { Arrays.sort(splitKeys, Bytes.BYTES_COMPARATOR); // Verify there are no duplicate split keys byte[] lastKey = null; for (byte[] splitKey : splitKeys) { if (Bytes.compareTo(splitKey, HConstants.EMPTY_BYTE_ARRAY) == 0) { return failedFuture(new IllegalArgumentException( "Empty split key must not be passed in the split keys.")); } if (lastKey != null && Bytes.equals(splitKey, lastKey)) { return failedFuture(new IllegalArgumentException("All split keys must be unique, " + "found duplicate: " + Bytes.toStringBinary(splitKey) + ", " + Bytes.toStringBinary(lastKey))); } lastKey = splitKey; } } return this.<CreateTableRequest, CreateTableResponse> procedureCall( RequestConverter.buildCreateTableRequest(desc, splitKeys, ng.getNonceGroup(), ng.newNonce()), (s, c, req, done) -> s.createTable(c, req, done), (resp) -> resp.getProcId(), new CreateTableProcedureBiConsumer(this, desc.getTableName())); } @Override public CompletableFuture<Void> deleteTable(TableName tableName) { return this.<DeleteTableRequest, DeleteTableResponse> procedureCall(RequestConverter .buildDeleteTableRequest(tableName, ng.getNonceGroup(), ng.newNonce()), (s, c, req, done) -> s.deleteTable(c, req, done), (resp) -> resp.getProcId(), new DeleteTableProcedureBiConsumer(this, tableName)); } @Override public CompletableFuture<TableDescriptor[]> deleteTables(String regex) { return deleteTables(Pattern.compile(regex)); } @Override public CompletableFuture<TableDescriptor[]> deleteTables(Pattern pattern) { return batchTableOperations(pattern, (table) -> deleteTable(table), "DELETE"); } @Override public CompletableFuture<Void> truncateTable(TableName tableName, boolean preserveSplits) { return this.<TruncateTableRequest, TruncateTableResponse> procedureCall( RequestConverter.buildTruncateTableRequest(tableName, preserveSplits, ng.getNonceGroup(), ng.newNonce()), (s, c, req, done) -> s.truncateTable(c, req, done), (resp) -> resp.getProcId(), new TruncateTableProcedureBiConsumer(this, tableName)); } @Override public CompletableFuture<Void> enableTable(TableName tableName) { return this.<EnableTableRequest, EnableTableResponse> procedureCall(RequestConverter .buildEnableTableRequest(tableName, ng.getNonceGroup(), ng.newNonce()), (s, c, req, done) -> s.enableTable(c, req, done), (resp) -> resp.getProcId(), new EnableTableProcedureBiConsumer(this, tableName)); } @Override public CompletableFuture<TableDescriptor[]> enableTables(String regex) { return enableTables(Pattern.compile(regex)); } @Override public CompletableFuture<TableDescriptor[]> enableTables(Pattern pattern) { return batchTableOperations(pattern, (table) -> enableTable(table), "ENABLE"); } @Override public CompletableFuture<Void> disableTable(TableName tableName) { return this.<DisableTableRequest, DisableTableResponse> procedureCall(RequestConverter .buildDisableTableRequest(tableName, ng.getNonceGroup(), ng.newNonce()), (s, c, req, done) -> s.disableTable(c, req, done), (resp) -> resp.getProcId(), new DisableTableProcedureBiConsumer(this, tableName)); } @Override public CompletableFuture<TableDescriptor[]> disableTables(String regex) { return disableTables(Pattern.compile(regex)); } @Override public CompletableFuture<TableDescriptor[]> disableTables(Pattern pattern) { return batchTableOperations(pattern, (table) -> disableTable(table), "DISABLE"); } @Override public CompletableFuture<Boolean> isTableEnabled(TableName tableName) { CompletableFuture<Boolean> future = new CompletableFuture<>(); AsyncMetaTableAccessor.getTableState(metaTable, tableName).whenComplete((state, error) -> { if (error != null) { future.completeExceptionally(error); return; } if (state.isPresent()) { future.complete(state.get().inStates(TableState.State.ENABLED)); } else { future.completeExceptionally(new TableNotFoundException(tableName)); } }); return future; } @Override public CompletableFuture<Boolean> isTableDisabled(TableName tableName) { CompletableFuture<Boolean> future = new CompletableFuture<>(); AsyncMetaTableAccessor.getTableState(metaTable, tableName).whenComplete((state, error) -> { if (error != null) { future.completeExceptionally(error); return; } if (state.isPresent()) { future.complete(state.get().inStates(TableState.State.DISABLED)); } else { future.completeExceptionally(new TableNotFoundException(tableName)); } }); return future; } @Override public CompletableFuture<Boolean> isTableAvailable(TableName tableName) { return isTableAvailable(tableName, null); } @Override public CompletableFuture<Boolean> isTableAvailable(TableName tableName, byte[][] splitKeys) { CompletableFuture<Boolean> future = new CompletableFuture<>(); isTableEnabled(tableName).whenComplete( (enabled, error) -> { if (error != null) { future.completeExceptionally(error); return; } if (!enabled) { future.complete(false); } else { AsyncMetaTableAccessor.getTableRegionsAndLocations(metaTable, Optional.of(tableName)) .whenComplete( (locations, error1) -> { if (error1 != null) { future.completeExceptionally(error1); return; } int notDeployed = 0; int regionCount = 0; for (Pair<HRegionInfo, ServerName> pair : locations) { HRegionInfo info = pair.getFirst(); if (pair.getSecond() == null) { if (LOG.isDebugEnabled()) { LOG.debug("Table " + tableName + " has not deployed region " + pair.getFirst().getEncodedName()); } notDeployed++; } else if (splitKeys != null && !Bytes.equals(info.getStartKey(), HConstants.EMPTY_BYTE_ARRAY)) { for (byte[] splitKey : splitKeys) { // Just check if the splitkey is available if (Bytes.equals(info.getStartKey(), splitKey)) { regionCount++; break; } } } else { // Always empty start row should be counted regionCount++; } } if (notDeployed > 0) { if (LOG.isDebugEnabled()) { LOG.debug("Table " + tableName + " has " + notDeployed + " regions"); } future.complete(false); } else if (splitKeys != null && regionCount != splitKeys.length + 1) { if (LOG.isDebugEnabled()) { LOG.debug("Table " + tableName + " expected to have " + (splitKeys.length + 1) + " regions, but only " + regionCount + " available"); } future.complete(false); } else { if (LOG.isDebugEnabled()) { LOG.debug("Table " + tableName + " should be available"); } future.complete(true); } }); } }); return future; } @Override public CompletableFuture<Pair<Integer, Integer>> getAlterStatus(TableName tableName) { return this .<Pair<Integer, Integer>>newMasterCaller() .action( (controller, stub) -> this .<GetSchemaAlterStatusRequest, GetSchemaAlterStatusResponse, Pair<Integer, Integer>> call( controller, stub, RequestConverter.buildGetSchemaAlterStatusRequest(tableName), (s, c, req, done) -> s.getSchemaAlterStatus(c, req, done), (resp) -> new Pair<>( resp.getYetToUpdateRegions(), resp.getTotalRegions()))).call(); } @Override public CompletableFuture<Void> addColumnFamily(TableName tableName, HColumnDescriptor columnFamily) { return this.<AddColumnRequest, AddColumnResponse> procedureCall( RequestConverter.buildAddColumnRequest(tableName, columnFamily, ng.getNonceGroup(), ng.newNonce()), (s, c, req, done) -> s.addColumn(c, req, done), (resp) -> resp.getProcId(), new AddColumnFamilyProcedureBiConsumer(this, tableName)); } @Override public CompletableFuture<Void> deleteColumnFamily(TableName tableName, byte[] columnFamily) { return this.<DeleteColumnRequest, DeleteColumnResponse> procedureCall( RequestConverter.buildDeleteColumnRequest(tableName, columnFamily, ng.getNonceGroup(), ng.newNonce()), (s, c, req, done) -> s.deleteColumn(c, req, done), (resp) -> resp.getProcId(), new DeleteColumnFamilyProcedureBiConsumer(this, tableName)); } @Override public CompletableFuture<Void> modifyColumnFamily(TableName tableName, HColumnDescriptor columnFamily) { return this.<ModifyColumnRequest, ModifyColumnResponse> procedureCall( RequestConverter.buildModifyColumnRequest(tableName, columnFamily, ng.getNonceGroup(), ng.newNonce()), (s, c, req, done) -> s.modifyColumn(c, req, done), (resp) -> resp.getProcId(), new ModifyColumnFamilyProcedureBiConsumer(this, tableName)); } @Override public CompletableFuture<Void> createNamespace(NamespaceDescriptor descriptor) { return this.<CreateNamespaceRequest, CreateNamespaceResponse> procedureCall( RequestConverter.buildCreateNamespaceRequest(descriptor), (s, c, req, done) -> s.createNamespace(c, req, done), (resp) -> resp.getProcId(), new CreateNamespaceProcedureBiConsumer(this, descriptor.getName())); } @Override public CompletableFuture<Void> modifyNamespace(NamespaceDescriptor descriptor) { return this.<ModifyNamespaceRequest, ModifyNamespaceResponse> procedureCall( RequestConverter.buildModifyNamespaceRequest(descriptor), (s, c, req, done) -> s.modifyNamespace(c, req, done), (resp) -> resp.getProcId(), new ModifyNamespaceProcedureBiConsumer(this, descriptor.getName())); } @Override public CompletableFuture<Void> deleteNamespace(String name) { return this.<DeleteNamespaceRequest, DeleteNamespaceResponse> procedureCall( RequestConverter.buildDeleteNamespaceRequest(name), (s, c, req, done) -> s.deleteNamespace(c, req, done), (resp) -> resp.getProcId(), new DeleteNamespaceProcedureBiConsumer(this, name)); } @Override public CompletableFuture<NamespaceDescriptor> getNamespaceDescriptor(String name) { return this .<NamespaceDescriptor> newMasterCaller() .action( (controller, stub) -> this .<GetNamespaceDescriptorRequest, GetNamespaceDescriptorResponse, NamespaceDescriptor> call( controller, stub, RequestConverter.buildGetNamespaceDescriptorRequest(name), (s, c, req, done) -> s.getNamespaceDescriptor(c, req, done), (resp) -> ProtobufUtil .toNamespaceDescriptor(resp.getNamespaceDescriptor()))).call(); } @Override public CompletableFuture<NamespaceDescriptor[]> listNamespaceDescriptors() { return this .<NamespaceDescriptor[]> newMasterCaller() .action( (controller, stub) -> this .<ListNamespaceDescriptorsRequest, ListNamespaceDescriptorsResponse, NamespaceDescriptor[]> call( controller, stub, ListNamespaceDescriptorsRequest.newBuilder().build(), (s, c, req, done) -> s.listNamespaceDescriptors(c, req, done), (resp) -> ProtobufUtil .getNamespaceDescriptorArray(resp))).call(); } @Override public CompletableFuture<Boolean> setBalancerRunning(final boolean on) { return this .<Boolean>newMasterCaller() .action( (controller, stub) -> this .<SetBalancerRunningRequest, SetBalancerRunningResponse, Boolean> call(controller, stub, RequestConverter.buildSetBalancerRunningRequest(on, true), (s, c, req, done) -> s.setBalancerRunning(c, req, done), (resp) -> resp.getPrevBalanceValue())).call(); } @Override public CompletableFuture<Boolean> balancer() { return balancer(false); } @Override public CompletableFuture<Boolean> balancer(boolean force) { return this .<Boolean>newMasterCaller() .action( (controller, stub) -> this.<BalanceRequest, BalanceResponse, Boolean> call(controller, stub, RequestConverter.buildBalanceRequest(force), (s, c, req, done) -> s.balance(c, req, done), (resp) -> resp.getBalancerRan())).call(); } @Override public CompletableFuture<Boolean> isBalancerEnabled() { return this .<Boolean>newMasterCaller() .action( (controller, stub) -> this.<IsBalancerEnabledRequest, IsBalancerEnabledResponse, Boolean> call( controller, stub, RequestConverter.buildIsBalancerEnabledRequest(), (s, c, req, done) -> s.isBalancerEnabled(c, req, done), (resp) -> resp.getEnabled())) .call(); } @Override public CompletableFuture<Void> closeRegion(String regionname, String serverName) { return closeRegion(Bytes.toBytes(regionname), serverName); } @Override public CompletableFuture<Void> closeRegion(byte[] regionName, String serverName) { CompletableFuture<Void> future = new CompletableFuture<>(); getRegion(regionName).whenComplete((p, err) -> { if (err != null) { future.completeExceptionally(err); return; } if (p == null || p.getFirst() == null) { future.completeExceptionally(new UnknownRegionException(Bytes.toStringBinary(regionName))); return; } if (serverName != null) { closeRegion(ServerName.valueOf(serverName), p.getFirst()).whenComplete((p2, err2) -> { if (err2 != null) { future.completeExceptionally(err2); }else{ future.complete(null); } }); } else { if (p.getSecond() == null) { future.completeExceptionally(new NotServingRegionException(regionName)); } else { closeRegion(p.getSecond(), p.getFirst()).whenComplete((p2, err2) -> { if (err2 != null) { future.completeExceptionally(err2); }else{ future.complete(null); } }); } } }); return future; } CompletableFuture<Pair<HRegionInfo, ServerName>> getRegion(byte[] regionName) { if (regionName == null) { return failedFuture(new IllegalArgumentException("Pass region name")); } CompletableFuture<Pair<HRegionInfo, ServerName>> future = new CompletableFuture<>(); AsyncMetaTableAccessor.getRegion(metaTable, regionName).whenComplete( (p, err) -> { if (err != null) { future.completeExceptionally(err); } else if (p != null) { future.complete(p); } else { metaTable.scanAll( new Scan().setReadType(ReadType.PREAD).addFamily(HConstants.CATALOG_FAMILY)) .whenComplete((results, err2) -> { if (err2 != null) { future.completeExceptionally(err2); return; } String encodedName = Bytes.toString(regionName); if (results != null && !results.isEmpty()) { for (Result r : results) { if (r.isEmpty() || MetaTableAccessor.getHRegionInfo(r) == null) continue; RegionLocations rl = MetaTableAccessor.getRegionLocations(r); if (rl != null) { for (HRegionLocation h : rl.getRegionLocations()) { if (h != null && encodedName.equals(h.getRegionInfo().getEncodedName())) { future.complete(new Pair<>(h.getRegionInfo(), h.getServerName())); return; } } } } } future.complete(null); }); } }); return future; } @Override public CompletableFuture<Boolean> closeRegionWithEncodedRegionName(String encodedRegionName, String serverName) { return this .<Boolean> newAdminCaller() .action( (controller, stub) -> this.<CloseRegionRequest, CloseRegionResponse, Boolean> adminCall( controller, stub, ProtobufUtil.buildCloseRegionRequest(ServerName.valueOf(serverName), encodedRegionName), (s, c, req, done) -> s.closeRegion(controller, req, done), (resp) -> resp.getClosed())) .serverName(ServerName.valueOf(serverName)).call(); } @Override public CompletableFuture<Void> closeRegion(ServerName sn, HRegionInfo hri) { return this.<Void> newAdminCaller() .action( (controller, stub) -> this.<CloseRegionRequest, CloseRegionResponse, Void> adminCall( controller, stub, ProtobufUtil.buildCloseRegionRequest(sn, hri.getRegionName()), (s, c, req, done) -> s.closeRegion(controller, req, done), resp -> null)) .serverName(sn).call(); } @Override public CompletableFuture<List<HRegionInfo>> getOnlineRegions(ServerName sn) { return this.<List<HRegionInfo>> newAdminCaller() .action((controller, stub) -> this .<GetOnlineRegionRequest, GetOnlineRegionResponse, List<HRegionInfo>> adminCall( controller, stub, RequestConverter.buildGetOnlineRegionRequest(), (s, c, req, done) -> s.getOnlineRegion(c, req, done), resp -> ProtobufUtil.getRegionInfos(resp))) .serverName(sn).call(); } @Override public CompletableFuture<Void> flush(TableName tableName) { CompletableFuture<Void> future = new CompletableFuture<>(); tableExists(tableName).whenComplete((exists, err) -> { if (err != null) { future.completeExceptionally(err); } else if (!exists) { future.completeExceptionally(new TableNotFoundException(tableName)); } else { isTableEnabled(tableName).whenComplete((tableEnabled, err2) -> { if (err2 != null) { future.completeExceptionally(err2); } else if (!tableEnabled) { future.completeExceptionally(new TableNotEnabledException(tableName)); } else { execProcedure(FLUSH_TABLE_PROCEDURE_SIGNATURE, tableName.getNameAsString(), new HashMap<>()).whenComplete((ret, err3) -> { if (err3 != null) { future.completeExceptionally(err3); } else { future.complete(ret); } }); } }); } }); return future; } @Override public CompletableFuture<Void> flushRegion(byte[] regionName) { CompletableFuture<Void> future = new CompletableFuture<>(); getRegion(regionName).whenComplete((p, err) -> { if (err != null) { future.completeExceptionally(err); return; } if (p == null || p.getFirst() == null) { future.completeExceptionally( new IllegalArgumentException("Invalid region: " + Bytes.toStringBinary(regionName))); return; } if (p.getSecond() == null) { future.completeExceptionally( new NoServerForRegionException(Bytes.toStringBinary(regionName))); return; } this.<Void> newAdminCaller().serverName(p.getSecond()) .action((controller, stub) -> this .<FlushRegionRequest, FlushRegionResponse, Void> adminCall(controller, stub, RequestConverter.buildFlushRegionRequest(p.getFirst().getRegionName()), (s, c, req, done) -> s.flushRegion(c, req, done), resp -> null)) .call().whenComplete((ret, err2) -> { if (err2 != null) { future.completeExceptionally(err2); } else { future.complete(ret); } }); }); return future; } @Override public CompletableFuture<Void> compact(TableName tableName) { return compact(tableName, null, false, CompactType.NORMAL); } @Override public CompletableFuture<Void> compact(TableName tableName, byte[] columnFamily) { return compact(tableName, columnFamily, false, CompactType.NORMAL); } @Override public CompletableFuture<Void> compactRegion(byte[] regionName) { return compactRegion(regionName, null, false); } @Override public CompletableFuture<Void> compactRegion(byte[] regionName, byte[] columnFamily) { return compactRegion(regionName, columnFamily, false); } @Override public CompletableFuture<Void> majorCompact(TableName tableName) { return compact(tableName, null, true, CompactType.NORMAL); } @Override public CompletableFuture<Void> majorCompact(TableName tableName, byte[] columnFamily) { return compact(tableName, columnFamily, true, CompactType.NORMAL); } @Override public CompletableFuture<Void> majorCompactRegion(byte[] regionName) { return compactRegion(regionName, null, true); } @Override public CompletableFuture<Void> majorCompactRegion(byte[] regionName, byte[] columnFamily) { return compactRegion(regionName, columnFamily, true); } @Override public CompletableFuture<Void> compactRegionServer(ServerName sn) { return compactRegionServer(sn, false); } @Override public CompletableFuture<Void> majorCompactRegionServer(ServerName sn) { return compactRegionServer(sn, true); } private CompletableFuture<Void> compactRegionServer(ServerName sn, boolean major) { CompletableFuture<Void> future = new CompletableFuture<>(); getOnlineRegions(sn).whenComplete((hRegionInfos, err) -> { if (err != null) { future.completeExceptionally(err); return; } List<CompletableFuture<Void>> compactFutures = new ArrayList<>(); if (hRegionInfos != null) { hRegionInfos.forEach(region -> compactFutures.add(compact(sn, region, major, null))); } CompletableFuture .allOf(compactFutures.toArray(new CompletableFuture<?>[compactFutures.size()])) .whenComplete((ret, err2) -> { if (err2 != null) { future.completeExceptionally(err2); } else { future.complete(ret); } }); }); return future; } private CompletableFuture<Void> compactRegion(final byte[] regionName, final byte[] columnFamily, final boolean major) { CompletableFuture<Void> future = new CompletableFuture<>(); getRegion(regionName).whenComplete((p, err) -> { if (err != null) { future.completeExceptionally(err); return; } if (p == null || p.getFirst() == null) { future.completeExceptionally( new IllegalArgumentException("Invalid region: " + Bytes.toStringBinary(regionName))); return; } if (p.getSecond() == null) { // found a region without region server assigned. future.completeExceptionally( new NoServerForRegionException(Bytes.toStringBinary(regionName))); return; } compact(p.getSecond(), p.getFirst(), major, columnFamily).whenComplete((ret, err2) -> { if (err2 != null) { future.completeExceptionally(err2); } else { future.complete(ret); } }); }); return future; } /** * List all region locations for the specific table. */ private CompletableFuture<List<HRegionLocation>> getTableHRegionLocations(TableName tableName) { CompletableFuture<List<HRegionLocation>> future = new CompletableFuture<>(); if (TableName.META_TABLE_NAME.equals(tableName)) { // For meta table, we use zk to fetch all locations. AsyncRegistry registry = AsyncRegistryFactory.getRegistry(connection.getConfiguration()); registry.getMetaRegionLocation().whenComplete((metaRegions, err) -> { if (err != null) { future.completeExceptionally(err); } else if (metaRegions == null || metaRegions.isEmpty() || metaRegions.getDefaultRegionLocation() == null) { future.completeExceptionally(new IOException("meta region does not found")); } else { future.complete(Collections.singletonList(metaRegions.getDefaultRegionLocation())); } // close the registry. IOUtils.closeQuietly(registry); }); } else { // For non-meta table, we fetch all locations by scanning hbase:meta table AsyncMetaTableAccessor.getTableRegionsAndLocations(metaTable, Optional.of(tableName)) .whenComplete((locations, err) -> { if (err != null) { future.completeExceptionally(err); } else if (locations == null || locations.isEmpty()) { future.complete(Collections.emptyList()); } else { List<HRegionLocation> regionLocations = locations.stream() .map(loc -> new HRegionLocation(loc.getFirst(), loc.getSecond())) .collect(Collectors.toList()); future.complete(regionLocations); } }); } return future; } /** * Compact column family of a table, Asynchronous operation even if CompletableFuture.get() */ private CompletableFuture<Void> compact(final TableName tableName, final byte[] columnFamily, final boolean major, CompactType compactType) { if (CompactType.MOB.equals(compactType)) { // TODO support MOB compact. return failedFuture(new UnsupportedOperationException("MOB compact does not support")); } CompletableFuture<Void> future = new CompletableFuture<>(); getTableHRegionLocations(tableName).whenComplete((locations, err) -> { if (err != null) { future.completeExceptionally(err); return; } List<CompletableFuture<Void>> compactFutures = new ArrayList<>(); for (HRegionLocation location : locations) { if (location.getRegionInfo() == null || location.getRegionInfo().isOffline()) continue; if (location.getServerName() == null) continue; compactFutures .add(compact(location.getServerName(), location.getRegionInfo(), major, columnFamily)); } // future complete unless all of the compact futures are completed. CompletableFuture .allOf(compactFutures.toArray(new CompletableFuture<?>[compactFutures.size()])) .whenComplete((ret, err2) -> { if (err2 != null) { future.completeExceptionally(err2); } else { future.complete(ret); } }); }); return future; } /** * Compact the region at specific region server. */ private CompletableFuture<Void> compact(final ServerName sn, final HRegionInfo hri, final boolean major, final byte[] family) { return this.<Void> newAdminCaller().serverName(sn) .action((controller, stub) -> this .<CompactRegionRequest, CompactRegionResponse, Void> adminCall(controller, stub, RequestConverter.buildCompactRegionRequest(hri.getRegionName(), major, family), (s, c, req, done) -> s.compactRegion(c, req, done), resp -> null)) .call(); } private byte[] toEncodeRegionName(byte[] regionName) { try { return HRegionInfo.isEncodedRegionName(regionName) ? regionName : Bytes.toBytes(HRegionInfo.encodeRegionName(regionName)); } catch (IOException e) { return regionName; } } private void checkAndGetTableName(byte[] encodeRegionName, AtomicReference<TableName> tableName, CompletableFuture<TableName> result) { getRegion(encodeRegionName).whenComplete((p, err) -> { if (err != null) { result.completeExceptionally(err); return; } if (p == null) { result.completeExceptionally(new UnknownRegionException( "Can't invoke merge on unknown region " + Bytes.toStringBinary(encodeRegionName))); return; } if (p.getFirst().getReplicaId() != HRegionInfo.DEFAULT_REPLICA_ID) { result.completeExceptionally( new IllegalArgumentException("Can't invoke merge on non-default regions directly")); return; } if (!tableName.compareAndSet(null, p.getFirst().getTable())) { if (!tableName.get().equals(p.getFirst().getTable())) { // tables of this two region should be same. result.completeExceptionally( new IllegalArgumentException("Cannot merge regions from two different tables " + tableName.get() + " and " + p.getFirst().getTable())); } else { result.complete(tableName.get()); } } }); } private CompletableFuture<TableName> checkRegionsAndGetTableName(byte[] encodeRegionNameA, byte[] encodeRegionNameB) { AtomicReference<TableName> tableNameRef = new AtomicReference<>(); CompletableFuture<TableName> future = new CompletableFuture<>(); checkAndGetTableName(encodeRegionNameA, tableNameRef, future); checkAndGetTableName(encodeRegionNameB, tableNameRef, future); return future; } @Override public CompletableFuture<Void> mergeRegions(byte[] nameOfRegionA, byte[] nameOfRegionB, boolean forcible) { CompletableFuture<Void> future = new CompletableFuture<>(); final byte[] encodeRegionNameA = toEncodeRegionName(nameOfRegionA); final byte[] encodeRegionNameB = toEncodeRegionName(nameOfRegionB); checkRegionsAndGetTableName(encodeRegionNameA, encodeRegionNameB) .whenComplete((tableName, err) -> { if (err != null) { future.completeExceptionally(err); return; } MergeTableRegionsRequest request = null; try { request = RequestConverter.buildMergeTableRegionsRequest( new byte[][] { encodeRegionNameA, encodeRegionNameB }, forcible, ng.getNonceGroup(), ng.newNonce()); } catch (DeserializationException e) { future.completeExceptionally(e); return; } this.<MergeTableRegionsRequest, MergeTableRegionsResponse> procedureCall(request, (s, c, req, done) -> s.mergeTableRegions(c, req, done), (resp) -> resp.getProcId(), new MergeTableRegionProcedureBiConsumer(this, tableName)).whenComplete((ret, err2) -> { if (err2 != null) { future.completeExceptionally(err2); } else { future.complete(ret); } }); }); return future; } @Override public CompletableFuture<Void> split(TableName tableName) { CompletableFuture<Void> future = new CompletableFuture<>(); tableExists(tableName).whenComplete((exist, error) -> { if (error != null) { future.completeExceptionally(error); return; } if (!exist) { future.completeExceptionally(new TableNotFoundException(tableName)); return; } metaTable .scanAll(new Scan().setReadType(ReadType.PREAD).addFamily(HConstants.CATALOG_FAMILY) .withStartRow(MetaTableAccessor.getTableStartRowForMeta(tableName, QueryType.REGION)) .withStopRow(MetaTableAccessor.getTableStopRowForMeta(tableName, QueryType.REGION))) .whenComplete((results, err2) -> { if (err2 != null) { future.completeExceptionally(err2); return; } if (results != null && !results.isEmpty()) { List<CompletableFuture<Void>> splitFutures = new ArrayList<>(); for (Result r : results) { if (r.isEmpty() || MetaTableAccessor.getHRegionInfo(r) == null) continue; RegionLocations rl = MetaTableAccessor.getRegionLocations(r); if (rl != null) { for (HRegionLocation h : rl.getRegionLocations()) { if (h != null && h.getServerName() != null) { HRegionInfo hri = h.getRegionInfo(); if (hri == null || hri.isSplitParent() || hri.getReplicaId() != HRegionInfo.DEFAULT_REPLICA_ID) continue; splitFutures.add(split(h.getServerName(), hri, null)); } } } } CompletableFuture .allOf(splitFutures.toArray(new CompletableFuture<?>[splitFutures.size()])) .whenComplete((ret, exception) -> { if (exception != null) { future.completeExceptionally(exception); return; } future.complete(ret); }); } else { future.complete(null); } }); }); return future; } @Override public CompletableFuture<Void> splitRegion(byte[] regionName) { return splitRegion(regionName, null); } @Override public CompletableFuture<Void> split(TableName tableName, byte[] splitPoint) { CompletableFuture<Void> result = new CompletableFuture<>(); if (splitPoint == null) { return failedFuture(new IllegalArgumentException("splitPoint can not be null.")); } connection.getRegionLocator(tableName).getRegionLocation(splitPoint) .whenComplete((loc, err) -> { if (err != null) { result.completeExceptionally(err); } else if (loc == null || loc.getRegionInfo() == null) { result.completeExceptionally(new IllegalArgumentException( "Region does not found: rowKey=" + Bytes.toStringBinary(splitPoint))); } else { splitRegion(loc.getRegionInfo().getRegionName(), splitPoint) .whenComplete((ret, err2) -> { if (err2 != null) { result.completeExceptionally(err2); } else { result.complete(ret); } }); } }); return result; } @Override public CompletableFuture<Void> splitRegion(byte[] regionName, byte[] splitPoint) { CompletableFuture<Void> future = new CompletableFuture<>(); getRegion(regionName).whenComplete((p, err) -> { if (p == null) { future.completeExceptionally( new IllegalArgumentException("Invalid region: " + Bytes.toStringBinary(regionName))); return; } if (p.getFirst() != null && p.getFirst().getReplicaId() != HRegionInfo.DEFAULT_REPLICA_ID) { future.completeExceptionally(new IllegalArgumentException("Can't split replicas directly. " + "Replicas are auto-split when their primary is split.")); return; } if (p.getSecond() == null) { future.completeExceptionally( new NoServerForRegionException(Bytes.toStringBinary(regionName))); return; } split(p.getSecond(), p.getFirst(), splitPoint).whenComplete((ret, err2) -> { if (err2 != null) { future.completeExceptionally(err2); } else { future.complete(ret); } }); }); return future; } @VisibleForTesting public CompletableFuture<Void> split(final ServerName sn, final HRegionInfo hri, byte[] splitPoint) { if (hri.getStartKey() != null && splitPoint != null && Bytes.compareTo(hri.getStartKey(), splitPoint) == 0) { return failedFuture( new IllegalArgumentException("should not give a splitkey which equals to startkey!")); } return this.<Void> newAdminCaller() .action( (controller, stub) -> this.<SplitRegionRequest, SplitRegionResponse, Void> adminCall( controller, stub, ProtobufUtil.buildSplitRegionRequest(hri.getRegionName(), splitPoint), (s, c, req, done) -> s.splitRegion(controller, req, done), resp -> null)) .serverName(sn).call(); } /** * Turn regionNameOrEncodedRegionName into regionName, if region does not found, then it'll throw * an IllegalArgumentException wrapped by a {@link CompletableFuture} * @param regionNameOrEncodedRegionName * @return */ CompletableFuture<byte[]> getRegionName(byte[] regionNameOrEncodedRegionName) { CompletableFuture<byte[]> future = new CompletableFuture<>(); if (Bytes .equals(regionNameOrEncodedRegionName, HRegionInfo.FIRST_META_REGIONINFO.getRegionName()) || Bytes.equals(regionNameOrEncodedRegionName, HRegionInfo.FIRST_META_REGIONINFO.getEncodedNameAsBytes())) { future.complete(HRegionInfo.FIRST_META_REGIONINFO.getRegionName()); return future; } getRegion(regionNameOrEncodedRegionName).whenComplete((p, err) -> { if (err != null) { future.completeExceptionally(err); } if (p != null && p.getFirst() != null) { future.complete(p.getFirst().getRegionName()); } else { future.completeExceptionally( new IllegalArgumentException("Invalid region name or encoded region name: " + Bytes.toStringBinary(regionNameOrEncodedRegionName))); } }); return future; } @Override public CompletableFuture<Void> assign(byte[] regionName) { CompletableFuture<Void> future = new CompletableFuture<>(); getRegionName(regionName).whenComplete((fullRegionName, err) -> { if (err != null) { future.completeExceptionally(err); } else { this.<Void> newMasterCaller() .action( ((controller, stub) -> this.<AssignRegionRequest, AssignRegionResponse, Void> call( controller, stub, RequestConverter.buildAssignRegionRequest(fullRegionName), (s, c, req, done) -> s.assignRegion(c, req, done), resp -> null))) .call().whenComplete((ret, err2) -> { if (err2 != null) { future.completeExceptionally(err2); } else { future.complete(ret); } }); } }); return future; } @Override public CompletableFuture<Void> unassign(byte[] regionName, boolean force) { CompletableFuture<Void> future = new CompletableFuture<>(); getRegionName(regionName).whenComplete((fullRegionName, err) -> { if (err != null) { future.completeExceptionally(err); } else { this.<Void> newMasterCaller() .action(((controller, stub) -> this .<UnassignRegionRequest, UnassignRegionResponse, Void> call(controller, stub, RequestConverter.buildUnassignRegionRequest(fullRegionName, force), (s, c, req, done) -> s.unassignRegion(c, req, done), resp -> null))) .call().whenComplete((ret, err2) -> { if (err2 != null) { future.completeExceptionally(err2); } else { future.complete(ret); } }); } }); return future; } @Override public CompletableFuture<Void> offline(byte[] regionName) { CompletableFuture<Void> future = new CompletableFuture<>(); getRegionName(regionName).whenComplete((fullRegionName, err) -> { if (err != null) { future.completeExceptionally(err); } else { this.<Void> newMasterCaller() .action( ((controller, stub) -> this.<OfflineRegionRequest, OfflineRegionResponse, Void> call( controller, stub, RequestConverter.buildOfflineRegionRequest(fullRegionName), (s, c, req, done) -> s.offlineRegion(c, req, done), resp -> null))) .call().whenComplete((ret, err2) -> { if (err2 != null) { future.completeExceptionally(err2); } else { future.complete(ret); } }); } }); return future; } @Override public CompletableFuture<Void> move(byte[] regionName, byte[] destServerName) { CompletableFuture<Void> future = new CompletableFuture<>(); getRegionName(regionName).whenComplete((fullRegionName, err) -> { if (err != null) { future.completeExceptionally(err); } else { final MoveRegionRequest request; try { request = RequestConverter.buildMoveRegionRequest( Bytes.toBytes(HRegionInfo.encodeRegionName(fullRegionName)), destServerName); } catch (DeserializationException e) { future.completeExceptionally(e); return; } this.<Void> newMasterCaller() .action((controller, stub) -> this.<MoveRegionRequest, MoveRegionResponse, Void> call( controller, stub, request, (s, c, req, done) -> s.moveRegion(c, req, done), resp -> null)) .call().whenComplete((ret, err2) -> { if (err2 != null) { future.completeExceptionally(err2); } else { future.complete(ret); } }); } }); return future; } @Override public CompletableFuture<Void> setQuota(QuotaSettings quota) { return this .<Void> newMasterCaller() .action( (controller, stub) -> this.<SetQuotaRequest, SetQuotaResponse, Void> call(controller, stub, QuotaSettings.buildSetQuotaRequestProto(quota), (s, c, req, done) -> s.setQuota(c, req, done), (resp) -> null)).call(); } @Override public CompletableFuture<List<QuotaSettings>> getQuota(QuotaFilter filter) { CompletableFuture<List<QuotaSettings>> future = new CompletableFuture<>(); Scan scan = QuotaTableUtil.makeScan(filter); this.connection.getRawTableBuilder(QuotaTableUtil.QUOTA_TABLE_NAME).build() .scan(scan, new RawScanResultConsumer() { List<QuotaSettings> settings = new ArrayList<>(); @Override public void onNext(Result[] results, ScanController controller) { for (Result result : results) { try { QuotaTableUtil.parseResultToCollection(result, settings); } catch (IOException e) { controller.terminate(); future.completeExceptionally(e); } } } @Override public void onError(Throwable error) { future.completeExceptionally(error); } @Override public void onComplete() { future.complete(settings); } }); return future; } public CompletableFuture<Void> addReplicationPeer(String peerId, ReplicationPeerConfig peerConfig) { return this .<Void> newMasterCaller() .action( (controller, stub) -> this .<AddReplicationPeerRequest, AddReplicationPeerResponse, Void> call(controller, stub, RequestConverter.buildAddReplicationPeerRequest(peerId, peerConfig), (s, c, req, done) -> s.addReplicationPeer(c, req, done), (resp) -> null)).call(); } @Override public CompletableFuture<Void> removeReplicationPeer(String peerId) { return this .<Void> newMasterCaller() .action( (controller, stub) -> this .<RemoveReplicationPeerRequest, RemoveReplicationPeerResponse, Void> call(controller, stub, RequestConverter.buildRemoveReplicationPeerRequest(peerId), (s, c, req, done) -> s.removeReplicationPeer(c, req, done), (resp) -> null)).call(); } @Override public CompletableFuture<Void> enableReplicationPeer(String peerId) { return this .<Void> newMasterCaller() .action( (controller, stub) -> this .<EnableReplicationPeerRequest, EnableReplicationPeerResponse, Void> call(controller, stub, RequestConverter.buildEnableReplicationPeerRequest(peerId), (s, c, req, done) -> s.enableReplicationPeer(c, req, done), (resp) -> null)).call(); } @Override public CompletableFuture<Void> disableReplicationPeer(String peerId) { return this .<Void> newMasterCaller() .action( (controller, stub) -> this .<DisableReplicationPeerRequest, DisableReplicationPeerResponse, Void> call( controller, stub, RequestConverter.buildDisableReplicationPeerRequest(peerId), (s, c, req, done) -> s.disableReplicationPeer(c, req, done), (resp) -> null)) .call(); } public CompletableFuture<ReplicationPeerConfig> getReplicationPeerConfig(String peerId) { return this .<ReplicationPeerConfig> newMasterCaller() .action( (controller, stub) -> this .<GetReplicationPeerConfigRequest, GetReplicationPeerConfigResponse, ReplicationPeerConfig> call( controller, stub, RequestConverter.buildGetReplicationPeerConfigRequest(peerId), ( s, c, req, done) -> s.getReplicationPeerConfig(c, req, done), (resp) -> ReplicationSerDeHelper.convert(resp.getPeerConfig()))).call(); } @Override public CompletableFuture<Void> updateReplicationPeerConfig(String peerId, ReplicationPeerConfig peerConfig) { return this .<Void> newMasterCaller() .action( (controller, stub) -> this .<UpdateReplicationPeerConfigRequest, UpdateReplicationPeerConfigResponse, Void> call( controller, stub, RequestConverter.buildUpdateReplicationPeerConfigRequest(peerId, peerConfig), (s, c, req, done) -> s.updateReplicationPeerConfig(c, req, done), ( resp) -> null)).call(); } @Override public CompletableFuture<Void> appendReplicationPeerTableCFs(String id, Map<TableName, ? extends Collection<String>> tableCfs) { if (tableCfs == null) { return failedFuture(new ReplicationException("tableCfs is null")); } CompletableFuture<Void> future = new CompletableFuture<Void>(); getReplicationPeerConfig(id).whenComplete((peerConfig, error) -> { if (!completeExceptionally(future, error)) { ReplicationSerDeHelper.appendTableCFsToReplicationPeerConfig(tableCfs, peerConfig); updateReplicationPeerConfig(id, peerConfig).whenComplete((result, err) -> { if (!completeExceptionally(future, error)) { future.complete(result); } }); } }); return future; } @Override public CompletableFuture<Void> removeReplicationPeerTableCFs(String id, Map<TableName, ? extends Collection<String>> tableCfs) { if (tableCfs == null) { return failedFuture(new ReplicationException("tableCfs is null")); } CompletableFuture<Void> future = new CompletableFuture<Void>(); getReplicationPeerConfig(id).whenComplete((peerConfig, error) -> { if (!completeExceptionally(future, error)) { try { ReplicationSerDeHelper.removeTableCFsFromReplicationPeerConfig(tableCfs, peerConfig, id); } catch (ReplicationException e) { future.completeExceptionally(e); return; } updateReplicationPeerConfig(id, peerConfig).whenComplete((result, err) -> { if (!completeExceptionally(future, error)) { future.complete(result); } }); } }); return future; } @Override public CompletableFuture<List<ReplicationPeerDescription>> listReplicationPeers() { return listReplicationPeers((Pattern) null); } @Override public CompletableFuture<List<ReplicationPeerDescription>> listReplicationPeers(String regex) { return listReplicationPeers(Pattern.compile(regex)); } @Override public CompletableFuture<List<ReplicationPeerDescription>> listReplicationPeers(Pattern pattern) { return this .<List<ReplicationPeerDescription>> newMasterCaller() .action( (controller, stub) -> this .<ListReplicationPeersRequest, ListReplicationPeersResponse, List<ReplicationPeerDescription>> call( controller, stub, RequestConverter.buildListReplicationPeersRequest(pattern), (s, c, req, done) -> s.listReplicationPeers(c, req, done), (resp) -> resp.getPeerDescList().stream() .map(ReplicationSerDeHelper::toReplicationPeerDescription) .collect(Collectors.toList()))).call(); } @Override public CompletableFuture<List<TableCFs>> listReplicatedTableCFs() { CompletableFuture<List<TableCFs>> future = new CompletableFuture<List<TableCFs>>(); listTables().whenComplete( (tables, error) -> { if (!completeExceptionally(future, error)) { List<TableCFs> replicatedTableCFs = new ArrayList<>(); Arrays.asList(tables).forEach( table -> { Map<String, Integer> cfs = new HashMap<>(); Arrays.asList(table.getColumnFamilies()).stream() .filter(column -> column.getScope() != HConstants.REPLICATION_SCOPE_LOCAL) .forEach(column -> { cfs.put(column.getNameAsString(), column.getScope()); }); if (!cfs.isEmpty()) { replicatedTableCFs.add(new TableCFs(table.getTableName(), cfs)); } }); future.complete(replicatedTableCFs); } }); return future; } @Override public CompletableFuture<Void> snapshot(String snapshotName, TableName tableName) { return snapshot(snapshotName, tableName, SnapshotType.FLUSH); } @Override public CompletableFuture<Void> snapshot(String snapshotName, TableName tableName, SnapshotType type) { return snapshot(new SnapshotDescription(snapshotName, tableName, type)); } @Override public CompletableFuture<Void> snapshot(SnapshotDescription snapshotDesc) { SnapshotProtos.SnapshotDescription snapshot = ProtobufUtil.createHBaseProtosSnapshotDesc(snapshotDesc); try { ClientSnapshotDescriptionUtils.assertSnapshotRequestIsValid(snapshot); } catch (IllegalArgumentException e) { return failedFuture(e); } CompletableFuture<Void> future = new CompletableFuture<>(); final SnapshotRequest request = SnapshotRequest.newBuilder().setSnapshot(snapshot).build(); this.<Long> newMasterCaller() .action((controller, stub) -> this.<SnapshotRequest, SnapshotResponse, Long> call( controller, stub, request, (s, c, req, done) -> s.snapshot(c, req, done), resp -> resp.getExpectedTimeout())) .call().whenComplete((expectedTimeout, err) -> { if (err != null) { future.completeExceptionally(err); return; } TimerTask pollingTask = new TimerTask() { int tries = 0; long startTime = EnvironmentEdgeManager.currentTime(); long endTime = startTime + expectedTimeout; long maxPauseTime = expectedTimeout / maxAttempts; @Override public void run(Timeout timeout) throws Exception { if (EnvironmentEdgeManager.currentTime() < endTime) { isSnapshotFinished(snapshotDesc).whenComplete((done, err) -> { if (err != null) { future.completeExceptionally(err); } else if (done) { future.complete(null); } else { // retry again after pauseTime. long pauseTime = ConnectionUtils .getPauseTime(TimeUnit.NANOSECONDS.toMillis(pauseNs), ++tries); pauseTime = Math.min(pauseTime, maxPauseTime); AsyncConnectionImpl.RETRY_TIMER.newTimeout(this, pauseTime, TimeUnit.MILLISECONDS); } }); } else { future.completeExceptionally(new SnapshotCreationException( "Snapshot '" + snapshot.getName() + "' wasn't completed in expectedTime:" + expectedTimeout + " ms", snapshotDesc)); } } }; AsyncConnectionImpl.RETRY_TIMER.newTimeout(pollingTask, 1, TimeUnit.MILLISECONDS); }); return future; } @Override public CompletableFuture<Boolean> isSnapshotFinished(SnapshotDescription snapshot) { return this.<Boolean> newMasterCaller() .action((controller, stub) -> this .<IsSnapshotDoneRequest, IsSnapshotDoneResponse, Boolean> call(controller, stub, IsSnapshotDoneRequest.newBuilder() .setSnapshot(ProtobufUtil.createHBaseProtosSnapshotDesc(snapshot)).build(), (s, c, req, done) -> s.isSnapshotDone(c, req, done), resp -> resp.getDone())) .call(); } @Override public CompletableFuture<Void> restoreSnapshot(String snapshotName) { boolean takeFailSafeSnapshot = this.connection.getConfiguration().getBoolean( HConstants.SNAPSHOT_RESTORE_TAKE_FAILSAFE_SNAPSHOT, HConstants.DEFAULT_SNAPSHOT_RESTORE_TAKE_FAILSAFE_SNAPSHOT); return restoreSnapshot(snapshotName, takeFailSafeSnapshot); } private CompletableFuture<Void> restoreSnapshotWithFailSafe(String snapshotName, TableName tableName, boolean takeFailSafeSnapshot) { if (takeFailSafeSnapshot) { CompletableFuture<Void> future = new CompletableFuture<>(); // Step.1 Take a snapshot of the current state String failSafeSnapshotSnapshotNameFormat = this.connection.getConfiguration().get(HConstants.SNAPSHOT_RESTORE_FAILSAFE_NAME, HConstants.DEFAULT_SNAPSHOT_RESTORE_FAILSAFE_NAME); final String failSafeSnapshotSnapshotName = failSafeSnapshotSnapshotNameFormat.replace("{snapshot.name}", snapshotName) .replace("{table.name}", tableName.toString().replace(TableName.NAMESPACE_DELIM, '.')) .replace("{restore.timestamp}", String.valueOf(EnvironmentEdgeManager.currentTime())); LOG.info("Taking restore-failsafe snapshot: " + failSafeSnapshotSnapshotName); snapshot(failSafeSnapshotSnapshotName, tableName).whenComplete((ret, err) -> { if (err != null) { future.completeExceptionally(err); } else { // Step.2 Restore snapshot internalRestoreSnapshot(snapshotName, tableName).whenComplete((ret2, err2) -> { if (err2 != null) { // Step.3.a Something went wrong during the restore and try to rollback. internalRestoreSnapshot(failSafeSnapshotSnapshotName, tableName) .whenComplete((ret3, err3) -> { if (err3 != null) { future.completeExceptionally(err3); } else { String msg = "Restore snapshot=" + snapshotName + " failed. Rollback to snapshot=" + failSafeSnapshotSnapshotName + " succeeded."; future.completeExceptionally(new RestoreSnapshotException(msg)); } }); } else { // Step.3.b If the restore is succeeded, delete the pre-restore snapshot. LOG.info("Deleting restore-failsafe snapshot: " + failSafeSnapshotSnapshotName); deleteSnapshot(failSafeSnapshotSnapshotName).whenComplete((ret3, err3) -> { if (err3 != null) { LOG.error( "Unable to remove the failsafe snapshot: " + failSafeSnapshotSnapshotName, err3); future.completeExceptionally(err3); } else { future.complete(ret3); } }); } }); } }); return future; } else { return internalRestoreSnapshot(snapshotName, tableName); } } @Override public CompletableFuture<Void> restoreSnapshot(String snapshotName, boolean takeFailSafeSnapshot) { CompletableFuture<Void> future = new CompletableFuture<>(); listSnapshots(snapshotName).whenComplete((snapshotDescriptions, err) -> { if (err != null) { future.completeExceptionally(err); return; } TableName tableName = null; if (snapshotDescriptions != null && !snapshotDescriptions.isEmpty()) { for (SnapshotDescription snap : snapshotDescriptions) { if (snap.getName().equals(snapshotName)) { tableName = snap.getTableName(); break; } } } if (tableName == null) { future.completeExceptionally(new RestoreSnapshotException( "Unable to find the table name for snapshot=" + snapshotName)); return; } final TableName finalTableName = tableName; tableExists(finalTableName).whenComplete((exists, err2) -> { if (err2 != null) { future.completeExceptionally(err2); } else if (!exists) { // if table does not exist, then just clone snapshot into new table. internalRestoreSnapshot(snapshotName, finalTableName).whenComplete((ret, err3) -> { if (err3 != null) { future.completeExceptionally(err3); } else { future.complete(ret); } }); } else { isTableDisabled(finalTableName).whenComplete((disabled, err4) -> { if (err4 != null) { future.completeExceptionally(err4); } else if (!disabled) { future.completeExceptionally(new TableNotDisabledException(finalTableName)); } else { restoreSnapshotWithFailSafe(snapshotName, finalTableName, takeFailSafeSnapshot) .whenComplete((ret, err5) -> { if (err5 != null) { future.completeExceptionally(err5); } else { future.complete(ret); } }); } }); } }); }); return future; } @Override public CompletableFuture<Void> cloneSnapshot(String snapshotName, TableName tableName) { CompletableFuture<Void> future = new CompletableFuture<>(); tableExists(tableName).whenComplete((exists, err) -> { if (err != null) { future.completeExceptionally(err); } else if (exists) { future.completeExceptionally(new TableExistsException(tableName)); } else { internalRestoreSnapshot(snapshotName, tableName).whenComplete((ret, err2) -> { if (err2 != null) { future.completeExceptionally(err2); } else { future.complete(ret); } }); } }); return future; } private CompletableFuture<Void> internalRestoreSnapshot(String snapshotName, TableName tableName) { SnapshotProtos.SnapshotDescription snapshot = SnapshotProtos.SnapshotDescription.newBuilder() .setName(snapshotName).setTable(tableName.getNameAsString()).build(); try { ClientSnapshotDescriptionUtils.assertSnapshotRequestIsValid(snapshot); } catch (IllegalArgumentException e) { return failedFuture(e); } return this.<Void> newMasterCaller() .action((controller, stub) -> this .<RestoreSnapshotRequest, RestoreSnapshotResponse, Void> call(controller, stub, RestoreSnapshotRequest.newBuilder().setSnapshot(snapshot) .setNonceGroup(ng.getNonceGroup()).setNonce(ng.newNonce()).build(), (s, c, req, done) -> s.restoreSnapshot(c, req, done), resp -> null)) .call(); } @Override public CompletableFuture<List<SnapshotDescription>> listSnapshots() { return this.<List<SnapshotDescription>> newMasterCaller() .action((controller, stub) -> this .<GetCompletedSnapshotsRequest, GetCompletedSnapshotsResponse, List<SnapshotDescription>> call( controller, stub, GetCompletedSnapshotsRequest.newBuilder().build(), (s, c, req, done) -> s.getCompletedSnapshots(c, req, done), resp -> resp.getSnapshotsList().stream().map(ProtobufUtil::createSnapshotDesc) .collect(Collectors.toList()))) .call(); } @Override public CompletableFuture<List<SnapshotDescription>> listSnapshots(String regex) { return listSnapshots(Pattern.compile(regex)); } @Override public CompletableFuture<List<SnapshotDescription>> listSnapshots(Pattern pattern) { CompletableFuture<List<SnapshotDescription>> future = new CompletableFuture<>(); listSnapshots().whenComplete((snapshotDescList, err) -> { if (err != null) { future.completeExceptionally(err); return; } if (snapshotDescList == null || snapshotDescList.isEmpty()) { future.complete(Collections.emptyList()); return; } future.complete(snapshotDescList.stream() .filter(snap -> pattern.matcher(snap.getName()).matches()).collect(Collectors.toList())); }); return future; } @Override public CompletableFuture<List<SnapshotDescription>> listTableSnapshots(String tableNameRegex, String snapshotNameRegex) { return listTableSnapshots(Pattern.compile(tableNameRegex), Pattern.compile(snapshotNameRegex)); } @Override public CompletableFuture<List<SnapshotDescription>> listTableSnapshots(Pattern tableNamePattern, Pattern snapshotNamePattern) { CompletableFuture<List<SnapshotDescription>> future = new CompletableFuture<>(); listTableNames(tableNamePattern, false).whenComplete((tableNames, err) -> { if (err != null) { future.completeExceptionally(err); return; } if (tableNames == null || tableNames.length <= 0) { future.complete(Collections.emptyList()); return; } List<TableName> tableNameList = Arrays.asList(tableNames); listSnapshots(snapshotNamePattern).whenComplete((snapshotDescList, err2) -> { if (err2 != null) { future.completeExceptionally(err2); return; } if (snapshotDescList == null || snapshotDescList.isEmpty()) { future.complete(Collections.emptyList()); return; } future.complete(snapshotDescList.stream() .filter(snap -> (snap != null && tableNameList.contains(snap.getTableName()))) .collect(Collectors.toList())); }); }); return future; } @Override public CompletableFuture<Void> deleteSnapshot(String snapshotName) { return internalDeleteSnapshot(new SnapshotDescription(snapshotName)); } @Override public CompletableFuture<Void> deleteSnapshots(String regex) { return deleteSnapshots(Pattern.compile(regex)); } @Override public CompletableFuture<Void> deleteSnapshots(Pattern snapshotNamePattern) { return deleteTableSnapshots(null, snapshotNamePattern); } @Override public CompletableFuture<Void> deleteTableSnapshots(String tableNameRegex, String snapshotNameRegex) { return deleteTableSnapshots(Pattern.compile(tableNameRegex), Pattern.compile(snapshotNameRegex)); } @Override public CompletableFuture<Void> deleteTableSnapshots(Pattern tableNamePattern, Pattern snapshotNamePattern) { CompletableFuture<Void> future = new CompletableFuture<>(); listTableSnapshots(tableNamePattern, snapshotNamePattern) .whenComplete(((snapshotDescriptions, err) -> { if (err != null) { future.completeExceptionally(err); return; } if (snapshotDescriptions == null || snapshotDescriptions.isEmpty()) { future.complete(null); return; } List<CompletableFuture<Void>> deleteSnapshotFutures = new ArrayList<>(); snapshotDescriptions .forEach(snapDesc -> deleteSnapshotFutures.add(internalDeleteSnapshot(snapDesc))); CompletableFuture .allOf(deleteSnapshotFutures .toArray(new CompletableFuture<?>[deleteSnapshotFutures.size()])) .thenAccept(v -> future.complete(v)); })); return future; } @Override public CompletableFuture<Void> execProcedure(String signature, String instance, Map<String, String> props) { CompletableFuture<Void> future = new CompletableFuture<>(); ProcedureDescription procDesc = ProtobufUtil.buildProcedureDescription(signature, instance, props); this.<Long> newMasterCaller() .action((controller, stub) -> this.<ExecProcedureRequest, ExecProcedureResponse, Long> call( controller, stub, ExecProcedureRequest.newBuilder().setProcedure(procDesc).build(), (s, c, req, done) -> s.execProcedure(c, req, done), resp -> resp.getExpectedTimeout())) .call().whenComplete((expectedTimeout, err) -> { if (err != null) { future.completeExceptionally(err); return; } TimerTask pollingTask = new TimerTask() { int tries = 0; long startTime = EnvironmentEdgeManager.currentTime(); long endTime = startTime + expectedTimeout; long maxPauseTime = expectedTimeout / maxAttempts; @Override public void run(Timeout timeout) throws Exception { if (EnvironmentEdgeManager.currentTime() < endTime) { isProcedureFinished(signature, instance, props).whenComplete((done, err) -> { if (err != null) { future.completeExceptionally(err); return; } if (done) { future.complete(null); } else { // retry again after pauseTime. long pauseTime = ConnectionUtils .getPauseTime(TimeUnit.NANOSECONDS.toMillis(pauseNs), ++tries); pauseTime = Math.min(pauseTime, maxPauseTime); AsyncConnectionImpl.RETRY_TIMER.newTimeout(this, pauseTime, TimeUnit.MICROSECONDS); } }); } else { future.completeExceptionally(new IOException("Procedure '" + signature + " : " + instance + "' wasn't completed in expectedTime:" + expectedTimeout + " ms")); } } }; // Queue the polling task into RETRY_TIMER to poll procedure state asynchronously. AsyncConnectionImpl.RETRY_TIMER.newTimeout(pollingTask, 1, TimeUnit.MILLISECONDS); }); return future; } @Override public CompletableFuture<byte[]> execProcedureWithRet(String signature, String instance, Map<String, String> props) { ProcedureDescription proDesc = ProtobufUtil.buildProcedureDescription(signature, instance, props); return this.<byte[]> newMasterCaller() .action( (controller, stub) -> this.<ExecProcedureRequest, ExecProcedureResponse, byte[]> call( controller, stub, ExecProcedureRequest.newBuilder().setProcedure(proDesc).build(), (s, c, req, done) -> s.execProcedureWithRet(c, req, done), resp -> resp.hasReturnData() ? resp.getReturnData().toByteArray() : null)) .call(); } @Override public CompletableFuture<Boolean> isProcedureFinished(String signature, String instance, Map<String, String> props) { ProcedureDescription proDesc = ProtobufUtil.buildProcedureDescription(signature, instance, props); return this.<Boolean> newMasterCaller() .action((controller, stub) -> this .<IsProcedureDoneRequest, IsProcedureDoneResponse, Boolean> call(controller, stub, IsProcedureDoneRequest.newBuilder().setProcedure(proDesc).build(), (s, c, req, done) -> s.isProcedureDone(c, req, done), resp -> resp.getDone())) .call(); } @Override public CompletableFuture<Boolean> abortProcedure(long procId, boolean mayInterruptIfRunning) { return this.<Boolean> newMasterCaller().action( (controller, stub) -> this.<AbortProcedureRequest, AbortProcedureResponse, Boolean> call( controller, stub, AbortProcedureRequest.newBuilder().setProcId(procId).build(), (s, c, req, done) -> s.abortProcedure(c, req, done), resp -> resp.getIsProcedureAborted())) .call(); } @Override public CompletableFuture<ProcedureInfo[]> listProcedures() { return this.<ProcedureInfo[]> newMasterCaller() .action((controller, stub) -> this .<ListProceduresRequest, ListProceduresResponse, ProcedureInfo[]> call(controller, stub, ListProceduresRequest.newBuilder().build(), (s, c, req, done) -> s.listProcedures(c, req, done), resp -> resp.getProcedureList() .stream().map(ProtobufUtil::toProcedureInfo).toArray(ProcedureInfo[]::new))) .call(); } private CompletableFuture<Void> internalDeleteSnapshot(SnapshotDescription snapshot) { return this.<Void> newMasterCaller() .action((controller, stub) -> this .<DeleteSnapshotRequest, DeleteSnapshotResponse, Void> call(controller, stub, DeleteSnapshotRequest.newBuilder() .setSnapshot(ProtobufUtil.createHBaseProtosSnapshotDesc(snapshot)).build(), (s, c, req, done) -> s.deleteSnapshot(c, req, done), resp -> null)) .call(); } private byte[][] getSplitKeys(byte[] startKey, byte[] endKey, int numRegions) { if (numRegions < 3) { throw new IllegalArgumentException("Must create at least three regions"); } else if (Bytes.compareTo(startKey, endKey) >= 0) { throw new IllegalArgumentException("Start key must be smaller than end key"); } if (numRegions == 3) { return new byte[][] { startKey, endKey }; } byte[][] splitKeys = Bytes.split(startKey, endKey, numRegions - 3); if (splitKeys == null || splitKeys.length != numRegions - 1) { throw new IllegalArgumentException("Unable to split key range into enough regions"); } return splitKeys; } private abstract class ProcedureBiConsumer implements BiConsumer<Void, Throwable> { protected final AsyncAdmin admin; ProcedureBiConsumer(AsyncAdmin admin) { this.admin = admin; } abstract void onFinished(); abstract void onError(Throwable error); @Override public void accept(Void v, Throwable error) { if (error != null) { onError(error); return; } onFinished(); } } private abstract class TableProcedureBiConsumer extends ProcedureBiConsumer { protected final TableName tableName; TableProcedureBiConsumer(final AsyncAdmin admin, final TableName tableName) { super(admin); this.tableName = tableName; } abstract String getOperationType(); String getDescription() { return "Operation: " + getOperationType() + ", " + "Table Name: " + tableName.getNameWithNamespaceInclAsString(); } @Override void onFinished() { LOG.info(getDescription() + " completed"); } @Override void onError(Throwable error) { LOG.info(getDescription() + " failed with " + error.getMessage()); } } private abstract class NamespaceProcedureBiConsumer extends ProcedureBiConsumer { protected final String namespaceName; NamespaceProcedureBiConsumer(final AsyncAdmin admin, final String namespaceName) { super(admin); this.namespaceName = namespaceName; } abstract String getOperationType(); String getDescription() { return "Operation: " + getOperationType() + ", Namespace: " + namespaceName; } @Override void onFinished() { LOG.info(getDescription() + " completed"); } @Override void onError(Throwable error) { LOG.info(getDescription() + " failed with " + error.getMessage()); } } private class CreateTableProcedureBiConsumer extends TableProcedureBiConsumer { CreateTableProcedureBiConsumer(AsyncAdmin admin, TableName tableName) { super(admin, tableName); } String getOperationType() { return "CREATE"; } } private class DeleteTableProcedureBiConsumer extends TableProcedureBiConsumer { DeleteTableProcedureBiConsumer(AsyncAdmin admin, TableName tableName) { super(admin, tableName); } String getOperationType() { return "DELETE"; } @Override void onFinished() { this.admin.getConnection().getLocator().clearCache(this.tableName); super.onFinished(); } } private class TruncateTableProcedureBiConsumer extends TableProcedureBiConsumer { TruncateTableProcedureBiConsumer(AsyncAdmin admin, TableName tableName) { super(admin, tableName); } String getOperationType() { return "TRUNCATE"; } } private class EnableTableProcedureBiConsumer extends TableProcedureBiConsumer { EnableTableProcedureBiConsumer(AsyncAdmin admin, TableName tableName) { super(admin, tableName); } String getOperationType() { return "ENABLE"; } } private class DisableTableProcedureBiConsumer extends TableProcedureBiConsumer { DisableTableProcedureBiConsumer(AsyncAdmin admin, TableName tableName) { super(admin, tableName); } String getOperationType() { return "DISABLE"; } } private class AddColumnFamilyProcedureBiConsumer extends TableProcedureBiConsumer { AddColumnFamilyProcedureBiConsumer(AsyncAdmin admin, TableName tableName) { super(admin, tableName); } String getOperationType() { return "ADD_COLUMN_FAMILY"; } } private class DeleteColumnFamilyProcedureBiConsumer extends TableProcedureBiConsumer { DeleteColumnFamilyProcedureBiConsumer(AsyncAdmin admin, TableName tableName) { super(admin, tableName); } String getOperationType() { return "DELETE_COLUMN_FAMILY"; } } private class ModifyColumnFamilyProcedureBiConsumer extends TableProcedureBiConsumer { ModifyColumnFamilyProcedureBiConsumer(AsyncAdmin admin, TableName tableName) { super(admin, tableName); } String getOperationType() { return "MODIFY_COLUMN_FAMILY"; } } private class CreateNamespaceProcedureBiConsumer extends NamespaceProcedureBiConsumer { CreateNamespaceProcedureBiConsumer(AsyncAdmin admin, String namespaceName) { super(admin, namespaceName); } String getOperationType() { return "CREATE_NAMESPACE"; } } private class DeleteNamespaceProcedureBiConsumer extends NamespaceProcedureBiConsumer { DeleteNamespaceProcedureBiConsumer(AsyncAdmin admin, String namespaceName) { super(admin, namespaceName); } String getOperationType() { return "DELETE_NAMESPACE"; } } private class ModifyNamespaceProcedureBiConsumer extends NamespaceProcedureBiConsumer { ModifyNamespaceProcedureBiConsumer(AsyncAdmin admin, String namespaceName) { super(admin, namespaceName); } String getOperationType() { return "MODIFY_NAMESPACE"; } } private class MergeTableRegionProcedureBiConsumer extends TableProcedureBiConsumer { MergeTableRegionProcedureBiConsumer(AsyncAdmin admin, TableName tableName) { super(admin, tableName); } String getOperationType() { return "MERGE_REGIONS"; } } private CompletableFuture<Void> waitProcedureResult(CompletableFuture<Long> procFuture) { CompletableFuture<Void> future = new CompletableFuture<>(); procFuture.whenComplete((procId, error) -> { if (error != null) { future.completeExceptionally(error); return; } getProcedureResult(procId, future); }); return future; } private void getProcedureResult(final long procId, CompletableFuture<Void> future) { this.<GetProcedureResultResponse> newMasterCaller() .action( (controller, stub) -> this .<GetProcedureResultRequest, GetProcedureResultResponse, GetProcedureResultResponse> call( controller, stub, GetProcedureResultRequest.newBuilder().setProcId(procId).build(), (s, c, req, done) -> s.getProcedureResult(c, req, done), (resp) -> resp)) .call() .whenComplete( (response, error) -> { if (error != null) { LOG.warn("failed to get the procedure result procId=" + procId, ConnectionUtils.translateException(error)); connection.RETRY_TIMER.newTimeout(t -> getProcedureResult(procId, future), pauseNs, TimeUnit.NANOSECONDS); return; } if (response.getState() == GetProcedureResultResponse.State.RUNNING) { connection.RETRY_TIMER.newTimeout(t -> getProcedureResult(procId, future), pauseNs, TimeUnit.NANOSECONDS); return; } if (response.hasException()) { IOException ioe = ForeignExceptionUtil.toIOException(response.getException()); future.completeExceptionally(ioe); } else { future.complete(null); } }); } private <T> CompletableFuture<T> failedFuture(Throwable error) { CompletableFuture<T> future = new CompletableFuture<>(); future.completeExceptionally(error); return future; } private <T> boolean completeExceptionally(CompletableFuture<T> future, Throwable error) { if (error != null) { future.completeExceptionally(error); return true; } return false; } }