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.assertNull;
023import static org.junit.Assert.assertTrue;
024import static org.junit.Assert.fail;
025
026import java.io.IOException;
027import java.security.SecureRandom;
028import java.util.ArrayList;
029import java.util.EnumSet;
030import java.util.HashSet;
031import java.util.Iterator;
032import java.util.LinkedList;
033import java.util.List;
034import java.util.Map;
035import java.util.Set;
036import java.util.TreeMap;
037import org.apache.hadoop.hbase.ClusterMetrics;
038import org.apache.hadoop.hbase.ClusterMetrics.Option;
039import org.apache.hadoop.hbase.HBaseCluster;
040import org.apache.hadoop.hbase.HBaseTestingUtility;
041import org.apache.hadoop.hbase.HColumnDescriptor;
042import org.apache.hadoop.hbase.HTableDescriptor;
043import org.apache.hadoop.hbase.NamespaceDescriptor;
044import org.apache.hadoop.hbase.RegionMetrics;
045import org.apache.hadoop.hbase.ServerMetrics;
046import org.apache.hadoop.hbase.ServerName;
047import org.apache.hadoop.hbase.TableName;
048import org.apache.hadoop.hbase.Waiter;
049import org.apache.hadoop.hbase.client.Admin;
050import org.apache.hadoop.hbase.client.ClusterConnection;
051import org.apache.hadoop.hbase.client.RegionInfo;
052import org.apache.hadoop.hbase.constraint.ConstraintException;
053import org.apache.hadoop.hbase.master.HMaster;
054import org.apache.hadoop.hbase.net.Address;
055import org.apache.hadoop.hbase.util.Bytes;
056import org.junit.Assert;
057import org.junit.Before;
058import org.junit.Rule;
059import org.junit.Test;
060import org.junit.rules.TestName;
061import org.slf4j.Logger;
062import org.slf4j.LoggerFactory;
063
064import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
065import org.apache.hbase.thirdparty.com.google.common.collect.Maps;
066import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
067import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
068import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos;
069import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.GetServerInfoRequest;
070
071public abstract class TestRSGroupsBase {
072  protected static final Logger LOG = LoggerFactory.getLogger(TestRSGroupsBase.class);
073  @Rule
074  public TestName name = new TestName();
075
076  //shared
077  protected final static String groupPrefix = "Group";
078  protected final static String tablePrefix = "Group";
079  protected final static SecureRandom rand = new SecureRandom();
080
081  //shared, cluster type specific
082  protected static HBaseTestingUtility TEST_UTIL;
083  protected static Admin admin;
084  protected static HBaseCluster cluster;
085  protected static RSGroupAdmin rsGroupAdmin;
086  protected static HMaster master;
087
088  public final static long WAIT_TIMEOUT = 60000*5;
089  public final static int NUM_SLAVES_BASE = 4; //number of slaves for the smallest cluster
090  public static int NUM_DEAD_SERVERS = 0;
091
092  // Per test variables
093  TableName tableName;
094  @Before
095  public void setup() {
096    LOG.info(name.getMethodName());
097    tableName = TableName.valueOf(tablePrefix + "_" + name.getMethodName());
098  }
099
100  protected RSGroupInfo addGroup(String groupName, int serverCount)
101      throws IOException, InterruptedException {
102    RSGroupInfo defaultInfo = rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP);
103    assertTrue(defaultInfo != null);
104    assertTrue(defaultInfo.getServers().size() >= serverCount);
105    rsGroupAdmin.addRSGroup(groupName);
106
107    Set<Address> set = new HashSet<>();
108    for(Address server: defaultInfo.getServers()) {
109      if(set.size() == serverCount) {
110        break;
111      }
112      set.add(server);
113    }
114    rsGroupAdmin.moveServers(set, groupName);
115    RSGroupInfo result = rsGroupAdmin.getRSGroupInfo(groupName);
116    assertTrue(result.getServers().size() >= serverCount);
117    return result;
118  }
119
120  void removeGroup(String groupName) throws IOException {
121    RSGroupInfo RSGroupInfo = rsGroupAdmin.getRSGroupInfo(groupName);
122    rsGroupAdmin.moveTables(RSGroupInfo.getTables(), RSGroupInfo.DEFAULT_GROUP);
123    rsGroupAdmin.moveServers(RSGroupInfo.getServers(), RSGroupInfo.DEFAULT_GROUP);
124    rsGroupAdmin.removeRSGroup(groupName);
125  }
126
127  protected void deleteTableIfNecessary() throws IOException {
128    for (HTableDescriptor desc : TEST_UTIL.getAdmin().listTables(tablePrefix+".*")) {
129      TEST_UTIL.deleteTable(desc.getTableName());
130    }
131  }
132
133  protected void deleteNamespaceIfNecessary() throws IOException {
134    for (NamespaceDescriptor desc : TEST_UTIL.getAdmin().listNamespaceDescriptors()) {
135      if(desc.getName().startsWith(tablePrefix)) {
136        admin.deleteNamespace(desc.getName());
137      }
138    }
139  }
140
141  protected void deleteGroups() throws IOException {
142    RSGroupAdmin groupAdmin =
143        new RSGroupAdminClient(TEST_UTIL.getConnection());
144    for(RSGroupInfo group: groupAdmin.listRSGroups()) {
145      if(!group.getName().equals(RSGroupInfo.DEFAULT_GROUP)) {
146        groupAdmin.moveTables(group.getTables(), RSGroupInfo.DEFAULT_GROUP);
147        groupAdmin.moveServers(group.getServers(), RSGroupInfo.DEFAULT_GROUP);
148        groupAdmin.removeRSGroup(group.getName());
149      }
150    }
151  }
152
153  public Map<TableName, List<String>> getTableRegionMap() throws IOException {
154    Map<TableName, List<String>> map = Maps.newTreeMap();
155    Map<TableName, Map<ServerName, List<String>>> tableServerRegionMap
156        = getTableServerRegionMap();
157    for(TableName tableName : tableServerRegionMap.keySet()) {
158      if(!map.containsKey(tableName)) {
159        map.put(tableName, new LinkedList<>());
160      }
161      for(List<String> subset: tableServerRegionMap.get(tableName).values()) {
162        map.get(tableName).addAll(subset);
163      }
164    }
165    return map;
166  }
167
168  public Map<TableName, Map<ServerName, List<String>>> getTableServerRegionMap()
169      throws IOException {
170    Map<TableName, Map<ServerName, List<String>>> map = Maps.newTreeMap();
171    ClusterMetrics status = TEST_UTIL.getHBaseClusterInterface().getClusterMetrics();
172    for (Map.Entry<ServerName, ServerMetrics> entry : status.getLiveServerMetrics().entrySet()) {
173      ServerName serverName = entry.getKey();
174      for(RegionMetrics rl : entry.getValue().getRegionMetrics().values()) {
175        TableName tableName = null;
176        try {
177          tableName = RegionInfo.getTable(rl.getRegionName());
178        } catch (IllegalArgumentException e) {
179          LOG.warn("Failed parse a table name from regionname=" +
180            Bytes.toStringBinary(rl.getRegionName()));
181          continue;
182        }
183        if(!map.containsKey(tableName)) {
184          map.put(tableName, new TreeMap<>());
185        }
186        if(!map.get(tableName).containsKey(serverName)) {
187          map.get(tableName).put(serverName, new LinkedList<>());
188        }
189        map.get(tableName).get(serverName).add(rl.getNameAsString());
190      }
191    }
192    return map;
193  }
194
195  @Test
196  public void testBogusArgs() throws Exception {
197    assertNull(rsGroupAdmin.getRSGroupInfoOfTable(TableName.valueOf("nonexistent")));
198    assertNull(rsGroupAdmin.getRSGroupOfServer(Address.fromParts("bogus",123)));
199    assertNull(rsGroupAdmin.getRSGroupInfo("bogus"));
200
201    try {
202      rsGroupAdmin.removeRSGroup("bogus");
203      fail("Expected removing bogus group to fail");
204    } catch(ConstraintException ex) {
205      //expected
206    }
207
208    try {
209      rsGroupAdmin.moveTables(Sets.newHashSet(TableName.valueOf("bogustable")), "bogus");
210      fail("Expected move with bogus group to fail");
211    } catch(ConstraintException ex) {
212      //expected
213    }
214
215    try {
216      rsGroupAdmin.moveServers(Sets.newHashSet(Address.fromParts("bogus",123)), "bogus");
217      fail("Expected move with bogus group to fail");
218    } catch(ConstraintException ex) {
219      //expected
220    }
221
222    try {
223      admin.setBalancerRunning(true,true);
224      rsGroupAdmin.balanceRSGroup("bogus");
225      admin.setBalancerRunning(false,true);
226      fail("Expected move with bogus group to fail");
227    } catch(ConstraintException ex) {
228      //expected
229    }
230  }
231
232  @Test
233  public void testCreateMultiRegion() throws IOException {
234    byte[] end = {1,3,5,7,9};
235    byte[] start = {0,2,4,6,8};
236    byte[][] f = {Bytes.toBytes("f")};
237    TEST_UTIL.createTable(tableName, f,1,start,end,10);
238  }
239
240  @Test
241  public void testCreateAndDrop() throws Exception {
242    TEST_UTIL.createTable(tableName, Bytes.toBytes("cf"));
243    //wait for created table to be assigned
244    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
245      @Override
246      public boolean evaluate() throws Exception {
247        return getTableRegionMap().get(tableName) != null;
248      }
249    });
250    TEST_UTIL.deleteTable(tableName);
251  }
252
253
254  @Test
255  public void testSimpleRegionServerMove() throws IOException,
256      InterruptedException {
257    int initNumGroups = rsGroupAdmin.listRSGroups().size();
258    RSGroupInfo appInfo = addGroup(getGroupName(name.getMethodName()), 1);
259    RSGroupInfo adminInfo = addGroup(getGroupName(name.getMethodName()), 1);
260    RSGroupInfo dInfo = rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP);
261    Assert.assertEquals(initNumGroups + 2, rsGroupAdmin.listRSGroups().size());
262    assertEquals(1, adminInfo.getServers().size());
263    assertEquals(1, appInfo.getServers().size());
264    assertEquals(getNumServers() - 2, dInfo.getServers().size());
265    rsGroupAdmin.moveServers(appInfo.getServers(),
266        RSGroupInfo.DEFAULT_GROUP);
267    rsGroupAdmin.removeRSGroup(appInfo.getName());
268    rsGroupAdmin.moveServers(adminInfo.getServers(),
269        RSGroupInfo.DEFAULT_GROUP);
270    rsGroupAdmin.removeRSGroup(adminInfo.getName());
271    Assert.assertEquals(rsGroupAdmin.listRSGroups().size(), initNumGroups);
272  }
273
274  // return the real number of region servers, excluding the master embedded region server in 2.0+
275  public int getNumServers() throws IOException {
276    ClusterMetrics status =
277        admin.getClusterMetrics(EnumSet.of(Option.MASTER, Option.LIVE_SERVERS));
278    ServerName masterName = status.getMasterName();
279    int count = 0;
280    for (ServerName sn : status.getLiveServerMetrics().keySet()) {
281      if (!sn.equals(masterName)) {
282        count++;
283      }
284    }
285    return count;
286  }
287
288  @Test
289  public void testMoveServers() throws Exception {
290    //create groups and assign servers
291    addGroup("bar", 3);
292    rsGroupAdmin.addRSGroup("foo");
293
294    RSGroupInfo barGroup = rsGroupAdmin.getRSGroupInfo("bar");
295    RSGroupInfo fooGroup = rsGroupAdmin.getRSGroupInfo("foo");
296    assertEquals(3, barGroup.getServers().size());
297    assertEquals(0, fooGroup.getServers().size());
298
299    //test fail bogus server move
300    try {
301      rsGroupAdmin.moveServers(Sets.newHashSet(Address.fromString("foo:9999")),"foo");
302      fail("Bogus servers shouldn't have been successfully moved.");
303    } catch(IOException ex) {
304      String exp = "Source RSGroup for server foo:9999 does not exist.";
305      String msg = "Expected '"+exp+"' in exception message: ";
306      assertTrue(msg+" "+ex.getMessage(), ex.getMessage().contains(exp));
307    }
308
309    //test success case
310    LOG.info("moving servers "+barGroup.getServers()+" to group foo");
311    rsGroupAdmin.moveServers(barGroup.getServers(), fooGroup.getName());
312
313    barGroup = rsGroupAdmin.getRSGroupInfo("bar");
314    fooGroup = rsGroupAdmin.getRSGroupInfo("foo");
315    assertEquals(0,barGroup.getServers().size());
316    assertEquals(3,fooGroup.getServers().size());
317
318    LOG.info("moving servers "+fooGroup.getServers()+" to group default");
319    rsGroupAdmin.moveServers(fooGroup.getServers(), RSGroupInfo.DEFAULT_GROUP);
320
321    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
322      @Override
323      public boolean evaluate() throws Exception {
324        return getNumServers() ==
325        rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getServers().size();
326      }
327    });
328
329    fooGroup = rsGroupAdmin.getRSGroupInfo("foo");
330    assertEquals(0,fooGroup.getServers().size());
331
332    //test group removal
333    LOG.info("Remove group "+barGroup.getName());
334    rsGroupAdmin.removeRSGroup(barGroup.getName());
335    Assert.assertEquals(null, rsGroupAdmin.getRSGroupInfo(barGroup.getName()));
336    LOG.info("Remove group "+fooGroup.getName());
337    rsGroupAdmin.removeRSGroup(fooGroup.getName());
338    Assert.assertEquals(null, rsGroupAdmin.getRSGroupInfo(fooGroup.getName()));
339  }
340
341  @Test
342  public void testTableMoveTruncateAndDrop() throws Exception {
343    final byte[] familyNameBytes = Bytes.toBytes("f");
344    String newGroupName = getGroupName(name.getMethodName());
345    final RSGroupInfo newGroup = addGroup(newGroupName, 2);
346
347    TEST_UTIL.createMultiRegionTable(tableName, familyNameBytes, 5);
348    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
349      @Override
350      public boolean evaluate() throws Exception {
351        List<String> regions = getTableRegionMap().get(tableName);
352        if (regions == null) {
353          return false;
354        }
355
356        return getTableRegionMap().get(tableName).size() >= 5;
357      }
358    });
359
360    RSGroupInfo tableGrp = rsGroupAdmin.getRSGroupInfoOfTable(tableName);
361    assertTrue(tableGrp.getName().equals(RSGroupInfo.DEFAULT_GROUP));
362
363    //change table's group
364    LOG.info("Moving table "+tableName+" to "+newGroup.getName());
365    rsGroupAdmin.moveTables(Sets.newHashSet(tableName), newGroup.getName());
366
367    //verify group change
368    Assert.assertEquals(newGroup.getName(),
369        rsGroupAdmin.getRSGroupInfoOfTable(tableName).getName());
370
371    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
372      @Override
373      public boolean evaluate() throws Exception {
374        Map<ServerName, List<String>> serverMap = getTableServerRegionMap().get(tableName);
375        int count = 0;
376        if (serverMap != null) {
377          for (ServerName rs : serverMap.keySet()) {
378            if (newGroup.containsServer(rs.getAddress())) {
379              count += serverMap.get(rs).size();
380            }
381          }
382        }
383        return count == 5;
384      }
385    });
386
387    //test truncate
388    admin.disableTable(tableName);
389    admin.truncateTable(tableName, true);
390    Assert.assertEquals(1, rsGroupAdmin.getRSGroupInfo(newGroup.getName()).getTables().size());
391    Assert.assertEquals(tableName, rsGroupAdmin.getRSGroupInfo(
392        newGroup.getName()).getTables().first());
393
394    //verify removed table is removed from group
395    TEST_UTIL.deleteTable(tableName);
396    Assert.assertEquals(0, rsGroupAdmin.getRSGroupInfo(newGroup.getName()).getTables().size());
397  }
398
399  @Test
400  public void testGroupBalance() throws Exception {
401    LOG.info(name.getMethodName());
402    String newGroupName = getGroupName(name.getMethodName());
403    final RSGroupInfo newGroup = addGroup(newGroupName, 3);
404
405    final TableName tableName = TableName.valueOf(tablePrefix+"_ns", name.getMethodName());
406    admin.createNamespace(
407        NamespaceDescriptor.create(tableName.getNamespaceAsString())
408            .addConfiguration(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP, newGroupName).build());
409    final byte[] familyNameBytes = Bytes.toBytes("f");
410    final HTableDescriptor desc = new HTableDescriptor(tableName);
411    desc.addFamily(new HColumnDescriptor("f"));
412    byte [] startKey = Bytes.toBytes("aaaaa");
413    byte [] endKey = Bytes.toBytes("zzzzz");
414    admin.createTable(desc, startKey, endKey, 6);
415    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
416      @Override
417      public boolean evaluate() throws Exception {
418        List<String> regions = getTableRegionMap().get(tableName);
419        if (regions == null) {
420          return false;
421        }
422        return regions.size() >= 6;
423      }
424    });
425
426    //make assignment uneven, move all regions to one server
427    Map<ServerName,List<String>> assignMap =
428        getTableServerRegionMap().get(tableName);
429    final ServerName first = assignMap.entrySet().iterator().next().getKey();
430    for(RegionInfo region: admin.getTableRegions(tableName)) {
431      if(!assignMap.get(first).contains(region.getRegionNameAsString())) {
432        admin.move(region.getEncodedNameAsBytes(), Bytes.toBytes(first.getServerName()));
433      }
434    }
435    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
436      @Override
437      public boolean evaluate() throws Exception {
438        Map<ServerName, List<String>> map = getTableServerRegionMap().get(tableName);
439        if (map == null) {
440          return true;
441        }
442        List<String> regions = map.get(first);
443        if (regions == null) {
444          return true;
445        }
446        return regions.size() >= 6;
447      }
448    });
449
450    //balance the other group and make sure it doesn't affect the new group
451    admin.setBalancerRunning(true,true);
452    rsGroupAdmin.balanceRSGroup(RSGroupInfo.DEFAULT_GROUP);
453    assertEquals(6, getTableServerRegionMap().get(tableName).get(first).size());
454
455    //disable balance, balancer will not be run and return false
456    admin.setBalancerRunning(false,true);
457    assertFalse(rsGroupAdmin.balanceRSGroup(newGroupName));
458    assertEquals(6, getTableServerRegionMap().get(tableName).get(first).size());
459
460    //enable balance
461    admin.setBalancerRunning(true,true);
462    rsGroupAdmin.balanceRSGroup(newGroupName);
463    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
464      @Override
465      public boolean evaluate() throws Exception {
466        for (List<String> regions : getTableServerRegionMap().get(tableName).values()) {
467          if (2 != regions.size()) {
468            return false;
469          }
470        }
471        return true;
472      }
473    });
474    admin.setBalancerRunning(false,true);
475  }
476
477  @Test
478  public void testRegionMove() throws Exception {
479    final RSGroupInfo newGroup = addGroup(getGroupName(name.getMethodName()), 1);
480    final byte[] familyNameBytes = Bytes.toBytes("f");
481    // All the regions created below will be assigned to the default group.
482    TEST_UTIL.createMultiRegionTable(tableName, familyNameBytes, 6);
483    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
484      @Override
485      public boolean evaluate() throws Exception {
486        List<String> regions = getTableRegionMap().get(tableName);
487        if (regions == null) {
488          return false;
489        }
490        return getTableRegionMap().get(tableName).size() >= 6;
491      }
492    });
493
494    //get target region to move
495    Map<ServerName,List<String>> assignMap =
496        getTableServerRegionMap().get(tableName);
497    String targetRegion = null;
498    for(ServerName server : assignMap.keySet()) {
499      targetRegion = assignMap.get(server).size() > 0 ? assignMap.get(server).get(0) : null;
500      if(targetRegion != null) {
501        break;
502      }
503    }
504    //get server which is not a member of new group
505    ServerName targetServer = null;
506    for (ServerName server : admin.getClusterMetrics(EnumSet.of(Option.LIVE_SERVERS))
507                                  .getLiveServerMetrics().keySet()) {
508      if (!newGroup.containsServer(server.getAddress())) {
509        targetServer = server;
510        break;
511      }
512    }
513
514    final AdminProtos.AdminService.BlockingInterface targetRS =
515      ((ClusterConnection) admin.getConnection()).getAdmin(targetServer);
516
517    //move target server to group
518    rsGroupAdmin.moveServers(Sets.newHashSet(targetServer.getAddress()),
519        newGroup.getName());
520    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
521      @Override
522      public boolean evaluate() throws Exception {
523        return ProtobufUtil.getOnlineRegions(targetRS).size() <= 0;
524      }
525    });
526
527    // Lets move this region to the new group.
528    TEST_UTIL.getAdmin().move(Bytes.toBytes(RegionInfo.encodeRegionName(
529        Bytes.toBytes(targetRegion))), Bytes.toBytes(targetServer.getServerName()));
530    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
531      @Override
532      public boolean evaluate() throws Exception {
533        return
534            getTableRegionMap().get(tableName) != null &&
535                getTableRegionMap().get(tableName).size() == 6 &&
536                admin.getClusterMetrics(EnumSet.of(Option.REGIONS_IN_TRANSITION))
537                     .getRegionStatesInTransition().size() < 1;
538      }
539    });
540
541    //verify that targetServer didn't open it
542    for (RegionInfo region: ProtobufUtil.getOnlineRegions(targetRS)) {
543      if (targetRegion.equals(region.getRegionNameAsString())) {
544        fail("Target server opened region");
545      }
546    }
547  }
548
549  @Test
550  public void testFailRemoveGroup() throws IOException, InterruptedException {
551    int initNumGroups = rsGroupAdmin.listRSGroups().size();
552    addGroup("bar", 3);
553    TEST_UTIL.createTable(tableName, Bytes.toBytes("f"));
554    rsGroupAdmin.moveTables(Sets.newHashSet(tableName), "bar");
555    RSGroupInfo barGroup = rsGroupAdmin.getRSGroupInfo("bar");
556    //group is not empty therefore it should fail
557    try {
558      rsGroupAdmin.removeRSGroup(barGroup.getName());
559      fail("Expected remove group to fail");
560    } catch(IOException e) {
561    }
562    //group cannot lose all it's servers therefore it should fail
563    try {
564      rsGroupAdmin.moveServers(barGroup.getServers(), RSGroupInfo.DEFAULT_GROUP);
565      fail("Expected move servers to fail");
566    } catch(IOException e) {
567    }
568
569    rsGroupAdmin.moveTables(barGroup.getTables(), RSGroupInfo.DEFAULT_GROUP);
570    try {
571      rsGroupAdmin.removeRSGroup(barGroup.getName());
572      fail("Expected move servers to fail");
573    } catch(IOException e) {
574    }
575
576    rsGroupAdmin.moveServers(barGroup.getServers(), RSGroupInfo.DEFAULT_GROUP);
577    rsGroupAdmin.removeRSGroup(barGroup.getName());
578
579    Assert.assertEquals(initNumGroups, rsGroupAdmin.listRSGroups().size());
580  }
581
582  @Test
583  public void testKillRS() throws Exception {
584    RSGroupInfo appInfo = addGroup("appInfo", 1);
585
586    final TableName tableName = TableName.valueOf(tablePrefix+"_ns", name.getMethodName());
587    admin.createNamespace(
588        NamespaceDescriptor.create(tableName.getNamespaceAsString())
589            .addConfiguration(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP, appInfo.getName()).build());
590    final HTableDescriptor desc = new HTableDescriptor(tableName);
591    desc.addFamily(new HColumnDescriptor("f"));
592    admin.createTable(desc);
593    //wait for created table to be assigned
594    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
595      @Override
596      public boolean evaluate() throws Exception {
597        return getTableRegionMap().get(desc.getTableName()) != null;
598      }
599    });
600
601    ServerName targetServer = ServerName.parseServerName(
602        appInfo.getServers().iterator().next().toString());
603    AdminProtos.AdminService.BlockingInterface targetRS =
604      ((ClusterConnection) admin.getConnection()).getAdmin(targetServer);
605    RegionInfo targetRegion = ProtobufUtil.getOnlineRegions(targetRS).get(0);
606    Assert.assertEquals(1, ProtobufUtil.getOnlineRegions(targetRS).size());
607
608    try {
609      //stopping may cause an exception
610      //due to the connection loss
611      targetRS.stopServer(null,
612          AdminProtos.StopServerRequest.newBuilder().setReason("Die").build());
613    } catch(Exception e) {
614    }
615    assertFalse(cluster.getClusterMetrics().getLiveServerMetrics().containsKey(targetServer));
616
617    //wait for created table to be assigned
618    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
619      @Override
620      public boolean evaluate() throws Exception {
621        return cluster.getClusterMetrics().getRegionStatesInTransition().isEmpty();
622      }
623    });
624    Set<Address> newServers = Sets.newHashSet();
625    newServers.add(
626        rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getServers().iterator().next());
627    rsGroupAdmin.moveServers(newServers, appInfo.getName());
628
629    //Make sure all the table's regions get reassigned
630    //disabling the table guarantees no conflicting assign/unassign (ie SSH) happens
631    admin.disableTable(tableName);
632    admin.enableTable(tableName);
633
634    //wait for region to be assigned
635    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
636      @Override
637      public boolean evaluate() throws Exception {
638        return cluster.getClusterMetrics().getRegionStatesInTransition().isEmpty();
639      }
640    });
641
642    targetServer = ServerName.parseServerName(
643        newServers.iterator().next().toString());
644    targetRS =
645      ((ClusterConnection) admin.getConnection()).getAdmin(targetServer);
646    Assert.assertEquals(1, ProtobufUtil.getOnlineRegions(targetRS).size());
647    Assert.assertEquals(tableName,
648        ProtobufUtil.getOnlineRegions(targetRS).get(0).getTable());
649  }
650
651  @Test
652  public void testValidGroupNames() throws IOException {
653    String[] badNames = {"foo*","foo@","-"};
654    String[] goodNames = {"foo_123"};
655
656    for(String entry: badNames) {
657      try {
658        rsGroupAdmin.addRSGroup(entry);
659        fail("Expected a constraint exception for: "+entry);
660      } catch(ConstraintException ex) {
661        //expected
662      }
663    }
664
665    for(String entry: goodNames) {
666      rsGroupAdmin.addRSGroup(entry);
667    }
668  }
669
670  private String getGroupName(String baseName) {
671    return groupPrefix+"_"+baseName+"_"+rand.nextInt(Integer.MAX_VALUE);
672  }
673
674  @Test
675  public void testMultiTableMove() throws Exception {
676    final TableName tableNameA = TableName.valueOf(tablePrefix + name.getMethodName() + "A");
677    final TableName tableNameB = TableName.valueOf(tablePrefix + name.getMethodName() + "B");
678    final byte[] familyNameBytes = Bytes.toBytes("f");
679    String newGroupName = getGroupName(name.getMethodName());
680    final RSGroupInfo newGroup = addGroup(newGroupName, 1);
681
682    TEST_UTIL.createTable(tableNameA, familyNameBytes);
683    TEST_UTIL.createTable(tableNameB, familyNameBytes);
684    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
685      @Override
686      public boolean evaluate() throws Exception {
687        List<String> regionsA = getTableRegionMap().get(tableNameA);
688        if (regionsA == null) {
689          return false;
690        }
691
692        List<String> regionsB = getTableRegionMap().get(tableNameB);
693        if (regionsB == null) {
694          return false;
695        }
696
697        return getTableRegionMap().get(tableNameA).size() >= 1
698                && getTableRegionMap().get(tableNameB).size() >= 1;
699      }
700    });
701
702    RSGroupInfo tableGrpA = rsGroupAdmin.getRSGroupInfoOfTable(tableNameA);
703    assertTrue(tableGrpA.getName().equals(RSGroupInfo.DEFAULT_GROUP));
704
705    RSGroupInfo tableGrpB = rsGroupAdmin.getRSGroupInfoOfTable(tableNameB);
706    assertTrue(tableGrpB.getName().equals(RSGroupInfo.DEFAULT_GROUP));
707    //change table's group
708    LOG.info("Moving table [" + tableNameA + "," + tableNameB + "] to " + newGroup.getName());
709    rsGroupAdmin.moveTables(Sets.newHashSet(tableNameA, tableNameB), newGroup.getName());
710
711    //verify group change
712    Assert.assertEquals(newGroup.getName(),
713            rsGroupAdmin.getRSGroupInfoOfTable(tableNameA).getName());
714
715    Assert.assertEquals(newGroup.getName(),
716            rsGroupAdmin.getRSGroupInfoOfTable(tableNameB).getName());
717
718    //verify tables' not exist in old group
719    Set<TableName> DefaultTables = rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP)
720        .getTables();
721    assertFalse(DefaultTables.contains(tableNameA));
722    assertFalse(DefaultTables.contains(tableNameB));
723
724    //verify tables' exist in new group
725    Set<TableName> newGroupTables = rsGroupAdmin.getRSGroupInfo(newGroupName).getTables();
726    assertTrue(newGroupTables.contains(tableNameA));
727    assertTrue(newGroupTables.contains(tableNameB));
728  }
729
730  @Test
731  public void testDisabledTableMove() throws Exception {
732    final byte[] familyNameBytes = Bytes.toBytes("f");
733    String newGroupName = getGroupName(name.getMethodName());
734    final RSGroupInfo newGroup = addGroup(newGroupName, 2);
735
736    TEST_UTIL.createMultiRegionTable(tableName, familyNameBytes, 5);
737    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
738      @Override
739      public boolean evaluate() throws Exception {
740        List<String> regions = getTableRegionMap().get(tableName);
741        if (regions == null) {
742          return false;
743        }
744        return getTableRegionMap().get(tableName).size() >= 5;
745      }
746    });
747
748    RSGroupInfo tableGrp = rsGroupAdmin.getRSGroupInfoOfTable(tableName);
749    assertTrue(tableGrp.getName().equals(RSGroupInfo.DEFAULT_GROUP));
750
751    //test disable table
752    admin.disableTable(tableName);
753
754    //change table's group
755    LOG.info("Moving table "+ tableName + " to " + newGroup.getName());
756    rsGroupAdmin.moveTables(Sets.newHashSet(tableName), newGroup.getName());
757
758    //verify group change
759    Assert.assertEquals(newGroup.getName(),
760        rsGroupAdmin.getRSGroupInfoOfTable(tableName).getName());
761  }
762
763  @Test
764  public void testMoveServersAndTables() throws Exception {
765    LOG.info("testMoveServersAndTables");
766    final RSGroupInfo newGroup = addGroup(getGroupName(name.getMethodName()), 1);
767    //create table
768    final byte[] familyNameBytes = Bytes.toBytes("f");
769    TEST_UTIL.createMultiRegionTable(tableName, familyNameBytes, 5);
770    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
771      @Override
772      public boolean evaluate() throws Exception {
773        List<String> regions = getTableRegionMap().get(tableName);
774        if (regions == null) {
775          return false;
776        }
777        return getTableRegionMap().get(tableName).size() >= 5;
778      }
779    });
780
781    //get server which is not a member of new group
782    ServerName targetServer = null;
783    for(ServerName server : admin.getClusterMetrics(EnumSet.of(Option.LIVE_SERVERS))
784      .getLiveServerMetrics().keySet()) {
785      if(!newGroup.containsServer(server.getAddress()) &&
786           !rsGroupAdmin.getRSGroupInfo("master").containsServer(server.getAddress())) {
787        targetServer = server;
788        break;
789      }
790    }
791
792    LOG.debug("Print group info : " + rsGroupAdmin.listRSGroups());
793    int oldDefaultGroupServerSize =
794            rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getServers().size();
795    int oldDefaultGroupTableSize =
796            rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getTables().size();
797
798    //test fail bogus server move
799    try {
800      rsGroupAdmin.moveServersAndTables(Sets.newHashSet(Address.fromString("foo:9999")),
801              Sets.newHashSet(tableName), newGroup.getName());
802      fail("Bogus servers shouldn't have been successfully moved.");
803    } catch(IOException ex) {
804      String exp = "Source RSGroup for server foo:9999 does not exist.";
805      String msg = "Expected '" + exp + "' in exception message: ";
806      assertTrue(msg + " " + ex.getMessage(), ex.getMessage().contains(exp));
807    }
808
809    //test fail server move
810    try {
811      rsGroupAdmin.moveServersAndTables(Sets.newHashSet(targetServer.getAddress()),
812              Sets.newHashSet(tableName), RSGroupInfo.DEFAULT_GROUP);
813      fail("servers shouldn't have been successfully moved.");
814    } catch(IOException ex) {
815      String exp = "Target RSGroup " + RSGroupInfo.DEFAULT_GROUP +
816              " is same as source " + RSGroupInfo.DEFAULT_GROUP + " RSGroup.";
817      String msg = "Expected '" + exp + "' in exception message: ";
818      assertTrue(msg + " " + ex.getMessage(), ex.getMessage().contains(exp));
819    }
820
821    //verify default group info
822    Assert.assertEquals(oldDefaultGroupServerSize,
823            rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getServers().size());
824    Assert.assertEquals(oldDefaultGroupTableSize,
825            rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getTables().size());
826
827    //verify new group info
828    Assert.assertEquals(1,
829            rsGroupAdmin.getRSGroupInfo(newGroup.getName()).getServers().size());
830    Assert.assertEquals(0,
831            rsGroupAdmin.getRSGroupInfo(newGroup.getName()).getTables().size());
832
833    //get all region to move targetServer
834    List<String> regionList = getTableRegionMap().get(tableName);
835    for(String region : regionList) {
836      // Lets move this region to the targetServer
837      TEST_UTIL.getAdmin().move(Bytes.toBytes(RegionInfo.encodeRegionName(Bytes.toBytes(region))),
838              Bytes.toBytes(targetServer.getServerName()));
839    }
840
841    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
842      @Override
843      public boolean evaluate() throws Exception {
844        return getTableRegionMap().get(tableName) != null &&
845                getTableRegionMap().get(tableName).size() == 5 &&
846                getTableServerRegionMap().get(tableName).size() == 1 &&
847                admin.getClusterMetrics(EnumSet.of(Option.REGIONS_IN_TRANSITION))
848                     .getRegionStatesInTransition().size() < 1;
849      }
850    });
851
852    //verify that all region move to targetServer
853    Assert.assertEquals(5, getTableServerRegionMap().get(tableName).get(targetServer).size());
854
855    //move targetServer and table to newGroup
856    LOG.info("moving server and table to newGroup");
857    rsGroupAdmin.moveServersAndTables(Sets.newHashSet(targetServer.getAddress()),
858            Sets.newHashSet(tableName), newGroup.getName());
859
860    //verify group change
861    Assert.assertEquals(newGroup.getName(),
862            rsGroupAdmin.getRSGroupInfoOfTable(tableName).getName());
863
864    //verify servers' not exist in old group
865    Set<Address> defaultServers = rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP)
866        .getServers();
867    assertFalse(defaultServers.contains(targetServer.getAddress()));
868
869    //verify servers' exist in new group
870    Set<Address> newGroupServers = rsGroupAdmin.getRSGroupInfo(newGroup.getName()).getServers();
871    assertTrue(newGroupServers.contains(targetServer.getAddress()));
872
873    //verify tables' not exist in old group
874    Set<TableName> defaultTables = rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP)
875        .getTables();
876    assertFalse(defaultTables.contains(tableName));
877
878    //verify tables' exist in new group
879    Set<TableName> newGroupTables = rsGroupAdmin.getRSGroupInfo(newGroup.getName()).getTables();
880    assertTrue(newGroupTables.contains(tableName));
881
882    //verify that all region still assgin on targetServer
883    Assert.assertEquals(5, getTableServerRegionMap().get(tableName).get(targetServer).size());
884  }
885
886  @Test
887  public void testClearDeadServers() throws Exception {
888    LOG.info("testClearDeadServers");
889    final RSGroupInfo newGroup = addGroup(getGroupName(name.getMethodName()), 3);
890    NUM_DEAD_SERVERS = cluster.getClusterMetrics().getDeadServerNames().size();
891
892    ServerName targetServer = ServerName.parseServerName(
893        newGroup.getServers().iterator().next().toString());
894    AdminProtos.AdminService.BlockingInterface targetRS =
895        ((ClusterConnection) admin.getConnection()).getAdmin(targetServer);
896    try {
897      targetServer = ProtobufUtil.toServerName(targetRS.getServerInfo(null,
898          GetServerInfoRequest.newBuilder().build()).getServerInfo().getServerName());
899      //stopping may cause an exception
900      //due to the connection loss
901      targetRS.stopServer(null,
902          AdminProtos.StopServerRequest.newBuilder().setReason("Die").build());
903      NUM_DEAD_SERVERS ++;
904    } catch(Exception e) {
905    }
906    //wait for stopped regionserver to dead server list
907    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
908      @Override
909      public boolean evaluate() throws Exception {
910        return !master.getServerManager().areDeadServersInProgress()
911            && cluster.getClusterMetrics().getDeadServerNames().size() == NUM_DEAD_SERVERS;
912      }
913    });
914    assertFalse(cluster.getClusterMetrics().getLiveServerMetrics().containsKey(targetServer));
915    assertTrue(cluster.getClusterMetrics().getDeadServerNames().contains(targetServer));
916    assertTrue(newGroup.getServers().contains(targetServer.getAddress()));
917
918    //clear dead servers list
919    List<ServerName> notClearedServers = admin.clearDeadServers(Lists.newArrayList(targetServer));
920    assertEquals(0, notClearedServers.size());
921
922    Set<Address> newGroupServers = rsGroupAdmin.getRSGroupInfo(newGroup.getName()).getServers();
923    assertFalse(newGroupServers.contains(targetServer.getAddress()));
924    assertEquals(2, newGroupServers.size());
925  }
926
927  @Test
928  public void testRemoveServers() throws Exception {
929    LOG.info("testRemoveServers");
930    final RSGroupInfo newGroup = addGroup(getGroupName(name.getMethodName()), 3);
931    Iterator<Address> iterator = newGroup.getServers().iterator();
932    ServerName targetServer = ServerName.parseServerName(iterator.next().toString());
933
934    // remove online servers
935    try {
936      rsGroupAdmin.removeServers(Sets.newHashSet(targetServer.getAddress()));
937      fail("Online servers shouldn't have been successfully removed.");
938    } catch(IOException ex) {
939      String exp = "Server " + targetServer.getAddress()
940          + " is an online server, not allowed to remove.";
941      String msg = "Expected '" + exp + "' in exception message: ";
942      assertTrue(msg + " " + ex.getMessage(), ex.getMessage().contains(exp));
943    }
944    assertTrue(newGroup.getServers().contains(targetServer.getAddress()));
945
946    // remove dead servers
947    NUM_DEAD_SERVERS = cluster.getClusterMetrics().getDeadServerNames().size();
948    AdminProtos.AdminService.BlockingInterface targetRS =
949        ((ClusterConnection) admin.getConnection()).getAdmin(targetServer);
950    try {
951      targetServer = ProtobufUtil.toServerName(targetRS.getServerInfo(null,
952          GetServerInfoRequest.newBuilder().build()).getServerInfo().getServerName());
953      //stopping may cause an exception
954      //due to the connection loss
955      LOG.info("stopping server " + targetServer.getHostAndPort());
956      targetRS.stopServer(null,
957          AdminProtos.StopServerRequest.newBuilder().setReason("Die").build());
958      NUM_DEAD_SERVERS ++;
959    } catch(Exception e) {
960    }
961
962    //wait for stopped regionserver to dead server list
963    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
964      @Override
965      public boolean evaluate() throws Exception {
966        return !master.getServerManager().areDeadServersInProgress()
967            && cluster.getClusterMetrics().getDeadServerNames().size() == NUM_DEAD_SERVERS;
968      }
969    });
970
971    try {
972      rsGroupAdmin.removeServers(Sets.newHashSet(targetServer.getAddress()));
973      fail("Dead servers shouldn't have been successfully removed.");
974    } catch(IOException ex) {
975      String exp = "Server " + targetServer.getAddress() + " is on the dead servers list,"
976          + " Maybe it will come back again, not allowed to remove.";
977      String msg = "Expected '" + exp + "' in exception message: ";
978      assertTrue(msg + " " + ex.getMessage(), ex.getMessage().contains(exp));
979    }
980    assertTrue(newGroup.getServers().contains(targetServer.getAddress()));
981
982    // remove decommissioned servers
983    List<ServerName> serversToDecommission = new ArrayList<>();
984    targetServer = ServerName.parseServerName(iterator.next().toString());
985    targetRS = ((ClusterConnection) admin.getConnection()).getAdmin(targetServer);
986    targetServer = ProtobufUtil.toServerName(targetRS.getServerInfo(null,
987          GetServerInfoRequest.newBuilder().build()).getServerInfo().getServerName());
988    assertTrue(master.getServerManager().getOnlineServers().containsKey(targetServer));
989    serversToDecommission.add(targetServer);
990
991    admin.decommissionRegionServers(serversToDecommission, true);
992    assertEquals(1, admin.listDecommissionedRegionServers().size());
993
994    assertTrue(newGroup.getServers().contains(targetServer.getAddress()));
995    rsGroupAdmin.removeServers(Sets.newHashSet(targetServer.getAddress()));
996    Set<Address> newGroupServers = rsGroupAdmin.getRSGroupInfo(newGroup.getName()).getServers();
997    assertFalse(newGroupServers.contains(targetServer.getAddress()));
998    assertEquals(2, newGroupServers.size());
999  }
1000
1001  @Test
1002  public void testCreateWhenRsgroupNoOnlineServers() throws Exception {
1003    LOG.info("testCreateWhenRsgroupNoOnlineServers");
1004
1005    // set rsgroup has no online servers and test create table
1006    final RSGroupInfo appInfo = addGroup("appInfo", 1);
1007    Iterator<Address> iterator = appInfo.getServers().iterator();
1008    List<ServerName> serversToDecommission = new ArrayList<>();
1009    ServerName targetServer = ServerName.parseServerName(iterator.next().toString());
1010    AdminProtos.AdminService.BlockingInterface targetRS =
1011        ((ClusterConnection) admin.getConnection()).getAdmin(targetServer);
1012    targetServer = ProtobufUtil.toServerName(
1013        targetRS.getServerInfo(null, GetServerInfoRequest.newBuilder().build()).getServerInfo()
1014            .getServerName());
1015    assertTrue(master.getServerManager().getOnlineServers().containsKey(targetServer));
1016    serversToDecommission.add(targetServer);
1017    admin.decommissionRegionServers(serversToDecommission, true);
1018    assertEquals(1, admin.listDecommissionedRegionServers().size());
1019
1020    final TableName tableName = TableName.valueOf(tablePrefix + "_ns", name.getMethodName());
1021    admin.createNamespace(NamespaceDescriptor.create(tableName.getNamespaceAsString())
1022        .addConfiguration(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP, appInfo.getName()).build());
1023    final HTableDescriptor desc = new HTableDescriptor(tableName);
1024    desc.addFamily(new HColumnDescriptor("f"));
1025    try {
1026      admin.createTable(desc);
1027      fail("Shouldn't create table successfully!");
1028    } catch (Exception e) {
1029      LOG.debug("create table error", e);
1030    }
1031
1032    // recommission and test create table
1033    admin.recommissionRegionServer(targetServer, null);
1034    assertEquals(0, admin.listDecommissionedRegionServers().size());
1035    admin.createTable(desc);
1036    // wait for created table to be assigned
1037    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
1038      @Override public boolean evaluate() throws Exception {
1039        return getTableRegionMap().get(desc.getTableName()) != null;
1040      }
1041    });
1042  }
1043}