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.apache.hadoop.hbase.util.Threads.sleep;
021import static org.junit.Assert.assertEquals;
022import static org.junit.Assert.assertFalse;
023import static org.junit.Assert.assertTrue;
024import static org.junit.Assert.fail;
025
026import java.io.IOException;
027import java.util.ArrayList;
028import java.util.EnumSet;
029import java.util.Iterator;
030import java.util.List;
031import java.util.Map;
032import java.util.Set;
033import java.util.concurrent.atomic.AtomicBoolean;
034import org.apache.hadoop.hbase.ClusterMetrics.Option;
035import org.apache.hadoop.hbase.HBaseClassTestRule;
036import org.apache.hadoop.hbase.ServerName;
037import org.apache.hadoop.hbase.TableName;
038import org.apache.hadoop.hbase.Waiter;
039import org.apache.hadoop.hbase.client.RegionInfo;
040import org.apache.hadoop.hbase.constraint.ConstraintException;
041import org.apache.hadoop.hbase.master.RegionState;
042import org.apache.hadoop.hbase.master.assignment.RegionStateNode;
043import org.apache.hadoop.hbase.net.Address;
044import org.apache.hadoop.hbase.testclassification.LargeTests;
045import org.apache.hadoop.hbase.util.Bytes;
046import org.junit.After;
047import org.junit.AfterClass;
048import org.junit.Assert;
049import org.junit.Before;
050import org.junit.BeforeClass;
051import org.junit.ClassRule;
052import org.junit.Test;
053import org.junit.experimental.categories.Category;
054import org.slf4j.Logger;
055import org.slf4j.LoggerFactory;
056
057import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
058
059@Category({ LargeTests.class })
060public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
061
062  @ClassRule
063  public static final HBaseClassTestRule CLASS_RULE =
064    HBaseClassTestRule.forClass(TestRSGroupsAdmin2.class);
065
066  protected static final Logger LOG = LoggerFactory.getLogger(TestRSGroupsAdmin2.class);
067
068  @BeforeClass
069  public static void setUp() throws Exception {
070    setUpTestBeforeClass();
071  }
072
073  @AfterClass
074  public static void tearDown() throws Exception {
075    tearDownAfterClass();
076  }
077
078  @Before
079  public void beforeMethod() throws Exception {
080    setUpBeforeMethod();
081  }
082
083  @After
084  public void afterMethod() throws Exception {
085    tearDownAfterMethod();
086  }
087
088  @Test
089  public void testRegionMove() throws Exception {
090    final RSGroupInfo newGroup = addGroup(getGroupName(name.getMethodName()), 1);
091    final byte[] familyNameBytes = Bytes.toBytes("f");
092    // All the regions created below will be assigned to the default group.
093    TEST_UTIL.createMultiRegionTable(tableName, familyNameBytes, 6);
094    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
095      @Override
096      public boolean evaluate() throws Exception {
097        List<String> regions = getTableRegionMap().get(tableName);
098        if (regions == null) {
099          return false;
100        }
101
102        return getTableRegionMap().get(tableName).size() >= 6;
103      }
104    });
105
106    // get target region to move
107    Map<ServerName, List<String>> assignMap = getTableServerRegionMap().get(tableName);
108    String targetRegion = null;
109    for (ServerName server : assignMap.keySet()) {
110      targetRegion = assignMap.get(server).size() > 0 ? assignMap.get(server).get(0) : null;
111      if (targetRegion != null) {
112        break;
113      }
114    }
115    // get server which is not a member of new group
116    ServerName tmpTargetServer = null;
117    for (ServerName server : admin.getClusterMetrics(EnumSet.of(Option.LIVE_SERVERS))
118      .getLiveServerMetrics().keySet()) {
119      if (!newGroup.containsServer(server.getAddress())) {
120        tmpTargetServer = server;
121        break;
122      }
123    }
124    final ServerName targetServer = tmpTargetServer;
125    // move target server to group
126    rsGroupAdmin.moveServers(Sets.newHashSet(targetServer.getAddress()), newGroup.getName());
127    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
128      @Override
129      public boolean evaluate() throws Exception {
130        return admin.getRegions(targetServer).size() <= 0;
131      }
132    });
133
134    // Lets move this region to the new group.
135    TEST_UTIL.getAdmin()
136      .move(Bytes.toBytes(RegionInfo.encodeRegionName(Bytes.toBytes(targetRegion))), targetServer);
137    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
138      @Override
139      public boolean evaluate() throws Exception {
140        return getTableRegionMap().get(tableName) != null &&
141          getTableRegionMap().get(tableName).size() == 6 &&
142          admin.getClusterMetrics(EnumSet.of(Option.REGIONS_IN_TRANSITION))
143            .getRegionStatesInTransition().size() < 1;
144      }
145    });
146
147    // verify that targetServer didn't open it
148    for (RegionInfo region : admin.getRegions(targetServer)) {
149      if (targetRegion.equals(region.getRegionNameAsString())) {
150        fail("Target server opened region");
151      }
152    }
153  }
154
155  @Test
156  public void testRegionServerMove() throws IOException, InterruptedException {
157    int initNumGroups = rsGroupAdmin.listRSGroups().size();
158    RSGroupInfo appInfo = addGroup(getGroupName(name.getMethodName()), 1);
159    RSGroupInfo adminInfo = addGroup(getGroupName(name.getMethodName()), 1);
160    RSGroupInfo dInfo = rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP);
161    Assert.assertEquals(initNumGroups + 2, rsGroupAdmin.listRSGroups().size());
162    assertEquals(1, adminInfo.getServers().size());
163    assertEquals(1, appInfo.getServers().size());
164    assertEquals(getNumServers() - 2, dInfo.getServers().size());
165    rsGroupAdmin.moveServers(appInfo.getServers(), RSGroupInfo.DEFAULT_GROUP);
166    rsGroupAdmin.removeRSGroup(appInfo.getName());
167    rsGroupAdmin.moveServers(adminInfo.getServers(), RSGroupInfo.DEFAULT_GROUP);
168    rsGroupAdmin.removeRSGroup(adminInfo.getName());
169    Assert.assertEquals(rsGroupAdmin.listRSGroups().size(), initNumGroups);
170  }
171
172  @Test
173  public void testMoveServers() throws Exception {
174    // create groups and assign servers
175    addGroup("bar", 3);
176    rsGroupAdmin.addRSGroup("foo");
177
178    RSGroupInfo barGroup = rsGroupAdmin.getRSGroupInfo("bar");
179    RSGroupInfo fooGroup = rsGroupAdmin.getRSGroupInfo("foo");
180    assertEquals(3, barGroup.getServers().size());
181    assertEquals(0, fooGroup.getServers().size());
182
183    // test fail bogus server move
184    try {
185      rsGroupAdmin.moveServers(Sets.newHashSet(Address.fromString("foo:9999")), "foo");
186      fail("Bogus servers shouldn't have been successfully moved.");
187    } catch (IOException ex) {
188      String exp = "Source RSGroup for server foo:9999 does not exist.";
189      String msg = "Expected '" + exp + "' in exception message: ";
190      assertTrue(msg + " " + ex.getMessage(), ex.getMessage().contains(exp));
191    }
192
193    // test success case
194    LOG.info("moving servers " + barGroup.getServers() + " to group foo");
195    rsGroupAdmin.moveServers(barGroup.getServers(), fooGroup.getName());
196
197    barGroup = rsGroupAdmin.getRSGroupInfo("bar");
198    fooGroup = rsGroupAdmin.getRSGroupInfo("foo");
199    assertEquals(0, barGroup.getServers().size());
200    assertEquals(3, fooGroup.getServers().size());
201
202    LOG.info("moving servers " + fooGroup.getServers() + " to group default");
203    rsGroupAdmin.moveServers(fooGroup.getServers(), RSGroupInfo.DEFAULT_GROUP);
204
205    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
206      @Override
207      public boolean evaluate() throws Exception {
208        return getNumServers() == rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP)
209          .getServers().size();
210      }
211    });
212
213    fooGroup = rsGroupAdmin.getRSGroupInfo("foo");
214    assertEquals(0, fooGroup.getServers().size());
215
216    // test group removal
217    LOG.info("Remove group " + barGroup.getName());
218    rsGroupAdmin.removeRSGroup(barGroup.getName());
219    Assert.assertEquals(null, rsGroupAdmin.getRSGroupInfo(barGroup.getName()));
220    LOG.info("Remove group " + fooGroup.getName());
221    rsGroupAdmin.removeRSGroup(fooGroup.getName());
222    Assert.assertEquals(null, rsGroupAdmin.getRSGroupInfo(fooGroup.getName()));
223  }
224
225  @Test
226  public void testRemoveServers() throws Exception {
227    LOG.info("testRemoveServers");
228    final RSGroupInfo newGroup = addGroup(getGroupName(name.getMethodName()), 3);
229    Iterator<Address> iterator = newGroup.getServers().iterator();
230    ServerName targetServer = getServerName(iterator.next());
231
232    // remove online servers
233    try {
234      rsGroupAdmin.removeServers(Sets.newHashSet(targetServer.getAddress()));
235      fail("Online servers shouldn't have been successfully removed.");
236    } catch (IOException ex) {
237      String exp =
238        "Server " + targetServer.getAddress() + " is an online server, not allowed to remove.";
239      String msg = "Expected '" + exp + "' in exception message: ";
240      assertTrue(msg + " " + ex.getMessage(), ex.getMessage().contains(exp));
241    }
242    assertTrue(newGroup.getServers().contains(targetServer.getAddress()));
243
244    // remove dead servers
245    NUM_DEAD_SERVERS = cluster.getClusterMetrics().getDeadServerNames().size();
246    try {
247      // stopping may cause an exception
248      // due to the connection loss
249      LOG.info("stopping server " + targetServer.getServerName());
250      admin.stopRegionServer(targetServer.getAddress().toString());
251      NUM_DEAD_SERVERS++;
252    } catch (Exception e) {
253    }
254
255    // wait for stopped regionserver to dead server list
256    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
257      @Override
258      public boolean evaluate() throws Exception {
259        return !master.getServerManager().areDeadServersInProgress() &&
260          cluster.getClusterMetrics().getDeadServerNames().size() == NUM_DEAD_SERVERS;
261      }
262    });
263
264    try {
265      rsGroupAdmin.removeServers(Sets.newHashSet(targetServer.getAddress()));
266      fail("Dead servers shouldn't have been successfully removed.");
267    } catch (IOException ex) {
268      String exp = "Server " + targetServer.getAddress() + " is on the dead servers list," +
269        " Maybe it will come back again, not allowed to remove.";
270      String msg = "Expected '" + exp + "' in exception message: ";
271      assertTrue(msg + " " + ex.getMessage(), ex.getMessage().contains(exp));
272    }
273    assertTrue(newGroup.getServers().contains(targetServer.getAddress()));
274
275    // remove decommissioned servers
276    List<ServerName> serversToDecommission = new ArrayList<>();
277    targetServer = getServerName(iterator.next());
278    assertTrue(master.getServerManager().getOnlineServers().containsKey(targetServer));
279    serversToDecommission.add(targetServer);
280
281    admin.decommissionRegionServers(serversToDecommission, true);
282    assertEquals(1, admin.listDecommissionedRegionServers().size());
283
284    assertTrue(newGroup.getServers().contains(targetServer.getAddress()));
285    rsGroupAdmin.removeServers(Sets.newHashSet(targetServer.getAddress()));
286    Set<Address> newGroupServers = rsGroupAdmin.getRSGroupInfo(newGroup.getName()).getServers();
287    assertFalse(newGroupServers.contains(targetServer.getAddress()));
288    assertEquals(2, newGroupServers.size());
289
290    assertTrue(observer.preRemoveServersCalled);
291    assertTrue(observer.postRemoveServersCalled);
292  }
293
294  @Test
295  public void testMoveServersAndTables() throws Exception {
296    LOG.info("testMoveServersAndTables");
297    final RSGroupInfo newGroup = addGroup(getGroupName(name.getMethodName()), 1);
298    // create table
299    final byte[] familyNameBytes = Bytes.toBytes("f");
300    TEST_UTIL.createMultiRegionTable(tableName, familyNameBytes, 5);
301    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
302      @Override
303      public boolean evaluate() throws Exception {
304        List<String> regions = getTableRegionMap().get(tableName);
305        if (regions == null) {
306          return false;
307        }
308
309        return getTableRegionMap().get(tableName).size() >= 5;
310      }
311    });
312
313    // get server which is not a member of new group
314    ServerName targetServer = null;
315    for (ServerName server : admin.getClusterMetrics(EnumSet.of(Option.LIVE_SERVERS))
316      .getLiveServerMetrics().keySet()) {
317      if (!newGroup.containsServer(server.getAddress()) &&
318        !rsGroupAdmin.getRSGroupInfo("master").containsServer(server.getAddress())) {
319        targetServer = server;
320        break;
321      }
322    }
323
324    LOG.debug("Print group info : " + rsGroupAdmin.listRSGroups());
325    int oldDefaultGroupServerSize =
326      rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getServers().size();
327    int oldDefaultGroupTableSize =
328      rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getTables().size();
329
330    // test fail bogus server move
331    try {
332      rsGroupAdmin.moveServersAndTables(Sets.newHashSet(Address.fromString("foo:9999")),
333        Sets.newHashSet(tableName), newGroup.getName());
334      fail("Bogus servers shouldn't have been successfully moved.");
335    } catch (IOException ex) {
336      String exp = "Source RSGroup for server foo:9999 does not exist.";
337      String msg = "Expected '" + exp + "' in exception message: ";
338      assertTrue(msg + " " + ex.getMessage(), ex.getMessage().contains(exp));
339    }
340
341    // test move when src = dst
342    rsGroupAdmin.moveServersAndTables(Sets.newHashSet(targetServer.getAddress()),
343      Sets.newHashSet(tableName), RSGroupInfo.DEFAULT_GROUP);
344
345    // verify default group info
346    Assert.assertEquals(oldDefaultGroupServerSize,
347      rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getServers().size());
348    Assert.assertEquals(oldDefaultGroupTableSize,
349      rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getTables().size());
350
351    // verify new group info
352    Assert.assertEquals(1, rsGroupAdmin.getRSGroupInfo(newGroup.getName()).getServers().size());
353    Assert.assertEquals(0, rsGroupAdmin.getRSGroupInfo(newGroup.getName()).getTables().size());
354
355    // get all region to move targetServer
356    List<String> regionList = getTableRegionMap().get(tableName);
357    for (String region : regionList) {
358      // Lets move this region to the targetServer
359      TEST_UTIL.getAdmin().move(Bytes.toBytes(RegionInfo.encodeRegionName(Bytes.toBytes(region))),
360        targetServer);
361    }
362
363    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
364      @Override
365      public boolean evaluate() throws Exception {
366        return getTableRegionMap().get(tableName) != null &&
367          getTableRegionMap().get(tableName).size() == 5 &&
368          getTableServerRegionMap().get(tableName).size() == 1 &&
369          admin.getClusterMetrics(EnumSet.of(Option.REGIONS_IN_TRANSITION))
370            .getRegionStatesInTransition().size() < 1;
371      }
372    });
373
374    // verify that all region move to targetServer
375    Assert.assertEquals(5, getTableServerRegionMap().get(tableName).get(targetServer).size());
376
377    // move targetServer and table to newGroup
378    LOG.info("moving server and table to newGroup");
379    rsGroupAdmin.moveServersAndTables(Sets.newHashSet(targetServer.getAddress()),
380      Sets.newHashSet(tableName), newGroup.getName());
381
382    // verify group change
383    Assert.assertEquals(newGroup.getName(),
384      rsGroupAdmin.getRSGroupInfoOfTable(tableName).getName());
385
386    // verify servers' not exist in old group
387    Set<Address> defaultServers =
388      rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getServers();
389    assertFalse(defaultServers.contains(targetServer.getAddress()));
390
391    // verify servers' exist in new group
392    Set<Address> newGroupServers = rsGroupAdmin.getRSGroupInfo(newGroup.getName()).getServers();
393    assertTrue(newGroupServers.contains(targetServer.getAddress()));
394
395    // verify tables' not exist in old group
396    Set<TableName> defaultTables =
397      rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getTables();
398    assertFalse(defaultTables.contains(tableName));
399
400    // verify tables' exist in new group
401    Set<TableName> newGroupTables = rsGroupAdmin.getRSGroupInfo(newGroup.getName()).getTables();
402    assertTrue(newGroupTables.contains(tableName));
403
404    // verify that all region still assgin on targetServer
405    Assert.assertEquals(5, getTableServerRegionMap().get(tableName).get(targetServer).size());
406
407    assertTrue(observer.preMoveServersAndTables);
408    assertTrue(observer.postMoveServersAndTables);
409  }
410
411  @Test
412  public void testMoveServersFromDefaultGroup() throws Exception {
413    // create groups and assign servers
414    rsGroupAdmin.addRSGroup("foo");
415
416    RSGroupInfo fooGroup = rsGroupAdmin.getRSGroupInfo("foo");
417    assertEquals(0, fooGroup.getServers().size());
418    RSGroupInfo defaultGroup = rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP);
419
420    // test remove all servers from default
421    try {
422      rsGroupAdmin.moveServers(defaultGroup.getServers(), fooGroup.getName());
423      fail(RSGroupAdminServer.KEEP_ONE_SERVER_IN_DEFAULT_ERROR_MESSAGE);
424    } catch (ConstraintException ex) {
425      assertTrue(
426        ex.getMessage().contains(RSGroupAdminServer.KEEP_ONE_SERVER_IN_DEFAULT_ERROR_MESSAGE));
427    }
428
429    // test success case, remove one server from default ,keep at least one server
430    if (defaultGroup.getServers().size() > 1) {
431      Address serverInDefaultGroup = defaultGroup.getServers().iterator().next();
432      LOG.info("moving server " + serverInDefaultGroup + " from group default to group " +
433        fooGroup.getName());
434      rsGroupAdmin.moveServers(Sets.newHashSet(serverInDefaultGroup), fooGroup.getName());
435    }
436
437    fooGroup = rsGroupAdmin.getRSGroupInfo("foo");
438    LOG.info("moving servers " + fooGroup.getServers() + " to group default");
439    rsGroupAdmin.moveServers(fooGroup.getServers(), RSGroupInfo.DEFAULT_GROUP);
440
441    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
442      @Override
443      public boolean evaluate() throws Exception {
444        return getNumServers() == rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP)
445          .getServers().size();
446      }
447    });
448
449    fooGroup = rsGroupAdmin.getRSGroupInfo("foo");
450    assertEquals(0, fooGroup.getServers().size());
451
452    // test group removal
453    LOG.info("Remove group " + fooGroup.getName());
454    rsGroupAdmin.removeRSGroup(fooGroup.getName());
455    Assert.assertEquals(null, rsGroupAdmin.getRSGroupInfo(fooGroup.getName()));
456  }
457
458  @Test
459  public void testFailedMoveWhenMoveServer() throws Exception {
460    final RSGroupInfo newGroup = addGroup(getGroupName(name.getMethodName()), 1);
461    final byte[] familyNameBytes = Bytes.toBytes("f");
462    final int tableRegionCount = 10;
463    // All the regions created below will be assigned to the default group.
464    TEST_UTIL.createMultiRegionTable(tableName, familyNameBytes, tableRegionCount);
465    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
466      @Override
467      public boolean evaluate() throws Exception {
468        List<String> regions = getTableRegionMap().get(tableName);
469        if (regions == null) {
470          return false;
471        }
472        return getTableRegionMap().get(tableName).size() >= tableRegionCount;
473      }
474    });
475
476    // get target server to move, which should has more than one regions
477    // randomly set a region state to SPLITTING
478    Map<ServerName, List<String>> assignMap = getTableServerRegionMap().get(tableName);
479    String rregion = null;
480    ServerName toMoveServer = null;
481    for (ServerName server : assignMap.keySet()) {
482      rregion = assignMap.get(server).size() > 1 && !newGroup.containsServer(server.getAddress()) ?
483          assignMap.get(server).get(0) :
484          null;
485      if (rregion != null) {
486        toMoveServer = server;
487        break;
488      }
489    }
490    assert toMoveServer != null;
491    RegionInfo ri = TEST_UTIL.getMiniHBaseCluster().getMaster().getAssignmentManager().
492        getRegionInfo(Bytes.toBytesBinary(rregion));
493    RegionStateNode rsn =
494        TEST_UTIL.getMiniHBaseCluster().getMaster().getAssignmentManager().getRegionStates()
495            .getRegionStateNode(ri);
496    rsn.setState(RegionState.State.SPLITTING);
497
498    // start thread to recover region state
499    final ServerName movedServer = toMoveServer;
500    final String sregion = rregion;
501    AtomicBoolean changed = new AtomicBoolean(false);
502    Thread t1 = new Thread(() -> {
503      LOG.debug("thread1 start running, will recover region state");
504      long current = System.currentTimeMillis();
505      while (System.currentTimeMillis() - current <= 50000) {
506        List<RegionInfo> regions = master.getAssignmentManager().getRegionsOnServer(movedServer);
507        LOG.debug("server region size is:{}", regions.size());
508        assert regions.size() >= 1;
509        // when there is exactly one region left, we can determine the move operation encountered
510        // exception caused by the strange region state.
511        if (regions.size() == 1) {
512          assertEquals(regions.get(0).getRegionNameAsString(), sregion);
513          rsn.setState(RegionState.State.OPEN);
514          LOG.info("set region {} state OPEN", sregion);
515          changed.set(true);
516          break;
517        }
518        sleep(5000);
519      }
520    });
521    t1.start();
522
523    // move target server to group
524    Thread t2 = new Thread(() -> {
525      LOG.info("thread2 start running, to move regions");
526      try {
527        rsGroupAdmin.moveServers(Sets.newHashSet(movedServer.getAddress()), newGroup.getName());
528      } catch (IOException e) {
529        LOG.error("move server error", e);
530      }
531    });
532    t2.start();
533
534    t1.join();
535    t2.join();
536
537    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
538      @Override
539      public boolean evaluate() {
540        if (changed.get()) {
541          return master.getAssignmentManager().getRegionsOnServer(movedServer).size() == 0 && !rsn
542              .getRegionLocation().equals(movedServer);
543        }
544        return false;
545      }
546    });
547  }
548
549  @Test
550  public void testFailedMoveWhenMoveTable() throws Exception {
551    final RSGroupInfo newGroup = addGroup(getGroupName(name.getMethodName()), 1);
552    final byte[] familyNameBytes = Bytes.toBytes("f");
553    final int tableRegionCount = 5;
554    // All the regions created below will be assigned to the default group.
555    TEST_UTIL.createMultiRegionTable(tableName, familyNameBytes, tableRegionCount);
556    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
557      @Override
558      public boolean evaluate() throws Exception {
559        List<String> regions = getTableRegionMap().get(tableName);
560        if (regions == null) {
561          return false;
562        }
563        return getTableRegionMap().get(tableName).size() >= tableRegionCount;
564      }
565    });
566
567    // randomly set a region state to SPLITTING
568    Map<ServerName, List<String>> assignMap = getTableServerRegionMap().get(tableName);
569    String rregion = null;
570    ServerName srcServer = null;
571    for (ServerName server : assignMap.keySet()) {
572      rregion = assignMap.get(server).size() >= 1 && !newGroup.containsServer(server.getAddress()) ?
573          assignMap.get(server).get(0) :
574          null;
575      if (rregion != null) {
576        srcServer = server;
577        break;
578      }
579    }
580    assert srcServer != null;
581    RegionInfo ri = TEST_UTIL.getMiniHBaseCluster().getMaster().getAssignmentManager().
582        getRegionInfo(Bytes.toBytesBinary(rregion));
583    RegionStateNode rsn =
584        TEST_UTIL.getMiniHBaseCluster().getMaster().getAssignmentManager().getRegionStates()
585            .getRegionStateNode(ri);
586    rsn.setState(RegionState.State.SPLITTING);
587
588    // move table to group
589    Thread t2 = new Thread(() -> {
590      LOG.info("thread2 start running, to move regions");
591      try {
592        rsGroupAdmin.moveTables(Sets.newHashSet(tableName), newGroup.getName());
593      } catch (IOException e) {
594        LOG.error("move server error", e);
595      }
596    });
597    t2.start();
598
599    // start thread to recover region state
600    final ServerName ss = srcServer;
601    final String sregion = rregion;
602    AtomicBoolean changed = new AtomicBoolean(false);
603    Thread t1 = new Thread(() -> {
604      LOG.info("thread1 start running, will recover region state");
605      long current = System.currentTimeMillis();
606      while (System.currentTimeMillis() - current <= 50000) {
607        List<RegionInfo> regions = master.getAssignmentManager().getRegionsOnServer(ss);
608        List<RegionInfo> tableRegions = new ArrayList<>();
609        for (RegionInfo regionInfo : regions) {
610          if (regionInfo.getTable().equals(tableName)) {
611            tableRegions.add(regionInfo);
612          }
613        }
614        LOG.debug("server table region size is:{}", tableRegions.size());
615        assert tableRegions.size() >= 1;
616        // when there is exactly one region left, we can determine the move operation encountered
617        // exception caused by the strange region state.
618        if (tableRegions.size() == 1) {
619          assertEquals(tableRegions.get(0).getRegionNameAsString(), sregion);
620          rsn.setState(RegionState.State.OPEN);
621          LOG.info("set region {} state OPEN", sregion);
622          changed.set(true);
623          break;
624        }
625        sleep(5000);
626      }
627    });
628    t1.start();
629
630    t1.join();
631    t2.join();
632
633    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
634      @Override
635      public boolean evaluate() {
636        if (changed.get()) {
637          boolean serverHasTableRegions = false;
638          for (RegionInfo regionInfo : master.getAssignmentManager().getRegionsOnServer(ss)) {
639            if (regionInfo.getTable().equals(tableName)) {
640              serverHasTableRegions = true;
641              break;
642            }
643          }
644          return !serverHasTableRegions && !rsn.getRegionLocation().equals(ss);
645        }
646        return false;
647      }
648    });
649  }
650}