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}