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.Assert.assertThrows; 022import static org.junit.Assert.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.HBaseClassTestRule; 030import org.apache.hadoop.hbase.HBaseTestingUtility; 031import org.apache.hadoop.hbase.ServerName; 032import org.apache.hadoop.hbase.TableName; 033import org.apache.hadoop.hbase.client.Admin; 034import org.apache.hadoop.hbase.client.RegionInfo; 035import org.apache.hadoop.hbase.master.assignment.TransitRegionStateProcedure.TransitionType; 036import org.apache.hadoop.hbase.master.procedure.MasterProcedureEnv; 037import org.apache.hadoop.hbase.procedure2.Procedure; 038import org.apache.hadoop.hbase.procedure2.ProcedureSuspendedException; 039import org.apache.hadoop.hbase.procedure2.ProcedureYieldException; 040import org.apache.hadoop.hbase.testclassification.LargeTests; 041import org.apache.hadoop.hbase.util.Bytes; 042import org.junit.AfterClass; 043import org.junit.Before; 044import org.junit.BeforeClass; 045import org.junit.ClassRule; 046import org.junit.Rule; 047import org.junit.Test; 048import org.junit.experimental.categories.Category; 049import org.junit.rules.TestName; 050import org.slf4j.Logger; 051import org.slf4j.LoggerFactory; 052 053import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.RegionStateTransitionState; 054 055/** 056 * Tests bypass on a region assign/unassign 057 */ 058@Category({ LargeTests.class }) 059public class TestRegionBypass { 060 private final static Logger LOG = LoggerFactory.getLogger(TestRegionBypass.class); 061 062 @ClassRule 063 public static final HBaseClassTestRule CLASS_RULE = 064 HBaseClassTestRule.forClass(TestRegionBypass.class); 065 066 @Rule 067 public TestName name = new TestName(); 068 069 private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 070 private TableName tableName; 071 072 @BeforeClass 073 public static void startCluster() throws Exception { 074 TEST_UTIL.startMiniCluster(2); 075 } 076 077 @AfterClass 078 public static void stopCluster() throws Exception { 079 TEST_UTIL.shutdownMiniCluster(); 080 } 081 082 @Before 083 public void before() throws IOException { 084 this.tableName = TableName.valueOf(this.name.getMethodName()); 085 // Create a table. Has one region at least. 086 TEST_UTIL.createTable(this.tableName, Bytes.toBytes("cf")); 087 088 } 089 090 @Test 091 public void testBypass() throws IOException, InterruptedException { 092 Admin admin = TEST_UTIL.getAdmin(); 093 MasterProcedureEnv env = 094 TEST_UTIL.getHBaseCluster().getMaster().getMasterProcedureExecutor().getEnvironment(); 095 List<RegionInfo> regions = admin.getRegions(this.tableName); 096 for (RegionInfo ri : regions) { 097 admin.unassign(ri.getRegionName()); 098 } 099 List<Long> pids = new ArrayList<>(regions.size()); 100 for (RegionInfo ri : regions) { 101 Procedure<MasterProcedureEnv> p = 102 new StallingAssignProcedure(env, ri, null, false, TransitionType.ASSIGN); 103 pids.add( 104 TEST_UTIL.getHBaseCluster().getMaster().getMasterProcedureExecutor().submitProcedure(p)); 105 } 106 TEST_UTIL.waitFor(30000, () -> pids.stream().allMatch( 107 pid -> TEST_UTIL.getHBaseCluster().getMaster().getMasterProcedureExecutor().isStarted(pid))); 108 List<Procedure<MasterProcedureEnv>> ps = 109 TEST_UTIL.getHBaseCluster().getMaster().getMasterProcedureExecutor().getProcedures(); 110 for (Procedure<MasterProcedureEnv> p : ps) { 111 if (p instanceof StallingAssignProcedure) { 112 List<Boolean> bs = 113 TEST_UTIL.getHbck().bypassProcedure(Arrays.asList(p.getProcId()), 1000, true, false); 114 for (Boolean b : bs) { 115 LOG.info("BYPASSED {} {}", p.getProcId(), b); 116 } 117 } 118 } 119 // Try and assign WITHOUT override flag. Should fail!. 120 for (RegionInfo ri : regions) { 121 IOException error = assertThrows(IOException.class, () -> admin.assign(ri.getRegionName())); 122 LOG.info("Expected {}", error); 123 } 124 TEST_UTIL.waitFor(30000, () -> TEST_UTIL.getHBaseCluster().getMaster() 125 .getMasterProcedureExecutor().getActiveProcIds().isEmpty()); 126 // Now assign with the override flag. 127 for (RegionInfo ri : regions) { 128 TEST_UTIL.getHbck().assigns(Arrays.<String> asList(ri.getEncodedName()), true); 129 } 130 TEST_UTIL.waitFor(60000, () -> TEST_UTIL.getHBaseCluster().getMaster() 131 .getMasterProcedureExecutor().getActiveProcIds().isEmpty()); 132 for (RegionInfo ri : regions) { 133 assertTrue(ri.toString(), TEST_UTIL.getMiniHBaseCluster().getMaster().getAssignmentManager() 134 .getRegionStates().isRegionOnline(ri)); 135 } 136 } 137 138 /** 139 * An AssignProcedure that Stalls just before the finish. 140 */ 141 public static class StallingAssignProcedure extends TransitRegionStateProcedure { 142 public final CountDownLatch latch = new CountDownLatch(2); 143 144 public StallingAssignProcedure() { 145 } 146 147 public StallingAssignProcedure(MasterProcedureEnv env, RegionInfo hri, 148 ServerName assignCandidate, boolean forceNewPlan, TransitionType type) { 149 super(env, hri, assignCandidate, forceNewPlan, type); 150 init(env); 151 } 152 153 private void init(MasterProcedureEnv env) { 154 RegionStateNode regionNode = 155 env.getAssignmentManager().getRegionStates().getOrCreateRegionStateNode(getRegion()); 156 regionNode.setProcedure(this); 157 } 158 159 @Override 160 protected Flow executeFromState(MasterProcedureEnv env, RegionStateTransitionState state) 161 throws ProcedureSuspendedException, ProcedureYieldException, InterruptedException { 162 // add a sleep so we will not consume all the CPUs and write a bunch of logs 163 Thread.sleep(100); 164 switch (state) { 165 case REGION_STATE_TRANSITION_GET_ASSIGN_CANDIDATE: 166 LOG.info("LATCH1 {}", this.latch.getCount()); 167 this.latch.countDown(); 168 setNextState(REGION_STATE_TRANSITION_OPEN); 169 return Flow.HAS_MORE_STATE; 170 case REGION_STATE_TRANSITION_OPEN: 171 if (latch.getCount() == 0) { 172 LOG.info("LATCH3 {}", this.latch.getCount()); 173 return Flow.NO_MORE_STATE; 174 } else { 175 LOG.info("LATCH2 {}", this.latch.getCount()); 176 return Flow.HAS_MORE_STATE; 177 } 178 default: 179 throw new UnsupportedOperationException("unhandled state=" + state); 180 } 181 } 182 } 183}