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.util;
019
020import java.io.IOException;
021import java.util.ArrayList;
022import java.util.List;
023import java.util.concurrent.TimeUnit;
024import java.util.stream.Collectors;
025import org.apache.hadoop.hbase.HBaseClassTestRule;
026import org.apache.hadoop.hbase.HBaseTestingUtil;
027import org.apache.hadoop.hbase.HRegionLocation;
028import org.apache.hadoop.hbase.ServerName;
029import org.apache.hadoop.hbase.SingleProcessHBaseCluster;
030import org.apache.hadoop.hbase.TableName;
031import org.apache.hadoop.hbase.client.Admin;
032import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
033import org.apache.hadoop.hbase.client.Put;
034import org.apache.hadoop.hbase.client.RegionInfoBuilder;
035import org.apache.hadoop.hbase.client.Table;
036import org.apache.hadoop.hbase.client.TableDescriptor;
037import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
038import org.apache.hadoop.hbase.master.RegionState;
039import org.apache.hadoop.hbase.regionserver.HRegion;
040import org.apache.hadoop.hbase.regionserver.HRegionServer;
041import org.apache.hadoop.hbase.testclassification.LargeTests;
042import org.apache.hadoop.hbase.testclassification.MiscTests;
043import org.apache.hadoop.hbase.zookeeper.MetaTableLocator;
044import org.apache.hadoop.hbase.zookeeper.ZKWatcher;
045import org.apache.hadoop.hbase.zookeeper.ZNodePaths;
046import org.junit.After;
047import org.junit.AfterClass;
048import org.junit.Assert;
049import org.junit.Before;
050import org.junit.BeforeClass;
051import org.junit.ClassRule;
052import org.junit.Rule;
053import org.junit.Test;
054import org.junit.experimental.categories.Category;
055import org.junit.rules.TestName;
056import org.slf4j.Logger;
057import org.slf4j.LoggerFactory;
058
059/**
060 * Tests for Region Mover Load/Unload functionality with and without ack mode and also to test
061 * exclude functionality useful for rack decommissioning
062 */
063@Category({ MiscTests.class, LargeTests.class })
064public class TestRegionMover2 {
065
066  @ClassRule
067  public static final HBaseClassTestRule CLASS_RULE =
068    HBaseClassTestRule.forClass(TestRegionMover2.class);
069  private static final String CF = "fam1";
070
071  @Rule
072  public TestName name = new TestName();
073
074  private static final Logger LOG = LoggerFactory.getLogger(TestRegionMover2.class);
075  private static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil();
076
077  @BeforeClass
078  public static void setUpBeforeClass() throws Exception {
079    TEST_UTIL.startMiniCluster(3);
080    TEST_UTIL.getAdmin().balancerSwitch(false, true);
081  }
082
083  @AfterClass
084  public static void tearDownAfterClass() throws Exception {
085    TEST_UTIL.shutdownMiniCluster();
086  }
087
088  @Before
089  public void setUp() throws Exception {
090    createTable(name.getMethodName());
091  }
092
093  @After
094  public void tearDown() throws Exception {
095    final TableName tableName = TableName.valueOf(name.getMethodName());
096    TEST_UTIL.getAdmin().disableTable(tableName);
097    TEST_UTIL.getAdmin().deleteTable(tableName);
098  }
099
100  private TableName createTable(String name) throws IOException {
101    final TableName tableName = TableName.valueOf(name);
102    TableDescriptor tableDesc = TableDescriptorBuilder.newBuilder(tableName)
103      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(CF)).build();
104    int startKey = 0;
105    int endKey = 80000;
106    TEST_UTIL.getAdmin().createTable(tableDesc, Bytes.toBytes(startKey), Bytes.toBytes(endKey), 9);
107    return tableName;
108  }
109
110  @Test
111  public void testWithMergedRegions() throws Exception {
112    final TableName tableName = TableName.valueOf(name.getMethodName());
113    SingleProcessHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
114    Admin admin = TEST_UTIL.getAdmin();
115    Table table = TEST_UTIL.getConnection().getTable(tableName);
116    List<Put> puts = createPuts(10000);
117    table.put(puts);
118    admin.flush(tableName);
119    HRegionServer regionServer = cluster.getRegionServer(0);
120    String rsName = regionServer.getServerName().getAddress().toString();
121    int numRegions = regionServer.getNumberOfOnlineRegions();
122    List<HRegion> hRegions = regionServer.getRegions().stream()
123      .filter(hRegion -> hRegion.getRegionInfo().getTable().equals(tableName))
124      .collect(Collectors.toList());
125    RegionMover.RegionMoverBuilder rmBuilder =
126      new RegionMover.RegionMoverBuilder(rsName, TEST_UTIL.getConfiguration()).ack(true)
127        .maxthreads(8);
128    try (RegionMover rm = rmBuilder.build()) {
129      LOG.debug("Unloading {}", regionServer.getServerName());
130      rm.unload();
131      Assert.assertEquals(0, regionServer.getNumberOfOnlineRegions());
132      LOG.debug("Successfully Unloaded, now Loading");
133      admin.mergeRegionsAsync(new byte[][] { hRegions.get(0).getRegionInfo().getRegionName(),
134        hRegions.get(1).getRegionInfo().getRegionName() }, true).get(5, TimeUnit.SECONDS);
135      Assert.assertTrue(rm.load());
136      Assert.assertEquals(numRegions - 2, regionServer.getNumberOfOnlineRegions());
137    }
138  }
139
140  @Test
141  public void testWithSplitRegions() throws Exception {
142    final TableName tableName = TableName.valueOf(name.getMethodName());
143    SingleProcessHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
144    Admin admin = TEST_UTIL.getAdmin();
145    Table table = TEST_UTIL.getConnection().getTable(tableName);
146    List<Put> puts = createPuts(50000);
147    table.put(puts);
148    admin.flush(tableName);
149    admin.compact(tableName);
150    HRegionServer regionServer = cluster.getRegionServer(0);
151    String rsName = regionServer.getServerName().getAddress().toString();
152    int numRegions = regionServer.getNumberOfOnlineRegions();
153    List<HRegion> hRegions = regionServer.getRegions().stream()
154      .filter(hRegion -> hRegion.getRegionInfo().getTable().equals(tableName))
155      .collect(Collectors.toList());
156
157    RegionMover.RegionMoverBuilder rmBuilder =
158      new RegionMover.RegionMoverBuilder(rsName, TEST_UTIL.getConfiguration()).ack(true)
159        .maxthreads(8);
160    try (RegionMover rm = rmBuilder.build()) {
161      LOG.debug("Unloading {}", regionServer.getServerName());
162      rm.unload();
163      Assert.assertEquals(0, regionServer.getNumberOfOnlineRegions());
164      LOG.debug("Successfully Unloaded, now Loading");
165      HRegion hRegion = hRegions.get(1);
166      if (hRegion.getRegionInfo().getStartKey().length == 0) {
167        hRegion = hRegions.get(0);
168      }
169      int startKey = 0;
170      int endKey = Integer.MAX_VALUE;
171      if (hRegion.getRegionInfo().getStartKey().length > 0) {
172        startKey = Bytes.toInt(hRegion.getRegionInfo().getStartKey());
173      }
174      if (hRegion.getRegionInfo().getEndKey().length > 0) {
175        endKey = Bytes.toInt(hRegion.getRegionInfo().getEndKey());
176      }
177      int midKey = startKey + (endKey - startKey) / 2;
178      admin.splitRegionAsync(hRegion.getRegionInfo().getRegionName(), Bytes.toBytes(midKey)).get(5,
179        TimeUnit.SECONDS);
180      Assert.assertTrue(rm.load());
181      Assert.assertEquals(numRegions - 1, regionServer.getNumberOfOnlineRegions());
182    }
183  }
184
185  @Test
186  public void testFailedRegionMove() throws Exception {
187    final TableName tableName = TableName.valueOf(name.getMethodName());
188    SingleProcessHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
189    Admin admin = TEST_UTIL.getAdmin();
190    Table table = TEST_UTIL.getConnection().getTable(tableName);
191    List<Put> puts = createPuts(1000);
192    table.put(puts);
193    admin.flush(tableName);
194    HRegionServer regionServer = cluster.getRegionServer(0);
195    String rsName = regionServer.getServerName().getAddress().toString();
196    List<HRegion> hRegions = regionServer.getRegions().stream()
197      .filter(hRegion -> hRegion.getRegionInfo().getTable().equals(tableName))
198      .collect(Collectors.toList());
199    RegionMover.RegionMoverBuilder rmBuilder =
200      new RegionMover.RegionMoverBuilder(rsName, TEST_UTIL.getConfiguration()).ack(true)
201        .maxthreads(8);
202    try (RegionMover rm = rmBuilder.build()) {
203      LOG.debug("Unloading {}", regionServer.getServerName());
204      rm.unload();
205      Assert.assertEquals(0, regionServer.getNumberOfOnlineRegions());
206      LOG.debug("Successfully Unloaded, now Loading");
207      admin.offline(hRegions.get(0).getRegionInfo().getRegionName());
208      // loading regions will fail because of offline region
209      Assert.assertFalse(rm.load());
210    }
211  }
212
213  @Test
214  public void testDeletedTable() throws Exception {
215    TableName tableNameToDelete = createTable(name.getMethodName() + "ToDelete");
216    SingleProcessHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
217    HRegionServer regionServer = cluster.getRegionServer(0);
218    String rsName = regionServer.getServerName().getAddress().toString();
219    RegionMover.RegionMoverBuilder rmBuilder =
220      new RegionMover.RegionMoverBuilder(rsName, TEST_UTIL.getConfiguration()).ack(true)
221        .maxthreads(8);
222    try (Admin admin = TEST_UTIL.getAdmin(); RegionMover rm = rmBuilder.build()) {
223      LOG.debug("Unloading {}", regionServer.getServerName());
224      rm.unload();
225      Assert.assertEquals(0, regionServer.getNumberOfOnlineRegions());
226      LOG.debug("Successfully Unloaded, now delete table");
227      admin.disableTable(tableNameToDelete);
228      admin.deleteTable(tableNameToDelete);
229      Assert.assertTrue(rm.load());
230    }
231  }
232
233  public void loadDummyDataInTable(TableName tableName) throws Exception {
234    Admin admin = TEST_UTIL.getAdmin();
235    Table table = TEST_UTIL.getConnection().getTable(tableName);
236    List<Put> puts = createPuts(1000);
237    table.put(puts);
238    admin.flush(tableName);
239  }
240
241  @Test
242  public void testIsolateSingleRegionOnTheSameServer() throws Exception {
243    final TableName tableName = TableName.valueOf(name.getMethodName());
244    loadDummyDataInTable(tableName);
245    ServerName sourceServerName = findSourceServerName(tableName);
246    // Isolating 1 region on the same region server.
247    regionIsolationOperation(sourceServerName, sourceServerName, 1, false);
248  }
249
250  @Test
251  public void testIsolateSingleRegionOnTheDifferentServer() throws Exception {
252    final TableName tableName = TableName.valueOf(name.getMethodName());
253    loadDummyDataInTable(tableName);
254    ServerName sourceServerName = findSourceServerName(tableName);
255    ServerName destinationServerName = findDestinationServerName(sourceServerName);
256    // Isolating 1 region on the different region server.
257    regionIsolationOperation(sourceServerName, destinationServerName, 1, false);
258  }
259
260  @Test
261  public void testIsolateMultipleRegionsOnTheSameServer() throws Exception {
262    final TableName tableName = TableName.valueOf(name.getMethodName());
263    loadDummyDataInTable(tableName);
264    ServerName sourceServerName = findSourceServerName(tableName);
265    // Isolating 2 regions on the same region server.
266    regionIsolationOperation(sourceServerName, sourceServerName, 2, false);
267  }
268
269  @Test
270  public void testIsolateMultipleRegionsOnTheDifferentServer() throws Exception {
271    final TableName tableName = TableName.valueOf(name.getMethodName());
272    loadDummyDataInTable(tableName);
273    // Isolating 2 regions on the different region server.
274    ServerName sourceServerName = findSourceServerName(tableName);
275    ServerName destinationServerName = findDestinationServerName(sourceServerName);
276    regionIsolationOperation(sourceServerName, destinationServerName, 2, false);
277  }
278
279  @Test
280  public void testIsolateMetaOnTheSameSever() throws Exception {
281    ServerName metaServerSource = findMetaRSLocation();
282    regionIsolationOperation(metaServerSource, metaServerSource, 1, true);
283  }
284
285  @Test
286  public void testIsolateMetaOnTheDifferentServer() throws Exception {
287    ServerName metaServerSource = findMetaRSLocation();
288    ServerName metaServerDestination = findDestinationServerName(metaServerSource);
289    regionIsolationOperation(metaServerSource, metaServerDestination, 1, true);
290  }
291
292  @Test
293  public void testIsolateMetaAndRandomRegionOnTheMetaServer() throws Exception {
294    final TableName tableName = TableName.valueOf(name.getMethodName());
295    loadDummyDataInTable(tableName);
296    ServerName metaServerSource = findMetaRSLocation();
297    ServerName randomSeverRegion = findSourceServerName(tableName);
298    regionIsolationOperation(randomSeverRegion, metaServerSource, 2, true);
299  }
300
301  @Test
302  public void testIsolateMetaAndRandomRegionOnTheRandomServer() throws Exception {
303    final TableName tableName = TableName.valueOf(name.getMethodName());
304    loadDummyDataInTable(tableName);
305    ServerName randomSeverRegion = findSourceServerName(tableName);
306    regionIsolationOperation(randomSeverRegion, randomSeverRegion, 2, true);
307  }
308
309  private List<Put> createPuts(int count) {
310    List<Put> puts = new ArrayList<>();
311    for (int i = 0; i < count; i++) {
312      puts.add(new Put(Bytes.toBytes("rowkey_" + i)).addColumn(Bytes.toBytes(CF),
313        Bytes.toBytes("q1"), Bytes.toBytes("val_" + i)));
314    }
315    return puts;
316  }
317
318  public ServerName findMetaRSLocation() throws Exception {
319    ZKWatcher zkWatcher = new ZKWatcher(TEST_UTIL.getConfiguration(), null, null);
320    List<HRegionLocation> result = new ArrayList<>();
321    for (String znode : zkWatcher.getMetaReplicaNodes()) {
322      String path = ZNodePaths.joinZNode(zkWatcher.getZNodePaths().baseZNode, znode);
323      int replicaId = zkWatcher.getZNodePaths().getMetaReplicaIdFromPath(path);
324      RegionState state = MetaTableLocator.getMetaRegionState(zkWatcher, replicaId);
325      result.add(new HRegionLocation(state.getRegion(), state.getServerName()));
326    }
327    return result.get(0).getServerName();
328  }
329
330  public ServerName findSourceServerName(TableName tableName) throws Exception {
331    SingleProcessHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
332    int numOfRS = cluster.getNumLiveRegionServers();
333    ServerName sourceServer = null;
334    for (int i = 0; i < numOfRS; i++) {
335      HRegionServer regionServer = cluster.getRegionServer(i);
336      List<HRegion> hRegions = regionServer.getRegions().stream()
337        .filter(hRegion -> hRegion.getRegionInfo().getTable().equals(tableName))
338        .collect(Collectors.toList());
339      if (hRegions.size() >= 2) {
340        sourceServer = regionServer.getServerName();
341        break;
342      }
343    }
344    if (sourceServer == null) {
345      throw new Exception(
346        "This shouldn't happen, No RS found with more than 2 regions of table : " + tableName);
347    }
348    return sourceServer;
349  }
350
351  public ServerName findDestinationServerName(ServerName sourceServerName) throws Exception {
352    SingleProcessHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
353    ServerName destinationServerName = null;
354    int numOfRS = cluster.getNumLiveRegionServers();
355    for (int i = 0; i < numOfRS; i++) {
356      destinationServerName = cluster.getRegionServer(i).getServerName();
357      if (!destinationServerName.equals(sourceServerName)) {
358        break;
359      }
360    }
361    if (destinationServerName == null) {
362      throw new Exception("This shouldn't happen, No RS found which is different than source RS");
363    }
364    return destinationServerName;
365  }
366
367  public void regionIsolationOperation(ServerName sourceServerName,
368    ServerName destinationServerName, int numRegionsToIsolate, boolean isolateMetaAlso)
369    throws Exception {
370    final TableName tableName = TableName.valueOf(name.getMethodName());
371    SingleProcessHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
372    Admin admin = TEST_UTIL.getAdmin();
373    HRegionServer sourceRS = cluster.getRegionServer(sourceServerName);
374    List<HRegion> hRegions = sourceRS.getRegions().stream()
375      .filter(hRegion -> hRegion.getRegionInfo().getTable().equals(tableName))
376      .collect(Collectors.toList());
377    List<String> listOfRegionIDsToIsolate = new ArrayList<>();
378    for (int i = 0; i < numRegionsToIsolate; i++) {
379      listOfRegionIDsToIsolate.add(hRegions.get(i).getRegionInfo().getEncodedName());
380    }
381
382    if (isolateMetaAlso) {
383      listOfRegionIDsToIsolate.remove(0);
384      listOfRegionIDsToIsolate.add(RegionInfoBuilder.FIRST_META_REGIONINFO.getEncodedName());
385    }
386
387    HRegionServer destinationRS = cluster.getRegionServer(destinationServerName);
388    String destinationRSName = destinationRS.getServerName().getAddress().toString();
389    RegionMover.RegionMoverBuilder rmBuilder =
390      new RegionMover.RegionMoverBuilder(destinationRSName, TEST_UTIL.getConfiguration()).ack(true)
391        .maxthreads(8).isolateRegionIdArray(listOfRegionIDsToIsolate);
392    try (RegionMover rm = rmBuilder.build()) {
393      LOG.debug("Unloading {} except regions: {}", destinationRS.getServerName(),
394        listOfRegionIDsToIsolate);
395      rm.isolateRegions();
396      Assert.assertEquals(numRegionsToIsolate, destinationRS.getNumberOfOnlineRegions());
397      List<HRegion> onlineRegions = destinationRS.getRegions();
398      for (int i = 0; i < numRegionsToIsolate; i++) {
399        Assert.assertTrue(
400          listOfRegionIDsToIsolate.contains(onlineRegions.get(i).getRegionInfo().getEncodedName()));
401      }
402      LOG.debug("Successfully Isolated {} regions: {} on {}", listOfRegionIDsToIsolate.size(),
403        listOfRegionIDsToIsolate, destinationRS.getServerName());
404    } finally {
405      admin.recommissionRegionServer(destinationRS.getServerName(), null);
406    }
407  }
408}