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.master.balancer;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertFalse;
022import static org.junit.Assert.assertTrue;
023
024import java.io.FileNotFoundException;
025import java.io.IOException;
026import java.security.SecureRandom;
027import java.util.ArrayList;
028import java.util.Collections;
029import java.util.HashMap;
030import java.util.HashSet;
031import java.util.Iterator;
032import java.util.List;
033import java.util.Map;
034import java.util.Set;
035import java.util.TreeMap;
036import java.util.TreeSet;
037import org.apache.commons.lang3.StringUtils;
038import org.apache.hadoop.conf.Configuration;
039import org.apache.hadoop.hbase.HBaseClassTestRule;
040import org.apache.hadoop.hbase.HBaseConfiguration;
041import org.apache.hadoop.hbase.HTableDescriptor;
042import org.apache.hadoop.hbase.ServerName;
043import org.apache.hadoop.hbase.TableDescriptors;
044import org.apache.hadoop.hbase.TableName;
045import org.apache.hadoop.hbase.client.RegionInfo;
046import org.apache.hadoop.hbase.client.RegionInfoBuilder;
047import org.apache.hadoop.hbase.master.HMaster;
048import org.apache.hadoop.hbase.master.LoadBalancer;
049import org.apache.hadoop.hbase.master.MasterServices;
050import org.apache.hadoop.hbase.master.RegionPlan;
051import org.apache.hadoop.hbase.master.assignment.AssignmentManager;
052import org.apache.hadoop.hbase.master.assignment.RegionStates;
053import org.apache.hadoop.hbase.net.Address;
054import org.apache.hadoop.hbase.rsgroup.RSGroupBasedLoadBalancer;
055import org.apache.hadoop.hbase.rsgroup.RSGroupInfo;
056import org.apache.hadoop.hbase.rsgroup.RSGroupInfoManager;
057import org.apache.hadoop.hbase.testclassification.SmallTests;
058import org.apache.hadoop.hbase.util.Bytes;
059import org.junit.BeforeClass;
060import org.junit.ClassRule;
061import org.junit.Test;
062import org.junit.experimental.categories.Category;
063import org.mockito.Mockito;
064import org.mockito.invocation.InvocationOnMock;
065import org.mockito.stubbing.Answer;
066import org.slf4j.Logger;
067import org.slf4j.LoggerFactory;
068
069import org.apache.hbase.thirdparty.com.google.common.collect.ArrayListMultimap;
070import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
071
072//TODO use stochastic based load balancer instead
073@Category(SmallTests.class)
074public class TestRSGroupBasedLoadBalancer {
075
076  @ClassRule
077  public static final HBaseClassTestRule CLASS_RULE =
078      HBaseClassTestRule.forClass(TestRSGroupBasedLoadBalancer.class);
079
080  private static final Logger LOG = LoggerFactory.getLogger(TestRSGroupBasedLoadBalancer.class);
081  private static RSGroupBasedLoadBalancer loadBalancer;
082  private static SecureRandom rand;
083
084  static String[]  groups = new String[] { RSGroupInfo.DEFAULT_GROUP, "dg2", "dg3", "dg4" };
085  static TableName table0 = TableName.valueOf("dt0");
086  static TableName[] tables =
087      new TableName[] { TableName.valueOf("dt1"),
088          TableName.valueOf("dt2"),
089          TableName.valueOf("dt3"),
090          TableName.valueOf("dt4")};
091  static List<ServerName> servers;
092  static Map<String, RSGroupInfo> groupMap;
093  static Map<TableName, String> tableMap;
094  static List<HTableDescriptor> tableDescs;
095  int[] regionAssignment = new int[] { 2, 5, 7, 10, 4, 3, 1 };
096  static int regionId = 0;
097
098  @BeforeClass
099  public static void beforeAllTests() throws Exception {
100    rand = new SecureRandom();
101    servers = generateServers(7);
102    groupMap = constructGroupInfo(servers, groups);
103    tableMap = new HashMap<>();
104    tableDescs = constructTableDesc();
105    Configuration conf = HBaseConfiguration.create();
106    conf.set("hbase.regions.slop", "0");
107    conf.set("hbase.rsgroup.grouploadbalancer.class", SimpleLoadBalancer.class.getCanonicalName());
108    loadBalancer = new RSGroupBasedLoadBalancer();
109    loadBalancer.setRsGroupInfoManager(getMockedGroupInfoManager());
110    loadBalancer.setMasterServices(getMockedMaster());
111    loadBalancer.setConf(conf);
112    loadBalancer.initialize();
113  }
114
115  /**
116   * Test the load balancing algorithm.
117   *
118   * Invariant is that all servers of the group should be hosting either floor(average) or
119   * ceiling(average)
120   */
121  @Test
122  public void testBalanceCluster() throws Exception {
123    Map<ServerName, List<RegionInfo>> servers = mockClusterServers();
124    ArrayListMultimap<String, ServerAndLoad> list = convertToGroupBasedMap(servers);
125    LOG.info("Mock Cluster :  " + printStats(list));
126    List<RegionPlan> plans = loadBalancer.balanceCluster(servers);
127    ArrayListMultimap<String, ServerAndLoad> balancedCluster = reconcile(
128        list, plans);
129    LOG.info("Mock Balance : " + printStats(balancedCluster));
130    assertClusterAsBalanced(balancedCluster);
131  }
132
133  /**
134   * Invariant is that all servers of a group have load between floor(avg) and
135   * ceiling(avg) number of regions.
136   */
137  private void assertClusterAsBalanced(
138      ArrayListMultimap<String, ServerAndLoad> groupLoadMap) {
139    for (String gName : groupLoadMap.keySet()) {
140      List<ServerAndLoad> groupLoad = groupLoadMap.get(gName);
141      int numServers = groupLoad.size();
142      int numRegions = 0;
143      int maxRegions = 0;
144      int minRegions = Integer.MAX_VALUE;
145      for (ServerAndLoad server : groupLoad) {
146        int nr = server.getLoad();
147        if (nr > maxRegions) {
148          maxRegions = nr;
149        }
150        if (nr < minRegions) {
151          minRegions = nr;
152        }
153        numRegions += nr;
154      }
155      if (maxRegions - minRegions < 2) {
156        // less than 2 between max and min, can't balance
157        return;
158      }
159      int min = numRegions / numServers;
160      int max = numRegions % numServers == 0 ? min : min + 1;
161
162      for (ServerAndLoad server : groupLoad) {
163        assertTrue(server.getLoad() <= max);
164        assertTrue(server.getLoad() >= min);
165      }
166    }
167  }
168
169  /**
170   * All regions have an assignment.
171   */
172  private void assertImmediateAssignment(List<RegionInfo> regions,
173                                         List<ServerName> servers,
174                                         Map<RegionInfo, ServerName> assignments)
175      throws IOException {
176    for (RegionInfo region : regions) {
177      assertTrue(assignments.containsKey(region));
178      ServerName server = assignments.get(region);
179      TableName tableName = region.getTable();
180
181      String groupName = getMockedGroupInfoManager().getRSGroupOfTable(tableName);
182      assertTrue(StringUtils.isNotEmpty(groupName));
183      RSGroupInfo gInfo = getMockedGroupInfoManager().getRSGroup(groupName);
184      assertTrue("Region is not correctly assigned to group servers.",
185          gInfo.containsServer(server.getAddress()));
186    }
187  }
188
189  /**
190   * Tests the bulk assignment used during cluster startup.
191   *
192   * Round-robin. Should yield a balanced cluster so same invariant as the
193   * load balancer holds, all servers holding either floor(avg) or
194   * ceiling(avg).
195   */
196  @Test
197  public void testBulkAssignment() throws Exception {
198    List<RegionInfo> regions = randomRegions(25);
199    Map<ServerName, List<RegionInfo>> assignments = loadBalancer
200        .roundRobinAssignment(regions, servers);
201    //test empty region/servers scenario
202    //this should not throw an NPE
203    loadBalancer.roundRobinAssignment(regions,
204        Collections.EMPTY_LIST);
205    //test regular scenario
206    assertTrue(assignments.keySet().size() == servers.size());
207    for (ServerName sn : assignments.keySet()) {
208      List<RegionInfo> regionAssigned = assignments.get(sn);
209      for (RegionInfo region : regionAssigned) {
210        TableName tableName = region.getTable();
211        String groupName =
212            getMockedGroupInfoManager().getRSGroupOfTable(tableName);
213        assertTrue(StringUtils.isNotEmpty(groupName));
214        RSGroupInfo gInfo = getMockedGroupInfoManager().getRSGroup(
215            groupName);
216        assertTrue(
217            "Region is not correctly assigned to group servers.",
218            gInfo.containsServer(sn.getAddress()));
219      }
220    }
221    ArrayListMultimap<String, ServerAndLoad> loadMap = convertToGroupBasedMap(assignments);
222    assertClusterAsBalanced(loadMap);
223  }
224
225  /**
226   * Test the cluster startup bulk assignment which attempts to retain assignment info.
227   */
228  @Test
229  public void testRetainAssignment() throws Exception {
230    // Test simple case where all same servers are there
231    Map<ServerName, List<RegionInfo>> currentAssignments = mockClusterServers();
232    Map<RegionInfo, ServerName> inputForTest = new HashMap<>();
233    for (ServerName sn : currentAssignments.keySet()) {
234      for (RegionInfo region : currentAssignments.get(sn)) {
235        inputForTest.put(region, sn);
236      }
237    }
238    //verify region->null server assignment is handled
239    inputForTest.put(randomRegions(1).get(0), null);
240    Map<ServerName, List<RegionInfo>> newAssignment = loadBalancer
241        .retainAssignment(inputForTest, servers);
242    assertRetainedAssignment(inputForTest, servers, newAssignment);
243  }
244
245  /**
246   * Test BOGUS_SERVER_NAME among groups do not overwrite each other.
247   */
248  @Test
249  public void testRoundRobinAssignment() throws Exception {
250    List<ServerName> onlineServers = new ArrayList<ServerName>(servers.size());
251    onlineServers.addAll(servers);
252    List<RegionInfo> regions = randomRegions(25);
253    int bogusRegion = 0;
254    for(RegionInfo region : regions){
255      String group = tableMap.get(region.getTable());
256      if("dg3".equals(group) || "dg4".equals(group)){
257        bogusRegion++;
258      }
259    }
260    Set<Address> offlineServers = new HashSet<Address>();
261    offlineServers.addAll(groupMap.get("dg3").getServers());
262    offlineServers.addAll(groupMap.get("dg4").getServers());
263    for(Iterator<ServerName> it =  onlineServers.iterator(); it.hasNext();){
264      ServerName server = it.next();
265      Address address = server.getAddress();
266      if(offlineServers.contains(address)){
267        it.remove();
268      }
269    }
270    Map<ServerName, List<RegionInfo>> assignments = loadBalancer
271        .roundRobinAssignment(regions, onlineServers);
272    assertEquals(bogusRegion, assignments.get(LoadBalancer.BOGUS_SERVER_NAME).size());
273  }
274
275  /**
276   * Asserts a valid retained assignment plan.
277   * <p>
278   * Must meet the following conditions:
279   * <ul>
280   * <li>Every input region has an assignment, and to an online server
281   * <li>If a region had an existing assignment to a server with the same
282   * address a a currently online server, it will be assigned to it
283   * </ul>
284   */
285  private void assertRetainedAssignment(
286      Map<RegionInfo, ServerName> existing, List<ServerName> servers,
287      Map<ServerName, List<RegionInfo>> assignment)
288      throws FileNotFoundException, IOException {
289    // Verify condition 1, every region assigned, and to online server
290    Set<ServerName> onlineServerSet = new TreeSet<>(servers);
291    Set<RegionInfo> assignedRegions = new TreeSet<>(RegionInfo.COMPARATOR);
292    for (Map.Entry<ServerName, List<RegionInfo>> a : assignment.entrySet()) {
293      assertTrue(
294          "Region assigned to server that was not listed as online",
295          onlineServerSet.contains(a.getKey()));
296      for (RegionInfo r : a.getValue()) {
297        assignedRegions.add(r);
298      }
299    }
300    assertEquals(existing.size(), assignedRegions.size());
301
302    // Verify condition 2, every region must be assigned to correct server.
303    Set<String> onlineHostNames = new TreeSet<>();
304    for (ServerName s : servers) {
305      onlineHostNames.add(s.getHostname());
306    }
307
308    for (Map.Entry<ServerName, List<RegionInfo>> a : assignment.entrySet()) {
309      ServerName currentServer = a.getKey();
310      for (RegionInfo r : a.getValue()) {
311        ServerName oldAssignedServer = existing.get(r);
312        TableName tableName = r.getTable();
313        String groupName =
314            getMockedGroupInfoManager().getRSGroupOfTable(tableName);
315        assertTrue(StringUtils.isNotEmpty(groupName));
316        RSGroupInfo gInfo = getMockedGroupInfoManager().getRSGroup(
317            groupName);
318        assertTrue(
319            "Region is not correctly assigned to group servers.",
320            gInfo.containsServer(currentServer.getAddress()));
321        if (oldAssignedServer != null
322            && onlineHostNames.contains(oldAssignedServer
323            .getHostname())) {
324          // this region was previously assigned somewhere, and that
325          // host is still around, then the host must have been is a
326          // different group.
327          if (!oldAssignedServer.getAddress().equals(currentServer.getAddress())) {
328            assertFalse(gInfo.containsServer(oldAssignedServer.getAddress()));
329          }
330        }
331      }
332    }
333  }
334
335  private String printStats(
336      ArrayListMultimap<String, ServerAndLoad> groupBasedLoad) {
337    StringBuffer sb = new StringBuffer();
338    sb.append("\n");
339    for (String groupName : groupBasedLoad.keySet()) {
340      sb.append("Stats for group: " + groupName);
341      sb.append("\n");
342      sb.append(groupMap.get(groupName).getServers());
343      sb.append("\n");
344      List<ServerAndLoad> groupLoad = groupBasedLoad.get(groupName);
345      int numServers = groupLoad.size();
346      int totalRegions = 0;
347      sb.append("Per Server Load: \n");
348      for (ServerAndLoad sLoad : groupLoad) {
349        sb.append("Server :" + sLoad.getServerName() + " Load : "
350            + sLoad.getLoad() + "\n");
351        totalRegions += sLoad.getLoad();
352      }
353      sb.append(" Group Statistics : \n");
354      float average = (float) totalRegions / numServers;
355      int max = (int) Math.ceil(average);
356      int min = (int) Math.floor(average);
357      sb.append("[srvr=" + numServers + " rgns=" + totalRegions + " avg="
358          + average + " max=" + max + " min=" + min + "]");
359      sb.append("\n");
360      sb.append("===============================");
361      sb.append("\n");
362    }
363    return sb.toString();
364  }
365
366  private ArrayListMultimap<String, ServerAndLoad> convertToGroupBasedMap(
367      final Map<ServerName, List<RegionInfo>> serversMap) throws IOException {
368    ArrayListMultimap<String, ServerAndLoad> loadMap = ArrayListMultimap
369        .create();
370    for (RSGroupInfo gInfo : getMockedGroupInfoManager().listRSGroups()) {
371      Set<Address> groupServers = gInfo.getServers();
372      for (Address hostPort : groupServers) {
373        ServerName actual = null;
374        for(ServerName entry: servers) {
375          if(entry.getAddress().equals(hostPort)) {
376            actual = entry;
377            break;
378          }
379        }
380        List<RegionInfo> regions = serversMap.get(actual);
381        assertTrue("No load for " + actual, regions != null);
382        loadMap.put(gInfo.getName(),
383            new ServerAndLoad(actual, regions.size()));
384      }
385    }
386    return loadMap;
387  }
388
389  private ArrayListMultimap<String, ServerAndLoad> reconcile(
390      ArrayListMultimap<String, ServerAndLoad> previousLoad,
391      List<RegionPlan> plans) {
392    ArrayListMultimap<String, ServerAndLoad> result = ArrayListMultimap
393        .create();
394    result.putAll(previousLoad);
395    if (plans != null) {
396      for (RegionPlan plan : plans) {
397        ServerName source = plan.getSource();
398        updateLoad(result, source, -1);
399        ServerName destination = plan.getDestination();
400        updateLoad(result, destination, +1);
401      }
402    }
403    return result;
404  }
405
406  private void updateLoad(
407      ArrayListMultimap<String, ServerAndLoad> previousLoad,
408      final ServerName sn, final int diff) {
409    for (String groupName : previousLoad.keySet()) {
410      ServerAndLoad newSAL = null;
411      ServerAndLoad oldSAL = null;
412      for (ServerAndLoad sal : previousLoad.get(groupName)) {
413        if (ServerName.isSameAddress(sn, sal.getServerName())) {
414          oldSAL = sal;
415          newSAL = new ServerAndLoad(sn, sal.getLoad() + diff);
416          break;
417        }
418      }
419      if (newSAL != null) {
420        previousLoad.remove(groupName, oldSAL);
421        previousLoad.put(groupName, newSAL);
422        break;
423      }
424    }
425  }
426
427  private Map<ServerName, List<RegionInfo>> mockClusterServers() throws IOException {
428    assertTrue(servers.size() == regionAssignment.length);
429    Map<ServerName, List<RegionInfo>> assignment = new TreeMap<>();
430    for (int i = 0; i < servers.size(); i++) {
431      int numRegions = regionAssignment[i];
432      List<RegionInfo> regions = assignedRegions(numRegions, servers.get(i));
433      assignment.put(servers.get(i), regions);
434    }
435    return assignment;
436  }
437
438  /**
439   * Generate a list of regions evenly distributed between the tables.
440   *
441   * @param numRegions The number of regions to be generated.
442   * @return List of RegionInfo.
443   */
444  private List<RegionInfo> randomRegions(int numRegions) {
445    List<RegionInfo> regions = new ArrayList<>(numRegions);
446    byte[] start = new byte[16];
447    byte[] end = new byte[16];
448    rand.nextBytes(start);
449    rand.nextBytes(end);
450    int regionIdx = rand.nextInt(tables.length);
451    for (int i = 0; i < numRegions; i++) {
452      Bytes.putInt(start, 0, numRegions << 1);
453      Bytes.putInt(end, 0, (numRegions << 1) + 1);
454      int tableIndex = (i + regionIdx) % tables.length;
455      regions.add(RegionInfoBuilder.newBuilder(tables[tableIndex])
456          .setStartKey(start)
457          .setEndKey(end)
458          .setSplit(false)
459          .setRegionId(regionId++)
460          .build());
461    }
462    return regions;
463  }
464
465  /**
466   * Generate assigned regions to a given server using group information.
467   *
468   * @param numRegions the num regions to generate
469   * @param sn the servername
470   * @return the list of regions
471   * @throws java.io.IOException Signals that an I/O exception has occurred.
472   */
473  private List<RegionInfo> assignedRegions(int numRegions, ServerName sn) throws IOException {
474    List<RegionInfo> regions = new ArrayList<>(numRegions);
475    byte[] start = new byte[16];
476    byte[] end = new byte[16];
477    Bytes.putInt(start, 0, numRegions << 1);
478    Bytes.putInt(end, 0, (numRegions << 1) + 1);
479    for (int i = 0; i < numRegions; i++) {
480      TableName tableName = getTableName(sn);
481      regions.add(RegionInfoBuilder.newBuilder(tableName)
482          .setStartKey(start)
483          .setEndKey(end)
484          .setSplit(false)
485          .setRegionId(regionId++)
486          .build());
487    }
488    return regions;
489  }
490
491  private static List<ServerName> generateServers(int numServers) {
492    List<ServerName> servers = new ArrayList<>(numServers);
493    for (int i = 0; i < numServers; i++) {
494      String host = "server" + rand.nextInt(100000);
495      int port = rand.nextInt(60000);
496      servers.add(ServerName.valueOf(host, port, -1));
497    }
498    return servers;
499  }
500
501  /**
502   * Construct group info, with each group having at least one server.
503   *
504   * @param servers the servers
505   * @param groups the groups
506   * @return the map
507   */
508  private static Map<String, RSGroupInfo> constructGroupInfo(
509      List<ServerName> servers, String[] groups) {
510    assertTrue(servers != null);
511    assertTrue(servers.size() >= groups.length);
512    int index = 0;
513    Map<String, RSGroupInfo> groupMap = new HashMap<>();
514    for (String grpName : groups) {
515      RSGroupInfo RSGroupInfo = new RSGroupInfo(grpName);
516      RSGroupInfo.addServer(servers.get(index).getAddress());
517      groupMap.put(grpName, RSGroupInfo);
518      index++;
519    }
520    while (index < servers.size()) {
521      int grpIndex = rand.nextInt(groups.length);
522      groupMap.get(groups[grpIndex]).addServer(
523          servers.get(index).getAddress());
524      index++;
525    }
526    return groupMap;
527  }
528
529  /**
530   * Construct table descriptors evenly distributed between the groups.
531   *
532   * @return the list
533   */
534  private static List<HTableDescriptor> constructTableDesc() {
535    List<HTableDescriptor> tds = Lists.newArrayList();
536    int index = rand.nextInt(groups.length);
537    for (int i = 0; i < tables.length; i++) {
538      HTableDescriptor htd = new HTableDescriptor(tables[i]);
539      int grpIndex = (i + index) % groups.length ;
540      String groupName = groups[grpIndex];
541      tableMap.put(tables[i], groupName);
542      tds.add(htd);
543    }
544    tableMap.put(table0, "");
545    tds.add(new HTableDescriptor(table0));
546    return tds;
547  }
548
549  private static MasterServices getMockedMaster() throws IOException {
550    TableDescriptors tds = Mockito.mock(TableDescriptors.class);
551    Mockito.when(tds.get(tables[0])).thenReturn(tableDescs.get(0));
552    Mockito.when(tds.get(tables[1])).thenReturn(tableDescs.get(1));
553    Mockito.when(tds.get(tables[2])).thenReturn(tableDescs.get(2));
554    Mockito.when(tds.get(tables[3])).thenReturn(tableDescs.get(3));
555    MasterServices services = Mockito.mock(HMaster.class);
556    Mockito.when(services.getTableDescriptors()).thenReturn(tds);
557    AssignmentManager am = Mockito.mock(AssignmentManager.class);
558    Mockito.when(services.getAssignmentManager()).thenReturn(am);
559    RegionStates rss = Mockito.mock(RegionStates.class);
560    Mockito.when(am.getRegionStates()).thenReturn(rss);
561    return services;
562  }
563
564  private static RSGroupInfoManager getMockedGroupInfoManager() throws IOException {
565    RSGroupInfoManager gm = Mockito.mock(RSGroupInfoManager.class);
566    Mockito.when(gm.getRSGroup(groups[0])).thenReturn(
567        groupMap.get(groups[0]));
568    Mockito.when(gm.getRSGroup(groups[1])).thenReturn(
569        groupMap.get(groups[1]));
570    Mockito.when(gm.getRSGroup(groups[2])).thenReturn(
571        groupMap.get(groups[2]));
572    Mockito.when(gm.getRSGroup(groups[3])).thenReturn(
573        groupMap.get(groups[3]));
574    Mockito.when(gm.listRSGroups()).thenReturn(
575        Lists.newLinkedList(groupMap.values()));
576    Mockito.when(gm.isOnline()).thenReturn(true);
577    Mockito.when(gm.getRSGroupOfTable(Mockito.any()))
578        .thenAnswer(new Answer<String>() {
579          @Override
580          public String answer(InvocationOnMock invocation) throws Throwable {
581            return tableMap.get(invocation.getArgument(0));
582          }
583        });
584    return gm;
585  }
586
587  private TableName getTableName(ServerName sn) throws IOException {
588    TableName tableName = null;
589    RSGroupInfoManager gm = getMockedGroupInfoManager();
590    RSGroupInfo groupOfServer = null;
591    for(RSGroupInfo gInfo : gm.listRSGroups()){
592      if(gInfo.containsServer(sn.getAddress())){
593        groupOfServer = gInfo;
594        break;
595      }
596    }
597
598    for(HTableDescriptor desc : tableDescs){
599      if(gm.getRSGroupOfTable(desc.getTableName()).endsWith(groupOfServer.getName())){
600        tableName = desc.getTableName();
601      }
602    }
603    return tableName;
604  }
605}