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.assertNull;
022import static org.junit.Assert.assertTrue;
023
024import java.util.Collections;
025import java.util.concurrent.Executors;
026import java.util.concurrent.Future;
027import org.apache.hadoop.hbase.HBaseClassTestRule;
028import org.apache.hadoop.hbase.HBaseTestingUtility;
029import org.apache.hadoop.hbase.MetaTableAccessor;
030import org.apache.hadoop.hbase.TableName;
031import org.apache.hadoop.hbase.client.RegionInfo;
032import org.apache.hadoop.hbase.client.RegionInfoBuilder;
033import org.apache.hadoop.hbase.master.RegionState.State;
034import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility;
035import org.apache.hadoop.hbase.procedure2.util.StringUtils;
036import org.apache.hadoop.hbase.testclassification.LargeTests;
037import org.apache.hadoop.hbase.testclassification.MasterTests;
038import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
039import org.junit.ClassRule;
040import org.junit.Test;
041import org.junit.experimental.categories.Category;
042import org.slf4j.Logger;
043import org.slf4j.LoggerFactory;
044
045@Category({ MasterTests.class, LargeTests.class })
046public class TestAssignmentManager extends TestAssignmentManagerBase {
047
048  @ClassRule
049  public static final HBaseClassTestRule CLASS_RULE =
050    HBaseClassTestRule.forClass(TestAssignmentManager.class);
051
052  private static final Logger LOG = LoggerFactory.getLogger(TestAssignmentManager.class);
053
054  @Test
055  public void testAssignWithGoodExec() throws Exception {
056    // collect AM metrics before test
057    collectAssignmentManagerMetrics();
058
059    testAssign(new GoodRsExecutor());
060
061    assertEquals(assignSubmittedCount + NREGIONS,
062      assignProcMetrics.getSubmittedCounter().getCount());
063    assertEquals(assignFailedCount, assignProcMetrics.getFailedCounter().getCount());
064  }
065
066  @Test
067  public void testAssignAndCrashBeforeResponse() throws Exception {
068    TableName tableName = TableName.valueOf("testAssignAndCrashBeforeResponse");
069    RegionInfo hri = createRegionInfo(tableName, 1);
070    rsDispatcher.setMockRsExecutor(new HangThenRSCrashExecutor());
071    TransitRegionStateProcedure proc = createAssignProcedure(hri);
072    waitOnFuture(submitProcedure(proc));
073  }
074
075  @Test
076  public void testUnassignAndCrashBeforeResponse() throws Exception {
077    TableName tableName = TableName.valueOf("testAssignAndCrashBeforeResponse");
078    RegionInfo hri = createRegionInfo(tableName, 1);
079    rsDispatcher.setMockRsExecutor(new HangOnCloseThenRSCrashExecutor());
080    for (int i = 0; i < HangOnCloseThenRSCrashExecutor.TYPES_OF_FAILURE; i++) {
081      TransitRegionStateProcedure assign = createAssignProcedure(hri);
082      waitOnFuture(submitProcedure(assign));
083      TransitRegionStateProcedure unassign = createUnassignProcedure(hri);
084      waitOnFuture(submitProcedure(unassign));
085    }
086  }
087
088  @Test
089  public void testAssignSocketTimeout() throws Exception {
090    TableName tableName = TableName.valueOf(this.name.getMethodName());
091    RegionInfo hri = createRegionInfo(tableName, 1);
092
093    // collect AM metrics before test
094    collectAssignmentManagerMetrics();
095
096    rsDispatcher.setMockRsExecutor(new SocketTimeoutRsExecutor(20));
097    waitOnFuture(submitProcedure(createAssignProcedure(hri)));
098
099    // we crashed a rs, so it is possible that there are other regions on the rs which will also be
100    // reassigned, so here we just assert greater than, not the exact number.
101    assertTrue(assignProcMetrics.getSubmittedCounter().getCount() > assignSubmittedCount);
102    assertEquals(assignFailedCount, assignProcMetrics.getFailedCounter().getCount());
103  }
104
105  @Test
106  public void testAssignQueueFullOnce() throws Exception {
107    TableName tableName = TableName.valueOf(this.name.getMethodName());
108    RegionInfo hri = createRegionInfo(tableName, 1);
109
110    // collect AM metrics before test
111    collectAssignmentManagerMetrics();
112
113    rsDispatcher.setMockRsExecutor(new CallQueueTooBigOnceRsExecutor());
114    waitOnFuture(submitProcedure(createAssignProcedure(hri)));
115
116    assertEquals(assignSubmittedCount + 1, assignProcMetrics.getSubmittedCounter().getCount());
117    assertEquals(assignFailedCount, assignProcMetrics.getFailedCounter().getCount());
118  }
119
120  @Test
121  public void testTimeoutThenQueueFull() throws Exception {
122    TableName tableName = TableName.valueOf(this.name.getMethodName());
123    RegionInfo hri = createRegionInfo(tableName, 1);
124
125    // collect AM metrics before test
126    collectAssignmentManagerMetrics();
127
128    rsDispatcher.setMockRsExecutor(new TimeoutThenCallQueueTooBigRsExecutor(10));
129    waitOnFuture(submitProcedure(createAssignProcedure(hri)));
130    rsDispatcher.setMockRsExecutor(new TimeoutThenCallQueueTooBigRsExecutor(15));
131    waitOnFuture(submitProcedure(createUnassignProcedure(hri)));
132
133    assertEquals(assignSubmittedCount + 1, assignProcMetrics.getSubmittedCounter().getCount());
134    assertEquals(assignFailedCount, assignProcMetrics.getFailedCounter().getCount());
135    assertEquals(unassignSubmittedCount + 1, unassignProcMetrics.getSubmittedCounter().getCount());
136    assertEquals(unassignFailedCount, unassignProcMetrics.getFailedCounter().getCount());
137  }
138
139  private void testAssign(final MockRSExecutor executor) throws Exception {
140    testAssign(executor, NREGIONS);
141  }
142
143  private void testAssign(MockRSExecutor executor, int nRegions) throws Exception {
144    rsDispatcher.setMockRsExecutor(executor);
145
146    TransitRegionStateProcedure[] assignments = new TransitRegionStateProcedure[nRegions];
147
148    long st = EnvironmentEdgeManager.currentTime();
149    bulkSubmit(assignments);
150
151    for (int i = 0; i < assignments.length; ++i) {
152      ProcedureTestingUtility.waitProcedure(master.getMasterProcedureExecutor(), assignments[i]);
153      assertTrue(assignments[i].toString(), assignments[i].isSuccess());
154    }
155    long et = EnvironmentEdgeManager.currentTime();
156    float sec = ((et - st) / 1000.0f);
157    LOG.info(String.format("[T] Assigning %dprocs in %s (%.2fproc/sec)", assignments.length,
158      StringUtils.humanTimeDiff(et - st), assignments.length / sec));
159  }
160
161  @Test
162  public void testAssignAnAssignedRegion() throws Exception {
163    final TableName tableName = TableName.valueOf("testAssignAnAssignedRegion");
164    final RegionInfo hri = createRegionInfo(tableName, 1);
165
166    // collect AM metrics before test
167    collectAssignmentManagerMetrics();
168
169    rsDispatcher.setMockRsExecutor(new GoodRsExecutor());
170
171    Future<byte[]> futureA = submitProcedure(createAssignProcedure(hri));
172
173    // wait first assign
174    waitOnFuture(futureA);
175    am.getRegionStates().isRegionInState(hri, State.OPEN);
176    // Second should be a noop. We should recognize region is already OPEN internally
177    // and skip out doing nothing.
178    // wait second assign
179    Future<byte[]> futureB = submitProcedure(createAssignProcedure(hri));
180    waitOnFuture(futureB);
181    am.getRegionStates().isRegionInState(hri, State.OPEN);
182    // TODO: What else can we do to ensure just a noop.
183
184    // TODO: Though second assign is noop, it's considered success, can noop be handled in a
185    // better way?
186    assertEquals(assignSubmittedCount + 2, assignProcMetrics.getSubmittedCounter().getCount());
187    assertEquals(assignFailedCount, assignProcMetrics.getFailedCounter().getCount());
188  }
189
190  @Test
191  public void testUnassignAnUnassignedRegion() throws Exception {
192    final TableName tableName = TableName.valueOf("testUnassignAnUnassignedRegion");
193    final RegionInfo hri = createRegionInfo(tableName, 1);
194
195    // collect AM metrics before test
196    collectAssignmentManagerMetrics();
197
198    rsDispatcher.setMockRsExecutor(new GoodRsExecutor());
199
200    // assign the region first
201    waitOnFuture(submitProcedure(createAssignProcedure(hri)));
202
203    final Future<byte[]> futureA = submitProcedure(createUnassignProcedure(hri));
204
205    // Wait first unassign.
206    waitOnFuture(futureA);
207    am.getRegionStates().isRegionInState(hri, State.CLOSED);
208    // Second should be a noop. We should recognize region is already CLOSED internally
209    // and skip out doing nothing.
210    final Future<byte[]> futureB = submitProcedure(createUnassignProcedure(hri));
211    waitOnFuture(futureB);
212    // Ensure we are still CLOSED.
213    am.getRegionStates().isRegionInState(hri, State.CLOSED);
214    // TODO: What else can we do to ensure just a noop.
215
216    assertEquals(assignSubmittedCount + 1, assignProcMetrics.getSubmittedCounter().getCount());
217    assertEquals(assignFailedCount, assignProcMetrics.getFailedCounter().getCount());
218    // TODO: Though second unassign is noop, it's considered success, can noop be handled in a
219    // better way?
220    assertEquals(unassignSubmittedCount + 2, unassignProcMetrics.getSubmittedCounter().getCount());
221    assertEquals(unassignFailedCount, unassignProcMetrics.getFailedCounter().getCount());
222  }
223
224  /**
225   * It is possible that when AM send assign meta request to a RS successfully, but RS can not send
226   * back any response, which cause master startup hangs forever
227   */
228  @Test
229  public void testAssignMetaAndCrashBeforeResponse() throws Exception {
230    tearDown();
231    // See setUp(), start HBase until set up meta
232    util = new HBaseTestingUtility();
233    this.executor = Executors.newSingleThreadScheduledExecutor();
234    setupConfiguration(util.getConfiguration());
235    master = new MockMasterServices(util.getConfiguration(), this.regionsToRegionServers);
236    rsDispatcher = new MockRSProcedureDispatcher(master);
237    master.start(NSERVERS, rsDispatcher);
238    am = master.getAssignmentManager();
239
240    // Assign meta
241    rsDispatcher.setMockRsExecutor(new HangThenRSRestartExecutor());
242    am.assign(RegionInfoBuilder.FIRST_META_REGIONINFO);
243    assertEquals(true, am.isMetaAssigned());
244
245    // set it back as default, see setUpMeta()
246    am.wakeMetaLoadedEvent();
247  }
248
249  private void assertCloseThenOpen() {
250    assertEquals(closeSubmittedCount + 1, closeProcMetrics.getSubmittedCounter().getCount());
251    assertEquals(closeFailedCount, closeProcMetrics.getFailedCounter().getCount());
252    assertEquals(openSubmittedCount + 1, openProcMetrics.getSubmittedCounter().getCount());
253    assertEquals(openFailedCount, openProcMetrics.getFailedCounter().getCount());
254  }
255
256  @Test
257  public void testMove() throws Exception {
258    TableName tableName = TableName.valueOf("testMove");
259    RegionInfo hri = createRegionInfo(tableName, 1);
260    rsDispatcher.setMockRsExecutor(new GoodRsExecutor());
261    am.assign(hri);
262
263    // collect AM metrics before test
264    collectAssignmentManagerMetrics();
265
266    am.move(hri);
267
268    assertEquals(moveSubmittedCount + 1, moveProcMetrics.getSubmittedCounter().getCount());
269    assertEquals(moveFailedCount, moveProcMetrics.getFailedCounter().getCount());
270    assertCloseThenOpen();
271  }
272
273  @Test
274  public void testReopen() throws Exception {
275    TableName tableName = TableName.valueOf("testReopen");
276    RegionInfo hri = createRegionInfo(tableName, 1);
277    rsDispatcher.setMockRsExecutor(new GoodRsExecutor());
278    am.assign(hri);
279
280    // collect AM metrics before test
281    collectAssignmentManagerMetrics();
282
283    TransitRegionStateProcedure proc =
284      TransitRegionStateProcedure.reopen(master.getMasterProcedureExecutor().getEnvironment(), hri);
285    am.getRegionStates().getRegionStateNode(hri).setProcedure(proc);
286    waitOnFuture(submitProcedure(proc));
287
288    assertEquals(reopenSubmittedCount + 1, reopenProcMetrics.getSubmittedCounter().getCount());
289    assertEquals(reopenFailedCount, reopenProcMetrics.getFailedCounter().getCount());
290    assertCloseThenOpen();
291  }
292
293  @Test
294  public void testLoadRegionFromMetaAfterRegionManuallyAdded() throws Exception {
295    try {
296      this.util.startMiniCluster();
297      final AssignmentManager am = this.util.getHBaseCluster().getMaster().getAssignmentManager();
298      final TableName tableName =
299        TableName.valueOf("testLoadRegionFromMetaAfterRegionManuallyAdded");
300      this.util.createTable(tableName, "f");
301      RegionInfo hri = createRegionInfo(tableName, 1);
302      assertNull("RegionInfo was just instantiated by the test, but "
303        + "shouldn't be in AM regionStates yet.", am.getRegionStates().getRegionState(hri));
304      MetaTableAccessor.addRegionsToMeta(this.util.getConnection(), Collections.singletonList(hri),
305        1);
306      assertNull(
307        "RegionInfo was manually added in META, but " + "shouldn't be in AM regionStates yet.",
308        am.getRegionStates().getRegionState(hri));
309      hri = am.loadRegionFromMeta(hri.getEncodedName());
310      assertEquals(hri.getEncodedName(),
311        am.getRegionStates().getRegionState(hri).getRegion().getEncodedName());
312    } finally {
313      this.util.killMiniHBaseCluster();
314    }
315  }
316
317  @Test
318  public void testLoadRegionFromMetaRegionNotInMeta() throws Exception {
319    try {
320      this.util.startMiniCluster();
321      final AssignmentManager am = this.util.getHBaseCluster().getMaster().getAssignmentManager();
322      final TableName tableName = TableName.valueOf("testLoadRegionFromMetaRegionNotInMeta");
323      this.util.createTable(tableName, "f");
324      final RegionInfo hri = createRegionInfo(tableName, 1);
325      assertNull("RegionInfo was just instantiated by the test, but "
326        + "shouldn't be in AM regionStates yet.", am.getRegionStates().getRegionState(hri));
327      assertNull("RegionInfo was never added in META, should had returned null.",
328        am.loadRegionFromMeta(hri.getEncodedName()));
329    } finally {
330      this.util.killMiniHBaseCluster();
331    }
332  }
333}