/** * 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.assignment; import static org.mockito.ArgumentMatchers.any; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.NavigableMap; import java.util.SortedSet; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.hbase.CoordinatedStateManager; import org.apache.hadoop.hbase.ServerMetricsBuilder; import org.apache.hadoop.hbase.ServerName; import org.apache.hadoop.hbase.TableDescriptors; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; import org.apache.hadoop.hbase.client.Connection; import org.apache.hadoop.hbase.client.HConnectionTestingUtility; import org.apache.hadoop.hbase.client.TableDescriptor; import org.apache.hadoop.hbase.client.TableDescriptorBuilder; import org.apache.hadoop.hbase.client.TableState; import org.apache.hadoop.hbase.master.LoadBalancer; import org.apache.hadoop.hbase.master.MasterFileSystem; import org.apache.hadoop.hbase.master.MasterServices; import org.apache.hadoop.hbase.master.MasterWalManager; import org.apache.hadoop.hbase.master.MockNoopMasterServices; import org.apache.hadoop.hbase.master.ServerManager; import org.apache.hadoop.hbase.master.TableStateManager; import org.apache.hadoop.hbase.master.balancer.LoadBalancerFactory; import org.apache.hadoop.hbase.master.procedure.MasterProcedureConstants; import org.apache.hadoop.hbase.master.procedure.MasterProcedureEnv; import org.apache.hadoop.hbase.master.procedure.RSProcedureDispatcher; import org.apache.hadoop.hbase.procedure2.ProcedureEvent; import org.apache.hadoop.hbase.procedure2.ProcedureExecutor; import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility; import org.apache.hadoop.hbase.procedure2.store.NoopProcedureStore; import org.apache.hadoop.hbase.procedure2.store.ProcedureStore; import org.apache.hadoop.hbase.procedure2.store.ProcedureStore.ProcedureStoreListener; import org.apache.hadoop.hbase.security.Superusers; import org.apache.hadoop.hbase.util.CommonFSUtils; import org.apache.zookeeper.KeeperException; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.apache.hbase.thirdparty.com.google.protobuf.ServiceException; import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos; import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos.MultiRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos.MultiResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos.MutateResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos.RegionAction; import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos.RegionActionResult; import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos.ResultOrException; /** * A mocked master services. * Tries to fake it. May not always work. */ public class MockMasterServices extends MockNoopMasterServices { private final MasterFileSystem fileSystemManager; private final MasterWalManager walManager; private final AssignmentManager assignmentManager; private final TableStateManager tableStateManager; private MasterProcedureEnv procedureEnv; private ProcedureExecutor<MasterProcedureEnv> procedureExecutor; private ProcedureStore procedureStore; private final Connection connection; private final LoadBalancer balancer; private final ServerManager serverManager; private final ProcedureEvent<?> initialized = new ProcedureEvent<>("master initialized"); public static final String DEFAULT_COLUMN_FAMILY_NAME = "cf"; public static final ServerName MOCK_MASTER_SERVERNAME = ServerName.valueOf("mockmaster.example.org", 1234, -1L); public MockMasterServices(Configuration conf, NavigableMap<ServerName, SortedSet<byte[]>> regionsToRegionServers) throws IOException { super(conf); Superusers.initialize(conf); this.fileSystemManager = new MasterFileSystem(conf); this.walManager = new MasterWalManager(this); // Mock an AM. this.assignmentManager = new AssignmentManager(this, new MockRegionStateStore(this)) { @Override public boolean isTableEnabled(final TableName tableName) { return true; } @Override public boolean isTableDisabled(final TableName tableName) { return false; } }; this.balancer = LoadBalancerFactory.getLoadBalancer(conf); this.serverManager = new ServerManager(this); this.tableStateManager = Mockito.mock(TableStateManager.class); Mockito.when(this.tableStateManager.getTableState(Mockito.any())). thenReturn(new TableState(TableName.valueOf("AnyTableNameSetInMockMasterServcies"), TableState.State.ENABLED)); // Mock up a Client Interface ClientProtos.ClientService.BlockingInterface ri = Mockito.mock(ClientProtos.ClientService.BlockingInterface.class); MutateResponse.Builder builder = MutateResponse.newBuilder(); builder.setProcessed(true); try { Mockito.when(ri.mutate(any(), any())).thenReturn(builder.build()); } catch (ServiceException se) { throw ProtobufUtil.handleRemoteException(se); } try { Mockito.when(ri.multi(any(), any())).thenAnswer(new Answer<MultiResponse>() { @Override public MultiResponse answer(InvocationOnMock invocation) throws Throwable { return buildMultiResponse(invocation.getArgument(1)); } }); } catch (ServiceException se) { throw ProtobufUtil.getRemoteException(se); } this.connection = HConnectionTestingUtility.getMockedConnection(getConfiguration()); // Set hbase.rootdir into test dir. Path rootdir = CommonFSUtils.getRootDir(getConfiguration()); CommonFSUtils.setRootDir(getConfiguration(), rootdir); } public void start(final int numServes, final RSProcedureDispatcher remoteDispatcher) throws IOException, KeeperException { startProcedureExecutor(remoteDispatcher); this.assignmentManager.start(); for (int i = 0; i < numServes; ++i) { ServerName sn = ServerName.valueOf("localhost", 100 + i, 1); serverManager.regionServerReport(sn, ServerMetricsBuilder.of(sn)); } this.procedureExecutor.getEnvironment().setEventReady(initialized, true); } /** * Call this restart method only after running MockMasterServices#start() * The RSs can be differentiated by the port number, see * ServerName in MockMasterServices#start() method above. * Restart of region server will have new startcode in server name * * @param serverName Server name to be restarted */ public void restartRegionServer(ServerName serverName) throws IOException { List<ServerName> onlineServers = serverManager.getOnlineServersList(); long startCode = -1; for (ServerName s : onlineServers) { if (s.getAddress().equals(serverName.getAddress())) { startCode = s.getStartcode() + 1; break; } } if (startCode == -1) { return; } ServerName sn = ServerName.valueOf(serverName.getAddress().toString(), startCode); serverManager.regionServerReport(sn, ServerMetricsBuilder.of(sn)); } @Override public void stop(String why) { stopProcedureExecutor(); this.assignmentManager.stop(); } private void startProcedureExecutor(final RSProcedureDispatcher remoteDispatcher) throws IOException { final Configuration conf = getConfiguration(); this.procedureStore = new NoopProcedureStore(); this.procedureStore.registerListener(new ProcedureStoreListener() { @Override public void abortProcess() { abort("The Procedure Store lost the lease", null); } }); this.procedureEnv = new MasterProcedureEnv(this, remoteDispatcher != null ? remoteDispatcher : new RSProcedureDispatcher(this)); this.procedureExecutor = new ProcedureExecutor<>(conf, procedureEnv, procedureStore, procedureEnv.getProcedureScheduler()); final int numThreads = conf.getInt(MasterProcedureConstants.MASTER_PROCEDURE_THREADS, Math.max(Runtime.getRuntime().availableProcessors(), MasterProcedureConstants.DEFAULT_MIN_MASTER_PROCEDURE_THREADS)); final boolean abortOnCorruption = conf.getBoolean( MasterProcedureConstants.EXECUTOR_ABORT_ON_CORRUPTION, MasterProcedureConstants.DEFAULT_EXECUTOR_ABORT_ON_CORRUPTION); this.procedureStore.start(numThreads); ProcedureTestingUtility.initAndStartWorkers(procedureExecutor, numThreads, abortOnCorruption); this.procedureEnv.getRemoteDispatcher().start(); } private void stopProcedureExecutor() { if (this.procedureEnv != null) { this.procedureEnv.getRemoteDispatcher().stop(); } if (this.procedureExecutor != null) { this.procedureExecutor.stop(); } if (this.procedureStore != null) { this.procedureStore.stop(isAborted()); } } @Override public boolean isInitialized() { return true; } @Override public ProcedureEvent<?> getInitializedEvent() { return this.initialized; } @Override public MasterFileSystem getMasterFileSystem() { return fileSystemManager; } @Override public MasterWalManager getMasterWalManager() { return walManager; } @Override public ProcedureExecutor<MasterProcedureEnv> getMasterProcedureExecutor() { return procedureExecutor; } @Override public LoadBalancer getLoadBalancer() { return balancer; } @Override public ServerManager getServerManager() { return serverManager; } @Override public AssignmentManager getAssignmentManager() { return assignmentManager; } @Override public TableStateManager getTableStateManager() { return tableStateManager; } @Override public Connection getConnection() { return this.connection; } @Override public ServerName getServerName() { return MOCK_MASTER_SERVERNAME; } @Override public CoordinatedStateManager getCoordinatedStateManager() { return super.getCoordinatedStateManager(); } private static class MockRegionStateStore extends RegionStateStore { public MockRegionStateStore(final MasterServices master) { super(master); } @Override public void updateRegionLocation(RegionStateNode regionNode) throws IOException { } } @Override public TableDescriptors getTableDescriptors() { return new TableDescriptors() { @Override public TableDescriptor remove(TableName tablename) throws IOException { // noop return null; } @Override public Map<String, TableDescriptor> getAll() throws IOException { // noop return null; } @Override public TableDescriptor get(TableName tablename) throws IOException { TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(tablename); builder.setColumnFamily(ColumnFamilyDescriptorBuilder.of(DEFAULT_COLUMN_FAMILY_NAME)); return builder.build(); } @Override public Map<String, TableDescriptor> getByNamespace(String name) throws IOException { return null; } @Override public void update(TableDescriptor htd) throws IOException { // noop } @Override public void setCacheOn() throws IOException { } @Override public void setCacheOff() throws IOException { } }; } private static MultiResponse buildMultiResponse(MultiRequest req) { MultiResponse.Builder builder = MultiResponse.newBuilder(); RegionActionResult.Builder regionActionResultBuilder = RegionActionResult.newBuilder(); ResultOrException.Builder roeBuilder = ResultOrException.newBuilder(); for (RegionAction regionAction: req.getRegionActionList()) { regionActionResultBuilder.clear(); for (ClientProtos.Action action: regionAction.getActionList()) { roeBuilder.clear(); roeBuilder.setResult(ClientProtos.Result.getDefaultInstance()); roeBuilder.setIndex(action.getIndex()); regionActionResultBuilder.addResultOrException(roeBuilder.build()); } builder.addRegionActionResult(regionActionResultBuilder.build()); } return builder.build(); } }