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.assertEquals;
021import static org.junit.Assert.assertTrue;
022import static org.junit.Assert.fail;
023
024import java.io.IOException;
025import java.util.concurrent.Executors;
026import java.util.concurrent.Future;
027import org.apache.hadoop.hbase.DoNotRetryIOException;
028import org.apache.hadoop.hbase.HBaseClassTestRule;
029import org.apache.hadoop.hbase.HBaseTestingUtility;
030import org.apache.hadoop.hbase.ServerName;
031import org.apache.hadoop.hbase.TableName;
032import org.apache.hadoop.hbase.client.RegionInfo;
033import org.apache.hadoop.hbase.client.RegionInfoBuilder;
034import org.apache.hadoop.hbase.client.RetriesExhaustedException;
035import org.apache.hadoop.hbase.exceptions.UnexpectedStateException;
036import org.apache.hadoop.hbase.master.RegionState.State;
037import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility;
038import org.apache.hadoop.hbase.procedure2.util.StringUtils;
039import org.apache.hadoop.hbase.testclassification.LargeTests;
040import org.apache.hadoop.hbase.testclassification.MasterTests;
041import org.junit.ClassRule;
042import org.junit.Ignore;
043import org.junit.Test;
044import org.junit.experimental.categories.Category;
045import org.slf4j.Logger;
046import org.slf4j.LoggerFactory;
047
048@Category({MasterTests.class, LargeTests.class})
049public class TestAssignmentManager extends TestAssignmentManagerBase {
050  private static final Logger LOG = LoggerFactory.getLogger(TestAssignmentManager.class);
051
052  @ClassRule
053  public static final HBaseClassTestRule CLASS_RULE =
054      HBaseClassTestRule.forClass(TestAssignmentManager.class);
055
056  @Test(expected = NullPointerException.class)
057  public void testWaitServerReportEventWithNullServer() throws UnexpectedStateException {
058    // Test what happens if we pass in null server. I'd expect it throws NPE.
059    if (this.am.waitServerReportEvent(null, null)) {
060      throw new UnexpectedStateException();
061    }
062  }
063
064  @Test
065  public void testAssignWithGoodExec() throws Exception {
066    // collect AM metrics before test
067    collectAssignmentManagerMetrics();
068
069    testAssign(new GoodRsExecutor());
070
071    assertEquals(assignSubmittedCount + NREGIONS,
072        assignProcMetrics.getSubmittedCounter().getCount());
073    assertEquals(assignFailedCount, assignProcMetrics.getFailedCounter().getCount());
074  }
075
076  @Test
077  public void testAssignAndCrashBeforeResponse() throws Exception {
078    final TableName tableName = TableName.valueOf("testAssignAndCrashBeforeResponse");
079    final RegionInfo hri = createRegionInfo(tableName, 1);
080    rsDispatcher.setMockRsExecutor(new HangThenRSCrashExecutor());
081    AssignProcedure proc = am.createAssignProcedure(hri);
082    waitOnFuture(submitProcedure(proc));
083  }
084
085  @Test
086  public void testUnassignAndCrashBeforeResponse() throws Exception {
087    final TableName tableName = TableName.valueOf("testAssignAndCrashBeforeResponse");
088    final RegionInfo hri = createRegionInfo(tableName, 1);
089    rsDispatcher.setMockRsExecutor(new HangOnCloseThenRSCrashExecutor());
090    for (int i = 0; i < HangOnCloseThenRSCrashExecutor.TYPES_OF_FAILURE; i++) {
091      AssignProcedure assign = am.createAssignProcedure(hri);
092      waitOnFuture(submitProcedure(assign));
093      UnassignProcedure unassign = am.createUnassignProcedure(hri,
094          am.getRegionStates().getRegionServerOfRegion(hri), false);
095      waitOnFuture(submitProcedure(unassign));
096    }
097  }
098
099  @Test
100  public void testAssignWithRandExec() throws Exception {
101    final TableName tableName = TableName.valueOf("testAssignWithRandExec");
102    final RegionInfo hri = createRegionInfo(tableName, 1);
103
104    rsDispatcher.setMockRsExecutor(new RandRsExecutor());
105    // Loop a bunch of times so we hit various combos of exceptions.
106    for (int i = 0; i < 10; i++) {
107      LOG.info("ROUND=" + i);
108      AssignProcedure proc = am.createAssignProcedure(hri);
109      waitOnFuture(submitProcedure(proc));
110    }
111  }
112
113  @Ignore @Test // Disabled for now. Since HBASE-18551, this mock is insufficient.
114  public void testSocketTimeout() throws Exception {
115    final TableName tableName = TableName.valueOf(this.name.getMethodName());
116    final RegionInfo hri = createRegionInfo(tableName, 1);
117
118    // collect AM metrics before test
119    collectAssignmentManagerMetrics();
120
121    rsDispatcher.setMockRsExecutor(new SocketTimeoutRsExecutor(20, 3));
122    waitOnFuture(submitProcedure(am.createAssignProcedure(hri)));
123
124    rsDispatcher.setMockRsExecutor(new SocketTimeoutRsExecutor(20, 1));
125    // exception.expect(ServerCrashException.class);
126    waitOnFuture(submitProcedure(am.createUnassignProcedure(hri, null, false)));
127
128    assertEquals(assignSubmittedCount + 1, assignProcMetrics.getSubmittedCounter().getCount());
129    assertEquals(assignFailedCount, assignProcMetrics.getFailedCounter().getCount());
130    assertEquals(unassignSubmittedCount + 1, unassignProcMetrics.getSubmittedCounter().getCount());
131    assertEquals(unassignFailedCount + 1, unassignProcMetrics.getFailedCounter().getCount());
132  }
133
134  @Test
135  public void testServerNotYetRunning() throws Exception {
136    testRetriesExhaustedFailure(TableName.valueOf(this.name.getMethodName()),
137      new ServerNotYetRunningRsExecutor());
138  }
139
140  private void testRetriesExhaustedFailure(final TableName tableName,
141      final MockRSExecutor executor) throws Exception {
142    final RegionInfo hri = createRegionInfo(tableName, 1);
143
144    // collect AM metrics before test
145    collectAssignmentManagerMetrics();
146
147    // Test Assign operation failure
148    rsDispatcher.setMockRsExecutor(executor);
149    try {
150      waitOnFuture(submitProcedure(am.createAssignProcedure(hri)));
151      fail("unexpected assign completion");
152    } catch (RetriesExhaustedException e) {
153      // expected exception
154      LOG.info("expected exception from assign operation: " + e.getMessage(), e);
155    }
156
157    // Assign the region (without problems)
158    rsDispatcher.setMockRsExecutor(new GoodRsExecutor());
159    waitOnFuture(submitProcedure(am.createAssignProcedure(hri)));
160
161    // TODO: Currently unassign just keeps trying until it sees a server crash.
162    // There is no count on unassign.
163    /*
164    // Test Unassign operation failure
165    rsDispatcher.setMockRsExecutor(executor);
166    waitOnFuture(submitProcedure(am.createUnassignProcedure(hri, null, false)));
167
168    assertEquals(assignSubmittedCount + 2, assignProcMetrics.getSubmittedCounter().getCount());
169    assertEquals(assignFailedCount + 1, assignProcMetrics.getFailedCounter().getCount());
170    assertEquals(unassignSubmittedCount + 1, unassignProcMetrics.getSubmittedCounter().getCount());
171
172    // TODO: We supposed to have 1 failed assign, 1 successful assign and a failed unassign
173    // operation. But ProcV2 framework marks aborted unassign operation as success. Fix it!
174    assertEquals(unassignFailedCount, unassignProcMetrics.getFailedCounter().getCount());
175    */
176  }
177
178
179  @Test
180  public void testIOExceptionOnAssignment() throws Exception {
181    // collect AM metrics before test
182    collectAssignmentManagerMetrics();
183
184    testFailedOpen(TableName.valueOf("testExceptionOnAssignment"),
185      new FaultyRsExecutor(new IOException("test fault")));
186
187    assertEquals(assignSubmittedCount + 1, assignProcMetrics.getSubmittedCounter().getCount());
188    assertEquals(assignFailedCount + 1, assignProcMetrics.getFailedCounter().getCount());
189  }
190
191  @Test
192  public void testDoNotRetryExceptionOnAssignment() throws Exception {
193    // collect AM metrics before test
194    collectAssignmentManagerMetrics();
195
196    testFailedOpen(TableName.valueOf("testDoNotRetryExceptionOnAssignment"),
197      new FaultyRsExecutor(new DoNotRetryIOException("test do not retry fault")));
198
199    assertEquals(assignSubmittedCount + 1, assignProcMetrics.getSubmittedCounter().getCount());
200    assertEquals(assignFailedCount + 1, assignProcMetrics.getFailedCounter().getCount());
201  }
202
203  private void testFailedOpen(final TableName tableName,
204      final MockRSExecutor executor) throws Exception {
205    final RegionInfo hri = createRegionInfo(tableName, 1);
206
207    // Test Assign operation failure
208    rsDispatcher.setMockRsExecutor(executor);
209    try {
210      waitOnFuture(submitProcedure(am.createAssignProcedure(hri)));
211      fail("unexpected assign completion");
212    } catch (RetriesExhaustedException e) {
213      // expected exception
214      LOG.info("REGION STATE " + am.getRegionStates().getRegionStateNode(hri));
215      LOG.info("expected exception from assign operation: " + e.getMessage(), e);
216      assertEquals(true, am.getRegionStates().getRegionState(hri).isFailedOpen());
217    }
218  }
219
220  private void testAssign(final MockRSExecutor executor) throws Exception {
221    testAssign(executor, NREGIONS);
222  }
223
224  private void testAssign(final MockRSExecutor executor, final int nregions) throws Exception {
225    rsDispatcher.setMockRsExecutor(executor);
226
227    AssignProcedure[] assignments = new AssignProcedure[nregions];
228
229    long st = System.currentTimeMillis();
230    bulkSubmit(assignments);
231
232    for (int i = 0; i < assignments.length; ++i) {
233      ProcedureTestingUtility.waitProcedure(
234        master.getMasterProcedureExecutor(), assignments[i]);
235      assertTrue(assignments[i].toString(), assignments[i].isSuccess());
236    }
237    long et = System.currentTimeMillis();
238    float sec = ((et - st) / 1000.0f);
239    LOG.info(String.format("[T] Assigning %dprocs in %s (%.2fproc/sec)",
240        assignments.length, StringUtils.humanTimeDiff(et - st), assignments.length / sec));
241  }
242
243  @Test
244  public void testAssignAnAssignedRegion() throws Exception {
245    final TableName tableName = TableName.valueOf("testAssignAnAssignedRegion");
246    final RegionInfo hri = createRegionInfo(tableName, 1);
247
248    // collect AM metrics before test
249    collectAssignmentManagerMetrics();
250
251    rsDispatcher.setMockRsExecutor(new GoodRsExecutor());
252
253    final Future<byte[]> futureA = submitProcedure(am.createAssignProcedure(hri));
254
255    // wait first assign
256    waitOnFuture(futureA);
257    am.getRegionStates().isRegionInState(hri, State.OPEN);
258    // Second should be a noop. We should recognize region is already OPEN internally
259    // and skip out doing nothing.
260    // wait second assign
261    final Future<byte[]> futureB = submitProcedure(am.createAssignProcedure(hri));
262    waitOnFuture(futureB);
263    am.getRegionStates().isRegionInState(hri, State.OPEN);
264    // TODO: What else can we do to ensure just a noop.
265
266    // TODO: Though second assign is noop, it's considered success, can noop be handled in a
267    // better way?
268    assertEquals(assignSubmittedCount + 2, assignProcMetrics.getSubmittedCounter().getCount());
269    assertEquals(assignFailedCount, assignProcMetrics.getFailedCounter().getCount());
270
271  }
272
273  @Test
274  public void testUnassignAnUnassignedRegion() throws Exception {
275    final TableName tableName = TableName.valueOf("testUnassignAnUnassignedRegion");
276    final RegionInfo hri = createRegionInfo(tableName, 1);
277
278    // collect AM metrics before test
279    collectAssignmentManagerMetrics();
280
281    rsDispatcher.setMockRsExecutor(new GoodRsExecutor());
282
283    // assign the region first
284    waitOnFuture(submitProcedure(am.createAssignProcedure(hri)));
285
286    final Future<byte[]> futureA = submitProcedure(am.createUnassignProcedure(hri, null, false));
287
288    // Wait first unassign.
289    waitOnFuture(futureA);
290    am.getRegionStates().isRegionInState(hri, State.CLOSED);
291    // Second should be a noop. We should recognize region is already CLOSED internally
292    // and skip out doing nothing.
293    final Future<byte[]> futureB =
294        submitProcedure(am.createUnassignProcedure(hri,
295            ServerName.valueOf("example.org,1234,1"), false));
296    waitOnFuture(futureB);
297    // Ensure we are still CLOSED.
298    am.getRegionStates().isRegionInState(hri, State.CLOSED);
299    // TODO: What else can we do to ensure just a noop.
300
301    assertEquals(assignSubmittedCount + 1, assignProcMetrics.getSubmittedCounter().getCount());
302    assertEquals(assignFailedCount, assignProcMetrics.getFailedCounter().getCount());
303    // TODO: Though second unassign is noop, it's considered success, can noop be handled in a
304    // better way?
305    assertEquals(unassignSubmittedCount + 2, unassignProcMetrics.getSubmittedCounter().getCount());
306    assertEquals(unassignFailedCount, unassignProcMetrics.getFailedCounter().getCount());
307  }
308
309  /**
310   * It is possible that when AM send assign meta request to a RS successfully,
311   * but RS can not send back any response, which cause master startup hangs forever
312   */
313  @Test
314  public void testAssignMetaAndCrashBeforeResponse() throws Exception {
315    tearDown();
316    // See setUp(), start HBase until set up meta
317    UTIL = new HBaseTestingUtility();
318    this.executor = Executors.newSingleThreadScheduledExecutor();
319    setupConfiguration(UTIL.getConfiguration());
320    master = new MockMasterServices(UTIL.getConfiguration(), this.regionsToRegionServers);
321    rsDispatcher = new MockRSProcedureDispatcher(master);
322    master.start(NSERVERS, rsDispatcher);
323    am = master.getAssignmentManager();
324
325    // Assign meta
326    rsDispatcher.setMockRsExecutor(new HangThenRSRestartExecutor());
327    am.assign(RegionInfoBuilder.FIRST_META_REGIONINFO);
328    assertEquals(true, am.isMetaAssigned());
329
330    // set it back as default, see setUpMeta()
331    am.wakeMetaLoadedEvent();
332  }
333
334  @Test
335  public void testAssignQueueFullOnce() throws Exception {
336    TableName tableName = TableName.valueOf(this.name.getMethodName());
337    RegionInfo hri = createRegionInfo(tableName, 1);
338
339    // collect AM metrics before test
340    collectAssignmentManagerMetrics();
341
342    rsDispatcher.setMockRsExecutor(new CallQueueTooBigOnceRsExecutor());
343    waitOnFuture(submitProcedure(am.createAssignProcedure(hri)));
344
345    assertEquals(assignSubmittedCount + 1, assignProcMetrics.getSubmittedCounter().getCount());
346    assertEquals(assignFailedCount, assignProcMetrics.getFailedCounter().getCount());
347  }
348
349  @Test
350  public void testTimeoutThenQueueFull() throws Exception {
351    TableName tableName = TableName.valueOf(this.name.getMethodName());
352    RegionInfo hri = createRegionInfo(tableName, 1);
353
354    // collect AM metrics before test
355    collectAssignmentManagerMetrics();
356
357    rsDispatcher.setMockRsExecutor(new TimeoutThenCallQueueTooBigRsExecutor(10));
358    waitOnFuture(submitProcedure(am.createAssignProcedure(hri)));
359    rsDispatcher.setMockRsExecutor(new TimeoutThenCallQueueTooBigRsExecutor(15));
360    waitOnFuture(submitProcedure(am.createUnassignProcedure(hri)));
361
362    assertEquals(assignSubmittedCount + 1, assignProcMetrics.getSubmittedCounter().getCount());
363    assertEquals(assignFailedCount, assignProcMetrics.getFailedCounter().getCount());
364    assertEquals(unassignSubmittedCount + 1, unassignProcMetrics.getSubmittedCounter().getCount());
365    assertEquals(unassignFailedCount, unassignProcMetrics.getFailedCounter().getCount());
366  }
367}