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.rsgroup;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertFalse;
022import static org.junit.Assert.assertTrue;
023
024import java.util.List;
025import java.util.Map;
026import org.apache.hadoop.hbase.HBaseClassTestRule;
027import org.apache.hadoop.hbase.HConstants;
028import org.apache.hadoop.hbase.NamespaceDescriptor;
029import org.apache.hadoop.hbase.ServerName;
030import org.apache.hadoop.hbase.TableName;
031import org.apache.hadoop.hbase.Waiter;
032import org.apache.hadoop.hbase.Waiter.Predicate;
033import org.apache.hadoop.hbase.client.BalanceRequest;
034import org.apache.hadoop.hbase.client.BalanceResponse;
035import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
036import org.apache.hadoop.hbase.client.RegionInfo;
037import org.apache.hadoop.hbase.client.TableDescriptor;
038import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
039import org.apache.hadoop.hbase.master.HMaster;
040import org.apache.hadoop.hbase.testclassification.MediumTests;
041import org.apache.hadoop.hbase.util.Bytes;
042import org.junit.After;
043import org.junit.AfterClass;
044import org.junit.Before;
045import org.junit.BeforeClass;
046import org.junit.ClassRule;
047import org.junit.Test;
048import org.junit.experimental.categories.Category;
049import org.slf4j.Logger;
050import org.slf4j.LoggerFactory;
051
052import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
053
054@Category({ MediumTests.class })
055public class TestRSGroupsBalance extends TestRSGroupsBase {
056
057  @ClassRule
058  public static final HBaseClassTestRule CLASS_RULE =
059    HBaseClassTestRule.forClass(TestRSGroupsBalance.class);
060
061  protected static final Logger LOG = LoggerFactory.getLogger(TestRSGroupsBalance.class);
062
063  @BeforeClass
064  public static void setUp() throws Exception {
065    setUpTestBeforeClass();
066  }
067
068  @AfterClass
069  public static void tearDown() throws Exception {
070    tearDownAfterClass();
071  }
072
073  @Before
074  public void beforeMethod() throws Exception {
075    setUpBeforeMethod();
076  }
077
078  @After
079  public void afterMethod() throws Exception {
080    tearDownAfterMethod();
081  }
082
083  @Test
084  public void testGroupBalance() throws Exception {
085    String methodName = name.getMethodName();
086
087    LOG.info(methodName);
088    String newGroupName = getGroupName(methodName);
089    TableName tableName = TableName.valueOf(tablePrefix + "_ns", methodName);
090
091    ServerName first = setupBalanceTest(newGroupName, tableName);
092
093    // balance the other group and make sure it doesn't affect the new group
094    admin.balancerSwitch(true, true);
095    rsGroupAdmin.balanceRSGroup(RSGroupInfo.DEFAULT_GROUP);
096    assertEquals(6, getTableServerRegionMap().get(tableName).get(first).size());
097
098    // disable balance, balancer will not be run and return false
099    admin.balancerSwitch(false, true);
100    assertFalse(rsGroupAdmin.balanceRSGroup(newGroupName).isBalancerRan());
101    assertEquals(6, getTableServerRegionMap().get(tableName).get(first).size());
102
103    // enable balance
104    admin.balancerSwitch(true, true);
105    rsGroupAdmin.balanceRSGroup(newGroupName);
106    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
107      @Override
108      public boolean evaluate() throws Exception {
109        for (List<String> regions : getTableServerRegionMap().get(tableName).values()) {
110          if (2 != regions.size()) {
111            return false;
112          }
113        }
114        return true;
115      }
116    });
117    admin.balancerSwitch(false, true);
118  }
119
120  @Test
121  public void testGroupDryRunBalance() throws Exception {
122    String methodName = name.getMethodName();
123
124    LOG.info(methodName);
125    String newGroupName = getGroupName(methodName);
126    final TableName tableName = TableName.valueOf(tablePrefix + "_ns", methodName);
127
128    ServerName first = setupBalanceTest(newGroupName, tableName);
129
130    // run the balancer in dry run mode. it should return true, but should not actually move any
131    // regions
132    admin.balancerSwitch(true, true);
133    BalanceResponse response = rsGroupAdmin.balanceRSGroup(newGroupName,
134      BalanceRequest.newBuilder().setDryRun(true).build());
135    assertTrue(response.isBalancerRan());
136    assertTrue(response.getMovesCalculated() > 0);
137    assertEquals(0, response.getMovesExecuted());
138    // validate imbalance still exists.
139    assertEquals(6, getTableServerRegionMap().get(tableName).get(first).size());
140  }
141
142  private ServerName setupBalanceTest(String newGroupName, TableName tableName) throws Exception {
143    addGroup(newGroupName, 3);
144
145    admin.createNamespace(NamespaceDescriptor.create(tableName.getNamespaceAsString())
146      .addConfiguration(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP, newGroupName).build());
147    final TableDescriptor desc = TableDescriptorBuilder.newBuilder(tableName)
148      .setColumnFamily(ColumnFamilyDescriptorBuilder.of("f")).build();
149    byte[] startKey = Bytes.toBytes("aaaaa");
150    byte[] endKey = Bytes.toBytes("zzzzz");
151    admin.createTable(desc, startKey, endKey, 6);
152    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
153      @Override
154      public boolean evaluate() throws Exception {
155        List<String> regions = getTableRegionMap().get(tableName);
156        if (regions == null) {
157          return false;
158        }
159        return regions.size() >= 6;
160      }
161    });
162
163    // make assignment uneven, move all regions to one server
164    Map<ServerName, List<String>> assignMap = getTableServerRegionMap().get(tableName);
165    final ServerName first = assignMap.entrySet().iterator().next().getKey();
166    for (RegionInfo region : admin.getRegions(tableName)) {
167      if (!assignMap.get(first).contains(region.getRegionNameAsString())) {
168        admin.move(region.getEncodedNameAsBytes(), first);
169      }
170    }
171    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
172      @Override
173      public boolean evaluate() throws Exception {
174        Map<ServerName, List<String>> map = getTableServerRegionMap().get(tableName);
175        if (map == null) {
176          return true;
177        }
178        List<String> regions = map.get(first);
179        if (regions == null) {
180          return true;
181        }
182        return regions.size() >= 6;
183      }
184    });
185
186    return first;
187  }
188
189  @Test
190  public void testMisplacedRegions() throws Exception {
191    final TableName tableName = TableName.valueOf(tablePrefix + "_testMisplacedRegions");
192    LOG.info("testMisplacedRegions");
193
194    final RSGroupInfo RSGroupInfo = addGroup("testMisplacedRegions", 1);
195
196    TEST_UTIL.createMultiRegionTable(tableName, new byte[] { 'f' }, 15);
197    TEST_UTIL.waitUntilAllRegionsAssigned(tableName);
198
199    rsGroupAdminEndpoint.getGroupInfoManager().moveTables(Sets.newHashSet(tableName),
200      RSGroupInfo.getName());
201
202    admin.balancerSwitch(true, true);
203    assertTrue(rsGroupAdmin.balanceRSGroup(RSGroupInfo.getName()).isBalancerRan());
204    admin.balancerSwitch(false, true);
205    assertTrue(observer.preBalanceRSGroupCalled);
206    assertTrue(observer.postBalanceRSGroupCalled);
207
208    TEST_UTIL.waitFor(60000, new Predicate<Exception>() {
209      @Override
210      public boolean evaluate() throws Exception {
211        ServerName serverName =
212          ServerName.valueOf(RSGroupInfo.getServers().iterator().next().toString(), 1);
213        return admin.getConnection().getAdmin().getRegions(serverName).size() == 15;
214      }
215    });
216  }
217
218  @Test
219  public void testGetRSGroupAssignmentsByTable() throws Exception {
220    final TableName tableName = TableName.valueOf(name.getMethodName());
221    TEST_UTIL.createMultiRegionTable(tableName, HConstants.CATALOG_FAMILY, 10);
222    // disable table
223    final TableName disableTableName = TableName.valueOf("testDisableTable");
224    TEST_UTIL.createMultiRegionTable(disableTableName, HConstants.CATALOG_FAMILY, 10);
225    TEST_UTIL.getAdmin().disableTable(disableTableName);
226
227    HMaster master = TEST_UTIL.getMiniHBaseCluster().getMaster();
228    Map<TableName, Map<ServerName, List<RegionInfo>>> assignments =
229      rsGroupAdminEndpoint.getGroupAdminServer()
230        .getRSGroupAssignmentsByTable(master.getTableStateManager(), RSGroupInfo.DEFAULT_GROUP);
231    assertFalse(assignments.containsKey(disableTableName));
232    assertTrue(assignments.containsKey(tableName));
233  }
234}