View Javadoc

1   /**
2    * Copyright The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  package org.apache.hadoop.hbase.rsgroup;
21  
22  import static org.junit.Assert.assertEquals;
23  import static org.junit.Assert.assertFalse;
24  import static org.junit.Assert.assertNotNull;
25  import static org.junit.Assert.assertNull;
26  import static org.junit.Assert.assertTrue;
27  import static org.junit.Assert.fail;
28  
29  import java.io.IOException;
30  import java.security.SecureRandom;
31  import java.util.HashSet;
32  import java.util.LinkedList;
33  import java.util.List;
34  import java.util.Map;
35  import java.util.Set;
36  import java.util.TreeMap;
37  
38  import org.apache.commons.logging.Log;
39  import org.apache.commons.logging.LogFactory;
40  import org.apache.hadoop.hbase.ClusterStatus;
41  import org.apache.hadoop.hbase.HBaseCluster;
42  import org.apache.hadoop.hbase.HBaseTestingUtility;
43  import org.apache.hadoop.hbase.HColumnDescriptor;
44  import org.apache.hadoop.hbase.HRegionInfo;
45  import org.apache.hadoop.hbase.HTableDescriptor;
46  import org.apache.hadoop.hbase.NamespaceDescriptor;
47  import org.apache.hadoop.hbase.RegionLoad;
48  import org.apache.hadoop.hbase.ServerName;
49  import org.apache.hadoop.hbase.TableName;
50  import org.apache.hadoop.hbase.Waiter;
51  import org.apache.hadoop.hbase.client.ClusterConnection;
52  import org.apache.hadoop.hbase.client.HBaseAdmin;
53  import org.apache.hadoop.hbase.constraint.ConstraintException;
54  import org.apache.hadoop.hbase.master.HMaster;
55  import org.apache.hadoop.hbase.master.RegionState;
56  import org.apache.hadoop.hbase.net.Address;
57  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
58  import org.apache.hadoop.hbase.protobuf.generated.AdminProtos;
59  import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.GetServerInfoRequest;
60  import org.apache.hadoop.hbase.util.Bytes;
61  
62  import org.junit.Assert;
63  import org.junit.Test;
64  
65  import com.google.common.collect.Lists;
66  import com.google.common.collect.Maps;
67  import com.google.common.collect.Sets;
68  
69  public abstract class TestRSGroupsBase {
70    protected static final Log LOG = LogFactory.getLog(TestRSGroupsBase.class);
71  
72    //shared
73    protected final static String groupPrefix = "Group";
74    protected final static String tablePrefix = "Group";
75    protected final static SecureRandom rand = new SecureRandom();
76  
77    //shared, cluster type specific
78    protected static HBaseTestingUtility TEST_UTIL;
79    protected static HBaseAdmin admin;
80    protected static HBaseCluster cluster;
81    protected static RSGroupAdmin rsGroupAdmin;
82  
83    public final static long WAIT_TIMEOUT = 60000*5;
84    public final static int NUM_SLAVES_BASE = 4; //number of slaves for the smallest cluster
85  
86  
87  
88    protected RSGroupInfo addGroup(RSGroupAdmin gAdmin, String groupName,
89                                   int serverCount) throws IOException, InterruptedException {
90      RSGroupInfo defaultInfo = gAdmin
91          .getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP);
92      assertTrue(defaultInfo != null);
93      assertTrue(defaultInfo.getServers().size() >= serverCount);
94      gAdmin.addRSGroup(groupName);
95  
96      Set<Address> set = new HashSet<Address>();
97      for(Address server: defaultInfo.getServers()) {
98        if(set.size() == serverCount) {
99          break;
100       }
101       set.add(server);
102     }
103     gAdmin.moveServers(set, groupName);
104     RSGroupInfo result = gAdmin.getRSGroupInfo(groupName);
105     assertTrue(result.getServers().size() >= serverCount);
106     return result;
107   }
108 
109   static void removeGroup(RSGroupAdminClient groupAdmin, String groupName) throws IOException {
110     RSGroupInfo info = groupAdmin.getRSGroupInfo(groupName);
111     groupAdmin.moveTables(info.getTables(), RSGroupInfo.DEFAULT_GROUP);
112     groupAdmin.moveServers(info.getServers(), RSGroupInfo.DEFAULT_GROUP);
113     groupAdmin.removeRSGroup(groupName);
114   }
115 
116   protected void deleteTableIfNecessary() throws IOException {
117     for (HTableDescriptor desc : TEST_UTIL.getHBaseAdmin().listTables(tablePrefix+".*")) {
118       TEST_UTIL.deleteTable(desc.getTableName());
119     }
120   }
121 
122   protected void deleteNamespaceIfNecessary() throws IOException {
123     for (NamespaceDescriptor desc : TEST_UTIL.getHBaseAdmin().listNamespaceDescriptors()) {
124       if(desc.getName().startsWith(tablePrefix)) {
125         admin.deleteNamespace(desc.getName());
126       }
127     }
128   }
129 
130   protected void deleteGroups() throws IOException {
131     try (RSGroupAdmin groupAdmin = new RSGroupAdminClient(TEST_UTIL.getConnection())) {
132       for(RSGroupInfo group: groupAdmin.listRSGroups()) {
133         if(!group.getName().equals(RSGroupInfo.DEFAULT_GROUP)) {
134           groupAdmin.moveTables(group.getTables(), RSGroupInfo.DEFAULT_GROUP);
135           groupAdmin.moveServers(group.getServers(), RSGroupInfo.DEFAULT_GROUP);
136           groupAdmin.removeRSGroup(group.getName());
137         }
138       }
139     }
140   }
141 
142   public Map<TableName, List<String>> getTableRegionMap() throws IOException {
143     Map<TableName, List<String>> map = Maps.newTreeMap();
144     Map<TableName, Map<ServerName, List<String>>> tableServerRegionMap
145         = getTableServerRegionMap();
146     for(TableName tableName : tableServerRegionMap.keySet()) {
147       if(!map.containsKey(tableName)) {
148         map.put(tableName, new LinkedList<String>());
149       }
150       for(List<String> subset: tableServerRegionMap.get(tableName).values()) {
151         map.get(tableName).addAll(subset);
152       }
153     }
154     return map;
155   }
156 
157   public Map<TableName, Map<ServerName, List<String>>> getTableServerRegionMap()
158       throws IOException {
159     Map<TableName, Map<ServerName, List<String>>> map = Maps.newTreeMap();
160     ClusterStatus status = TEST_UTIL.getHBaseClusterInterface().getClusterStatus();
161     for(ServerName serverName : status.getServers()) {
162       for(RegionLoad rl : status.getLoad(serverName).getRegionsLoad().values()) {
163         TableName tableName = null;
164         try {
165           tableName = HRegionInfo.getTable(rl.getName());
166         } catch (IllegalArgumentException e) {
167           LOG.warn("Failed parse a table name from regionname=" +
168               Bytes.toStringBinary(rl.getName()));
169           continue;
170         }
171         if(!map.containsKey(tableName)) {
172           map.put(tableName, new TreeMap<ServerName, List<String>>());
173         }
174         if(!map.get(tableName).containsKey(serverName)) {
175           map.get(tableName).put(serverName, new LinkedList<String>());
176         }
177         map.get(tableName).get(serverName).add(rl.getNameAsString());
178       }
179     }
180     return map;
181   }
182 
183   @Test
184   public void testBogusArgs() throws Exception {
185     assertNull(rsGroupAdmin.getRSGroupInfoOfTable(TableName.valueOf("nonexistent")));
186     assertNull(rsGroupAdmin.getRSGroupOfServer(Address.fromParts("bogus",123)));
187     assertNull(rsGroupAdmin.getRSGroupInfo("bogus"));
188 
189     try {
190       rsGroupAdmin.removeRSGroup("bogus");
191       fail("Expected removing bogus group to fail");
192     } catch(ConstraintException ex) {
193       //expected
194     }
195 
196     try {
197       rsGroupAdmin.moveTables(Sets.newHashSet(TableName.valueOf("bogustable")), "bogus");
198       fail("Expected move with bogus group to fail");
199     } catch(ConstraintException ex) {
200       //expected
201     }
202 
203     try {
204       rsGroupAdmin.moveServers(Sets.newHashSet(Address.fromParts("bogus",123)), "bogus");
205       fail("Expected move with bogus group to fail");
206     } catch(ConstraintException ex) {
207       //expected
208     }
209 
210     try {
211       rsGroupAdmin.balanceRSGroup("bogus");
212       fail("Expected move with bogus group to fail");
213     } catch(ConstraintException ex) {
214       //expected
215     }
216   }
217 
218   @Test
219   public void testCreateMultiRegion() throws IOException {
220     LOG.info("testCreateMultiRegion");
221     TableName tableName = TableName.valueOf(tablePrefix + "_testCreateMultiRegion");
222     byte[] end = {1,3,5,7,9};
223     byte[] start = {0,2,4,6,8};
224     byte[][] f = {Bytes.toBytes("f")};
225     TEST_UTIL.createTable(tableName, f,1,start,end,10);
226   }
227 
228   @Test
229   public void testCreateAndDrop() throws Exception {
230     LOG.info("testCreateAndDrop");
231 
232     final TableName tableName = TableName.valueOf(tablePrefix + "_testCreateAndDrop");
233     TEST_UTIL.createTable(tableName, Bytes.toBytes("cf"));
234     //wait for created table to be assigned
235     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
236       @Override
237       public boolean evaluate() throws Exception {
238         return getTableRegionMap().get(tableName) != null;
239       }
240     });
241     TEST_UTIL.deleteTable(tableName);
242   }
243 
244   @Test
245   public void testSimpleRegionServerMove() throws IOException,
246       InterruptedException {
247     LOG.info("testSimpleRegionServerMove");
248 
249     int initNumGroups = rsGroupAdmin.listRSGroups().size();
250     RSGroupInfo appInfo = addGroup(rsGroupAdmin, getGroupName("testSimpleRegionServerMove"), 1);
251     RSGroupInfo adminInfo = addGroup(rsGroupAdmin, getGroupName("testSimpleRegionServerMove"), 1);
252     RSGroupInfo dInfo = rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP);
253     Assert.assertEquals(initNumGroups + 2, rsGroupAdmin.listRSGroups().size());
254     assertEquals(1, adminInfo.getServers().size());
255     assertEquals(1, appInfo.getServers().size());
256     assertEquals(getNumServers() - 2, dInfo.getServers().size());
257     rsGroupAdmin.moveServers(appInfo.getServers(),
258         RSGroupInfo.DEFAULT_GROUP);
259     rsGroupAdmin.removeRSGroup(appInfo.getName());
260     rsGroupAdmin.moveServers(adminInfo.getServers(),
261         RSGroupInfo.DEFAULT_GROUP);
262     rsGroupAdmin.removeRSGroup(adminInfo.getName());
263     Assert.assertEquals(rsGroupAdmin.listRSGroups().size(), initNumGroups);
264   }
265 
266   // return the real number of region servers, excluding the master embedded region server in 2.0+
267   public int getNumServers() throws IOException {
268     ClusterStatus status = admin.getClusterStatus();
269     ServerName master = status.getMaster();
270     int count = 0;
271     for (ServerName sn : status.getServers()) {
272       if (!sn.equals(master)) {
273         count++;
274       }
275     }
276     return count;
277   }
278 
279   @Test
280   public void testMoveServers() throws Exception {
281     LOG.info("testMoveServers");
282 
283     //create groups and assign servers
284     addGroup(rsGroupAdmin, "bar", 3);
285     rsGroupAdmin.addRSGroup("foo");
286 
287     RSGroupInfo barGroup = rsGroupAdmin.getRSGroupInfo("bar");
288     RSGroupInfo fooGroup = rsGroupAdmin.getRSGroupInfo("foo");
289     assertEquals(3, barGroup.getServers().size());
290     assertEquals(0, fooGroup.getServers().size());
291 
292     //test fail bogus server move
293     try {
294       rsGroupAdmin.moveServers(Sets.newHashSet(Address.fromString("foo:9999")),"foo");
295       fail("Bogus servers shouldn't have been successfully moved.");
296     } catch(IOException ex) {
297       String exp = "Server foo:9999 does not have a group.";
298       String msg = "Expected '"+exp+"' in exception message: ";
299       assertTrue(msg+" "+ex.getMessage(), ex.getMessage().contains(exp));
300     }
301 
302     //test success case
303     LOG.info("moving servers "+barGroup.getServers()+" to group foo");
304     rsGroupAdmin.moveServers(barGroup.getServers(), fooGroup.getName());
305 
306     barGroup = rsGroupAdmin.getRSGroupInfo("bar");
307     fooGroup = rsGroupAdmin.getRSGroupInfo("foo");
308     assertEquals(0,barGroup.getServers().size());
309     assertEquals(3,fooGroup.getServers().size());
310 
311     LOG.info("moving servers "+fooGroup.getServers()+" to group default");
312     rsGroupAdmin.moveServers(fooGroup.getServers(), RSGroupInfo.DEFAULT_GROUP);
313 
314     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
315       @Override
316       public boolean evaluate() throws Exception {
317         return getNumServers() ==
318         rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getServers().size();
319       }
320     });
321 
322     fooGroup = rsGroupAdmin.getRSGroupInfo("foo");
323     assertEquals(0,fooGroup.getServers().size());
324 
325     //test group removal
326     LOG.info("Remove group "+barGroup.getName());
327     rsGroupAdmin.removeRSGroup(barGroup.getName());
328     Assert.assertEquals(null, rsGroupAdmin.getRSGroupInfo(barGroup.getName()));
329     LOG.info("Remove group "+fooGroup.getName());
330     rsGroupAdmin.removeRSGroup(fooGroup.getName());
331     Assert.assertEquals(null, rsGroupAdmin.getRSGroupInfo(fooGroup.getName()));
332   }
333 
334   @Test
335   public void testTableMoveTruncateAndDrop() throws Exception {
336     LOG.info("testTableMove");
337 
338     final TableName tableName = TableName.valueOf(tablePrefix + "_testTableMoveAndDrop");
339     final byte[] familyNameBytes = Bytes.toBytes("f");
340     String newGroupName = getGroupName("testTableMove");
341     final RSGroupInfo newGroup = addGroup(rsGroupAdmin, newGroupName, 2);
342 
343     TEST_UTIL.createMultiRegionTable(tableName, familyNameBytes, 5);
344     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
345       @Override
346       public boolean evaluate() throws Exception {
347         List<String> regions = getTableRegionMap().get(tableName);
348         if (regions == null)
349           return false;
350         return getTableRegionMap().get(tableName).size() >= 5;
351       }
352     });
353 
354     RSGroupInfo tableGrp = rsGroupAdmin.getRSGroupInfoOfTable(tableName);
355     assertTrue(tableGrp.getName().equals(RSGroupInfo.DEFAULT_GROUP));
356 
357     //change table's group
358     LOG.info("Moving table "+tableName+" to "+newGroup.getName());
359     rsGroupAdmin.moveTables(Sets.newHashSet(tableName), newGroup.getName());
360 
361     //verify group change
362     Assert.assertEquals(newGroup.getName(),
363         rsGroupAdmin.getRSGroupInfoOfTable(tableName).getName());
364 
365     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
366       @Override
367       public boolean evaluate() throws Exception {
368         Map<ServerName, List<String>> serverMap = getTableServerRegionMap().get(tableName);
369         int count = 0;
370         if (serverMap != null) {
371           for (ServerName rs : serverMap.keySet()) {
372             if (newGroup.containsServer(rs.getAddress())) {
373               count += serverMap.get(rs).size();
374             }
375           }
376         }
377         return count == 5;
378       }
379     });
380 
381     //test truncate
382     admin.disableTable(tableName);
383     admin.truncateTable(tableName, true);
384     Assert.assertEquals(1, rsGroupAdmin.getRSGroupInfo(newGroup.getName()).getTables().size());
385     Assert.assertEquals(tableName, rsGroupAdmin.getRSGroupInfo(
386         newGroup.getName()).getTables().first());
387 
388     //verify removed table is removed from group
389     TEST_UTIL.deleteTable(tableName);
390     Assert.assertEquals(0, rsGroupAdmin.getRSGroupInfo(newGroup.getName()).getTables().size());
391   }
392 
393   @Test
394   public void testGroupBalance() throws Exception {
395     LOG.info("testGroupBalance");
396     String newGroupName = getGroupName("testGroupBalance");
397     final RSGroupInfo newGroup = addGroup(rsGroupAdmin, newGroupName, 3);
398 
399     final TableName tableName = TableName.valueOf(tablePrefix+"_ns", "testGroupBalance");
400     admin.createNamespace(
401         NamespaceDescriptor.create(tableName.getNamespaceAsString())
402             .addConfiguration(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP, newGroupName).build());
403     final byte[] familyNameBytes = Bytes.toBytes("f");
404     final HTableDescriptor desc = new HTableDescriptor(tableName);
405     desc.addFamily(new HColumnDescriptor("f"));
406     byte [] startKey = Bytes.toBytes("aaaaa");
407     byte [] endKey = Bytes.toBytes("zzzzz");
408     admin.createTable(desc, startKey, endKey, 6);
409     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
410       @Override
411       public boolean evaluate() throws Exception {
412         List<String> regions = getTableRegionMap().get(tableName);
413         if (regions == null) {
414           return false;
415         }
416         return regions.size() >= 6;
417       }
418     });
419 
420     //make assignment uneven, move all regions to one server
421     Map<ServerName,List<String>> assignMap =
422         getTableServerRegionMap().get(tableName);
423     final ServerName first = assignMap.entrySet().iterator().next().getKey();
424     for(HRegionInfo region: admin.getTableRegions(tableName)) {
425       if(!assignMap.get(first).contains(region.getRegionNameAsString())) {
426         admin.move(region.getEncodedNameAsBytes(), Bytes.toBytes(first.getServerName()));
427       }
428     }
429     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
430       @Override
431       public boolean evaluate() throws Exception {
432         Map<ServerName, List<String>> map = getTableServerRegionMap().get(tableName);
433         if (map == null) {
434           return true;
435         }
436         List<String> regions = map.get(first);
437         if (regions == null) {
438           return true;
439         }
440         return regions.size() >= 6;
441       }
442     });
443 
444     //balance the other group and make sure it doesn't affect the new group
445     rsGroupAdmin.balanceRSGroup(RSGroupInfo.DEFAULT_GROUP);
446     assertEquals(6, getTableServerRegionMap().get(tableName).get(first).size());
447 
448     rsGroupAdmin.balanceRSGroup(newGroupName);
449     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
450       @Override
451       public boolean evaluate() throws Exception {
452         for (List<String> regions : getTableServerRegionMap().get(tableName).values()) {
453           if (2 != regions.size()) {
454             return false;
455           }
456         }
457         return true;
458       }
459     });
460   }
461 
462   @Test
463   public void testRegionMove() throws Exception {
464     LOG.info("testRegionMove");
465 
466     final RSGroupInfo newGroup = addGroup(rsGroupAdmin, getGroupName("testRegionMove"), 1);
467     final TableName tableName = TableName.valueOf(tablePrefix + rand.nextInt());
468     final byte[] familyNameBytes = Bytes.toBytes("f");
469     // All the regions created below will be assigned to the default group.
470     TEST_UTIL.createMultiRegionTable(tableName, familyNameBytes, 6);
471     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
472       @Override
473       public boolean evaluate() throws Exception {
474         List<String> regions = getTableRegionMap().get(tableName);
475         if (regions == null)
476           return false;
477         return getTableRegionMap().get(tableName).size() >= 6;
478       }
479     });
480 
481     //get target region to move
482     Map<ServerName,List<String>> assignMap =
483         getTableServerRegionMap().get(tableName);
484     String targetRegion = null;
485     for(ServerName server : assignMap.keySet()) {
486       targetRegion = assignMap.get(server).size() > 0 ? assignMap.get(server).get(0) : null;
487       if(targetRegion != null) {
488         break;
489       }
490     }
491     //get server which is not a member of new group
492     ServerName targetServer = null;
493     for(ServerName server : admin.getClusterStatus().getServers()) {
494       if(!newGroup.containsServer(server.getAddress())) {
495         targetServer = server;
496         break;
497       }
498     }
499     assertNotNull(targetServer);
500 
501     final AdminProtos.AdminService.BlockingInterface targetRS =
502         admin.getConnection().getAdmin(targetServer);
503 
504     //move target server to group
505     rsGroupAdmin.moveServers(Sets.newHashSet(targetServer.getAddress()),
506         newGroup.getName());
507     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
508       @Override
509       public boolean evaluate() throws Exception {
510         return ProtobufUtil.getOnlineRegions(targetRS).size() <= 0;
511       }
512     });
513 
514     // Lets move this region to the new group.
515     TEST_UTIL.getHBaseAdmin().move(Bytes.toBytes(HRegionInfo.encodeRegionName(Bytes.toBytes(targetRegion))),
516         Bytes.toBytes(targetServer.getServerName()));
517     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
518       @Override
519       public boolean evaluate() throws Exception {
520         List<String> regions = getTableRegionMap().get(tableName);
521         Set<RegionState> regionsInTransition = admin.getClusterStatus().getRegionsInTransition();
522         return (regions != null && getTableRegionMap().get(tableName).size() == 6) &&
523            ( regionsInTransition == null || regionsInTransition.size() < 1);
524       }
525     });
526 
527     //verify that targetServer didn't open it
528     for (HRegionInfo region: ProtobufUtil.getOnlineRegions(targetRS)) {
529       if (targetRegion.equals(region.getRegionNameAsString())) {
530         fail("Target server opened region");
531       }
532     }
533   }
534 
535   @Test
536   public void testFailRemoveGroup() throws IOException, InterruptedException {
537     LOG.info("testFailRemoveGroup");
538 
539     int initNumGroups = rsGroupAdmin.listRSGroups().size();
540     addGroup(rsGroupAdmin, "bar", 3);
541     TableName tableName = TableName.valueOf(tablePrefix+"_my_table");
542     TEST_UTIL.createTable(tableName, Bytes.toBytes("f"));
543     rsGroupAdmin.moveTables(Sets.newHashSet(tableName), "bar");
544     RSGroupInfo barGroup = rsGroupAdmin.getRSGroupInfo("bar");
545     //group is not empty therefore it should fail
546     try {
547       rsGroupAdmin.removeRSGroup(barGroup.getName());
548       fail("Expected remove group to fail");
549     } catch(IOException e) {
550     }
551     //group cannot lose all it's servers therefore it should fail
552     try {
553       rsGroupAdmin.moveServers(barGroup.getServers(), RSGroupInfo.DEFAULT_GROUP);
554       fail("Expected move servers to fail");
555     } catch(IOException e) {
556     }
557 
558     rsGroupAdmin.moveTables(barGroup.getTables(), RSGroupInfo.DEFAULT_GROUP);
559     try {
560       rsGroupAdmin.removeRSGroup(barGroup.getName());
561       fail("Expected move servers to fail");
562     } catch(IOException e) {
563     }
564 
565     rsGroupAdmin.moveServers(barGroup.getServers(), RSGroupInfo.DEFAULT_GROUP);
566     rsGroupAdmin.removeRSGroup(barGroup.getName());
567 
568     Assert.assertEquals(initNumGroups, rsGroupAdmin.listRSGroups().size());
569   }
570 
571   @Test
572   public void testKillRS() throws Exception {
573     LOG.info("testKillRS");
574     RSGroupInfo appInfo = addGroup(rsGroupAdmin, "appInfo", 1);
575 
576     final TableName tableName = TableName.valueOf(tablePrefix+"_ns", "_testKillRS");
577     admin.createNamespace(
578         NamespaceDescriptor.create(tableName.getNamespaceAsString())
579             .addConfiguration(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP, appInfo.getName()).build());
580     final HTableDescriptor desc = new HTableDescriptor(tableName);
581     desc.addFamily(new HColumnDescriptor("f"));
582     admin.createTable(desc);
583     //wait for created table to be assigned
584     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
585       @Override
586       public boolean evaluate() throws Exception {
587         return getTableRegionMap().get(desc.getTableName()) != null;
588       }
589     });
590 
591     ServerName targetServer = ServerName.parseServerName(
592         appInfo.getServers().iterator().next().toString());
593     AdminProtos.AdminService.BlockingInterface targetRS =
594         admin.getConnection().getAdmin(targetServer);
595     Assert.assertEquals(1, ProtobufUtil.getOnlineRegions(targetRS).size());
596 
597     try {
598       //stopping may cause an exception
599       //due to the connection loss
600       targetRS.stopServer(null,
601           AdminProtos.StopServerRequest.newBuilder().setReason("Die").build());
602     } catch(Exception e) {
603     }
604     assertFalse(cluster.getClusterStatus().getServers().contains(targetServer));
605 
606     //wait for created table to be assigned
607     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
608       @Override
609       public boolean evaluate() throws Exception {
610         return cluster.getClusterStatus().getRegionsInTransition().size() == 0;
611       }
612     });
613     Set<Address> newServers = Sets.newHashSet();
614     newServers.add(
615         rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getServers().iterator().next());
616     rsGroupAdmin.moveServers(newServers, appInfo.getName());
617 
618     //Make sure all the table's regions get reassigned
619     //disabling the table guarantees no conflicting assign/unassign (ie SSH) happens
620     admin.disableTable(tableName);
621     admin.enableTable(tableName);
622 
623     //wait for region to be assigned
624     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
625       @Override
626       public boolean evaluate() throws Exception {
627         return cluster.getClusterStatus().getRegionsInTransition().size() == 0;
628       }
629     });
630 
631     targetServer = ServerName.parseServerName(
632         newServers.iterator().next().toString());
633     targetRS =
634         admin.getConnection().getAdmin(targetServer);
635     Assert.assertEquals(1, ProtobufUtil.getOnlineRegions(targetRS).size());
636     Assert.assertEquals(tableName,
637         ProtobufUtil.getOnlineRegions(targetRS).get(0).getTable());
638   }
639 
640   @Test
641   public void testValidGroupNames() throws IOException {
642     String[] badNames = {"foo*","foo@","-"};
643     String[] goodNames = {"foo_123"};
644 
645     for(String entry: badNames) {
646       try {
647         rsGroupAdmin.addRSGroup(entry);
648         fail("Expected a constraint exception for: "+entry);
649       } catch(ConstraintException ex) {
650         //expected
651       }
652     }
653 
654     for(String entry: goodNames) {
655       rsGroupAdmin.addRSGroup(entry);
656     }
657   }
658 
659   private String getGroupName(String baseName) {
660     return groupPrefix+"_"+baseName+"_"+rand.nextInt(Integer.MAX_VALUE);
661   }
662 
663   @Test
664   public void testMultiTableMove() throws Exception {
665     LOG.info("testMultiTableMove");
666 
667     final TableName tableNameA = TableName.valueOf(tablePrefix + "_testMultiTableMoveA");
668     final TableName tableNameB = TableName.valueOf(tablePrefix + "_testMultiTableMoveB");
669     final byte[] familyNameBytes = Bytes.toBytes("f");
670     String newGroupName = getGroupName("testMultiTableMove");
671     final RSGroupInfo newGroup = addGroup(rsGroupAdmin, newGroupName, 1);
672 
673     TEST_UTIL.createTable(tableNameA, familyNameBytes);
674     TEST_UTIL.createTable(tableNameB, familyNameBytes);
675     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
676       @Override
677       public boolean evaluate() throws Exception {
678         List<String> regionsA = getTableRegionMap().get(tableNameA);
679         if (regionsA == null)
680           return false;
681         List<String> regionsB = getTableRegionMap().get(tableNameB);
682         if (regionsB == null)
683           return false;
684 
685         return getTableRegionMap().get(tableNameA).size() >= 1
686                 && getTableRegionMap().get(tableNameB).size() >= 1;
687       }
688     });
689 
690     RSGroupInfo tableGrpA = rsGroupAdmin.getRSGroupInfoOfTable(tableNameA);
691     assertTrue(tableGrpA.getName().equals(RSGroupInfo.DEFAULT_GROUP));
692 
693     RSGroupInfo tableGrpB = rsGroupAdmin.getRSGroupInfoOfTable(tableNameB);
694     assertTrue(tableGrpB.getName().equals(RSGroupInfo.DEFAULT_GROUP));
695     //change table's group
696     LOG.info("Moving table [" + tableNameA + "," + tableNameB + "] to " + newGroup.getName());
697     rsGroupAdmin.moveTables(Sets.newHashSet(tableNameA, tableNameB), newGroup.getName());
698 
699     //verify group change
700     Assert.assertEquals(newGroup.getName(),
701             rsGroupAdmin.getRSGroupInfoOfTable(tableNameA).getName());
702 
703     Assert.assertEquals(newGroup.getName(),
704             rsGroupAdmin.getRSGroupInfoOfTable(tableNameB).getName());
705 
706     //verify tables' not exist in old group
707     Set<TableName> DefaultTables = rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getTables();
708     assertFalse(DefaultTables.contains(tableNameA));
709     assertFalse(DefaultTables.contains(tableNameB));
710 
711     //verify tables' exist in new group
712     Set<TableName> newGroupTables = rsGroupAdmin.getRSGroupInfo(newGroupName).getTables();
713     assertTrue(newGroupTables.contains(tableNameA));
714     assertTrue(newGroupTables.contains(tableNameB));
715   }
716 
717   @Test
718   public void testMoveServersAndTables() throws Exception {
719     final TableName tableName = TableName.valueOf(tablePrefix + "_testMoveServersAndTables");
720     final RSGroupInfo newGroup = addGroup(rsGroupAdmin, getGroupName("testMoveServersAndTables"), 1);
721 
722     //create table
723     final byte[] familyNameBytes = Bytes.toBytes("f");
724     TEST_UTIL.createMultiRegionTable(tableName, familyNameBytes, 5);
725     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
726       @Override
727       public boolean evaluate() throws Exception {
728         List<String> regions = getTableRegionMap().get(tableName);
729         if (regions == null)
730           return false;
731         return getTableRegionMap().get(tableName).size() >= 5;
732       }
733     });
734 
735     //get server which is not a member of new group
736     ServerName targetServer = null;
737     for(ServerName server : admin.getClusterStatus().getServers()) {
738       if(!newGroup.containsServer(server.getAddress()) && 
739            !rsGroupAdmin.getRSGroupInfo("master").containsServer(server.getAddress())) {
740         targetServer = server;
741         break;
742       }
743     }
744 
745     LOG.debug("Print group info : " + rsGroupAdmin.listRSGroups());
746     int oldDefaultGroupServerSize =
747             rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getServers().size();
748     int oldDefaultGroupTableSize =
749             rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getTables().size();
750 
751     //test fail bogus server move
752     try {
753       rsGroupAdmin.moveServersAndTables(Sets.newHashSet(Address.fromString("foo:9999")),
754               Sets.newHashSet(tableName), newGroup.getName());
755       fail("Bogus servers shouldn't have been successfully moved.");
756     } catch(IOException ex) {
757     }
758 
759     //test fail server move
760     try {
761       rsGroupAdmin.moveServersAndTables(Sets.newHashSet(targetServer.getAddress()),
762               Sets.newHashSet(tableName), RSGroupInfo.DEFAULT_GROUP);
763       fail("servers shouldn't have been successfully moved.");
764     } catch(IOException ex) {
765     }
766 
767     //verify default group info
768     Assert.assertEquals(oldDefaultGroupServerSize,
769             rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getServers().size());
770     Assert.assertEquals(oldDefaultGroupTableSize,
771             rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getTables().size());
772 
773     //verify new group info
774     Assert.assertEquals(1,
775             rsGroupAdmin.getRSGroupInfo(newGroup.getName()).getServers().size());
776     Assert.assertEquals(0,
777             rsGroupAdmin.getRSGroupInfo(newGroup.getName()).getTables().size());
778 
779     //get all region to move targetServer
780     List<String> regionList = getTableRegionMap().get(tableName);
781     for(String region : regionList) {
782       // Lets move this region to the targetServer
783       admin.move(Bytes.toBytes(HRegionInfo.encodeRegionName(Bytes.toBytes(region))),
784               Bytes.toBytes(targetServer.getServerName()));
785     }
786 
787     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
788       @Override
789       public boolean evaluate() throws Exception {
790         List<String> regions = getTableRegionMap().get(tableName);
791         Map<ServerName, List<String>> serverMap = getTableServerRegionMap().get(tableName);
792         Set<RegionState> regionsInTransition = admin.getClusterStatus().getRegionsInTransition();
793         return (regions != null && regions.size() == 5) &&
794           (serverMap != null && serverMap.size() == 1) &&
795           (regionsInTransition == null || regionsInTransition.size() < 1);
796       }
797     });
798 
799     //verify that all region move to targetServer
800     Assert.assertNotNull(getTableServerRegionMap().get(tableName));
801     Assert.assertNotNull(getTableServerRegionMap().get(tableName).get(targetServer));
802     Assert.assertEquals(5, getTableServerRegionMap().get(tableName).get(targetServer).size());
803 
804     //move targetServer and table to newGroup
805     LOG.info("moving server and table to newGroup");
806     rsGroupAdmin.moveServersAndTables(Sets.newHashSet(targetServer.getAddress()),
807             Sets.newHashSet(tableName), newGroup.getName());
808 
809     //verify group change
810     Assert.assertEquals(newGroup.getName(),
811             rsGroupAdmin.getRSGroupInfoOfTable(tableName).getName());
812 
813     //verify servers' not exist in old group
814     Set<Address> defaultServers = rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getServers();
815     assertFalse(defaultServers.contains(targetServer.getAddress()));
816 
817     //verify servers' exist in new group
818     Set<Address> newGroupServers = rsGroupAdmin.getRSGroupInfo(newGroup.getName()).getServers();
819     assertTrue(newGroupServers.contains(targetServer.getAddress()));
820 
821     //verify tables' not exist in old group
822     Set<TableName> defaultTables = rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getTables();
823     assertFalse(defaultTables.contains(tableName));
824 
825     //verify tables' exist in new group
826     Set<TableName> newGroupTables = rsGroupAdmin.getRSGroupInfo(newGroup.getName()).getTables();
827     assertTrue(newGroupTables.contains(tableName));
828   }
829 
830   @Test
831   public void testDisabledTableMove() throws Exception {
832     final TableName tableName = TableName.valueOf(tablePrefix + "_testDisabledTableMove");
833     final byte[] familyNameBytes = Bytes.toBytes("f");
834     String newGroupName = getGroupName("testDisabledTableMove");
835     final RSGroupInfo newGroup = addGroup(rsGroupAdmin, newGroupName, 2);
836 
837     TEST_UTIL.createMultiRegionTable(tableName, familyNameBytes, 5);
838     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
839       @Override
840       public boolean evaluate() throws Exception {
841         List<String> regions = getTableRegionMap().get(tableName);
842         if (regions == null) {
843           return false;
844         }
845         return getTableRegionMap().get(tableName).size() >= 5;
846       }
847     });
848 
849     RSGroupInfo tableGrp = rsGroupAdmin.getRSGroupInfoOfTable(tableName);
850     assertTrue(tableGrp.getName().equals(RSGroupInfo.DEFAULT_GROUP));
851 
852     //test disable table
853     admin.disableTable(tableName);
854 
855     //change table's group
856     LOG.info("Moving table "+ tableName + " to " + newGroup.getName());
857     rsGroupAdmin.moveTables(Sets.newHashSet(tableName), newGroup.getName());
858 
859     //verify group change
860     Assert.assertEquals(newGroup.getName(),
861         rsGroupAdmin.getRSGroupInfoOfTable(tableName).getName());
862   }
863 
864   @Test
865   public void testClearDeadServers() throws Exception {
866     final RSGroupInfo newGroup = addGroup(rsGroupAdmin, "testClearDeadServers", 3);
867 
868     ServerName targetServer = ServerName.parseServerName(
869         newGroup.getServers().iterator().next().toString());
870     AdminProtos.AdminService.BlockingInterface targetRS =
871         ((ClusterConnection) admin.getConnection()).getAdmin(targetServer);
872     try {
873       targetServer = ProtobufUtil.toServerName(targetRS.getServerInfo(null,
874           GetServerInfoRequest.newBuilder().build()).getServerInfo().getServerName());
875       //stopping may cause an exception
876       //due to the connection loss
877       targetRS.stopServer(null,
878           AdminProtos.StopServerRequest.newBuilder().setReason("Die").build());
879     } catch(Exception e) {
880     }
881     final HMaster master = TEST_UTIL.getHBaseCluster().getMaster();
882     //wait for stopped regionserver to dead server list
883     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
884       @Override
885       public boolean evaluate() throws Exception {
886         return !master.getServerManager().areDeadServersInProgress()
887             && cluster.getClusterStatus().getDeadServerNames().size() > 0;
888       }
889     });
890     assertFalse(cluster.getClusterStatus().getServers().contains(targetServer));
891     assertTrue(cluster.getClusterStatus().getDeadServerNames().contains(targetServer));
892     assertTrue(newGroup.getServers().contains(targetServer.getAddress()));
893 
894     //clear dead servers list
895     List<ServerName> notClearedServers = admin.clearDeadServers(Lists.newArrayList(targetServer));
896     assertEquals(0, notClearedServers.size());
897 
898     Set<Address> newGroupServers = rsGroupAdmin.getRSGroupInfo(newGroup.getName()).getServers();
899     assertFalse(newGroupServers.contains(targetServer.getAddress()));
900     assertEquals(2, newGroupServers.size());
901   }
902 
903   @Test
904   public void testRemoveServers() throws Exception {
905     final RSGroupInfo newGroup = addGroup(rsGroupAdmin, "testRemoveServers", 3);
906     ServerName targetServer = ServerName.parseServerName(
907         newGroup.getServers().iterator().next().toString());
908     try {
909       rsGroupAdmin.removeServers(Sets.newHashSet(targetServer.getAddress()));
910       fail("Online servers shouldn't have been successfully removed.");
911     } catch(IOException ex) {
912       String exp = "Server " + targetServer.getAddress()
913           + " is an online server, not allowed to remove.";
914       String msg = "Expected '" + exp + "' in exception message: ";
915       assertTrue(msg + " " + ex.getMessage(), ex.getMessage().contains(exp));
916     }
917     assertTrue(newGroup.getServers().contains(targetServer.getAddress()));
918 
919     AdminProtos.AdminService.BlockingInterface targetRS =
920         ((ClusterConnection) admin.getConnection()).getAdmin(targetServer);
921     try {
922       targetServer = ProtobufUtil.toServerName(targetRS.getServerInfo(null,
923           GetServerInfoRequest.newBuilder().build()).getServerInfo().getServerName());
924       //stopping may cause an exception
925       //due to the connection loss
926       targetRS.stopServer(null,
927           AdminProtos.StopServerRequest.newBuilder().setReason("Die").build());
928     } catch(Exception e) {
929     }
930 
931     final HMaster master = TEST_UTIL.getHBaseCluster().getMaster();
932     //wait for stopped regionserver to dead server list
933     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
934       @Override
935       public boolean evaluate() throws Exception {
936         return !master.getServerManager().areDeadServersInProgress()
937             && cluster.getClusterStatus().getDeadServerNames().size() > 0;
938       }
939     });
940 
941     try {
942       rsGroupAdmin.removeServers(Sets.newHashSet(targetServer.getAddress()));
943       fail("Dead servers shouldn't have been successfully removed.");
944     } catch(IOException ex) {
945       String exp = "Server " + targetServer.getAddress() + " is on the dead servers list,"
946           + " Maybe it will come back again, not allowed to remove.";
947       String msg = "Expected '" + exp + "' in exception message: ";
948       assertTrue(msg + " " + ex.getMessage(), ex.getMessage().contains(exp));
949     }
950     assertTrue(newGroup.getServers().contains(targetServer.getAddress()));
951 
952     ServerName sn = TEST_UTIL.getHBaseClusterInterface().getClusterStatus().getMaster();
953     TEST_UTIL.getHBaseClusterInterface().stopMaster(sn);
954     TEST_UTIL.getHBaseClusterInterface().waitForMasterToStop(sn, 60000);
955     TEST_UTIL.getHBaseClusterInterface().startMaster(sn.getHostname(), 0);
956     TEST_UTIL.getHBaseClusterInterface().waitForActiveAndReadyMaster(60000);
957 
958     assertEquals(3, cluster.getClusterStatus().getServersSize());
959     assertFalse(cluster.getClusterStatus().getServers().contains(targetServer));
960     assertFalse(cluster.getClusterStatus().getDeadServerNames().contains(targetServer));
961     assertTrue(newGroup.getServers().contains(targetServer.getAddress()));
962 
963     rsGroupAdmin.removeServers(Sets.newHashSet(targetServer.getAddress()));
964     Set<Address> newGroupServers = rsGroupAdmin.getRSGroupInfo(newGroup.getName()).getServers();
965     assertFalse(newGroupServers.contains(targetServer.getAddress()));
966     assertEquals(2, newGroupServers.size());
967   }
968 }