blob: 579e5f71c79130e848e031d66c94cf1f53ba6be8 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hdds.scm.net;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.TreeMap;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;
import org.apache.hadoop.conf.Configuration;
import static org.apache.hadoop.hdds.scm.net.NetConstants.ROOT;
import static org.apache.hadoop.hdds.scm.net.NetConstants.SCOPE_REVERSE_STR;
import static org.apache.hadoop.hdds.scm.net.NetConstants.ANCESTOR_GENERATION_DEFAULT;
/**
* The class represents a cluster of computers with a tree hierarchical
* network topology. In the network topology, leaves represent data nodes
* (computers) and inner nodes represent datacenter/core-switches/routers that
* manages traffic in/out of data centers or racks.
*/
public class NetworkTopologyImpl implements NetworkTopology{
public static final Logger LOG =
LoggerFactory.getLogger(NetworkTopology.class);
/** The Inner node crate factory. */
private final InnerNode.Factory factory;
/** The root cluster tree. */
private final InnerNode clusterTree;
/** Depth of all leaf nodes. */
private final int maxLevel;
/** Schema manager. */
private final NodeSchemaManager schemaManager;
/** Lock to coordinate cluster tree access. */
private ReadWriteLock netlock = new ReentrantReadWriteLock(true);
public NetworkTopologyImpl(Configuration conf) {
schemaManager = NodeSchemaManager.getInstance();
schemaManager.init(conf);
maxLevel = schemaManager.getMaxLevel();
factory = InnerNodeImpl.FACTORY;
clusterTree = factory.newInnerNode(ROOT, null, null,
NetConstants.ROOT_LEVEL,
schemaManager.getCost(NetConstants.ROOT_LEVEL));
}
@VisibleForTesting
public NetworkTopologyImpl(NodeSchemaManager manager) {
schemaManager = manager;
maxLevel = schemaManager.getMaxLevel();
factory = InnerNodeImpl.FACTORY;
clusterTree = factory.newInnerNode(ROOT, null, null,
NetConstants.ROOT_LEVEL,
schemaManager.getCost(NetConstants.ROOT_LEVEL));
}
/**
* Add a leaf node. This will be called when a new datanode is added.
* @param node node to be added; can be null
* @exception IllegalArgumentException if add a node to a leave or node to be
* added is not a leaf
*/
public void add(Node node) {
Preconditions.checkArgument(node != null, "node cannot be null");
if (node instanceof InnerNode) {
throw new IllegalArgumentException(
"Not allowed to add an inner node: "+ node.getNetworkFullPath());
}
int newDepth = NetUtils.locationToDepth(node.getNetworkLocation()) + 1;
// Check depth
if (maxLevel != newDepth) {
throw new InvalidTopologyException("Failed to add " +
node.getNetworkFullPath() + ": Its path depth is not " + maxLevel);
}
netlock.writeLock().lock();
boolean add;
try {
add = clusterTree.add(node);
}finally {
netlock.writeLock().unlock();
}
if (add) {
LOG.info("Added a new node: " + node.getNetworkFullPath());
if (LOG.isDebugEnabled()) {
LOG.debug("NetworkTopology became:\n{}", this);
}
}
}
/**
* Remove a node from the network topology. This will be called when a
* existing datanode is removed from the system.
* @param node node to be removed; cannot be null
*/
public void remove(Node node) {
Preconditions.checkArgument(node != null, "node cannot be null");
if (node instanceof InnerNode) {
throw new IllegalArgumentException(
"Not allowed to remove an inner node: "+ node.getNetworkFullPath());
}
netlock.writeLock().lock();
try {
clusterTree.remove(node);
}finally {
netlock.writeLock().unlock();
}
LOG.info("Removed a node: " + node.getNetworkFullPath());
if (LOG.isDebugEnabled()) {
LOG.debug("NetworkTopology became:\n{}", this);
}
}
/**
* Check if the tree already contains node <i>node</i>.
* @param node a node
* @return true if <i>node</i> is already in the tree; false otherwise
*/
public boolean contains(Node node) {
Preconditions.checkArgument(node != null, "node cannot be null");
netlock.readLock().lock();
try {
Node parent = node.getParent();
while (parent != null && parent != clusterTree) {
parent = parent.getParent();
}
if (parent == clusterTree) {
return true;
}
} finally {
netlock.readLock().unlock();
}
return false;
}
/**
* Compare the specified ancestor generation of each node for equality.
* @return true if their specified generation ancestor are equal
*/
public boolean isSameAncestor(Node node1, Node node2, int ancestorGen) {
if (node1 == null || node2 == null || ancestorGen <= 0) {
return false;
}
netlock.readLock().lock();
try {
return node1.getAncestor(ancestorGen) == node2.getAncestor(ancestorGen);
} finally {
netlock.readLock().unlock();
}
}
/**
* Compare the direct parent of each node for equality.
* @return true if their parent are the same
*/
public boolean isSameParent(Node node1, Node node2) {
if (node1 == null || node2 == null) {
return false;
}
netlock.readLock().lock();
try {
node1 = node1.getParent();
node2 = node2.getParent();
return node1 == node2;
} finally {
netlock.readLock().unlock();
}
}
/**
* Get the ancestor for node on generation <i>ancestorGen</i>.
*
* @param node the node to get ancestor
* @param ancestorGen the ancestor generation
* @return the ancestor. If no ancestor is found, then null is returned.
*/
public Node getAncestor(Node node, int ancestorGen) {
if (node == null) {
return null;
}
netlock.readLock().lock();
try {
return node.getAncestor(ancestorGen);
} finally {
netlock.readLock().unlock();
}
}
/**
* Given a string representation of a node(leaf or inner), return its
* reference.
* @param loc a path string representing a node, can be leaf or inner node
* @return a reference to the node, null if the node is not in the tree
*/
public Node getNode(String loc) {
loc = NetUtils.normalize(loc);
netlock.readLock().lock();
try {
if (!ROOT.equals(loc)) {
return clusterTree.getNode(loc);
} else {
return clusterTree;
}
} finally {
netlock.readLock().unlock();
}
}
/**
* Given a string representation of Node, return its leaf nodes count.
* @param loc a path-like string representation of Node
* @return the number of leaf nodes for InnerNode, 1 for leaf node, 0 if node
* doesn't exist
*/
public int getNumOfLeafNode(String loc) {
netlock.readLock().lock();
try {
Node node = getNode(loc);
if (node != null) {
return node.getNumOfLeaves();
}
} finally {
netlock.readLock().unlock();
}
return 0;
}
/**
* Return the max level of this tree, start from 1 for ROOT. For example,
* topology like "/rack/node" has the max level '3'.
*/
public int getMaxLevel() {
return maxLevel;
}
/**
* Return the node numbers at level <i>level</i>.
* @param level topology level, start from 1, which means ROOT
* @return the number of nodes on the level
*/
public int getNumOfNodes(int level) {
Preconditions.checkArgument(level > 0 && level <= maxLevel,
"Invalid level");
netlock.readLock().lock();
try {
return clusterTree.getNumOfNodes(level);
} finally {
netlock.readLock().unlock();
}
}
/**
* Randomly choose a node in the scope.
* @param scope range of nodes from which a node will be chosen. If scope
* starts with ~, choose one from the all nodes except for the
* ones in <i>scope</i>; otherwise, choose one from <i>scope</i>.
* @return the chosen node
*/
public Node chooseRandom(String scope) {
if (scope == null) {
scope = ROOT;
}
if (scope.startsWith(SCOPE_REVERSE_STR)) {
ArrayList<String> excludedScopes = new ArrayList();
excludedScopes.add(scope.substring(1));
return chooseRandom(ROOT, excludedScopes, null, null,
ANCESTOR_GENERATION_DEFAULT);
} else {
return chooseRandom(scope, null, null, null, ANCESTOR_GENERATION_DEFAULT);
}
}
/**
* Randomly choose a node in the scope, ano not in the exclude scope.
* @param scope range of nodes from which a node will be chosen. cannot start
* with ~
* @param excludedScopes the chosen node cannot be in these ranges. cannot
* starts with ~
* @return the chosen node
*/
public Node chooseRandom(String scope, List<String> excludedScopes) {
return chooseRandom(scope, excludedScopes, null, null,
ANCESTOR_GENERATION_DEFAULT);
}
/**
* Randomly choose a leaf node from <i>scope</i>.
*
* If scope starts with ~, choose one from the all nodes except for the
* ones in <i>scope</i>; otherwise, choose nodes from <i>scope</i>.
* If excludedNodes is given, choose a node that's not in excludedNodes.
*
* @param scope range of nodes from which a node will be chosen
* @param excludedNodes nodes to be excluded
*
* @return the chosen node
*/
public Node chooseRandom(String scope, Collection<Node> excludedNodes) {
if (scope == null) {
scope = ROOT;
}
if (scope.startsWith(SCOPE_REVERSE_STR)) {
ArrayList<String> excludedScopes = new ArrayList();
excludedScopes.add(scope.substring(1));
return chooseRandom(ROOT, excludedScopes, excludedNodes, null,
ANCESTOR_GENERATION_DEFAULT);
} else {
return chooseRandom(scope, null, excludedNodes, null,
ANCESTOR_GENERATION_DEFAULT);
}
}
/**
* Randomly choose a leaf node from <i>scope</i>.
*
* If scope starts with ~, choose one from the all nodes except for the
* ones in <i>scope</i>; otherwise, choose nodes from <i>scope</i>.
* If excludedNodes is given, choose a node that's not in excludedNodes.
*
* @param scope range of nodes from which a node will be chosen
* @param excludedNodes nodes to be excluded from.
* @param ancestorGen matters when excludeNodes is not null. It means the
* ancestor generation that's not allowed to share between chosen node and the
* excludedNodes. For example, if ancestorGen is 1, means chosen node
* cannot share the same parent with excludeNodes. If value is 2, cannot
* share the same grand parent, and so on. If ancestorGen is 0, then no
* effect.
*
* @return the chosen node
*/
public Node chooseRandom(String scope, Collection<Node> excludedNodes,
int ancestorGen) {
if (scope == null) {
scope = ROOT;
}
if (scope.startsWith(SCOPE_REVERSE_STR)) {
ArrayList<String> excludedScopes = new ArrayList();
excludedScopes.add(scope.substring(1));
return chooseRandom(ROOT, excludedScopes, excludedNodes, null,
ancestorGen);
} else {
return chooseRandom(scope, null, excludedNodes, null, ancestorGen);
}
}
/**
* Randomly choose one leaf node from <i>scope</i>, share the same generation
* ancestor with <i>affinityNode</i>, and exclude nodes in
* <i>excludeScope</i> and <i>excludeNodes</i>.
*
* @param scope range of nodes from which a node will be chosen, cannot start
* with ~
* @param excludedScopes ranges of nodes to be excluded, cannot start with ~
* @param excludedNodes nodes to be excluded
* @param affinityNode when not null, the chosen node should share the same
* ancestor with this node at generation ancestorGen.
* Ignored when value is null
* @param ancestorGen If 0, then no same generation ancestor enforcement on
* both excludedNodes and affinityNode. If greater than 0,
* then apply to affinityNode(if not null), or apply to
* excludedNodes if affinityNode is null
* @return the chosen node
*/
public Node chooseRandom(String scope, List<String> excludedScopes,
Collection<Node> excludedNodes, Node affinityNode, int ancestorGen) {
if (scope == null) {
scope = ROOT;
}
checkScope(scope);
checkExcludedScopes(excludedScopes);
checkAffinityNode(affinityNode);
checkAncestorGen(ancestorGen);
netlock.readLock().lock();
try {
return chooseNodeInternal(scope, -1, excludedScopes,
excludedNodes, affinityNode, ancestorGen);
} finally {
netlock.readLock().unlock();
}
}
/**
* Choose the leaf node at index <i>index</i> from <i>scope</i>, share the
* same generation ancestor with <i>affinityNode</i>, and exclude nodes in
* <i>excludeScope</i> and <i>excludeNodes</i>.
*
* @param leafIndex node index, exclude nodes in excludedScope and
* excludedNodes
* @param scope range of nodes from which a node will be chosen, cannot start
* with ~
* @param excludedScopes ranges of nodes to be excluded, cannot start with ~
* @param excludedNodes nodes to be excluded
* @param affinityNode when not null, the chosen node should share the same
* ancestor with this node at generation ancestorGen.
* Ignored when value is null
* @param ancestorGen If 0, then no same generation ancestor enforcement on
* both excludedNodes and affinityNode. If greater than 0,
* then apply to affinityNode(if not null), or apply to
* excludedNodes if affinityNode is null
* @return the chosen node
* Example:
*
* / --- root
* / \
* / \
* / \
* / \
* dc1 dc2
* / \ / \
* / \ / \
* / \ / \
* rack1 rack2 rack1 rack2
* / \ / \ / \ / \
* n1 n2 n3 n4 n5 n6 n7 n8
*
* Input:
* leafIndex = 1
* excludedScope = /dc2
* excludedNodes = {/dc1/rack1/n1}
* affinityNode = /dc1/rack2/n2
* ancestorGen = 2
*
* Output:
* node /dc1/rack2/n4
*
* Explanation:
* With affinityNode n2 and ancestorGen 2, it means we can only pick node
* from subtree /dc1. LeafIndex 1, so we pick the 2nd available node n4.
*
*/
public Node getNode(int leafIndex, String scope, List<String> excludedScopes,
Collection<Node> excludedNodes, Node affinityNode, int ancestorGen) {
Preconditions.checkArgument(leafIndex >= 0);
if (scope == null) {
scope = ROOT;
}
checkScope(scope);
checkExcludedScopes(excludedScopes);
checkAffinityNode(affinityNode);
checkAncestorGen(ancestorGen);
netlock.readLock().lock();
try {
return chooseNodeInternal(scope, leafIndex, excludedScopes,
excludedNodes, affinityNode, ancestorGen);
} finally {
netlock.readLock().unlock();
}
}
private Node chooseNodeInternal(String scope, int leafIndex,
List<String> excludedScopes, Collection<Node> excludedNodes,
Node affinityNode, int ancestorGen) {
Preconditions.checkArgument(scope != null);
String finalScope = scope;
if (affinityNode != null && ancestorGen > 0) {
Node affinityAncestor = affinityNode.getAncestor(ancestorGen);
if (affinityAncestor == null) {
throw new IllegalArgumentException("affinityNode " +
affinityNode.getNetworkFullPath() + " doesn't have ancestor on" +
" generation " + ancestorGen);
}
// affinity ancestor should has overlap with scope
if (affinityAncestor.getNetworkFullPath().startsWith(scope)){
finalScope = affinityAncestor.getNetworkFullPath();
} else if (!scope.startsWith(affinityAncestor.getNetworkFullPath())) {
return null;
}
// reset ancestor generation since the new scope is identified now
ancestorGen = 0;
}
// check overlap of excludedScopes and finalScope
List<String> mutableExcludedScopes = null;
if (excludedScopes != null && !excludedScopes.isEmpty()) {
mutableExcludedScopes = new ArrayList<>();
for (String s: excludedScopes) {
// excludeScope covers finalScope
if (finalScope.startsWith(s)) {
return null;
}
// excludeScope and finalScope share nothing case
if (s.startsWith(finalScope)) {
if (!mutableExcludedScopes.stream().anyMatch(
e -> s.startsWith(e))) {
mutableExcludedScopes.add(s);
}
}
}
}
// clone excludedNodes before remove duplicate in it
Collection<Node> mutableExNodes = null;
// Remove duplicate in excludedNodes
if (excludedNodes != null) {
mutableExNodes =
excludedNodes.stream().distinct().collect(Collectors.toList());
}
// remove duplicate in mutableExNodes and mutableExcludedScopes
NetUtils.removeDuplicate(this, mutableExNodes, mutableExcludedScopes,
ancestorGen);
// calculate available node count
Node scopeNode = getNode(finalScope);
int availableNodes = getAvailableNodesCount(
scopeNode.getNetworkFullPath(), mutableExcludedScopes, mutableExNodes,
ancestorGen);
if (availableNodes <= 0) {
LOG.warn("No available node in (scope=\"{}\" excludedScope=\"{}\" " +
"excludedNodes=\"{}\" ancestorGen=\"{}\").",
scopeNode.getNetworkFullPath(), excludedScopes, excludedNodes,
ancestorGen);
return null;
}
// scope is a Leaf node
if (!(scopeNode instanceof InnerNode)) {
return scopeNode;
}
Node ret;
int nodeIndex;
if (leafIndex >= 0) {
nodeIndex = leafIndex % availableNodes;
ret = ((InnerNode)scopeNode).getLeaf(nodeIndex, mutableExcludedScopes,
mutableExNodes, ancestorGen);
} else {
nodeIndex = ThreadLocalRandom.current().nextInt(availableNodes);
ret = ((InnerNode)scopeNode).getLeaf(nodeIndex, mutableExcludedScopes,
mutableExNodes, ancestorGen);
}
if (LOG.isDebugEnabled()) {
LOG.debug("Choosing node[index={},random={}] from \"{}\" available " +
"nodes, scope=\"{}\", excludedScope=\"{}\", excludeNodes=\"{}\".",
nodeIndex, (leafIndex == -1 ? "true" : "false"), availableNodes,
scopeNode.getNetworkFullPath(), excludedScopes, excludedNodes);
LOG.debug("Chosen node = {}", (ret == null ? "not found" :
ret.toString()));
}
return ret;
}
/** Return the distance cost between two nodes
* The distance cost from one node to its parent is it's parent's cost
* The distance cost between two nodes is calculated by summing up their
* distances cost to their closest common ancestor.
* @param node1 one node
* @param node2 another node
* @return the distance cost between node1 and node2 which is zero if they
* are the same or {@link Integer#MAX_VALUE} if node1 or node2 do not belong
* to the cluster
*/
public int getDistanceCost(Node node1, Node node2) {
if ((node1 != null && node2 != null && node1.equals(node2)) ||
(node1 == null && node2 == null)) {
return 0;
}
if (node1 == null || node2 == null) {
LOG.warn("One of the nodes is a null pointer");
return Integer.MAX_VALUE;
}
int cost = 0;
netlock.readLock().lock();
try {
if ((node1.getAncestor(maxLevel - 1) != clusterTree) ||
(node2.getAncestor(maxLevel - 1) != clusterTree)) {
LOG.debug("One of the nodes is outside of network topology");
return Integer.MAX_VALUE;
}
int level1 = node1.getLevel();
int level2 = node2.getLevel();
if (level1 > maxLevel || level2 > maxLevel) {
return Integer.MAX_VALUE;
}
while(level1 > level2 && node1 != null) {
node1 = node1.getParent();
level1--;
cost += node1 == null? 0 : node1.getCost();
}
while(level2 > level1 && node2 != null) {
node2 = node2.getParent();
level2--;
cost += node2 == null? 0 : node2.getCost();
}
while(node1 != null && node2 != null && node1 != node2) {
node1 = node1.getParent();
node2 = node2.getParent();
cost += node1 == null? 0 : node1.getCost();
cost += node2 == null? 0 : node2.getCost();
}
return cost;
} finally {
netlock.readLock().unlock();
}
}
/**
* Sort nodes array by network distance to <i>reader</i> to reduces network
* traffic and improves performance.
*
* As an additional twist, we also randomize the nodes at each network
* distance. This helps with load balancing when there is data skew.
*
* @param reader Node where need the data
* @param nodes Available replicas with the requested data
* @param activeLen Number of active nodes at the front of the array
*/
public List<? extends Node> sortByDistanceCost(Node reader,
List<? extends Node> nodes, int activeLen) {
/** Sort weights for the nodes array */
if (reader == null) {
return nodes;
}
int[] costs = new int[activeLen];
for (int i = 0; i < activeLen; i++) {
costs[i] = getDistanceCost(reader, nodes.get(i));
}
// Add cost/node pairs to a TreeMap to sort
TreeMap<Integer, List<Node>> tree = new TreeMap<Integer, List<Node>>();
for (int i = 0; i < activeLen; i++) {
int cost = costs[i];
Node node = nodes.get(i);
List<Node> list = tree.get(cost);
if (list == null) {
list = Lists.newArrayListWithExpectedSize(1);
tree.put(cost, list);
}
list.add(node);
}
List<Node> ret = new ArrayList<>();
for (List<Node> list: tree.values()) {
if (list != null) {
Collections.shuffle(list);
for (Node n: list) {
ret.add(n);
}
}
}
Preconditions.checkState(ret.size() == activeLen,
"Wrong number of nodes sorted!");
return ret;
}
/**
* Return the number of leaves in <i>scope</i> but not in
* <i>excludedNodes</i> and <i>excludeScope</i>.
* @param scope the scope
* @param excludedScopes excluded scopes
* @param mutableExcludedNodes a list of excluded nodes, content might be
* changed after the call
* @param ancestorGen same generation ancestor prohibit on excludedNodes
* @return number of available nodes
*/
private int getAvailableNodesCount(String scope, List<String> excludedScopes,
Collection<Node> mutableExcludedNodes, int ancestorGen) {
Preconditions.checkArgument(scope != null);
Node scopeNode = getNode(scope);
if (scopeNode == null) {
return 0;
}
NetUtils.removeOutscope(mutableExcludedNodes, scope);
List<Node> excludedAncestorList =
NetUtils.getAncestorList(this, mutableExcludedNodes, ancestorGen);
for (Node ancestor : excludedAncestorList) {
if (scope.startsWith(ancestor.getNetworkFullPath())){
return 0;
}
}
// number of nodes to exclude
int excludedCount = 0;
if (excludedScopes != null) {
for (String excludedScope: excludedScopes) {
Node excludedScopeNode = getNode(excludedScope);
if (excludedScopeNode != null) {
if (excludedScope.startsWith(scope)) {
excludedCount += excludedScopeNode.getNumOfLeaves();
} else if (scope.startsWith(excludedScope)) {
return 0;
}
}
}
}
// excludedNodes is not null case
if (mutableExcludedNodes != null && (!mutableExcludedNodes.isEmpty())) {
if (ancestorGen == 0) {
for (Node node: mutableExcludedNodes) {
if (contains(node)) {
excludedCount++;
}
}
} else {
for (Node ancestor : excludedAncestorList) {
if (ancestor.getNetworkFullPath().startsWith(scope)) {
excludedCount += ancestor.getNumOfLeaves();
}
}
}
}
int availableCount = scopeNode.getNumOfLeaves() - excludedCount;
Preconditions.checkState(availableCount >= 0);
return availableCount;
}
@Override
public String toString() {
// print max level
StringBuilder tree = new StringBuilder();
tree.append("Level: ");
tree.append(maxLevel);
tree.append("\n");
netlock.readLock().lock();
try {
// print the number of leaves
int numOfLeaves = clusterTree.getNumOfLeaves();
tree.append("Number of leaves:");
tree.append(numOfLeaves);
tree.append("\n");
// print all nodes
for (int i = 0; i < numOfLeaves; i++) {
tree.append(clusterTree.getLeaf(i).getNetworkFullPath());
tree.append("\n");
}
} finally {
netlock.readLock().unlock();
}
return tree.toString();
}
private void checkScope(String scope) {
if (scope != null && scope.startsWith(SCOPE_REVERSE_STR)) {
throw new IllegalArgumentException("scope " + scope +
" should not start with " + SCOPE_REVERSE_STR);
}
}
private void checkExcludedScopes(List<String> excludedScopes) {
if (!CollectionUtils.isEmpty(excludedScopes)) {
excludedScopes.stream().forEach(scope -> {
if (scope.startsWith(SCOPE_REVERSE_STR)) {
throw new IllegalArgumentException("excludedScope " + scope +
" cannot start with " + SCOPE_REVERSE_STR);
}
});
}
}
private void checkAffinityNode(Node affinityNode) {
if (affinityNode != null && (!contains(affinityNode))) {
throw new IllegalArgumentException("Affinity node " +
affinityNode.getNetworkFullPath() + " is not a member of topology");
}
}
private void checkAncestorGen(int ancestorGen) {
if (ancestorGen > (maxLevel - 1) || ancestorGen < 0) {
throw new IllegalArgumentException("ancestorGen " + ancestorGen +
" exceeds this network topology acceptable level [0, " +
(maxLevel - 1) + "]");
}
}
}