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.junit.Assert.assertTrue; 021 022import java.util.List; 023import java.util.concurrent.atomic.AtomicBoolean; 024import java.util.stream.Collectors; 025 026import org.apache.hadoop.hbase.HBaseClassTestRule; 027import org.apache.hadoop.hbase.HBaseIOException; 028import org.apache.hadoop.hbase.HBaseTestingUtility; 029import org.apache.hadoop.hbase.TableName; 030import org.apache.hadoop.hbase.client.Admin; 031import org.apache.hadoop.hbase.client.Put; 032import org.apache.hadoop.hbase.client.RegionInfo; 033import org.apache.hadoop.hbase.client.Table; 034import org.apache.hadoop.hbase.master.HMaster; 035import org.apache.hadoop.hbase.master.RegionPlan; 036import org.apache.hadoop.hbase.master.RegionState; 037import org.apache.hadoop.hbase.master.procedure.MasterProcedureEnv; 038import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility; 039import org.apache.hadoop.hbase.regionserver.HRegionServer; 040import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos; 041import org.apache.hadoop.hbase.testclassification.LargeTests; 042import org.apache.hadoop.hbase.util.Bytes; 043import org.junit.AfterClass; 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 053/** 054 * Like TestRegionMove in regionserver package but in here in this package so I can get access to 055 * Procedure internals to mess with the assignment to manufacture states seen out on clusters. 056 */ 057@Category({LargeTests.class}) 058public class TestRegionMove2 { 059 private final static Logger LOG = LoggerFactory.getLogger(TestRegionMove2.class); 060 061 @ClassRule 062 public static final HBaseClassTestRule CLASS_RULE = 063 HBaseClassTestRule.forClass(TestRegionMove2.class); 064 065 @Rule 066 public TestName name = new TestName(); 067 068 private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 069 070 @BeforeClass 071 public static void startCluster() throws Exception { 072 TEST_UTIL.startMiniCluster(2); 073 } 074 075 @AfterClass 076 public static void stopCluster() throws Exception { 077 TEST_UTIL.shutdownMiniCluster(); 078 } 079 080 /** 081 * Test that we make it through to the end if parent Region is offlined between start of this 082 * Move and when we go to run the move UnassignProcedure. 083 */ 084 @Test 085 public void testMoveOfRegionOfflinedPostStart() throws Exception { 086 TableName tableName = TableName.valueOf(this.name.getMethodName()); 087 // Create a table with more than one region 088 byte [] cf = Bytes.toBytes("cf"); 089 Table t = TEST_UTIL.createMultiRegionTable(tableName, cf, 10); 090 TEST_UTIL.waitUntilAllRegionsAssigned(tableName); 091 HRegionServer rs1 = null; 092 HRegionServer rs2 = null; 093 List<RegionInfo> regionsOnRS1ForTable = null; 094 try (Admin admin = TEST_UTIL.getAdmin()) { 095 // Write an update to each region 096 for (RegionInfo regionInfo : admin.getRegions(tableName)) { 097 byte[] startKey = regionInfo.getStartKey(); 098 // StartKey of first region is "empty", which would throw an error if we try to Put that. 099 byte[] rowKey = 100 org.apache.hbase.thirdparty.com.google.common.primitives.Bytes.concat(startKey, 101 Bytes.toBytes("1")); 102 Put p = new Put(rowKey); 103 p.addColumn(cf, Bytes.toBytes("q1"), Bytes.toBytes("value")); 104 t.put(p); 105 } 106 107 // Get a Region which is on the first RS 108 rs1 = TEST_UTIL.getRSForFirstRegionInTable(tableName); 109 rs2 = TEST_UTIL.getOtherRegionServer(rs1); 110 regionsOnRS1ForTable = admin.getRegions(rs1.getServerName()).stream(). 111 filter((regionInfo) -> regionInfo.getTable().equals(tableName)). 112 collect(Collectors.toList()); 113 } 114 assertTrue("Expected to find at least one region for " + tableName + " on " + 115 rs1.getServerName() + ", but found none", !regionsOnRS1ForTable.isEmpty()); 116 final RegionInfo regionToMove = regionsOnRS1ForTable.get(0); 117 HMaster master = TEST_UTIL.getHBaseCluster().getMaster(); 118 119 // Try to move the region. HackedMoveRegionProcedure should intercede and mess up the region 120 // state setting it to SPLIT when we run the UnassignProcedure part of move region. 121 // Then when we go to do the unassignprocedure, we should notice the region-to-move is not 122 // online.... spew some log, and then fast-track to the end of the unassign. The assign under 123 // move will also notice that the parent is not-online but SPLIT and will skip it... so the 124 // move will "succeed" but we won't have moved the region! 125 RegionPlan rp = new RegionPlan(regionToMove, rs1.getServerName(), rs2.getServerName()); 126 MasterProcedureEnv env = master.getMasterProcedureExecutor().getEnvironment(); 127 HackedMoveRegionProcedure p = new HackedMoveRegionProcedure(env, rp); 128 master.getMasterProcedureExecutor().submitProcedure(p); 129 ProcedureTestingUtility.waitProcedure(master.getMasterProcedureExecutor(), p); 130 // Split should have been called. 131 assertTrue(p.split.get()); 132 // The region should not have been moved! 133 assertTrue(rs1.getOnlineRegion(regionToMove.getRegionName()) != null); 134 } 135 136 /** 137 * Class just so we can mess around with RegionStateNode state at a particular point in the 138 * Procedure to try and mess it up. 139 */ 140 public static class HackedMoveRegionProcedure extends MoveRegionProcedure { 141 /** 142 * Set to true after we hack this regions RSN to SPLIT 143 */ 144 public static AtomicBoolean split = new AtomicBoolean(false); 145 146 // Required by the Procedure framework to create the procedure on replay 147 public HackedMoveRegionProcedure() { 148 super(); 149 } 150 151 public HackedMoveRegionProcedure(MasterProcedureEnv env, RegionPlan plan) 152 throws HBaseIOException { 153 super(env, plan, false); 154 } 155 156 @Override 157 protected Flow executeFromState(MasterProcedureEnv env, 158 MasterProcedureProtos.MoveRegionState state) throws InterruptedException { 159 Flow flow = null; 160 switch (state) { 161 case MOVE_REGION_UNASSIGN: 162 // Just before the unassign, flip the state to SPLIT. The unassign should exit! 163 RegionStates.RegionStateNode rsn = 164 env.getAssignmentManager().getRegionStates().getOrCreateRegionStateNode(getRegion()); 165 rsn.setState(RegionState.State.SPLIT); 166 LOG.info("HACKED RSN, setting it to SPLIT: {}", rsn); 167 split.set(true); 168 default: 169 flow = super.executeFromState(env, state); 170 } 171 return flow; 172 } 173 } 174}