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.jupiter.api.Assertions.assertEquals;
021import static org.junit.jupiter.api.Assertions.assertFalse;
022import static org.junit.jupiter.api.Assertions.assertTrue;
023
024import java.util.ArrayList;
025import java.util.Collection;
026import java.util.HashSet;
027import java.util.List;
028import org.apache.hadoop.fs.Path;
029import org.apache.hadoop.hbase.HBaseTestingUtil;
030import org.apache.hadoop.hbase.ServerName;
031import org.apache.hadoop.hbase.TableName;
032import org.apache.hadoop.hbase.client.Admin;
033import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
034import org.apache.hadoop.hbase.client.TableDescriptor;
035import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
036import org.apache.hadoop.hbase.net.Address;
037import org.apache.hadoop.hbase.regionserver.HRegion;
038import org.apache.hadoop.hbase.regionserver.HRegionServer;
039import org.apache.hadoop.hbase.rsgroup.RSGroupInfo;
040import org.apache.hadoop.hbase.rsgroup.RSGroupUtil;
041import org.apache.hadoop.hbase.testclassification.MediumTests;
042import org.apache.hadoop.hbase.testclassification.MiscTests;
043import org.apache.hadoop.hbase.util.RegionMover.RegionMoverBuilder;
044import org.junit.jupiter.api.AfterAll;
045import org.junit.jupiter.api.AfterEach;
046import org.junit.jupiter.api.BeforeAll;
047import org.junit.jupiter.api.BeforeEach;
048import org.junit.jupiter.api.Tag;
049import org.junit.jupiter.api.Test;
050import org.slf4j.Logger;
051import org.slf4j.LoggerFactory;
052
053/**
054 * Test for rsgroup enable, unloaded regions from decommissoned host of a rsgroup should be assigned
055 * to those regionservers belonging to the same rsgroup.
056 */
057@Tag(MiscTests.TAG)
058@Tag(MediumTests.TAG)
059public class TestRegionMoverWithRSGroupEnable {
060
061  private static final Logger LOG = LoggerFactory.getLogger(TestRegionMoverWithRSGroupEnable.class);
062  private static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil();
063  private static final String TEST_RSGROUP = "test";
064
065  @BeforeAll
066  public static void setUpBeforeClass() throws Exception {
067    RSGroupUtil.enableRSGroup(TEST_UTIL.getConfiguration());
068    TEST_UTIL.startMiniCluster(5);
069  }
070
071  @AfterAll
072  public static void tearDownAfterClass() throws Exception {
073    TEST_UTIL.shutdownMiniCluster();
074  }
075
076  private static final TableName TABLE_NAME = TableName.valueOf("testRegionMoverWithRSGroupEnable");
077
078  private final List<Address> rsservers = new ArrayList<>(2);
079  private final List<ServerName> defaultGroupServers = new ArrayList<>();
080  private ServerName rsContainMeta;
081
082  @BeforeEach
083  public void setUp() throws Exception {
084    Admin admin = TEST_UTIL.getAdmin();
085
086    // Add a new rsgroup and assign two servers to it.
087    admin.addRSGroup(TEST_RSGROUP);
088    Collection<ServerName> allServers = admin.getRegionServers();
089    // Remove rs contains hbase:meta, otherwise test looks unstable and buggy in test env.
090    rsContainMeta = TEST_UTIL.getMiniHBaseCluster().getRegionServerThreads().stream()
091      .map(t -> t.getRegionServer())
092      .filter(rs -> rs.getRegions(TableName.META_TABLE_NAME).size() > 0).findFirst().get()
093      .getServerName();
094    LOG.info("{} contains hbase:meta", rsContainMeta);
095    List<ServerName> modifiable = new ArrayList<>(allServers);
096    modifiable.remove(rsContainMeta);
097    int i = 0;
098    for (ServerName server : modifiable) {
099      if (i == 2) break;
100      rsservers.add(Address.fromParts(server.getHostname(), server.getPort()));
101      i++;
102    }
103    admin.moveServersToRSGroup(new HashSet<>(rsservers), TEST_RSGROUP);
104    LOG.info("Servers in {} are {}", TEST_RSGROUP, rsservers);
105    assertEquals(3, admin.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getServers().size());
106    assertEquals(2, admin.getRSGroup(TEST_RSGROUP).getServers().size());
107
108    // Track the servers left in the default group, used for isolation assertions.
109    for (ServerName server : allServers) {
110      if (!rsservers.contains(Address.fromParts(server.getHostname(), server.getPort()))) {
111        defaultGroupServers.add(server);
112      }
113    }
114
115    // Create a pre-split table in test rsgroup
116    if (admin.tableExists(TABLE_NAME)) {
117      TEST_UTIL.deleteTable(TABLE_NAME);
118    }
119    TableDescriptor tableDesc = TableDescriptorBuilder.newBuilder(TABLE_NAME)
120      .setColumnFamily(ColumnFamilyDescriptorBuilder.of("f")).setRegionServerGroup(TEST_RSGROUP)
121      .build();
122    String startKey = "a";
123    String endKey = "z";
124    admin.createTable(tableDesc, Bytes.toBytes(startKey), Bytes.toBytes(endKey), 9);
125  }
126
127  @AfterEach
128  public void tearDown() throws Exception {
129    Admin admin = TEST_UTIL.getAdmin();
130    if (admin.tableExists(TABLE_NAME)) {
131      TEST_UTIL.deleteTable(TABLE_NAME);
132    }
133    if (!rsservers.isEmpty()) {
134      admin.moveServersToRSGroup(new HashSet<>(rsservers), RSGroupInfo.DEFAULT_GROUP);
135    }
136    if (admin.getRSGroup(TEST_RSGROUP) != null) {
137      admin.removeRSGroup(TEST_RSGROUP);
138    }
139    rsservers.clear();
140    defaultGroupServers.clear();
141    rsContainMeta = null;
142  }
143
144  @Test
145  public void testUnloadRegions() throws Exception {
146    Address decommission = rsservers.get(0);
147    Address online = rsservers.get(1);
148    String filename = new Path(TEST_UTIL.getDataTestDir(), "testRSGroupUnload").toString();
149    RegionMoverBuilder builder =
150      new RegionMoverBuilder(decommission.toString(), TEST_UTIL.getConfiguration());
151    try (RegionMover rm = builder.filename(filename).ack(true).build()) {
152      LOG.info("Unloading " + decommission.getHostname());
153      rm.unload();
154    }
155    HRegionServer onlineRS = TEST_UTIL.getMiniHBaseCluster().getRegionServerThreads().stream()
156      .map(JVMClusterUtil.RegionServerThread::getRegionServer)
157      .filter(rs -> rs.getServerName().getAddress().equals(online)).findFirst().get();
158    assertEquals(9, onlineRS.getNumberOfOnlineRegions());
159
160    // Isolation assertion: no default-group server must hold any region of the test table.
161    for (ServerName defaultSN : defaultGroupServers) {
162      HRegionServer defaultRS = TEST_UTIL.getMiniHBaseCluster().getRegionServerThreads().stream()
163        .map(JVMClusterUtil.RegionServerThread::getRegionServer)
164        .filter(rs -> rs.getServerName().equals(defaultSN)).findFirst().orElse(null);
165      if (defaultRS == null) {
166        continue;
167      }
168      List<HRegion> tableRegions = defaultRS.getRegions(TABLE_NAME);
169      assertTrue(tableRegions.isEmpty(), "Default-group server " + defaultSN
170        + " must not hold any regions of " + TABLE_NAME + " but had: " + tableRegions);
171    }
172  }
173
174  /**
175   * Unloading a server that is in the default RSGroup must still succeed end-to-end when RSGroups
176   * are enabled. Destinations must be filtered to the default group: regions may spread across the
177   * other default-group servers, but must not land on any test-group server.
178   */
179  @Test
180  public void testUnloadDefaultGroupServerWithRSGroupEnabled() throws Exception {
181    Admin admin = TEST_UTIL.getAdmin();
182    // Avoid unloading the meta-carrying server here too, for the same stability reason setUp()
183    // avoids it when picking rsservers.
184    ServerName defaultSN =
185      defaultGroupServers.stream().filter(sn -> !sn.equals(rsContainMeta)).findFirst().get();
186    Address decommission = defaultSN.getAddress();
187    String filename = new Path(TEST_UTIL.getDataTestDir(), "testDefaultGroupUnload").toString();
188
189    // Create a table in the default group; the balancer will distribute its regions naturally
190    // across the default-group servers, so defaultSN will hold at least some.
191    TableName defaultTable = TableName.valueOf("testDefaultGroupTable");
192    if (admin.tableExists(defaultTable)) {
193      TEST_UTIL.deleteTable(defaultTable);
194    }
195    try {
196      TableDescriptor td = TableDescriptorBuilder.newBuilder(defaultTable)
197        .setColumnFamily(ColumnFamilyDescriptorBuilder.of("f")).build();
198      admin.createTable(td, Bytes.toBytes("a"), Bytes.toBytes("z"), 6);
199      TEST_UTIL.waitTableAvailable(defaultTable);
200
201      HRegionServer decommRS = TEST_UTIL.getMiniHBaseCluster().getRegionServerThreads().stream()
202        .map(JVMClusterUtil.RegionServerThread::getRegionServer)
203        .filter(rs -> rs.getServerName().equals(defaultSN)).findFirst().get();
204      assertFalse(decommRS.getRegions(defaultTable).isEmpty(),
205        "Precondition: decommissioned server must actually host some regions of the default "
206          + "table, otherwise the post-unload check below is vacuous");
207
208      RegionMoverBuilder builder =
209        new RegionMoverBuilder(decommission.toString(), TEST_UTIL.getConfiguration());
210      try (RegionMover rm = builder.filename(filename).ack(true).build()) {
211        LOG.info("Unloading default-group server {}", decommission.getHostname());
212        rm.unload();
213      }
214
215      // After unload, the decommissioned server must hold no regions of the default table.
216      assertEquals(0, decommRS.getRegions(defaultTable).size(),
217        "Decommissioned default-group server must hold no regions after unload");
218
219      // Isolation assertion: no test-group server must hold any region of the default table.
220      for (JVMClusterUtil.RegionServerThread rst : TEST_UTIL.getMiniHBaseCluster()
221        .getRegionServerThreads()) {
222        HRegionServer rs = rst.getRegionServer();
223        Address addr = rs.getServerName().getAddress();
224        if (rsservers.contains(addr)) {
225          List<HRegion> found = rs.getRegions(defaultTable);
226          assertTrue(found.isEmpty(), "Test-group server " + addr + " must not hold any region of "
227            + defaultTable + " but had: " + found);
228        }
229      }
230    } finally {
231      if (admin.tableExists(defaultTable)) {
232        TEST_UTIL.deleteTable(defaultTable);
233      }
234    }
235  }
236
237}