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.assertFalse;
022import static org.junit.Assert.assertNotEquals;
023import static org.junit.Assert.assertTrue;
024import static org.junit.Assert.fail;
025
026import java.io.IOException;
027import java.util.ArrayList;
028import java.util.List;
029import java.util.concurrent.ExecutionException;
030import java.util.concurrent.atomic.AtomicInteger;
031import java.util.stream.Collectors;
032import org.apache.hadoop.hbase.HBaseClassTestRule;
033import org.apache.hadoop.hbase.ServerName;
034import org.apache.hadoop.hbase.TableName;
035import org.apache.hadoop.hbase.master.HMaster;
036import org.apache.hadoop.hbase.master.NoSuchProcedureException;
037import org.apache.hadoop.hbase.master.RegionState;
038import org.apache.hadoop.hbase.master.ServerManager;
039import org.apache.hadoop.hbase.master.assignment.AssignmentManager;
040import org.apache.hadoop.hbase.master.assignment.RegionStates;
041import org.apache.hadoop.hbase.regionserver.HRegionServer;
042import org.apache.hadoop.hbase.regionserver.Region;
043import org.apache.hadoop.hbase.testclassification.ClientTests;
044import org.apache.hadoop.hbase.testclassification.LargeTests;
045import org.apache.hadoop.hbase.util.Bytes;
046import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
047import org.apache.hadoop.hbase.util.JVMClusterUtil;
048import org.apache.hadoop.hbase.util.Threads;
049import org.junit.ClassRule;
050import org.junit.Test;
051import org.junit.experimental.categories.Category;
052import org.junit.runner.RunWith;
053import org.junit.runners.Parameterized;
054
055/**
056 * Class to test asynchronous region admin operations.
057 * @see TestAsyncRegionAdminApi2 This test and it used to be joined it was taking longer than our
058 * ten minute timeout so they were split.
059 */
060@RunWith(Parameterized.class)
061@Category({ LargeTests.class, ClientTests.class })
062public class TestAsyncRegionAdminApi extends TestAsyncAdminBase {
063  @ClassRule
064  public static final HBaseClassTestRule CLASS_RULE =
065      HBaseClassTestRule.forClass(TestAsyncRegionAdminApi.class);
066
067  @Test
068  public void testAssignRegionAndUnassignRegion() throws Exception {
069    createTableWithDefaultConf(tableName);
070
071    // assign region.
072    HMaster master = TEST_UTIL.getHBaseCluster().getMaster();
073    AssignmentManager am = master.getAssignmentManager();
074    RegionInfo hri = am.getRegionStates().getRegionsOfTable(tableName).get(0);
075
076    // assert region on server
077    RegionStates regionStates = am.getRegionStates();
078    ServerName serverName = regionStates.getRegionServerOfRegion(hri);
079    TEST_UTIL.assertRegionOnServer(hri, serverName, 200);
080    assertTrue(regionStates.getRegionState(hri).isOpened());
081
082    // Region is assigned now. Let's assign it again.
083    // Master should not abort, and region should stay assigned.
084    admin.assign(hri.getRegionName()).get();
085    try {
086      am.waitForAssignment(hri);
087      fail("Expected NoSuchProcedureException");
088    } catch (NoSuchProcedureException e) {
089      // Expected
090    }
091    assertTrue(regionStates.getRegionState(hri).isOpened());
092
093    // unassign region
094    admin.unassign(hri.getRegionName(), true).get();
095    try {
096      am.waitForAssignment(hri);
097      fail("Expected NoSuchProcedureException");
098    } catch (NoSuchProcedureException e) {
099      // Expected
100    }
101    assertTrue(regionStates.getRegionState(hri).isClosed());
102  }
103
104  RegionInfo createTableAndGetOneRegion(final TableName tableName)
105      throws IOException, InterruptedException, ExecutionException {
106    TableDescriptor desc =
107        TableDescriptorBuilder.newBuilder(tableName)
108            .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY)).build();
109    admin.createTable(desc, Bytes.toBytes("A"), Bytes.toBytes("Z"), 5).get();
110
111    // wait till the table is assigned
112    HMaster master = TEST_UTIL.getHBaseCluster().getMaster();
113    long timeoutTime = System.currentTimeMillis() + 3000;
114    while (true) {
115      List<RegionInfo> regions =
116          master.getAssignmentManager().getRegionStates().getRegionsOfTable(tableName);
117      if (regions.size() > 3) {
118        return regions.get(2);
119      }
120      long now = System.currentTimeMillis();
121      if (now > timeoutTime) {
122        fail("Could not find an online region");
123      }
124      Thread.sleep(10);
125    }
126  }
127
128  @Test
129  public void testGetRegionByStateOfTable() throws Exception {
130    RegionInfo hri = createTableAndGetOneRegion(tableName);
131
132    RegionStates regionStates =
133        TEST_UTIL.getHBaseCluster().getMaster().getAssignmentManager().getRegionStates();
134    assertTrue(regionStates.getRegionByStateOfTable(tableName).get(RegionState.State.OPEN)
135        .stream().anyMatch(r -> RegionInfo.COMPARATOR.compare(r, hri) == 0));
136    assertFalse(regionStates.getRegionByStateOfTable(TableName.valueOf("I_am_the_phantom"))
137        .get(RegionState.State.OPEN).stream().anyMatch(r -> RegionInfo.COMPARATOR.compare(r, hri) == 0));
138  }
139
140  @Test
141  public void testMoveRegion() throws Exception {
142    admin.balancerSwitch(false).join();
143
144    RegionInfo hri = createTableAndGetOneRegion(tableName);
145    RawAsyncHBaseAdmin rawAdmin = (RawAsyncHBaseAdmin) ASYNC_CONN.getAdmin();
146    ServerName serverName = rawAdmin.getRegionLocation(hri.getRegionName()).get().getServerName();
147
148    HMaster master = TEST_UTIL.getHBaseCluster().getMaster();
149    ServerManager serverManager = master.getServerManager();
150    ServerName destServerName = null;
151    List<JVMClusterUtil.RegionServerThread> regionServers =
152        TEST_UTIL.getHBaseCluster().getLiveRegionServerThreads();
153    for (JVMClusterUtil.RegionServerThread regionServer : regionServers) {
154      HRegionServer destServer = regionServer.getRegionServer();
155      destServerName = destServer.getServerName();
156      if (!destServerName.equals(serverName) && serverManager.isServerOnline(destServerName)) {
157        break;
158      }
159    }
160
161    assertTrue(destServerName != null && !destServerName.equals(serverName));
162    admin.move(hri.getRegionName(), destServerName).get();
163
164    long timeoutTime = System.currentTimeMillis() + 30000;
165    while (true) {
166      ServerName sn = rawAdmin.getRegionLocation(hri.getRegionName()).get().getServerName();
167      if (sn != null && sn.equals(destServerName)) {
168        break;
169      }
170      long now = System.currentTimeMillis();
171      if (now > timeoutTime) {
172        fail("Failed to move the region in time: " + hri);
173      }
174      Thread.sleep(100);
175    }
176    admin.balancerSwitch(true).join();
177  }
178
179  @Test
180  public void testGetOnlineRegions() throws Exception {
181    createTableAndGetOneRegion(tableName);
182    AtomicInteger regionServerCount = new AtomicInteger(0);
183    TEST_UTIL
184        .getHBaseCluster()
185        .getLiveRegionServerThreads()
186        .stream()
187        .map(rsThread -> rsThread.getRegionServer())
188        .forEach(
189          rs -> {
190            ServerName serverName = rs.getServerName();
191            try {
192              assertEquals(admin.getRegions(serverName).get().size(), rs
193                  .getRegions().size());
194            } catch (Exception e) {
195              fail("admin.getOnlineRegions() method throws a exception: " + e.getMessage());
196            }
197            regionServerCount.incrementAndGet();
198          });
199    assertEquals(2, regionServerCount.get());
200  }
201
202  @Test
203  public void testFlushTableAndRegion() throws Exception {
204    RegionInfo hri = createTableAndGetOneRegion(tableName);
205    ServerName serverName =
206        TEST_UTIL.getHBaseCluster().getMaster().getAssignmentManager().getRegionStates()
207            .getRegionServerOfRegion(hri);
208    HRegionServer regionServer =
209        TEST_UTIL.getHBaseCluster().getLiveRegionServerThreads().stream()
210            .map(rsThread -> rsThread.getRegionServer())
211            .filter(rs -> rs.getServerName().equals(serverName)).findFirst().get();
212
213    // write a put into the specific region
214    ASYNC_CONN.getTable(tableName)
215        .put(new Put(hri.getStartKey()).addColumn(FAMILY, FAMILY_0, Bytes.toBytes("value-1")))
216        .join();
217    assertTrue(regionServer.getOnlineRegion(hri.getRegionName()).getMemStoreDataSize() > 0);
218    // flush region and wait flush operation finished.
219    LOG.info("flushing region: " + Bytes.toStringBinary(hri.getRegionName()));
220    admin.flushRegion(hri.getRegionName()).get();
221    LOG.info("blocking until flush is complete: " + Bytes.toStringBinary(hri.getRegionName()));
222    Threads.sleepWithoutInterrupt(500);
223    while (regionServer.getOnlineRegion(hri.getRegionName()).getMemStoreDataSize() > 0) {
224      Threads.sleep(50);
225    }
226    // check the memstore.
227    assertEquals(regionServer.getOnlineRegion(hri.getRegionName()).getMemStoreDataSize(), 0);
228
229    // write another put into the specific region
230    ASYNC_CONN.getTable(tableName)
231        .put(new Put(hri.getStartKey()).addColumn(FAMILY, FAMILY_0, Bytes.toBytes("value-2")))
232        .join();
233    assertTrue(regionServer.getOnlineRegion(hri.getRegionName()).getMemStoreDataSize() > 0);
234    admin.flush(tableName).get();
235    Threads.sleepWithoutInterrupt(500);
236    while (regionServer.getOnlineRegion(hri.getRegionName()).getMemStoreDataSize() > 0) {
237      Threads.sleep(50);
238    }
239    // check the memstore.
240    assertEquals(regionServer.getOnlineRegion(hri.getRegionName()).getMemStoreDataSize(), 0);
241  }
242
243  private void waitUntilMobCompactionFinished(TableName tableName)
244      throws ExecutionException, InterruptedException {
245    long finished = EnvironmentEdgeManager.currentTime() + 60000;
246    CompactionState state = admin.getCompactionState(tableName, CompactType.MOB).get();
247    while (EnvironmentEdgeManager.currentTime() < finished) {
248      if (state == CompactionState.NONE) {
249        break;
250      }
251      Thread.sleep(10);
252      state = admin.getCompactionState(tableName, CompactType.MOB).get();
253    }
254    assertEquals(CompactionState.NONE, state);
255  }
256
257  @Test
258  public void testCompactMob() throws Exception {
259    ColumnFamilyDescriptor columnDescriptor =
260        ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("mob"))
261            .setMobEnabled(true).setMobThreshold(0).build();
262
263    TableDescriptor tableDescriptor = TableDescriptorBuilder.newBuilder(tableName)
264        .setColumnFamily(columnDescriptor).build();
265
266    admin.createTable(tableDescriptor).get();
267
268    byte[][] families = { Bytes.toBytes("mob") };
269    loadData(tableName, families, 3000, 8);
270
271    admin.majorCompact(tableName, CompactType.MOB).get();
272
273    CompactionState state = admin.getCompactionState(tableName, CompactType.MOB).get();
274    assertNotEquals(CompactionState.NONE, state);
275
276    waitUntilMobCompactionFinished(tableName);
277  }
278
279  @Test
280  public void testCompactRegionServer() throws Exception {
281    byte[][] families = { Bytes.toBytes("f1"), Bytes.toBytes("f2"), Bytes.toBytes("f3") };
282    createTableWithDefaultConf(tableName, null, families);
283    loadData(tableName, families, 3000, 8);
284
285    List<HRegionServer> rsList =
286        TEST_UTIL.getHBaseCluster().getLiveRegionServerThreads().stream()
287            .map(rsThread -> rsThread.getRegionServer()).collect(Collectors.toList());
288    List<Region> regions = new ArrayList<>();
289    rsList.forEach(rs -> regions.addAll(rs.getRegions(tableName)));
290    assertEquals(1, regions.size());
291    int countBefore = countStoreFilesInFamilies(regions, families);
292    assertTrue(countBefore > 0);
293
294    // Minor compaction for all region servers.
295    for (HRegionServer rs : rsList)
296      admin.compactRegionServer(rs.getServerName()).get();
297    Thread.sleep(5000);
298    int countAfterMinorCompaction = countStoreFilesInFamilies(regions, families);
299    assertTrue(countAfterMinorCompaction < countBefore);
300
301    // Major compaction for all region servers.
302    for (HRegionServer rs : rsList)
303      admin.majorCompactRegionServer(rs.getServerName()).get();
304    Thread.sleep(5000);
305    int countAfterMajorCompaction = countStoreFilesInFamilies(regions, families);
306    assertEquals(3, countAfterMajorCompaction);
307  }
308
309  @Test
310  public void testCompact() throws Exception {
311    compactionTest(TableName.valueOf("testCompact1"), 8, CompactionState.MAJOR, false);
312    compactionTest(TableName.valueOf("testCompact2"), 15, CompactionState.MINOR, false);
313    compactionTest(TableName.valueOf("testCompact3"), 8, CompactionState.MAJOR, true);
314    compactionTest(TableName.valueOf("testCompact4"), 15, CompactionState.MINOR, true);
315  }
316
317  private void compactionTest(final TableName tableName, final int flushes,
318      final CompactionState expectedState, boolean singleFamily) throws Exception {
319    // Create a table with regions
320    byte[] family = Bytes.toBytes("family");
321    byte[][] families =
322        { family, Bytes.add(family, Bytes.toBytes("2")), Bytes.add(family, Bytes.toBytes("3")) };
323    createTableWithDefaultConf(tableName, null, families);
324    loadData(tableName, families, 3000, flushes);
325
326    List<Region> regions = new ArrayList<>();
327    TEST_UTIL
328        .getHBaseCluster()
329        .getLiveRegionServerThreads()
330        .forEach(rsThread -> regions.addAll(rsThread.getRegionServer().getRegions(tableName)));
331    assertEquals(1, regions.size());
332
333    int countBefore = countStoreFilesInFamilies(regions, families);
334    int countBeforeSingleFamily = countStoreFilesInFamily(regions, family);
335    assertTrue(countBefore > 0); // there should be some data files
336    if (expectedState == CompactionState.MINOR) {
337      if (singleFamily) {
338        admin.compact(tableName, family).get();
339      } else {
340        admin.compact(tableName).get();
341      }
342    } else {
343      if (singleFamily) {
344        admin.majorCompact(tableName, family).get();
345      } else {
346        admin.majorCompact(tableName).get();
347      }
348    }
349
350    long curt = System.currentTimeMillis();
351    long waitTime = 5000;
352    long endt = curt + waitTime;
353    CompactionState state = admin.getCompactionState(tableName).get();
354    while (state == CompactionState.NONE && curt < endt) {
355      Thread.sleep(10);
356      state = admin.getCompactionState(tableName).get();
357      curt = System.currentTimeMillis();
358    }
359    // Now, should have the right compaction state,
360    // otherwise, the compaction should have already been done
361    if (expectedState != state) {
362      for (Region region : regions) {
363        state = CompactionState.valueOf(region.getCompactionState().toString());
364        assertEquals(CompactionState.NONE, state);
365      }
366    } else {
367      // Wait until the compaction is done
368      state = admin.getCompactionState(tableName).get();
369      while (state != CompactionState.NONE && curt < endt) {
370        Thread.sleep(10);
371        state = admin.getCompactionState(tableName).get();
372      }
373      // Now, compaction should be done.
374      assertEquals(CompactionState.NONE, state);
375    }
376
377    int countAfter = countStoreFilesInFamilies(regions, families);
378    int countAfterSingleFamily = countStoreFilesInFamily(regions, family);
379    assertTrue(countAfter < countBefore);
380    if (!singleFamily) {
381      if (expectedState == CompactionState.MAJOR) assertTrue(families.length == countAfter);
382      else assertTrue(families.length < countAfter);
383    } else {
384      int singleFamDiff = countBeforeSingleFamily - countAfterSingleFamily;
385      // assert only change was to single column family
386      assertTrue(singleFamDiff == (countBefore - countAfter));
387      if (expectedState == CompactionState.MAJOR) {
388        assertTrue(1 == countAfterSingleFamily);
389      } else {
390        assertTrue(1 < countAfterSingleFamily);
391      }
392    }
393  }
394
395  private static int countStoreFilesInFamily(List<Region> regions, final byte[] family) {
396    return countStoreFilesInFamilies(regions, new byte[][] { family });
397  }
398
399  private static int countStoreFilesInFamilies(List<Region> regions, final byte[][] families) {
400    int count = 0;
401    for (Region region : regions) {
402      count += region.getStoreFileList(families).size();
403    }
404    return count;
405  }
406
407  static void loadData(final TableName tableName, final byte[][] families, final int rows)
408      throws IOException {
409    loadData(tableName, families, rows, 1);
410  }
411
412  static void loadData(final TableName tableName, final byte[][] families, final int rows,
413      final int flushes) throws IOException {
414    AsyncTable<?> table = ASYNC_CONN.getTable(tableName);
415    List<Put> puts = new ArrayList<>(rows);
416    byte[] qualifier = Bytes.toBytes("val");
417    for (int i = 0; i < flushes; i++) {
418      for (int k = 0; k < rows; k++) {
419        byte[] row = Bytes.add(Bytes.toBytes(k), Bytes.toBytes(i));
420        Put p = new Put(row);
421        for (int j = 0; j < families.length; ++j) {
422          p.addColumn(families[j], qualifier, row);
423        }
424        puts.add(p);
425      }
426      table.putAll(puts).join();
427      TEST_UTIL.flush();
428      puts.clear();
429    }
430  }
431}