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