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