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