001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.master.assignment; 019 020import static org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.RegionStateTransitionState.REGION_STATE_TRANSITION_OPEN; 021import static org.junit.jupiter.api.Assertions.assertThrows; 022import static org.junit.jupiter.api.Assertions.assertTrue; 023 024import java.io.IOException; 025import java.util.ArrayList; 026import java.util.Arrays; 027import java.util.List; 028import java.util.concurrent.CountDownLatch; 029import org.apache.hadoop.hbase.HBaseTestingUtil; 030import org.apache.hadoop.hbase.ServerName; 031import org.apache.hadoop.hbase.TableName; 032import org.apache.hadoop.hbase.client.Admin; 033import org.apache.hadoop.hbase.client.RegionInfo; 034import org.apache.hadoop.hbase.master.assignment.TransitRegionStateProcedure.TransitionType; 035import org.apache.hadoop.hbase.master.procedure.MasterProcedureEnv; 036import org.apache.hadoop.hbase.procedure2.Procedure; 037import org.apache.hadoop.hbase.procedure2.ProcedureSuspendedException; 038import org.apache.hadoop.hbase.procedure2.ProcedureYieldException; 039import org.apache.hadoop.hbase.testclassification.LargeTests; 040import org.apache.hadoop.hbase.util.Bytes; 041import org.junit.jupiter.api.AfterAll; 042import org.junit.jupiter.api.BeforeAll; 043import org.junit.jupiter.api.BeforeEach; 044import org.junit.jupiter.api.Tag; 045import org.junit.jupiter.api.Test; 046import org.junit.jupiter.api.TestInfo; 047import org.slf4j.Logger; 048import org.slf4j.LoggerFactory; 049 050import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.RegionStateTransitionState; 051 052/** 053 * Tests bypass on a region assign/unassign 054 */ 055@Tag(LargeTests.TAG) 056public class TestRegionBypass { 057 private final static Logger LOG = LoggerFactory.getLogger(TestRegionBypass.class); 058 059 private static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil(); 060 private TableName tableName; 061 062 @BeforeAll 063 public static void startCluster() throws Exception { 064 TEST_UTIL.startMiniCluster(2); 065 } 066 067 @AfterAll 068 public static void stopCluster() throws Exception { 069 TEST_UTIL.shutdownMiniCluster(); 070 } 071 072 @BeforeEach 073 public void before(TestInfo testInfo) throws IOException { 074 this.tableName = TableName.valueOf(testInfo.getTestMethod().get().getName()); 075 // Create a table. Has one region at least. 076 TEST_UTIL.createTable(this.tableName, Bytes.toBytes("cf")); 077 078 } 079 080 @Test 081 public void testBypass() throws IOException, InterruptedException { 082 Admin admin = TEST_UTIL.getAdmin(); 083 MasterProcedureEnv env = 084 TEST_UTIL.getHBaseCluster().getMaster().getMasterProcedureExecutor().getEnvironment(); 085 List<RegionInfo> regions = admin.getRegions(this.tableName); 086 for (RegionInfo ri : regions) { 087 admin.unassign(ri.getRegionName()); 088 } 089 List<Long> pids = new ArrayList<>(regions.size()); 090 for (RegionInfo ri : regions) { 091 Procedure<MasterProcedureEnv> p = 092 new StallingAssignProcedure(env, ri, null, false, TransitionType.ASSIGN); 093 pids.add( 094 TEST_UTIL.getHBaseCluster().getMaster().getMasterProcedureExecutor().submitProcedure(p)); 095 } 096 TEST_UTIL.waitFor(30000, () -> pids.stream().allMatch( 097 pid -> TEST_UTIL.getHBaseCluster().getMaster().getMasterProcedureExecutor().isStarted(pid))); 098 List<Procedure<MasterProcedureEnv>> ps = 099 TEST_UTIL.getHBaseCluster().getMaster().getMasterProcedureExecutor().getProcedures(); 100 for (Procedure<MasterProcedureEnv> p : ps) { 101 if (p instanceof StallingAssignProcedure) { 102 List<Boolean> bs = 103 TEST_UTIL.getHbck().bypassProcedure(Arrays.asList(p.getProcId()), 1000, true, false); 104 for (Boolean b : bs) { 105 LOG.info("BYPASSED {} {}", p.getProcId(), b); 106 } 107 } 108 } 109 // Try and assign WITHOUT override flag. Should fail!. 110 for (RegionInfo ri : regions) { 111 IOException error = assertThrows(IOException.class, () -> admin.assign(ri.getRegionName())); 112 LOG.info("Expected {}", error); 113 } 114 TEST_UTIL.waitFor(30000, () -> TEST_UTIL.getHBaseCluster().getMaster() 115 .getMasterProcedureExecutor().getActiveProcIds().isEmpty()); 116 // Now assign with the override flag. 117 for (RegionInfo ri : regions) { 118 TEST_UTIL.getHbck().assigns(Arrays.<String> asList(ri.getEncodedName()), true, true); 119 } 120 TEST_UTIL.waitFor(60000, () -> TEST_UTIL.getHBaseCluster().getMaster() 121 .getMasterProcedureExecutor().getActiveProcIds().isEmpty()); 122 for (RegionInfo ri : regions) { 123 assertTrue(TEST_UTIL.getMiniHBaseCluster().getMaster().getAssignmentManager() 124 .getRegionStates().isRegionOnline(ri), ri.toString()); 125 } 126 } 127 128 /** 129 * An AssignProcedure that Stalls just before the finish. 130 */ 131 public static class StallingAssignProcedure extends TransitRegionStateProcedure { 132 public final CountDownLatch latch = new CountDownLatch(2); 133 134 public StallingAssignProcedure() { 135 } 136 137 public StallingAssignProcedure(MasterProcedureEnv env, RegionInfo hri, 138 ServerName assignCandidate, boolean forceNewPlan, TransitionType type) { 139 super(env, hri, assignCandidate, forceNewPlan, type); 140 init(env); 141 } 142 143 private void init(MasterProcedureEnv env) { 144 RegionStateNode regionNode = 145 env.getAssignmentManager().getRegionStates().getOrCreateRegionStateNode(getRegion()); 146 regionNode.setProcedure(this); 147 } 148 149 @Override 150 protected Flow executeFromState(MasterProcedureEnv env, RegionStateTransitionState state) 151 throws ProcedureSuspendedException, ProcedureYieldException, InterruptedException { 152 // add a sleep so we will not consume all the CPUs and write a bunch of logs 153 Thread.sleep(100); 154 switch (state) { 155 case REGION_STATE_TRANSITION_GET_ASSIGN_CANDIDATE: 156 LOG.info("LATCH1 {}", this.latch.getCount()); 157 this.latch.countDown(); 158 setNextState(REGION_STATE_TRANSITION_OPEN); 159 return Flow.HAS_MORE_STATE; 160 case REGION_STATE_TRANSITION_OPEN: 161 if (latch.getCount() == 0) { 162 LOG.info("LATCH3 {}", this.latch.getCount()); 163 return Flow.NO_MORE_STATE; 164 } else { 165 LOG.info("LATCH2 {}", this.latch.getCount()); 166 return Flow.HAS_MORE_STATE; 167 } 168 default: 169 throw new UnsupportedOperationException("unhandled state=" + state); 170 } 171 } 172 } 173}