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.client;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertNotEquals;
022import static org.junit.Assert.assertTrue;
023import java.io.IOException;
024import java.util.Arrays;
025import java.util.HashMap;
026import java.util.List;
027import java.util.Map;
028import java.util.Optional;
029import java.util.concurrent.CountDownLatch;
030import java.util.stream.Collectors;
031import org.apache.hadoop.hbase.Coprocessor;
032import org.apache.hadoop.hbase.CoprocessorEnvironment;
033import org.apache.hadoop.hbase.HBaseClassTestRule;
034import org.apache.hadoop.hbase.HBaseTestingUtility;
035import org.apache.hadoop.hbase.ServerName;
036import org.apache.hadoop.hbase.TableName;
037import org.apache.hadoop.hbase.coprocessor.MasterCoprocessor;
038import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment;
039import org.apache.hadoop.hbase.coprocessor.MasterObserver;
040import org.apache.hadoop.hbase.coprocessor.ObserverContext;
041import org.apache.hadoop.hbase.master.HMaster;
042import org.apache.hadoop.hbase.master.RegionState;
043import org.apache.hadoop.hbase.master.assignment.AssignmentManager;
044import org.apache.hadoop.hbase.master.procedure.MasterProcedureEnv;
045import org.apache.hadoop.hbase.master.procedure.TableProcedureInterface;
046import org.apache.hadoop.hbase.procedure2.Procedure;
047import org.apache.hadoop.hbase.procedure2.ProcedureExecutor;
048import org.apache.hadoop.hbase.procedure2.ProcedureSuspendedException;
049import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility;
050import org.apache.hadoop.hbase.regionserver.HRegionServer;
051import org.apache.hadoop.hbase.testclassification.ClientTests;
052import org.apache.hadoop.hbase.testclassification.LargeTests;
053import org.apache.hadoop.hbase.util.Bytes;
054import org.apache.hadoop.hbase.util.Pair;
055import org.junit.AfterClass;
056import org.junit.Before;
057import org.junit.BeforeClass;
058import org.junit.ClassRule;
059import org.junit.Rule;
060import org.junit.Test;
061import org.junit.experimental.categories.Category;
062import org.junit.rules.TestName;
063import org.junit.runner.RunWith;
064import org.junit.runners.Parameterized;
065import org.junit.runners.Parameterized.Parameter;
066import org.junit.runners.Parameterized.Parameters;
067import org.slf4j.Logger;
068import org.slf4j.LoggerFactory;
069import org.apache.hbase.thirdparty.com.google.common.io.Closeables;
070import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
071
072/**
073 * Class to test HBaseHbck. Spins up the minicluster once at test start and then takes it down
074 * afterward. Add any testing of HBaseHbck functionality here.
075 */
076@RunWith(Parameterized.class)
077@Category({ LargeTests.class, ClientTests.class })
078public class TestHbck {
079  @ClassRule
080  public static final HBaseClassTestRule CLASS_RULE = HBaseClassTestRule.forClass(TestHbck.class);
081
082  private static final Logger LOG = LoggerFactory.getLogger(TestHbck.class);
083  private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
084
085  @Rule
086  public TestName name = new TestName();
087
088  @SuppressWarnings("checkstyle:VisibilityModifier") @Parameter
089  public boolean async;
090
091  private static final TableName TABLE_NAME = TableName.valueOf(TestHbck.class.getSimpleName());
092
093  private static ProcedureExecutor<MasterProcedureEnv> procExec;
094
095  private static AsyncConnection ASYNC_CONN;
096
097  @Parameters(name = "{index}: async={0}")
098  public static List<Object[]> params() {
099    return Arrays.asList(new Object[] { false }, new Object[] { true });
100  }
101
102  private Hbck getHbck() throws Exception {
103    if (async) {
104      return ASYNC_CONN.getHbck().get();
105    } else {
106      return TEST_UTIL.getHbck();
107    }
108  }
109
110  @BeforeClass
111  public static void setUpBeforeClass() throws Exception {
112    TEST_UTIL.startMiniCluster(3);
113    TEST_UTIL.createMultiRegionTable(TABLE_NAME, Bytes.toBytes("family1"), 5);
114    procExec = TEST_UTIL.getMiniHBaseCluster().getMaster().getMasterProcedureExecutor();
115    ASYNC_CONN = ConnectionFactory.createAsyncConnection(TEST_UTIL.getConfiguration()).get();
116    TEST_UTIL.getHBaseCluster().getMaster().getMasterCoprocessorHost().load(
117      FailingMergeAfterMetaUpdatedMasterObserver.class, Coprocessor.PRIORITY_USER,
118      TEST_UTIL.getHBaseCluster().getMaster().getConfiguration());
119    TEST_UTIL.getHBaseCluster().getMaster().getMasterCoprocessorHost().load(
120      FailingSplitAfterMetaUpdatedMasterObserver.class, Coprocessor.PRIORITY_USER,
121      TEST_UTIL.getHBaseCluster().getMaster().getConfiguration());
122  }
123
124  @AfterClass
125  public static void tearDownAfterClass() throws Exception {
126    Closeables.close(ASYNC_CONN, true);
127    TEST_UTIL.shutdownMiniCluster();
128  }
129
130  @Before
131  public void setUp() throws IOException {
132    TEST_UTIL.ensureSomeRegionServersAvailable(3);
133  }
134
135  public static class SuspendProcedure extends
136      ProcedureTestingUtility.NoopProcedure<MasterProcedureEnv> implements TableProcedureInterface {
137    public SuspendProcedure() {
138      super();
139    }
140
141    @SuppressWarnings({ "rawtypes", "unchecked" })
142    @Override
143    protected Procedure[] execute(final MasterProcedureEnv env) throws ProcedureSuspendedException {
144      // Always suspend the procedure
145      throw new ProcedureSuspendedException();
146    }
147
148    @Override
149    public TableName getTableName() {
150      return TABLE_NAME;
151    }
152
153    @Override
154    public TableOperationType getTableOperationType() {
155      return TableOperationType.READ;
156    }
157  }
158
159  @Test
160  public void testBypassProcedure() throws Exception {
161    // SuspendProcedure
162    final SuspendProcedure proc = new SuspendProcedure();
163    long procId = procExec.submitProcedure(proc);
164    Thread.sleep(500);
165
166    // bypass the procedure
167    List<Long> pids = Arrays.<Long> asList(procId);
168    List<Boolean> results = getHbck().bypassProcedure(pids, 30000, false, false);
169    assertTrue("Failed to by pass procedure!", results.get(0));
170    TEST_UTIL.waitFor(5000, () -> proc.isSuccess() && proc.isBypass());
171    LOG.info("{} finished", proc);
172  }
173
174  @Test
175  public void testSetTableStateInMeta() throws Exception {
176    Hbck hbck = getHbck();
177    // set table state to DISABLED
178    hbck.setTableStateInMeta(new TableState(TABLE_NAME, TableState.State.DISABLED));
179    // Method {@link Hbck#setTableStateInMeta()} returns previous state, which in this case
180    // will be DISABLED
181    TableState prevState =
182      hbck.setTableStateInMeta(new TableState(TABLE_NAME, TableState.State.ENABLED));
183    assertTrue("Incorrect previous state! expeced=DISABLED, found=" + prevState.getState(),
184      prevState.isDisabled());
185  }
186
187  @Test
188  public void testSetRegionStateInMeta() throws Exception {
189    Hbck hbck = getHbck();
190    Admin admin = TEST_UTIL.getAdmin();
191    final List<RegionInfo> regions = admin.getRegions(TABLE_NAME);
192    final AssignmentManager am = TEST_UTIL.getHBaseCluster().getMaster().getAssignmentManager();
193    Map<String, RegionState.State> prevStates = new HashMap<>();
194    Map<String, RegionState.State> newStates = new HashMap<>();
195    final Map<String, Pair<RegionState.State, RegionState.State>> regionsMap = new HashMap<>();
196    regions.forEach(r -> {
197      RegionState prevState = am.getRegionStates().getRegionState(r);
198      prevStates.put(r.getEncodedName(), prevState.getState());
199      newStates.put(r.getEncodedName(), RegionState.State.CLOSED);
200      regionsMap.put(r.getEncodedName(),
201        new Pair<>(prevState.getState(), RegionState.State.CLOSED));
202    });
203    final Map<String, RegionState.State> result = hbck.setRegionStateInMeta(newStates);
204    result.forEach((k, v) -> {
205      RegionState.State prevState = regionsMap.get(k).getFirst();
206      assertEquals(prevState, v);
207    });
208    regions.forEach(r -> {
209      RegionState cachedState = am.getRegionStates().getRegionState(r.getEncodedName());
210      RegionState.State newState = regionsMap.get(r.getEncodedName()).getSecond();
211      assertEquals(newState, cachedState.getState());
212    });
213    hbck.setRegionStateInMeta(prevStates);
214  }
215
216  @Test
217  public void testAssigns() throws Exception {
218    Hbck hbck = getHbck();
219    try (Admin admin = TEST_UTIL.getConnection().getAdmin()) {
220      List<RegionInfo> regions = admin.getRegions(TABLE_NAME);
221      for (RegionInfo ri : regions) {
222        RegionState rs = TEST_UTIL.getHBaseCluster().getMaster().getAssignmentManager()
223          .getRegionStates().getRegionState(ri.getEncodedName());
224        LOG.info("RS: {}", rs.toString());
225      }
226      List<Long> pids =
227        hbck.unassigns(regions.stream().map(r -> r.getEncodedName()).collect(Collectors.toList()));
228      waitOnPids(pids);
229      // Rerun the unassign. Should fail for all Regions since they already unassigned; failed
230      // unassign will manifest as all pids being -1 (ever since HBASE-24885).
231      pids =
232        hbck.unassigns(regions.stream().map(r -> r.getEncodedName()).collect(Collectors.toList()));
233      waitOnPids(pids);
234      for (long pid: pids) {
235        assertEquals(Procedure.NO_PROC_ID, pid);
236      }
237      // If we pass override, then we should be able to unassign EVEN THOUGH Regions already
238      // unassigned.... makes for a mess but operator might want to do this at an extreme when
239      // doing fixup of broke cluster.
240      pids =
241        hbck.unassigns(regions.stream().map(r -> r.getEncodedName()).collect(Collectors.toList()),
242          true);
243      waitOnPids(pids);
244      for (long pid: pids) {
245        assertNotEquals(Procedure.NO_PROC_ID, pid);
246      }
247      // Clean-up by bypassing all the unassigns we just made so tests can continue.
248      hbck.bypassProcedure(pids, 10000, true, true);
249      for (RegionInfo ri : regions) {
250        RegionState rs = TEST_UTIL.getHBaseCluster().getMaster().getAssignmentManager()
251          .getRegionStates().getRegionState(ri.getEncodedName());
252        LOG.info("RS: {}", rs.toString());
253        assertTrue(rs.toString(), rs.isClosed());
254      }
255      pids =
256        hbck.assigns(regions.stream().map(r -> r.getEncodedName()).collect(Collectors.toList()));
257      waitOnPids(pids);
258      // Rerun the assign. Should fail for all Regions since they already assigned; failed
259      // assign will manifest as all pids being -1 (ever since HBASE-24885).
260      pids =
261        hbck.assigns(regions.stream().map(r -> r.getEncodedName()).collect(Collectors.toList()));
262      for (long pid: pids) {
263        assertEquals(Procedure.NO_PROC_ID, pid);
264      }
265      for (RegionInfo ri : regions) {
266        RegionState rs = TEST_UTIL.getHBaseCluster().getMaster().getAssignmentManager()
267          .getRegionStates().getRegionState(ri.getEncodedName());
268        LOG.info("RS: {}", rs.toString());
269        assertTrue(rs.toString(), rs.isOpened());
270      }
271      // What happens if crappy region list passed?
272      pids = hbck.assigns(
273        Arrays.stream(new String[] { "a", "some rubbish name" }).collect(Collectors.toList()));
274      for (long pid : pids) {
275        assertEquals(Procedure.NO_PROC_ID, pid);
276      }
277    }
278  }
279
280  @Test
281  public void testScheduleSCP() throws Exception {
282    HRegionServer testRs = TEST_UTIL.getRSForFirstRegionInTable(TABLE_NAME);
283    TEST_UTIL.loadTable(TEST_UTIL.getConnection().getTable(TABLE_NAME), Bytes.toBytes("family1"),
284      true);
285    ServerName serverName = testRs.getServerName();
286    Hbck hbck = getHbck();
287    List<Long> pids =
288      hbck.scheduleServerCrashProcedure(Arrays.asList(ProtobufUtil.toServerName(serverName)));
289    assertTrue(pids.get(0) > 0);
290    LOG.info("pid is {}", pids.get(0));
291
292    List<Long> newPids =
293      hbck.scheduleServerCrashProcedure(Arrays.asList(ProtobufUtil.toServerName(serverName)));
294    assertTrue(newPids.get(0) < 0);
295    LOG.info("pid is {}", newPids.get(0));
296    waitOnPids(pids);
297  }
298
299  @Test
300  public void testRunHbckChore() throws Exception {
301    HMaster master = TEST_UTIL.getMiniHBaseCluster().getMaster();
302    long endTimestamp = master.getHbckChore().getCheckingEndTimestamp();
303    Hbck hbck = getHbck();
304    boolean ran = false;
305    while (!ran) {
306      ran = hbck.runHbckChore();
307      if (ran) {
308        assertTrue(master.getHbckChore().getCheckingEndTimestamp() > endTimestamp);
309      }
310    }
311  }
312
313  public static class FailingSplitAfterMetaUpdatedMasterObserver
314      implements MasterCoprocessor, MasterObserver {
315    @SuppressWarnings("checkstyle:VisibilityModifier") public volatile CountDownLatch latch;
316
317    @Override
318    public void start(CoprocessorEnvironment e) throws IOException {
319      resetLatch();
320    }
321
322    @Override
323    public Optional<MasterObserver> getMasterObserver() {
324      return Optional.of(this);
325    }
326
327    @Override
328    public void preSplitRegionAfterMETAAction(ObserverContext<MasterCoprocessorEnvironment> ctx)
329        throws IOException {
330      LOG.info("I'm here");
331      latch.countDown();
332      throw new IOException("this procedure will fail at here forever");
333    }
334
335    public void resetLatch() {
336      this.latch = new CountDownLatch(1);
337    }
338  }
339
340  public static class FailingMergeAfterMetaUpdatedMasterObserver
341      implements MasterCoprocessor, MasterObserver {
342    @SuppressWarnings("checkstyle:VisibilityModifier") public volatile CountDownLatch latch;
343
344    @Override
345    public void start(CoprocessorEnvironment e) throws IOException {
346      resetLatch();
347    }
348
349    @Override
350    public Optional<MasterObserver> getMasterObserver() {
351      return Optional.of(this);
352    }
353
354    public void resetLatch() {
355      this.latch = new CountDownLatch(1);
356    }
357
358    @Override
359    public void postMergeRegionsCommitAction(
360        final ObserverContext<MasterCoprocessorEnvironment> ctx, final RegionInfo[] regionsToMerge,
361        final RegionInfo mergedRegion) throws IOException {
362      latch.countDown();
363      throw new IOException("this procedure will fail at here forever");
364    }
365  }
366
367  private void waitOnPids(List<Long> pids) {
368    TEST_UTIL.waitFor(60000, () -> pids.stream().allMatch(procExec::isFinished));
369  }
370}