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 static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertFalse;
022
023import java.io.File;
024import java.io.FileWriter;
025import org.apache.hadoop.conf.Configuration;
026import org.apache.hadoop.fs.Path;
027import org.apache.hadoop.hbase.HBaseClassTestRule;
028import org.apache.hadoop.hbase.HBaseTestingUtility;
029import org.apache.hadoop.hbase.HConstants;
030import org.apache.hadoop.hbase.MiniHBaseCluster;
031import org.apache.hadoop.hbase.TableName;
032import org.apache.hadoop.hbase.Waiter.Predicate;
033import org.apache.hadoop.hbase.client.Admin;
034import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
035import org.apache.hadoop.hbase.client.TableDescriptor;
036import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
037import org.apache.hadoop.hbase.regionserver.HRegionServer;
038import org.apache.hadoop.hbase.testclassification.MediumTests;
039import org.apache.hadoop.hbase.testclassification.MiscTests;
040import org.apache.hadoop.hbase.util.RegionMover.RegionMoverBuilder;
041import org.junit.AfterClass;
042import org.junit.Before;
043import org.junit.BeforeClass;
044import org.junit.ClassRule;
045import org.junit.Test;
046import org.junit.experimental.categories.Category;
047import org.slf4j.Logger;
048import org.slf4j.LoggerFactory;
049
050/**
051 * Tests for Region Mover Load/Unload functionality with and without ack mode and also to test
052 * exclude functionality useful for rack decommissioning
053 */
054@Category({ MiscTests.class, MediumTests.class })
055public class TestRegionMover {
056
057  @ClassRule
058  public static final HBaseClassTestRule CLASS_RULE =
059    HBaseClassTestRule.forClass(TestRegionMover.class);
060
061  private static final Logger LOG = LoggerFactory.getLogger(TestRegionMover.class);
062
063  private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
064
065  @BeforeClass
066  public static void setUpBeforeClass() throws Exception {
067    TEST_UTIL.startMiniCluster(3);
068    TEST_UTIL.getAdmin().balancerSwitch(false, true);
069  }
070
071  @AfterClass
072  public static void tearDownAfterClass() throws Exception {
073    TEST_UTIL.shutdownMiniCluster();
074  }
075
076  @Before
077  public void setUp() throws Exception {
078    // Create a pre-split table just to populate some regions
079    TableName tableName = TableName.valueOf("testRegionMover");
080    Admin admin = TEST_UTIL.getAdmin();
081    if (admin.tableExists(tableName)) {
082      TEST_UTIL.deleteTable(tableName);
083    }
084    TableDescriptor tableDesc = TableDescriptorBuilder.newBuilder(tableName)
085      .setColumnFamily(ColumnFamilyDescriptorBuilder.of("fam1")).build();
086    String startKey = "a";
087    String endKey = "z";
088    admin.createTable(tableDesc, startKey.getBytes(), endKey.getBytes(), 9);
089  }
090
091  @Test
092  public void testWithAck() throws Exception {
093    MiniHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
094    HRegionServer regionServer = cluster.getRegionServer(0);
095    String rsName = regionServer.getServerName().getAddress().toString();
096    int numRegions = regionServer.getNumberOfOnlineRegions();
097    RegionMoverBuilder rmBuilder =
098      new RegionMoverBuilder(rsName, TEST_UTIL.getConfiguration()).ack(true).maxthreads(8);
099    try (RegionMover rm = rmBuilder.build()) {
100      LOG.info("Unloading " + regionServer.getServerName());
101      rm.unload();
102      assertEquals(0, regionServer.getNumberOfOnlineRegions());
103      LOG.info("Successfully Unloaded\nNow Loading");
104      rm.load();
105      assertEquals(numRegions, regionServer.getNumberOfOnlineRegions());
106      // Repeat the same load. It should be very fast because all regions are already moved.
107      rm.load();
108    }
109  }
110
111  /**
112   * Test to unload a regionserver first and then load it using no Ack mode.
113   */
114  @Test
115  public void testWithoutAck() throws Exception {
116    MiniHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
117    HRegionServer regionServer = cluster.getRegionServer(0);
118    String rsName = regionServer.getServerName().getAddress().toString();
119    int numRegions = regionServer.getNumberOfOnlineRegions();
120    RegionMoverBuilder rmBuilder =
121      new RegionMoverBuilder(rsName, TEST_UTIL.getConfiguration()).ack(false);
122    try (RegionMover rm = rmBuilder.build()) {
123      LOG.info("Unloading " + regionServer.getServerName());
124      rm.unload();
125      TEST_UTIL.waitFor(30000, 1000, new Predicate<Exception>() {
126        @Override
127        public boolean evaluate() throws Exception {
128          return regionServer.getNumberOfOnlineRegions() == 0;
129        }
130      });
131      LOG.info("Successfully Unloaded\nNow Loading");
132      rm.load();
133      // In UT we only have 10 regions so it is not likely to fail, so here we check for all
134      // regions, in the real production this may not be true.
135      TEST_UTIL.waitFor(30000, 1000, new Predicate<Exception>() {
136        @Override
137        public boolean evaluate() throws Exception {
138          return regionServer.getNumberOfOnlineRegions() == numRegions;
139        }
140      });
141    }
142  }
143
144  /**
145   * To test that we successfully exclude a server from the unloading process We test for the number
146   * of regions on Excluded server and also test that regions are unloaded successfully
147   */
148  @Test
149  public void testExclude() throws Exception {
150    MiniHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
151    File excludeFile = new File(TEST_UTIL.getDataTestDir().toUri().getPath(), "exclude_file");
152    FileWriter fos = new FileWriter(excludeFile);
153    HRegionServer excludeServer = cluster.getRegionServer(1);
154    String excludeHostname = excludeServer.getServerName().getHostname();
155    int excludeServerPort = excludeServer.getServerName().getPort();
156    int regionsExcludeServer = excludeServer.getNumberOfOnlineRegions();
157    String excludeServerName = excludeHostname + ":" + Integer.toString(excludeServerPort);
158    fos.write(excludeServerName);
159    fos.close();
160    HRegionServer regionServer = cluster.getRegionServer(0);
161    String rsName = regionServer.getServerName().getHostname();
162    int port = regionServer.getServerName().getPort();
163    String rs = rsName + ":" + Integer.toString(port);
164    RegionMoverBuilder rmBuilder = new RegionMoverBuilder(rs, TEST_UTIL.getConfiguration())
165      .ack(true).excludeFile(excludeFile.getCanonicalPath());
166    try (RegionMover rm = rmBuilder.build()) {
167      rm.unload();
168      LOG.info("Unloading " + rs);
169      assertEquals(0, regionServer.getNumberOfOnlineRegions());
170      assertEquals(regionsExcludeServer, cluster.getRegionServer(1).getNumberOfOnlineRegions());
171      LOG.info("Before:" + regionsExcludeServer + " After:" +
172        cluster.getRegionServer(1).getNumberOfOnlineRegions());
173    }
174  }
175
176  @Test
177  public void testRegionServerPort() {
178    MiniHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
179    HRegionServer regionServer = cluster.getRegionServer(0);
180    String rsName = regionServer.getServerName().getHostname();
181
182    final int PORT = 16021;
183    Configuration conf = TEST_UTIL.getConfiguration();
184    String originalPort = conf.get(HConstants.REGIONSERVER_PORT);
185    conf.set(HConstants.REGIONSERVER_PORT, Integer.toString(PORT));
186    RegionMoverBuilder rmBuilder = new RegionMoverBuilder(rsName, conf);
187    assertEquals(PORT, rmBuilder.port);
188    if (originalPort != null) {
189      conf.set(HConstants.REGIONSERVER_PORT, originalPort);
190    }
191  }
192
193  /**
194   * UT for HBASE-21746
195   */
196  @Test
197  public void testLoadMetaRegion() throws Exception {
198    HRegionServer rsWithMeta = TEST_UTIL.getMiniHBaseCluster().getRegionServerThreads().stream()
199      .map(t -> t.getRegionServer())
200      .filter(rs -> rs.getRegions(TableName.META_TABLE_NAME).size() > 0).findFirst().get();
201    int onlineRegions = rsWithMeta.getNumberOfOnlineRegions();
202    String rsName = rsWithMeta.getServerName().getAddress().toString();
203    try (RegionMover rm =
204      new RegionMoverBuilder(rsName, TEST_UTIL.getConfiguration()).ack(true).build()) {
205      LOG.info("Unloading " + rsWithMeta.getServerName());
206      rm.unload();
207      assertEquals(0, rsWithMeta.getNumberOfOnlineRegions());
208      LOG.info("Loading " + rsWithMeta.getServerName());
209      rm.load();
210      assertEquals(onlineRegions, rsWithMeta.getNumberOfOnlineRegions());
211    }
212  }
213
214  /**
215   * UT for HBASE-21746
216   */
217  @Test
218  public void testTargetServerDeadWhenLoading() throws Exception {
219    HRegionServer rs = TEST_UTIL.getMiniHBaseCluster().getRegionServer(0);
220    String rsName = rs.getServerName().getAddress().toString();
221    Configuration conf = new Configuration(TEST_UTIL.getConfiguration());
222    // wait 5 seconds at most
223    conf.setInt(RegionMover.SERVERSTART_WAIT_MAX_KEY, 5);
224    String filename =
225      new Path(TEST_UTIL.getDataTestDir(), "testTargetServerDeadWhenLoading").toString();
226    // unload the region server
227    try (RegionMover rm =
228      new RegionMoverBuilder(rsName, conf).filename(filename).ack(true).build()) {
229      LOG.info("Unloading " + rs.getServerName());
230      rm.unload();
231      assertEquals(0, rs.getNumberOfOnlineRegions());
232    }
233    String inexistRsName = "whatever:123";
234    try (RegionMover rm =
235      new RegionMoverBuilder(inexistRsName, conf).filename(filename).ack(true).build()) {
236      // load the regions to an inexist region server, which should fail and return false
237      LOG.info("Loading to an inexist region server {}", inexistRsName);
238      assertFalse(rm.load());
239    }
240  }
241}