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;
019
020import java.text.DecimalFormat;
021import java.util.ArrayList;
022import java.util.HashMap;
023import java.util.HashSet;
024import java.util.List;
025import java.util.Map;
026import java.util.Set;
027import org.apache.hadoop.hbase.ServerName;
028import org.apache.hadoop.hbase.TableName;
029import org.apache.hadoop.hbase.client.RegionInfo;
030import org.apache.hadoop.hbase.favored.FavoredNodeAssignmentHelper;
031import org.apache.hadoop.hbase.favored.FavoredNodesPlan;
032import org.apache.yetus.audience.InterfaceAudience;
033import org.slf4j.Logger;
034import org.slf4j.LoggerFactory;
035
036/**
037 * Helper class that is used by {@link RegionPlacementMaintainer} to print information for favored
038 * nodes
039 */
040@InterfaceAudience.Private
041public class AssignmentVerificationReport {
042  private static final Logger LOG =
043    LoggerFactory.getLogger(AssignmentVerificationReport.class.getName());
044
045  private TableName tableName = null;
046  private boolean enforceLocality = false;
047  private boolean isFilledUp = false;
048
049  private int totalRegions = 0;
050  private int totalRegionServers = 0;
051  // for unassigned regions
052  private List<RegionInfo> unAssignedRegionsList = new ArrayList<>();
053
054  // For regions without valid favored nodes
055  private List<RegionInfo> regionsWithoutValidFavoredNodes = new ArrayList<>();
056
057  // For regions not running on the favored nodes
058  private List<RegionInfo> nonFavoredAssignedRegionList = new ArrayList<>();
059
060  // For regions running on the favored nodes
061  private int totalFavoredAssignments = 0;
062  private int[] favoredNodes = new int[FavoredNodeAssignmentHelper.FAVORED_NODES_NUM];
063  private float[] favoredNodesLocalitySummary =
064    new float[FavoredNodeAssignmentHelper.FAVORED_NODES_NUM];
065  private float actualLocalitySummary = 0;
066
067  // For region balancing information
068  private float avgRegionsOnRS = 0;
069  private int maxRegionsOnRS = 0;
070  private int minRegionsOnRS = Integer.MAX_VALUE;
071  private Set<ServerName> mostLoadedRSSet = new HashSet<>();
072  private Set<ServerName> leastLoadedRSSet = new HashSet<>();
073
074  private float avgDispersionScore = 0;
075  private float maxDispersionScore = 0;
076  private Set<ServerName> maxDispersionScoreServerSet = new HashSet<>();
077  private float minDispersionScore = Float.MAX_VALUE;
078  private Set<ServerName> minDispersionScoreServerSet = new HashSet<>();
079
080  private float avgDispersionNum = 0;
081  private float maxDispersionNum = 0;
082  private Set<ServerName> maxDispersionNumServerSet = new HashSet<>();
083  private float minDispersionNum = Float.MAX_VALUE;
084  private Set<ServerName> minDispersionNumServerSet = new HashSet<>();
085
086  public void fillUp(TableName tableName, SnapshotOfRegionAssignmentFromMeta snapshot,
087    Map<String, Map<String, Float>> regionLocalityMap) {
088    // Set the table name
089    this.tableName = tableName;
090
091    // Get all the regions for this table
092    List<RegionInfo> regionInfoList = snapshot.getTableToRegionMap().get(tableName);
093    // Get the total region num for the current table
094    this.totalRegions = regionInfoList.size();
095
096    // Get the existing assignment plan
097    FavoredNodesPlan favoredNodesAssignment = snapshot.getExistingAssignmentPlan();
098    // Get the region to region server mapping
099    Map<RegionInfo, ServerName> currentAssignment = snapshot.getRegionToRegionServerMap();
100    // Initialize the server to its hosing region counter map
101    Map<ServerName, Integer> serverToHostingRegionCounterMap = new HashMap<>();
102
103    Map<ServerName, Integer> primaryRSToRegionCounterMap = new HashMap<>();
104    Map<ServerName, Set<ServerName>> primaryToSecTerRSMap = new HashMap<>();
105
106    // Check the favored nodes and its locality information
107    // Also keep tracker of the most loaded and least loaded region servers
108    for (RegionInfo region : regionInfoList) {
109      try {
110        ServerName currentRS = currentAssignment.get(region);
111        // Handle unassigned regions
112        if (currentRS == null) {
113          unAssignedRegionsList.add(region);
114          continue;
115        }
116
117        // Keep updating the server to is hosting region counter map
118        Integer hostRegionCounter = serverToHostingRegionCounterMap.get(currentRS);
119        if (hostRegionCounter == null) {
120          hostRegionCounter = Integer.valueOf(0);
121        }
122        hostRegionCounter = hostRegionCounter.intValue() + 1;
123        serverToHostingRegionCounterMap.put(currentRS, hostRegionCounter);
124
125        // Get the favored nodes from the assignment plan and verify it.
126        List<ServerName> favoredNodes = favoredNodesAssignment.getFavoredNodes(region);
127        if (
128          favoredNodes == null
129            || favoredNodes.size() != FavoredNodeAssignmentHelper.FAVORED_NODES_NUM
130        ) {
131          regionsWithoutValidFavoredNodes.add(region);
132          continue;
133        }
134        // Get the primary, secondary and tertiary region server
135        ServerName primaryRS = favoredNodes.get(FavoredNodesPlan.Position.PRIMARY.ordinal());
136        ServerName secondaryRS = favoredNodes.get(FavoredNodesPlan.Position.SECONDARY.ordinal());
137        ServerName tertiaryRS = favoredNodes.get(FavoredNodesPlan.Position.TERTIARY.ordinal());
138
139        // Update the primary rs to its region set map
140        Integer regionCounter = primaryRSToRegionCounterMap.get(primaryRS);
141        if (regionCounter == null) {
142          regionCounter = Integer.valueOf(0);
143        }
144        regionCounter = regionCounter.intValue() + 1;
145        primaryRSToRegionCounterMap.put(primaryRS, regionCounter);
146
147        // Update the primary rs to secondary and tertiary rs map
148        Set<ServerName> secAndTerSet = primaryToSecTerRSMap.get(primaryRS);
149        if (secAndTerSet == null) {
150          secAndTerSet = new HashSet<>();
151        }
152        secAndTerSet.add(secondaryRS);
153        secAndTerSet.add(tertiaryRS);
154        primaryToSecTerRSMap.put(primaryRS, secAndTerSet);
155
156        // Get the position of the current region server in the favored nodes list
157        FavoredNodesPlan.Position favoredNodePosition =
158          FavoredNodesPlan.getFavoredServerPosition(favoredNodes, currentRS);
159
160        // Handle the non favored assignment.
161        if (favoredNodePosition == null) {
162          nonFavoredAssignedRegionList.add(region);
163          continue;
164        }
165        // Increase the favored nodes assignment.
166        this.favoredNodes[favoredNodePosition.ordinal()]++;
167        totalFavoredAssignments++;
168
169        // Summary the locality information for each favored nodes
170        if (regionLocalityMap != null) {
171          // Set the enforce locality as true;
172          this.enforceLocality = true;
173
174          // Get the region degree locality map
175          Map<String, Float> regionDegreeLocalityMap =
176            regionLocalityMap.get(region.getEncodedName());
177          if (regionDegreeLocalityMap == null) {
178            continue; // ignore the region which doesn't have any store files.
179          }
180
181          // Get the locality summary for each favored nodes
182          for (FavoredNodesPlan.Position p : FavoredNodesPlan.Position.values()) {
183            ServerName favoredNode = favoredNodes.get(p.ordinal());
184            // Get the locality for the current favored nodes
185            Float locality = regionDegreeLocalityMap.get(favoredNode.getHostname());
186            if (locality != null) {
187              this.favoredNodesLocalitySummary[p.ordinal()] += locality;
188            }
189          }
190
191          // Get the locality summary for the current region server
192          Float actualLocality = regionDegreeLocalityMap.get(currentRS.getHostname());
193          if (actualLocality != null) {
194            this.actualLocalitySummary += actualLocality;
195          }
196        }
197      } catch (Exception e) {
198        LOG.error("Cannot verify the region assignment for region "
199          + ((region == null) ? " null " : region.getRegionNameAsString()) + "because of " + e);
200      }
201    }
202
203    float dispersionScoreSummary = 0;
204    float dispersionNumSummary = 0;
205    // Calculate the secondary score for each primary region server
206    for (Map.Entry<ServerName, Integer> entry : primaryRSToRegionCounterMap.entrySet()) {
207      ServerName primaryRS = entry.getKey();
208      Integer regionsOnPrimary = entry.getValue();
209
210      // Process the dispersion number and score
211      float dispersionScore = 0;
212      int dispersionNum = 0;
213      if (primaryToSecTerRSMap.get(primaryRS) != null && regionsOnPrimary.intValue() != 0) {
214        dispersionNum = primaryToSecTerRSMap.get(primaryRS).size();
215        dispersionScore = dispersionNum / ((float) regionsOnPrimary.intValue() * 2);
216      }
217      // Update the max dispersion score
218      if (dispersionScore > this.maxDispersionScore) {
219        this.maxDispersionScoreServerSet.clear();
220        this.maxDispersionScoreServerSet.add(primaryRS);
221        this.maxDispersionScore = dispersionScore;
222      } else if (dispersionScore == this.maxDispersionScore) {
223        this.maxDispersionScoreServerSet.add(primaryRS);
224      }
225
226      // Update the max dispersion num
227      if (dispersionNum > this.maxDispersionNum) {
228        this.maxDispersionNumServerSet.clear();
229        this.maxDispersionNumServerSet.add(primaryRS);
230        this.maxDispersionNum = dispersionNum;
231      } else if (dispersionNum == this.maxDispersionNum) {
232        this.maxDispersionNumServerSet.add(primaryRS);
233      }
234
235      // Update the min dispersion score
236      if (dispersionScore < this.minDispersionScore) {
237        this.minDispersionScoreServerSet.clear();
238        this.minDispersionScoreServerSet.add(primaryRS);
239        this.minDispersionScore = dispersionScore;
240      } else if (dispersionScore == this.minDispersionScore) {
241        this.minDispersionScoreServerSet.add(primaryRS);
242      }
243
244      // Update the min dispersion num
245      if (dispersionNum < this.minDispersionNum) {
246        this.minDispersionNumServerSet.clear();
247        this.minDispersionNumServerSet.add(primaryRS);
248        this.minDispersionNum = dispersionNum;
249      } else if (dispersionNum == this.minDispersionNum) {
250        this.minDispersionNumServerSet.add(primaryRS);
251      }
252
253      dispersionScoreSummary += dispersionScore;
254      dispersionNumSummary += dispersionNum;
255    }
256
257    // Update the avg dispersion score
258    if (primaryRSToRegionCounterMap.keySet().size() != 0) {
259      this.avgDispersionScore =
260        dispersionScoreSummary / (float) primaryRSToRegionCounterMap.keySet().size();
261      this.avgDispersionNum =
262        dispersionNumSummary / (float) primaryRSToRegionCounterMap.keySet().size();
263    }
264
265    // Fill up the most loaded and least loaded region server information
266    for (Map.Entry<ServerName, Integer> entry : serverToHostingRegionCounterMap.entrySet()) {
267      ServerName currentRS = entry.getKey();
268      int hostRegionCounter = entry.getValue().intValue();
269
270      // Update the most loaded region server list and maxRegionsOnRS
271      if (hostRegionCounter > this.maxRegionsOnRS) {
272        maxRegionsOnRS = hostRegionCounter;
273        this.mostLoadedRSSet.clear();
274        this.mostLoadedRSSet.add(currentRS);
275      } else if (hostRegionCounter == this.maxRegionsOnRS) {
276        this.mostLoadedRSSet.add(currentRS);
277      }
278
279      // Update the least loaded region server list and minRegionsOnRS
280      if (hostRegionCounter < this.minRegionsOnRS) {
281        this.minRegionsOnRS = hostRegionCounter;
282        this.leastLoadedRSSet.clear();
283        this.leastLoadedRSSet.add(currentRS);
284      } else if (hostRegionCounter == this.minRegionsOnRS) {
285        this.leastLoadedRSSet.add(currentRS);
286      }
287    }
288
289    // and total region servers
290    this.totalRegionServers = serverToHostingRegionCounterMap.keySet().size();
291    this.avgRegionsOnRS =
292      (totalRegionServers == 0) ? 0 : (totalRegions / (float) totalRegionServers);
293    // Set the isFilledUp as true
294    isFilledUp = true;
295  }
296
297  /**
298   * Use this to project the dispersion scores
299   */
300  public void fillUpDispersion(TableName tableName, SnapshotOfRegionAssignmentFromMeta snapshot,
301    FavoredNodesPlan newPlan) {
302    // Set the table name
303    this.tableName = tableName;
304    // Get all the regions for this table
305    List<RegionInfo> regionInfoList = snapshot.getTableToRegionMap().get(tableName);
306    // Get the total region num for the current table
307    this.totalRegions = regionInfoList.size();
308    FavoredNodesPlan plan = null;
309    if (newPlan == null) {
310      plan = snapshot.getExistingAssignmentPlan();
311    } else {
312      plan = newPlan;
313    }
314    // Get the region to region server mapping
315    Map<ServerName, Integer> primaryRSToRegionCounterMap = new HashMap<>();
316    Map<ServerName, Set<ServerName>> primaryToSecTerRSMap = new HashMap<>();
317
318    // Check the favored nodes and its locality information
319    // Also keep tracker of the most loaded and least loaded region servers
320    for (RegionInfo region : regionInfoList) {
321      try {
322        // Get the favored nodes from the assignment plan and verify it.
323        List<ServerName> favoredNodes = plan.getFavoredNodes(region);
324        if (
325          favoredNodes == null
326            || favoredNodes.size() != FavoredNodeAssignmentHelper.FAVORED_NODES_NUM
327        ) {
328          regionsWithoutValidFavoredNodes.add(region);
329          continue;
330        }
331        // Get the primary, secondary and tertiary region server
332        ServerName primaryRS = favoredNodes.get(FavoredNodesPlan.Position.PRIMARY.ordinal());
333        ServerName secondaryRS = favoredNodes.get(FavoredNodesPlan.Position.SECONDARY.ordinal());
334        ServerName tertiaryRS = favoredNodes.get(FavoredNodesPlan.Position.TERTIARY.ordinal());
335
336        // Update the primary rs to its region set map
337        Integer regionCounter = primaryRSToRegionCounterMap.get(primaryRS);
338        if (regionCounter == null) {
339          regionCounter = Integer.valueOf(0);
340        }
341        regionCounter = regionCounter.intValue() + 1;
342        primaryRSToRegionCounterMap.put(primaryRS, regionCounter);
343
344        // Update the primary rs to secondary and tertiary rs map
345        Set<ServerName> secAndTerSet = primaryToSecTerRSMap.get(primaryRS);
346        if (secAndTerSet == null) {
347          secAndTerSet = new HashSet<>();
348        }
349        secAndTerSet.add(secondaryRS);
350        secAndTerSet.add(tertiaryRS);
351        primaryToSecTerRSMap.put(primaryRS, secAndTerSet);
352      } catch (Exception e) {
353        LOG.error("Cannot verify the region assignment for region "
354          + ((region == null) ? " null " : region.getRegionNameAsString()) + "because of " + e);
355      }
356    }
357    float dispersionScoreSummary = 0;
358    float dispersionNumSummary = 0;
359    // Calculate the secondary score for each primary region server
360    for (Map.Entry<ServerName, Integer> entry : primaryRSToRegionCounterMap.entrySet()) {
361      ServerName primaryRS = entry.getKey();
362      Integer regionsOnPrimary = entry.getValue();
363
364      // Process the dispersion number and score
365      float dispersionScore = 0;
366      int dispersionNum = 0;
367      if (primaryToSecTerRSMap.get(primaryRS) != null && regionsOnPrimary.intValue() != 0) {
368        dispersionNum = primaryToSecTerRSMap.get(primaryRS).size();
369        dispersionScore = dispersionNum / ((float) regionsOnPrimary.intValue() * 2);
370      }
371
372      // Update the max dispersion num
373      if (dispersionNum > this.maxDispersionNum) {
374        this.maxDispersionNumServerSet.clear();
375        this.maxDispersionNumServerSet.add(primaryRS);
376        this.maxDispersionNum = dispersionNum;
377      } else if (dispersionNum == this.maxDispersionNum) {
378        this.maxDispersionNumServerSet.add(primaryRS);
379      }
380
381      // Update the min dispersion score
382      if (dispersionScore < this.minDispersionScore) {
383        this.minDispersionScoreServerSet.clear();
384        this.minDispersionScoreServerSet.add(primaryRS);
385        this.minDispersionScore = dispersionScore;
386      } else if (dispersionScore == this.minDispersionScore) {
387        this.minDispersionScoreServerSet.add(primaryRS);
388      }
389
390      // Update the min dispersion num
391      if (dispersionNum < this.minDispersionNum) {
392        this.minDispersionNumServerSet.clear();
393        this.minDispersionNumServerSet.add(primaryRS);
394        this.minDispersionNum = dispersionNum;
395      } else if (dispersionNum == this.minDispersionNum) {
396        this.minDispersionNumServerSet.add(primaryRS);
397      }
398
399      dispersionScoreSummary += dispersionScore;
400      dispersionNumSummary += dispersionNum;
401    }
402
403    // Update the avg dispersion score
404    if (primaryRSToRegionCounterMap.keySet().size() != 0) {
405      this.avgDispersionScore =
406        dispersionScoreSummary / (float) primaryRSToRegionCounterMap.keySet().size();
407      this.avgDispersionNum =
408        dispersionNumSummary / (float) primaryRSToRegionCounterMap.keySet().size();
409    }
410  }
411
412  /**
413   * Return a list which contains 3 elements: average dispersion score, max dispersion score and min
414   * dispersion score as first, second and third elements, respectively.
415   */
416  public List<Float> getDispersionInformation() {
417    List<Float> dispersion = new ArrayList<>();
418    dispersion.add(avgDispersionScore);
419    dispersion.add(maxDispersionScore);
420    dispersion.add(minDispersionScore);
421    return dispersion;
422  }
423
424  public void print(boolean isDetailMode) {
425    if (!isFilledUp) {
426      System.err.println("[Error] Region assignment verification report" + "hasn't been filled up");
427    }
428    DecimalFormat df = new java.text.DecimalFormat("#.##");
429
430    // Print some basic information
431    System.out.println("Region Assignment Verification for Table: " + tableName
432      + "\n\tTotal regions : " + totalRegions);
433
434    // Print the number of regions on each kinds of the favored nodes
435    System.out.println("\tTotal regions on favored nodes " + totalFavoredAssignments);
436    for (FavoredNodesPlan.Position p : FavoredNodesPlan.Position.values()) {
437      System.out.println(
438        "\t\tTotal regions on " + p.toString() + " region servers: " + favoredNodes[p.ordinal()]);
439    }
440    // Print the number of regions in each kinds of invalid assignment
441    System.out.println("\tTotal unassigned regions: " + unAssignedRegionsList.size());
442    if (isDetailMode) {
443      for (RegionInfo region : unAssignedRegionsList) {
444        System.out.println("\t\t" + region.getRegionNameAsString());
445      }
446    }
447
448    System.out
449      .println("\tTotal regions NOT on favored nodes: " + nonFavoredAssignedRegionList.size());
450    if (isDetailMode) {
451      for (RegionInfo region : nonFavoredAssignedRegionList) {
452        System.out.println("\t\t" + region.getRegionNameAsString());
453      }
454    }
455
456    System.out
457      .println("\tTotal regions without favored nodes: " + regionsWithoutValidFavoredNodes.size());
458    if (isDetailMode) {
459      for (RegionInfo region : regionsWithoutValidFavoredNodes) {
460        System.out.println("\t\t" + region.getRegionNameAsString());
461      }
462    }
463
464    // Print the locality information if enabled
465    if (this.enforceLocality && totalRegions != 0) {
466      // Print the actual locality for this table
467      float actualLocality = 100 * this.actualLocalitySummary / (float) totalRegions;
468      System.out.println("\n\tThe actual avg locality is " + df.format(actualLocality) + " %");
469
470      // Print the expected locality if regions are placed on the each kinds of
471      // favored nodes
472      for (FavoredNodesPlan.Position p : FavoredNodesPlan.Position.values()) {
473        float avgLocality = 100 * (favoredNodesLocalitySummary[p.ordinal()] / (float) totalRegions);
474        System.out.println("\t\tThe expected avg locality if all regions" + " on the "
475          + p.toString() + " region servers: " + df.format(avgLocality) + " %");
476      }
477    }
478
479    // Print the region balancing information
480    System.out.println("\n\tTotal hosting region servers: " + totalRegionServers);
481    // Print the region balance information
482    if (totalRegionServers != 0) {
483      System.out.println("\tAvg dispersion num: " + df.format(avgDispersionNum)
484        + " hosts;\tMax dispersion num: " + df.format(maxDispersionNum)
485        + " hosts;\tMin dispersion num: " + df.format(minDispersionNum) + " hosts;");
486
487      System.out.println("\t\tThe number of the region servers with the max" + " dispersion num: "
488        + this.maxDispersionNumServerSet.size());
489      if (isDetailMode) {
490        printHServerAddressSet(maxDispersionNumServerSet);
491      }
492
493      System.out.println("\t\tThe number of the region servers with the min" + " dispersion num: "
494        + this.minDispersionNumServerSet.size());
495      if (isDetailMode) {
496        printHServerAddressSet(maxDispersionNumServerSet);
497      }
498
499      System.out.println("\tAvg dispersion score: " + df.format(avgDispersionScore)
500        + ";\tMax dispersion score: " + df.format(maxDispersionScore) + ";\tMin dispersion score: "
501        + df.format(minDispersionScore) + ";");
502
503      System.out.println("\t\tThe number of the region servers with the max" + " dispersion score: "
504        + this.maxDispersionScoreServerSet.size());
505      if (isDetailMode) {
506        printHServerAddressSet(maxDispersionScoreServerSet);
507      }
508
509      System.out.println("\t\tThe number of the region servers with the min" + " dispersion score: "
510        + this.minDispersionScoreServerSet.size());
511      if (isDetailMode) {
512        printHServerAddressSet(minDispersionScoreServerSet);
513      }
514
515      System.out.println("\tAvg regions/region server: " + df.format(avgRegionsOnRS)
516        + ";\tMax regions/region server: " + maxRegionsOnRS + ";\tMin regions/region server: "
517        + minRegionsOnRS + ";");
518
519      // Print the details about the most loaded region servers
520      System.out
521        .println("\t\tThe number of the most loaded region servers: " + mostLoadedRSSet.size());
522      if (isDetailMode) {
523        printHServerAddressSet(mostLoadedRSSet);
524      }
525
526      // Print the details about the least loaded region servers
527      System.out
528        .println("\t\tThe number of the least loaded region servers: " + leastLoadedRSSet.size());
529      if (isDetailMode) {
530        printHServerAddressSet(leastLoadedRSSet);
531      }
532    }
533    System.out.println("==============================");
534  }
535
536  /**
537   * Return the unassigned regions
538   * @return unassigned regions
539   */
540  List<RegionInfo> getUnassignedRegions() {
541    return unAssignedRegionsList;
542  }
543
544  /**
545   * Return the regions without favored nodes
546   * @return regions without favored nodes
547   */
548  List<RegionInfo> getRegionsWithoutValidFavoredNodes() {
549    return regionsWithoutValidFavoredNodes;
550  }
551
552  /**
553   * Return the regions not assigned to its favored nodes
554   * @return regions not assigned to its favored nodes
555   */
556  List<RegionInfo> getNonFavoredAssignedRegions() {
557    return nonFavoredAssignedRegionList;
558  }
559
560  /**
561   * Return the number of regions assigned to their favored nodes
562   * @return number of regions assigned to their favored nodes
563   */
564  int getTotalFavoredAssignments() {
565    return totalFavoredAssignments;
566  }
567
568  /**
569   * Return the number of regions based on the position (primary/secondary/ tertiary) assigned to
570   * their favored nodes
571   * @return the number of regions
572   */
573  int getNumRegionsOnFavoredNodeByPosition(FavoredNodesPlan.Position position) {
574    return favoredNodes[position.ordinal()];
575  }
576
577  private void printHServerAddressSet(Set<ServerName> serverSet) {
578    if (serverSet == null) {
579      return;
580    }
581    int i = 0;
582    for (ServerName addr : serverSet) {
583      if (i++ % 3 == 0) {
584        System.out.print("\n\t\t\t");
585      }
586      System.out.print(addr.getAddress() + " ; ");
587    }
588    System.out.println("\n");
589  }
590}