HDDS-699. Detect Ozone Network topology. Contributed by Sammi Chen.
This commit is contained in:
parent
926d548caa
commit
4d2a116d6e
@ -354,6 +354,12 @@ public final class ScmConfigKeys {
|
|||||||
public static final String
|
public static final String
|
||||||
HDDS_SCM_HTTP_KERBEROS_KEYTAB_FILE_KEY =
|
HDDS_SCM_HTTP_KERBEROS_KEYTAB_FILE_KEY =
|
||||||
"hdds.scm.http.kerberos.keytab";
|
"hdds.scm.http.kerberos.keytab";
|
||||||
|
|
||||||
|
// Network topology
|
||||||
|
public static final String OZONE_SCM_NETWORK_TOPOLOGY_SCHEMA_FILE =
|
||||||
|
"ozone.scm.network.topology.schema.file";
|
||||||
|
public static final String OZONE_SCM_NETWORK_TOPOLOGY_SCHEMA_FILE_DEFAULT =
|
||||||
|
"network-topology-default.xml";
|
||||||
/**
|
/**
|
||||||
* Never constructed.
|
* Never constructed.
|
||||||
*/
|
*/
|
||||||
|
@ -0,0 +1,84 @@
|
|||||||
|
/**
|
||||||
|
* 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 java.util.Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The interface defines an inner node in a network topology.
|
||||||
|
* An inner node represents network topology entities, such as data center,
|
||||||
|
* rack, switch or logical group.
|
||||||
|
*/
|
||||||
|
public interface InnerNode extends Node {
|
||||||
|
/** A factory interface to get new InnerNode instance. */
|
||||||
|
interface Factory<N extends InnerNode> {
|
||||||
|
/** Construct an InnerNode from name, location, parent, level and cost. */
|
||||||
|
N newInnerNode(String name, String location, InnerNode parent, int level,
|
||||||
|
int cost);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add node <i>n</i> to the subtree of this node.
|
||||||
|
* @param n node to be added
|
||||||
|
* @return true if the node is added; false otherwise
|
||||||
|
*/
|
||||||
|
boolean add(Node n);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove node <i>n</i> from the subtree of this node.
|
||||||
|
* @param n node to be deleted
|
||||||
|
*/
|
||||||
|
void remove(Node n);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a node's string representation, return a reference to the node.
|
||||||
|
* @param loc string location of the format /dc/rack/nodegroup/node
|
||||||
|
* @return null if the node is not found
|
||||||
|
*/
|
||||||
|
Node getNode(String loc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return number of its all nodes at level <i>level</i>. Here level is a
|
||||||
|
* relative level. If level is 1, means node itself. If level is 2, means its
|
||||||
|
* direct children, and so on.
|
||||||
|
**/
|
||||||
|
int getNumOfNodes(int level);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get <i>leafIndex</i> leaf of this subtree.
|
||||||
|
*
|
||||||
|
* @param leafIndex an indexed leaf of the node
|
||||||
|
* @return the leaf node corresponding to the given index.
|
||||||
|
*/
|
||||||
|
Node getLeaf(int leafIndex);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get <i>leafIndex</i> leaf of this subtree.
|
||||||
|
*
|
||||||
|
* @param leafIndex ode's index, start from 0, skip the nodes in
|
||||||
|
* excludedScope and excludedNodes with ancestorGen
|
||||||
|
* @param excludedScope the excluded scope
|
||||||
|
* @param excludedNodes nodes to be excluded. If ancestorGen is not 0,
|
||||||
|
* the chosen node will not share same ancestor with
|
||||||
|
* those in excluded nodes at the specified generation
|
||||||
|
* @param ancestorGen ignored with value is 0
|
||||||
|
* @return the leaf node corresponding to the given index
|
||||||
|
*/
|
||||||
|
Node getLeaf(int leafIndex, String excludedScope,
|
||||||
|
Collection<Node> excludedNodes, int ancestorGen);
|
||||||
|
}
|
@ -0,0 +1,495 @@
|
|||||||
|
/**
|
||||||
|
* 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 java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import static org.apache.hadoop.hdds.scm.net.NetConstants.PATH_SEPARATOR_STR;
|
||||||
|
import static org.apache.hadoop.hdds.scm.net.NetConstants.PATH_SEPARATOR;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A thread safe class that implements InnerNode interface.
|
||||||
|
*/
|
||||||
|
public class InnerNodeImpl extends NodeImpl implements InnerNode {
|
||||||
|
protected static class Factory implements InnerNode.Factory<InnerNodeImpl> {
|
||||||
|
protected Factory() {}
|
||||||
|
|
||||||
|
public InnerNodeImpl newInnerNode(String name, String location,
|
||||||
|
InnerNode parent, int level, int cost) {
|
||||||
|
return new InnerNodeImpl(name, location, parent, level, cost);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final Factory FACTORY = new Factory();
|
||||||
|
// a map of node's network name to Node for quick search and keep
|
||||||
|
// the insert order
|
||||||
|
private final HashMap<String, Node> childrenMap =
|
||||||
|
new LinkedHashMap<String, Node>();
|
||||||
|
// number of descendant leaves under this node
|
||||||
|
private int numOfLeaves;
|
||||||
|
// LOGGER
|
||||||
|
public static final Logger LOG = LoggerFactory.getLogger(InnerNodeImpl.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct an InnerNode from its name, network location, parent, level and
|
||||||
|
* its cost.
|
||||||
|
**/
|
||||||
|
protected InnerNodeImpl(String name, String location, InnerNode parent,
|
||||||
|
int level, int cost) {
|
||||||
|
super(name, location, parent, level, cost);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return the number of children this node has */
|
||||||
|
private int getNumOfChildren() {
|
||||||
|
return childrenMap.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return its leaf nodes number */
|
||||||
|
@Override
|
||||||
|
public int getNumOfLeaves() {
|
||||||
|
return numOfLeaves;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return number of its all nodes at level <i>level</i>. Here level is a
|
||||||
|
* relative level. If level is 1, means node itself. If level is 2, means its
|
||||||
|
* direct children, and so on.
|
||||||
|
**/
|
||||||
|
public int getNumOfNodes(int level) {
|
||||||
|
Preconditions.checkArgument(level > 0);
|
||||||
|
int count = 0;
|
||||||
|
if (level == 1) {
|
||||||
|
count += 1;
|
||||||
|
} else if (level == 2) {
|
||||||
|
count += getNumOfChildren();
|
||||||
|
} else {
|
||||||
|
for (Node node: childrenMap.values()) {
|
||||||
|
if (node instanceof InnerNode) {
|
||||||
|
count += ((InnerNode)node).getNumOfNodes(level -1);
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("Cannot support Level:" + level +
|
||||||
|
" on this node " + this.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Judge if this node is the parent of a leave node <i>n</i>.
|
||||||
|
* @return true if this node is the parent of <i>n</i>
|
||||||
|
*/
|
||||||
|
private boolean isLeafParent() {
|
||||||
|
if (childrenMap.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Node child = childrenMap.values().iterator().next();
|
||||||
|
return child instanceof InnerNode ? false : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Judge if this node is the parent of node <i>node</i>.
|
||||||
|
* @param node a node
|
||||||
|
* @return true if this node is the parent of <i>n</i>
|
||||||
|
*/
|
||||||
|
private boolean isParent(Node node) {
|
||||||
|
return node.getNetworkLocation().equals(this.getNetworkFullPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add node <i>node</i> to the subtree of this node.
|
||||||
|
* @param node node to be added
|
||||||
|
* @return true if the node is added, false if is only updated
|
||||||
|
*/
|
||||||
|
public boolean add(Node node) {
|
||||||
|
if (!isAncestor(node)) {
|
||||||
|
throw new IllegalArgumentException(node.getNetworkName()
|
||||||
|
+ ", which is located at " + node.getNetworkLocation()
|
||||||
|
+ ", is not a descendant of " + this.getNetworkFullPath());
|
||||||
|
}
|
||||||
|
if (isParent(node)) {
|
||||||
|
// this node is the parent, then add it directly
|
||||||
|
node.setParent(this);
|
||||||
|
node.setLevel(this.getLevel() + 1);
|
||||||
|
Node current = childrenMap.put(node.getNetworkName(), node);
|
||||||
|
if (current != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// find the next level ancestor node
|
||||||
|
String ancestorName = getNextLevelAncestorName(node);
|
||||||
|
InnerNode childNode = (InnerNode)childrenMap.get(ancestorName);
|
||||||
|
if (childNode == null) {
|
||||||
|
// create a new InnerNode for this ancestor node
|
||||||
|
childNode = createChildNode(ancestorName);
|
||||||
|
childrenMap.put(childNode.getNetworkName(), childNode);
|
||||||
|
}
|
||||||
|
// add node to the subtree of the next ancestor node
|
||||||
|
if (!childNode.add(node)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
numOfLeaves++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove node <i>node</i> from the subtree of this node.
|
||||||
|
* @param node node to be deleted
|
||||||
|
*/
|
||||||
|
public void remove(Node node) {
|
||||||
|
if (!isAncestor(node)) {
|
||||||
|
throw new IllegalArgumentException(node.getNetworkName()
|
||||||
|
+ ", which is located at " + node.getNetworkLocation()
|
||||||
|
+ ", is not a descendant of " + this.getNetworkFullPath());
|
||||||
|
}
|
||||||
|
if (isParent(node)) {
|
||||||
|
// this node is the parent, remove it directly
|
||||||
|
if (childrenMap.containsKey(node.getNetworkName())) {
|
||||||
|
childrenMap.remove(node.getNetworkName());
|
||||||
|
node.setParent(null);
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("Should not come to here. Node:" +
|
||||||
|
node.getNetworkFullPath() + ", Parent:" +
|
||||||
|
this.getNetworkFullPath());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// find the next ancestor node
|
||||||
|
String ancestorName = getNextLevelAncestorName(node);
|
||||||
|
InnerNodeImpl childNode = (InnerNodeImpl)childrenMap.get(ancestorName);
|
||||||
|
Preconditions.checkNotNull(childNode, "InnerNode is deleted before leaf");
|
||||||
|
// remove node from the parent node
|
||||||
|
childNode.remove(node);
|
||||||
|
// if the parent node has no children, remove the parent node too
|
||||||
|
if (childNode.getNumOfChildren() == 0) {
|
||||||
|
childrenMap.remove(ancestorName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
numOfLeaves--;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a node's string representation, return a reference to the node.
|
||||||
|
* Node can be leaf node or inner node.
|
||||||
|
*
|
||||||
|
* @param loc string location of a node. If loc starts with "/", it's a
|
||||||
|
* absolute path, otherwise a relative path. Following examples
|
||||||
|
* are all accepted,
|
||||||
|
* 1. /dc1/rm1/rack1 -> an inner node
|
||||||
|
* 2. /dc1/rm1/rack1/node1 -> a leaf node
|
||||||
|
* 3. rack1/node1 -> a relative path to this node
|
||||||
|
*
|
||||||
|
* @return null if the node is not found
|
||||||
|
*/
|
||||||
|
public Node getNode(String loc) {
|
||||||
|
if (loc == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String fullPath = this.getNetworkFullPath();
|
||||||
|
if (loc.equalsIgnoreCase(fullPath)) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove current node's location from loc when it's a absolute path
|
||||||
|
if (fullPath.equals(NetConstants.PATH_SEPARATOR_STR)) {
|
||||||
|
// current node is ROOT
|
||||||
|
if (loc.startsWith(PATH_SEPARATOR_STR)) {
|
||||||
|
loc = loc.substring(1);
|
||||||
|
}
|
||||||
|
} else if (loc.startsWith(fullPath)) {
|
||||||
|
loc = loc.substring(fullPath.length());
|
||||||
|
// skip the separator "/"
|
||||||
|
loc = loc.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] path = loc.split(PATH_SEPARATOR_STR, 2);
|
||||||
|
Node child = childrenMap.get(path[0]);
|
||||||
|
if (child == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (path.length == 1){
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
if (child instanceof InnerNode) {
|
||||||
|
return ((InnerNode)child).getNode(path[1]);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get <i>leafIndex</i> leaf of this subtree.
|
||||||
|
*
|
||||||
|
* @param leafIndex an indexed leaf of the node
|
||||||
|
* @return the leaf node corresponding to the given index.
|
||||||
|
*/
|
||||||
|
public Node getLeaf(int leafIndex) {
|
||||||
|
Preconditions.checkArgument(leafIndex >= 0);
|
||||||
|
// children are leaves
|
||||||
|
if (isLeafParent()) {
|
||||||
|
// range check
|
||||||
|
if (leafIndex >= getNumOfChildren()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return getChildNode(leafIndex);
|
||||||
|
} else {
|
||||||
|
for(Node node : childrenMap.values()) {
|
||||||
|
InnerNodeImpl child = (InnerNodeImpl)node;
|
||||||
|
int leafCount = child.getNumOfLeaves();
|
||||||
|
if (leafIndex < leafCount) {
|
||||||
|
return child.getLeaf(leafIndex);
|
||||||
|
} else {
|
||||||
|
leafIndex -= leafCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get <i>leafIndex</i> leaf of this subtree.
|
||||||
|
*
|
||||||
|
* @param leafIndex node's index, start from 0, skip the nodes in
|
||||||
|
* excludedScope and excludedNodes with ancestorGen
|
||||||
|
* @param excludedScope the exclude scope
|
||||||
|
* @param excludedNodes nodes to be excluded from. If ancestorGen is not 0,
|
||||||
|
* the chosen node will not share same ancestor with
|
||||||
|
* those in excluded nodes at the specified generation
|
||||||
|
* @param ancestorGen apply to excludeNodes, when value is 0, then no same
|
||||||
|
* ancestor enforcement on excludedNodes
|
||||||
|
* @return the leaf node corresponding to the given index.
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* / --- root
|
||||||
|
* / \
|
||||||
|
* / \
|
||||||
|
* / \
|
||||||
|
* / \
|
||||||
|
* dc1 dc2
|
||||||
|
* / \ / \
|
||||||
|
* / \ / \
|
||||||
|
* / \ / \
|
||||||
|
* rack1 rack2 rack1 rack2
|
||||||
|
* / \ / \ / \ / \
|
||||||
|
* n1 n2 n3 n4 n5 n6 n7 n8
|
||||||
|
*
|
||||||
|
* Input:
|
||||||
|
* leafIndex = 2
|
||||||
|
* excludedScope = /dc2
|
||||||
|
* excludedNodes = {/dc1/rack1/n1}
|
||||||
|
* ancestorGen = 1
|
||||||
|
*
|
||||||
|
* Output:
|
||||||
|
* node /dc1/rack2/n5
|
||||||
|
*
|
||||||
|
* Explanation:
|
||||||
|
* Since excludedNodes is n1 and ancestorGen is 1, it means nodes under
|
||||||
|
* /root/dc1/rack1 are excluded. Given leafIndex start from 0, LeafIndex 2
|
||||||
|
* means picking the 3th available node, which is n5.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public Node getLeaf(int leafIndex, String excludedScope,
|
||||||
|
Collection<Node> excludedNodes, int ancestorGen) {
|
||||||
|
Preconditions.checkArgument(leafIndex >= 0 && ancestorGen >= 0);
|
||||||
|
// come to leaf parent layer
|
||||||
|
if (isLeafParent()) {
|
||||||
|
return getLeafOnLeafParent(leafIndex, excludedScope, excludedNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
int maxLevel = NodeSchemaManager.getInstance().getMaxLevel();
|
||||||
|
// this node's children, it's generation as the ancestor of the leaf node
|
||||||
|
int currentGen = maxLevel - this.getLevel() - 1;
|
||||||
|
// build an ancestor(children) to exclude node count map
|
||||||
|
Map<Node, Integer> countMap =
|
||||||
|
getAncestorCountMap(excludedNodes, ancestorGen, currentGen);
|
||||||
|
// nodes covered by excluded scope
|
||||||
|
int excludedNodeCount = getExcludedScopeNodeCount(excludedScope);
|
||||||
|
|
||||||
|
for(Node child : childrenMap.values()) {
|
||||||
|
int leafCount = child.getNumOfLeaves();
|
||||||
|
// skip nodes covered by excluded scope
|
||||||
|
if (excludedScope != null &&
|
||||||
|
excludedScope.startsWith(child.getNetworkFullPath())) {
|
||||||
|
leafCount -= excludedNodeCount;
|
||||||
|
}
|
||||||
|
// skip nodes covered by excluded nodes and ancestorGen
|
||||||
|
Integer count = countMap.get(child);
|
||||||
|
if (count != null) {
|
||||||
|
leafCount -= count;
|
||||||
|
}
|
||||||
|
if (leafIndex < leafCount) {
|
||||||
|
return ((InnerNode)child).getLeaf(leafIndex, excludedScope,
|
||||||
|
excludedNodes, ancestorGen);
|
||||||
|
} else {
|
||||||
|
leafIndex -= leafCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object to) {
|
||||||
|
if (to == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this == to) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return this.toString().equals(to.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return super.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a ancestor to its excluded node count map.
|
||||||
|
*
|
||||||
|
* @param nodes a collection of leaf nodes to exclude
|
||||||
|
* @param genToExclude the ancestor generation to exclude
|
||||||
|
* @param genToReturn the ancestor generation to return the count map
|
||||||
|
* @return the map.
|
||||||
|
* example:
|
||||||
|
*
|
||||||
|
* * --- root
|
||||||
|
* / \
|
||||||
|
* * * -- genToReturn =2
|
||||||
|
* / \ / \
|
||||||
|
* * * * * -- genToExclude = 1
|
||||||
|
* /\ /\ /\ /\
|
||||||
|
* * * * * * * * * -- nodes
|
||||||
|
*/
|
||||||
|
private Map<Node, Integer> getAncestorCountMap(Collection<Node> nodes,
|
||||||
|
int genToExclude, int genToReturn) {
|
||||||
|
Preconditions.checkState(genToExclude >= 0);
|
||||||
|
Preconditions.checkState(genToReturn >= 0);
|
||||||
|
|
||||||
|
if (nodes == null || nodes.size() == 0) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
// with the recursive call, genToReturn can be smaller than genToExclude
|
||||||
|
if (genToReturn < genToExclude) {
|
||||||
|
genToExclude = genToReturn;
|
||||||
|
}
|
||||||
|
// ancestorToExclude to ancestorToReturn map
|
||||||
|
HashMap<Node, Node> ancestorMap = new HashMap<>();
|
||||||
|
for (Node node: nodes) {
|
||||||
|
Node ancestorToExclude = node.getAncestor(genToExclude);
|
||||||
|
Node ancestorToReturn = node.getAncestor(genToReturn);
|
||||||
|
if (ancestorToExclude == null || ancestorToReturn == null) {
|
||||||
|
LOG.warn("Ancestor not found, node: " + node.getNetworkFullPath() +
|
||||||
|
", generation to exclude: " + genToExclude +
|
||||||
|
", generation to return:" + genToReturn);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ancestorMap.put(ancestorToExclude, ancestorToReturn);
|
||||||
|
}
|
||||||
|
// ancestorToReturn to exclude node count map
|
||||||
|
HashMap<Node, Integer> countMap = new HashMap<>();
|
||||||
|
for (Map.Entry<Node, Node> entry : ancestorMap.entrySet()) {
|
||||||
|
countMap.compute(entry.getValue(),
|
||||||
|
(key, n) -> (n == null ? 0 : n) + entry.getKey().getNumOfLeaves());
|
||||||
|
}
|
||||||
|
|
||||||
|
return countMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the node with leafIndex, considering skip nodes in excludedScope
|
||||||
|
* and in excludeNodes list.
|
||||||
|
*/
|
||||||
|
private Node getLeafOnLeafParent(int leafIndex, String excludedScope,
|
||||||
|
Collection<Node> excludedNodes) {
|
||||||
|
Preconditions.checkArgument(isLeafParent() && leafIndex >= 0);
|
||||||
|
if (leafIndex >= getNumOfChildren()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for(Node node : childrenMap.values()) {
|
||||||
|
if ((excludedNodes != null && (excludedNodes.contains(node))) ||
|
||||||
|
(excludedScope != null &&
|
||||||
|
(node.getNetworkFullPath().startsWith(excludedScope)))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (leafIndex == 0) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
leafIndex--;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return child's name of this node which is an ancestor of node <i>n</i>.
|
||||||
|
*/
|
||||||
|
private String getNextLevelAncestorName(Node n) {
|
||||||
|
int parentPathLen = this.getNetworkFullPath().length();
|
||||||
|
String name = n.getNetworkLocation().substring(parentPathLen);
|
||||||
|
if (name.charAt(0) == PATH_SEPARATOR) {
|
||||||
|
name = name.substring(1);
|
||||||
|
}
|
||||||
|
int index = name.indexOf(PATH_SEPARATOR);
|
||||||
|
if (index != -1) {
|
||||||
|
name = name.substring(0, index);
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a child node to be added to the list of children.
|
||||||
|
* @param name The name of the child node
|
||||||
|
* @return A new inner node
|
||||||
|
* @see InnerNodeImpl(String, String, InnerNode, int)
|
||||||
|
*/
|
||||||
|
private InnerNodeImpl createChildNode(String name) {
|
||||||
|
int childLevel = this.getLevel() + 1;
|
||||||
|
int cost = NodeSchemaManager.getInstance().getCost(childLevel);
|
||||||
|
return new InnerNodeImpl(name, this.getNetworkFullPath(), this, childLevel,
|
||||||
|
cost);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get node with index <i>index</i>. */
|
||||||
|
private Node getChildNode(int index) {
|
||||||
|
Iterator iterator = childrenMap.values().iterator();
|
||||||
|
Node node = null;
|
||||||
|
while(index >= 0 && iterator.hasNext()) {
|
||||||
|
node = (Node)iterator.next();
|
||||||
|
index--;
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get how many leaf nodes are covered by the excludedScope. */
|
||||||
|
private int getExcludedScopeNodeCount(String excludedScope) {
|
||||||
|
if (excludedScope == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
Node excludedScopeNode = getNode(excludedScope);
|
||||||
|
return excludedScopeNode == null ? 0 : excludedScopeNode.getNumOfLeaves();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
* 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 org.apache.hadoop.hdds.scm.net.NodeSchema.LayerType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to hold network topology related constants and configurations.
|
||||||
|
*/
|
||||||
|
public final class NetConstants {
|
||||||
|
private NetConstants() {
|
||||||
|
// Prevent instantiation
|
||||||
|
}
|
||||||
|
public final static char PATH_SEPARATOR = '/';
|
||||||
|
/** Path separator as a string. */
|
||||||
|
public final static String PATH_SEPARATOR_STR = "/";
|
||||||
|
public final static String SCOPE_REVERSE_STR = "~";
|
||||||
|
/** string representation of root. */
|
||||||
|
public final static String ROOT = "";
|
||||||
|
public final static int INNER_NODE_COST_DEFAULT = 1;
|
||||||
|
public final static int NODE_COST_DEFAULT = 0;
|
||||||
|
public final static int ANCESTOR_GENERATION_DEFAULT = 0;
|
||||||
|
public final static int ROOT_LEVEL = 1;
|
||||||
|
public final static String NODE_COST_PREFIX = "$";
|
||||||
|
public final static String DEFAULT_RACK = "/default-rack";
|
||||||
|
public final static String DEFAULT_NODEGROUP = "/default-nodegroup";
|
||||||
|
public final static String DEFAULT_DATACENTER = "/default-datacenter";
|
||||||
|
public final static String DEFAULT_REGION = "/default-dataregion";
|
||||||
|
|
||||||
|
// Build-in network topology node schema
|
||||||
|
public static final NodeSchema ROOT_SCHEMA =
|
||||||
|
new NodeSchema.Builder().setType(LayerType.ROOT).build();
|
||||||
|
|
||||||
|
public static final NodeSchema REGION_SCHEMA =
|
||||||
|
new NodeSchema.Builder().setType(LayerType.INNER_NODE)
|
||||||
|
.setDefaultName(DEFAULT_REGION).build();
|
||||||
|
|
||||||
|
public static final NodeSchema DATACENTER_SCHEMA =
|
||||||
|
new NodeSchema.Builder().setType(LayerType.INNER_NODE)
|
||||||
|
.setDefaultName(DEFAULT_DATACENTER).build();
|
||||||
|
|
||||||
|
public static final NodeSchema RACK_SCHEMA =
|
||||||
|
new NodeSchema.Builder().setType(LayerType.INNER_NODE)
|
||||||
|
.setDefaultName(DEFAULT_RACK).build();
|
||||||
|
|
||||||
|
public static final NodeSchema NODEGROUP_SCHEMA =
|
||||||
|
new NodeSchema.Builder().setType(LayerType.INNER_NODE)
|
||||||
|
.setDefaultName(DEFAULT_NODEGROUP).build();
|
||||||
|
|
||||||
|
public static final NodeSchema LEAF_SCHEMA =
|
||||||
|
new NodeSchema.Builder().setType(LayerType.LEAF_NODE).build();
|
||||||
|
}
|
@ -0,0 +1,156 @@
|
|||||||
|
/**
|
||||||
|
* 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 org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class to facilitate network topology functions.
|
||||||
|
*/
|
||||||
|
public final class NetUtils {
|
||||||
|
public static final Logger LOG = LoggerFactory.getLogger(NetUtils.class);
|
||||||
|
private NetUtils() {
|
||||||
|
// Prevent instantiation
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Normalize a path by stripping off any trailing.
|
||||||
|
* {@link NetConstants#PATH_SEPARATOR}
|
||||||
|
* @param path path to normalize.
|
||||||
|
* @return the normalised path
|
||||||
|
* If <i>path</i>is empty or null, then {@link NetConstants#ROOT} is returned
|
||||||
|
*/
|
||||||
|
public static String normalize(String path) {
|
||||||
|
if (path == null || path.length() == 0) {
|
||||||
|
return NetConstants.ROOT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path.charAt(0) != NetConstants.PATH_SEPARATOR) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Network Location path does not start with "
|
||||||
|
+ NetConstants.PATH_SEPARATOR_STR + ": " + path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove any trailing NetConstants.PATH_SEPARATOR
|
||||||
|
return path.length() == 1 ? path :
|
||||||
|
path.replaceAll(NetConstants.PATH_SEPARATOR_STR + "+$", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a network topology location string, return its network topology
|
||||||
|
* depth, E.g. the depth of /dc1/rack1/ng1/node1 is 5.
|
||||||
|
*/
|
||||||
|
public static int locationToDepth(String location) {
|
||||||
|
String newLocation = normalize(location);
|
||||||
|
return newLocation.equals(NetConstants.PATH_SEPARATOR_STR) ? 1 :
|
||||||
|
newLocation.split(NetConstants.PATH_SEPARATOR_STR).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove node from mutableExcludedNodes if it's covered by excludedScope.
|
||||||
|
* Please noted that mutableExcludedNodes content might be changed after the
|
||||||
|
* function call.
|
||||||
|
* @return the new excludedScope
|
||||||
|
*/
|
||||||
|
public static String removeDuplicate(NetworkTopology topology,
|
||||||
|
Collection<Node> mutableExcludedNodes, String excludedScope,
|
||||||
|
int ancestorGen) {
|
||||||
|
if (mutableExcludedNodes == null || mutableExcludedNodes.size() == 0 ||
|
||||||
|
excludedScope == null || topology == null) {
|
||||||
|
return excludedScope;
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator<Node> iterator = mutableExcludedNodes.iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
Node node = iterator.next();
|
||||||
|
Node ancestor = topology.getAncestor(node, ancestorGen);
|
||||||
|
if (ancestor == null) {
|
||||||
|
LOG.warn("Fail to get ancestor generation " + ancestorGen +
|
||||||
|
" of node :" + node);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (excludedScope.startsWith(ancestor.getNetworkFullPath())) {
|
||||||
|
// reset excludedScope if it's covered by exclude node's ancestor
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (ancestor.getNetworkFullPath().startsWith(excludedScope)) {
|
||||||
|
// remove exclude node if it's covered by excludedScope
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return excludedScope;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove node from mutableExcludedNodes if it's not part of scope
|
||||||
|
* Please noted that mutableExcludedNodes content might be changed after the
|
||||||
|
* function call.
|
||||||
|
*/
|
||||||
|
public static void removeOutscope(Collection<Node> mutableExcludedNodes,
|
||||||
|
String scope) {
|
||||||
|
if (mutableExcludedNodes == null || scope == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
synchronized (mutableExcludedNodes) {
|
||||||
|
Iterator<Node> iterator = mutableExcludedNodes.iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
Node next = iterator.next();
|
||||||
|
if (!next.getNetworkFullPath().startsWith(scope)) {
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a ancestor list for nodes on generation <i>generation</i>.
|
||||||
|
*
|
||||||
|
* @param nodes a collection of leaf nodes
|
||||||
|
* @param generation the ancestor generation
|
||||||
|
* @return the ancestor list. If no ancestor is found, then a empty list is
|
||||||
|
* returned.
|
||||||
|
*/
|
||||||
|
public static List<Node> getAncestorList(NetworkTopology topology,
|
||||||
|
Collection<Node> nodes, int generation) {
|
||||||
|
List<Node> ancestorList = new ArrayList<>();
|
||||||
|
if (topology == null ||nodes == null || nodes.size() == 0 ||
|
||||||
|
generation == 0) {
|
||||||
|
return ancestorList;
|
||||||
|
}
|
||||||
|
Iterator<Node> iterator = nodes.iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
Node node = iterator.next();
|
||||||
|
Node ancestor = topology.getAncestor(node, generation);
|
||||||
|
if (ancestor == null) {
|
||||||
|
LOG.warn("Fail to get ancestor generation " + generation +
|
||||||
|
" of node :" + node);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!ancestorList.contains(ancestor)) {
|
||||||
|
ancestorList.add(ancestor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ancestorList;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,250 @@
|
|||||||
|
/**
|
||||||
|
* 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 java.util.Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The interface defines a network topology.
|
||||||
|
*/
|
||||||
|
public interface NetworkTopology {
|
||||||
|
/** Exception for invalid network topology detection. */
|
||||||
|
class InvalidTopologyException extends RuntimeException {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
public InvalidTopologyException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
void add(Node node);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
void remove(Node node);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
boolean contains(Node node);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare the direct parent of each node for equality.
|
||||||
|
* @return true if their parent are the same
|
||||||
|
*/
|
||||||
|
boolean isSameParent(Node node1, Node node2);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare the specified ancestor generation of each node for equality.
|
||||||
|
* ancestorGen 1 means parent.
|
||||||
|
* @return true if their specified generation ancestor are equal
|
||||||
|
*/
|
||||||
|
boolean isSameAncestor(Node node1, Node node2, int ancestorGen);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
Node getAncestor(Node node, int ancestorGen);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the max level of this topology, start from 1 for ROOT. For example,
|
||||||
|
* topology like "/rack/node" has the max level '3'.
|
||||||
|
*/
|
||||||
|
int getMaxLevel();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a string representation of a node, 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
|
||||||
|
*/
|
||||||
|
Node getNode(String loc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a string representation of a InnerNode, return its leaf nodes count.
|
||||||
|
* @param loc a path-like string representation of a InnerNode
|
||||||
|
* @return the number of leaf nodes, 0 if it's not an InnerNode or the node
|
||||||
|
* doesn't exist
|
||||||
|
*/
|
||||||
|
int getNumOfLeafNode(String loc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
int getNumOfNodes(int level);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
Node chooseRandom(String scope);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 excludedScope the chosen node cannot be in this range. cannot
|
||||||
|
* starts with ~
|
||||||
|
* @return the chosen node
|
||||||
|
*/
|
||||||
|
Node chooseRandom(String scope, String excludedScope);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
Node chooseRandom(String scope, Collection<Node> excludedNodes);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
Node chooseRandom(String scope, Collection<Node> excludedNodes,
|
||||||
|
int ancestorGen);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Randomly choose a leaf node.
|
||||||
|
*
|
||||||
|
* @param scope range from which a node will be chosen, cannot start with ~
|
||||||
|
* @param excludedNodes nodes to be excluded
|
||||||
|
* @param excludedScope excluded node range. Cannot start with ~
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
Node chooseRandom(String scope, String excludedScope,
|
||||||
|
Collection<Node> excludedNodes, int ancestorGen);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Randomly choose one 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 excludedScope range 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
|
||||||
|
*/
|
||||||
|
Node chooseRandom(String scope, String excludedScope,
|
||||||
|
Collection<Node> excludedNodes, Node affinityNode, int ancestorGen);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Choose the 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 excludedScope range 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
|
||||||
|
*/
|
||||||
|
Node getNode(int leafIndex, String scope, String excludedScope,
|
||||||
|
Collection<Node> excludedNodes, Node affinityNode, int ancestorGen);
|
||||||
|
|
||||||
|
/** 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
|
||||||
|
*/
|
||||||
|
int getDistanceCost(Node node1, Node node2);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
void sortByDistanceCost(Node reader, Node[] nodes, int activeLen);
|
||||||
|
}
|
@ -0,0 +1,778 @@
|
|||||||
|
/**
|
||||||
|
* 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.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
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());
|
||||||
|
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());
|
||||||
|
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)) {
|
||||||
|
return chooseRandom(ROOT, scope.substring(1), 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 excludedScope the chosen node cannot be in this range. cannot
|
||||||
|
* starts with ~
|
||||||
|
* @return the chosen node
|
||||||
|
*/
|
||||||
|
public Node chooseRandom(String scope, String excludedScope) {
|
||||||
|
return chooseRandom(scope, excludedScope, 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)) {
|
||||||
|
return chooseRandom(ROOT, scope.substring(1), 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)) {
|
||||||
|
return chooseRandom(ROOT, scope.substring(1), excludedNodes, null,
|
||||||
|
ancestorGen);
|
||||||
|
} else {
|
||||||
|
return chooseRandom(scope, null, excludedNodes, null, ancestorGen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Randomly choose a leaf node.
|
||||||
|
*
|
||||||
|
* @param scope range from which a node will be chosen, cannot start with ~
|
||||||
|
* @param excludedNodes nodes to be excluded
|
||||||
|
* @param excludedScope excluded node range. Cannot start with ~
|
||||||
|
* @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, String excludedScope,
|
||||||
|
Collection<Node> excludedNodes, int ancestorGen) {
|
||||||
|
return chooseRandom(scope, excludedScope, 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 excludedScope range 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, String excludedScope,
|
||||||
|
Collection<Node> excludedNodes, Node affinityNode, int ancestorGen) {
|
||||||
|
if (scope == null) {
|
||||||
|
scope = ROOT;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkScope(scope);
|
||||||
|
checkExcludedScope(excludedScope);
|
||||||
|
checkAffinityNode(affinityNode);
|
||||||
|
checkAncestorGen(ancestorGen);
|
||||||
|
|
||||||
|
netlock.readLock().lock();
|
||||||
|
try {
|
||||||
|
return chooseNodeInternal(scope, -1, excludedScope,
|
||||||
|
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 excludedScope range 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, String excludedScope,
|
||||||
|
Collection<Node> excludedNodes, Node affinityNode, int ancestorGen) {
|
||||||
|
Preconditions.checkArgument(leafIndex >= 0);
|
||||||
|
if (scope == null) {
|
||||||
|
scope = ROOT;
|
||||||
|
}
|
||||||
|
checkScope(scope);
|
||||||
|
checkExcludedScope(excludedScope);
|
||||||
|
checkAffinityNode(affinityNode);
|
||||||
|
checkAncestorGen(ancestorGen);
|
||||||
|
|
||||||
|
netlock.readLock().lock();
|
||||||
|
try {
|
||||||
|
return chooseNodeInternal(scope, leafIndex, excludedScope,
|
||||||
|
excludedNodes, affinityNode, ancestorGen);
|
||||||
|
} finally {
|
||||||
|
netlock.readLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node chooseNodeInternal(String scope, int leafIndex,
|
||||||
|
String excludedScope, 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 excludedScope and finalScope
|
||||||
|
if (excludedScope != null) {
|
||||||
|
// excludeScope covers finalScope
|
||||||
|
if (finalScope.startsWith(excludedScope)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// excludeScope and finalScope share nothing
|
||||||
|
if (!excludedScope.startsWith(finalScope)) {
|
||||||
|
excludedScope = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// clone excludedNodes before remove duplicate in it
|
||||||
|
Collection<Node> mutableExNodes = null;
|
||||||
|
if (excludedNodes != null) {
|
||||||
|
// Remove duplicate in excludedNodes
|
||||||
|
mutableExNodes =
|
||||||
|
excludedNodes.stream().distinct().collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove duplicate in mutableExNodes and excludedScope, given ancestorGen
|
||||||
|
excludedScope = NetUtils.removeDuplicate(this, mutableExNodes,
|
||||||
|
excludedScope, ancestorGen);
|
||||||
|
|
||||||
|
// calculate available node count
|
||||||
|
Node scopeNode = getNode(finalScope);
|
||||||
|
int availableNodes = getAvailableNodesCount(
|
||||||
|
scopeNode.getNetworkFullPath(), excludedScope, mutableExNodes,
|
||||||
|
ancestorGen);
|
||||||
|
|
||||||
|
if (availableNodes <= 0) {
|
||||||
|
LOG.warn("No available node in (scope=\"{}\" excludedScope=\"{}\" " +
|
||||||
|
"excludedNodes=\"{}\" ancestorGen=\"{}\").",
|
||||||
|
scopeNode.getNetworkFullPath(), excludedScope, excludedNodes,
|
||||||
|
ancestorGen);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
LOG.debug("Choosing random from \"{}\" available nodes on node \"{}\"," +
|
||||||
|
" scope=\"{}\", excludedScope=\"{}\", excludeNodes=\"{}\".",
|
||||||
|
availableNodes, scopeNode, scopeNode.getNetworkFullPath(),
|
||||||
|
excludedScope, excludedNodes);
|
||||||
|
|
||||||
|
// scope is a Leaf node
|
||||||
|
if (!(scopeNode instanceof InnerNode)) {
|
||||||
|
return scopeNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node ret;
|
||||||
|
if (leafIndex >= 0) {
|
||||||
|
ret = ((InnerNode)scopeNode).getLeaf(leafIndex % availableNodes,
|
||||||
|
excludedScope, mutableExNodes, ancestorGen);
|
||||||
|
} else {
|
||||||
|
final int index = ThreadLocalRandom.current().nextInt(availableNodes);
|
||||||
|
ret = ((InnerNode)scopeNode).getLeaf(index, excludedScope, mutableExNodes,
|
||||||
|
ancestorGen);
|
||||||
|
}
|
||||||
|
LOG.debug("chooseRandom return {}", ret);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
int cost = 0;
|
||||||
|
netlock.readLock().lock();
|
||||||
|
try {
|
||||||
|
if (node1 == null || node2 == null ||
|
||||||
|
(node1.getAncestor(maxLevel - 1) != clusterTree) ||
|
||||||
|
(node2.getAncestor(maxLevel - 1) != clusterTree)) {
|
||||||
|
LOG.warn("One of the nodes is a null pointer");
|
||||||
|
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 void sortByDistanceCost(Node reader, Node[] nodes, int activeLen) {
|
||||||
|
/** Sort weights for the nodes array */
|
||||||
|
int[] costs = new int[activeLen];
|
||||||
|
for (int i = 0; i < activeLen; i++) {
|
||||||
|
costs[i] = getDistanceCost(reader, nodes[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[i];
|
||||||
|
List<Node> list = tree.get(cost);
|
||||||
|
if (list == null) {
|
||||||
|
list = Lists.newArrayListWithExpectedSize(1);
|
||||||
|
tree.put(cost, list);
|
||||||
|
}
|
||||||
|
list.add(node);
|
||||||
|
}
|
||||||
|
int idx = 0;
|
||||||
|
for (List<Node> list: tree.values()) {
|
||||||
|
if (list != null) {
|
||||||
|
Collections.shuffle(list);
|
||||||
|
for (Node n: list) {
|
||||||
|
nodes[idx] = n;
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Preconditions.checkState(idx == activeLen, "Wrong number of nodes sorted!");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the number of leaves in <i>scope</i> but not in
|
||||||
|
* <i>excludedNodes</i> and <i>excludeScope</i>.
|
||||||
|
* @param scope the scope
|
||||||
|
* @param excludedScope excluded scope
|
||||||
|
* @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, String excludedScope,
|
||||||
|
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 (excludedScope != null) {
|
||||||
|
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("Expected 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 checkExcludedScope(String excludedScope) {
|
||||||
|
if (excludedScope != null &&
|
||||||
|
(excludedScope.startsWith(SCOPE_REVERSE_STR))) {
|
||||||
|
throw new IllegalArgumentException("excludedScope " + excludedScope +
|
||||||
|
" 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) + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The interface defines a node in a network topology.
|
||||||
|
* A node may be a leave representing a data node or an inner
|
||||||
|
* node representing a data center or rack.
|
||||||
|
* Each node has a name and its location in the network is
|
||||||
|
* decided by a string with syntax similar to a file name.
|
||||||
|
* For example, a data node's name is hostname:port# and if it's located at
|
||||||
|
* rack "orange" in data center "dog", the string representation of its
|
||||||
|
* network location will be /dog/orange.
|
||||||
|
*/
|
||||||
|
public interface Node {
|
||||||
|
/** @return the string representation of this node's network location path,
|
||||||
|
* exclude itself. In another words, its parent's full network location */
|
||||||
|
String getNetworkLocation();
|
||||||
|
|
||||||
|
/** @return this node's self name in network topology. This should be node's
|
||||||
|
* IP or hostname.
|
||||||
|
* */
|
||||||
|
String getNetworkName();
|
||||||
|
|
||||||
|
/** @return this node's full path in network topology. It's the concatenation
|
||||||
|
* of location and name.
|
||||||
|
* */
|
||||||
|
String getNetworkFullPath();
|
||||||
|
|
||||||
|
/** @return this node's parent */
|
||||||
|
InnerNode getParent();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set this node's parent.
|
||||||
|
* @param parent the parent
|
||||||
|
*/
|
||||||
|
void setParent(InnerNode parent);
|
||||||
|
|
||||||
|
/** @return this node's ancestor, generation 0 is itself, generation 1 is
|
||||||
|
* node's parent, and so on.*/
|
||||||
|
Node getAncestor(int generation);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return this node's level in the tree.
|
||||||
|
* E.g. the root of a tree returns 1 and root's children return 2
|
||||||
|
*/
|
||||||
|
int getLevel();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set this node's level in the tree.
|
||||||
|
* @param i the level
|
||||||
|
*/
|
||||||
|
void setLevel(int i);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return this node's cost when network traffic go through it.
|
||||||
|
* E.g. the cost of going cross a switch is 1, and cost of going through a
|
||||||
|
* datacenter can be 5.
|
||||||
|
* Be default the cost of leaf datanode is 0, all other node is 1.
|
||||||
|
*/
|
||||||
|
int getCost();
|
||||||
|
|
||||||
|
/** @return the leaf nodes number under this node. */
|
||||||
|
int getNumOfLeaves();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Judge if this node is an ancestor of node <i>n</i>.
|
||||||
|
* Ancestor includes itself and parents case.
|
||||||
|
*
|
||||||
|
* @param n a node
|
||||||
|
* @return true if this node is an ancestor of <i>n</i>
|
||||||
|
*/
|
||||||
|
boolean isAncestor(Node n);
|
||||||
|
}
|
@ -0,0 +1,200 @@
|
|||||||
|
/**
|
||||||
|
* 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.base.Preconditions;
|
||||||
|
|
||||||
|
import static org.apache.hadoop.hdds.scm.net.NetConstants.ROOT;
|
||||||
|
import static org.apache.hadoop.hdds.scm.net.NetConstants.PATH_SEPARATOR_STR;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A thread safe class that implements interface Node.
|
||||||
|
*/
|
||||||
|
public class NodeImpl implements Node {
|
||||||
|
// host:port#
|
||||||
|
private final String name;
|
||||||
|
// string representation of this node's location, such as /dc1/rack1
|
||||||
|
private final String location;
|
||||||
|
// location + "/" + name
|
||||||
|
private final String path;
|
||||||
|
// which level of the tree the node resides, start from 1 for root
|
||||||
|
private int level;
|
||||||
|
// node's parent
|
||||||
|
private InnerNode parent;
|
||||||
|
// the cost to go through this node
|
||||||
|
private final int cost;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a node from its name and its location.
|
||||||
|
* @param name this node's name (can be null, must not contain
|
||||||
|
* {@link NetConstants#PATH_SEPARATOR})
|
||||||
|
* @param location this node's location
|
||||||
|
*/
|
||||||
|
public NodeImpl(String name, String location, int cost) {
|
||||||
|
if (name != null && name.contains(PATH_SEPARATOR_STR)) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Network location name:" + name + " should not contain " +
|
||||||
|
PATH_SEPARATOR_STR);
|
||||||
|
}
|
||||||
|
this.name = (name == null) ? ROOT : name;
|
||||||
|
this.location = NetUtils.normalize(location);
|
||||||
|
this.path = this.location.equals(PATH_SEPARATOR_STR) ?
|
||||||
|
this.location + this.name :
|
||||||
|
this.location + PATH_SEPARATOR_STR + this.name;
|
||||||
|
|
||||||
|
this.cost = cost;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a node from its name and its location.
|
||||||
|
*
|
||||||
|
* @param name this node's name (can be null, must not contain
|
||||||
|
* {@link NetConstants#PATH_SEPARATOR})
|
||||||
|
* @param location this node's location
|
||||||
|
* @param parent this node's parent node
|
||||||
|
* @param level this node's level in the tree
|
||||||
|
* @param cost this node's cost if traffic goes through it
|
||||||
|
*/
|
||||||
|
public NodeImpl(String name, String location, InnerNode parent, int level,
|
||||||
|
int cost) {
|
||||||
|
this(name, location, cost);
|
||||||
|
this.parent = parent;
|
||||||
|
this.level = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return this node's name
|
||||||
|
*/
|
||||||
|
public String getNetworkName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return this node's network location
|
||||||
|
*/
|
||||||
|
public String getNetworkLocation() {
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return this node's full path in network topology. It's the concatenation
|
||||||
|
* of location and name.
|
||||||
|
*/
|
||||||
|
public String getNetworkFullPath() {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return this node's parent
|
||||||
|
*/
|
||||||
|
public InnerNode getParent() {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return this node's ancestor, generation 0 is itself, generation 1 is
|
||||||
|
* node's parent, and so on.
|
||||||
|
*/
|
||||||
|
public Node getAncestor(int generation) {
|
||||||
|
Preconditions.checkArgument(generation >= 0);
|
||||||
|
Node current = this;
|
||||||
|
while (generation > 0 && current != null) {
|
||||||
|
current = current.getParent();
|
||||||
|
generation--;
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set this node's parent.
|
||||||
|
*
|
||||||
|
* @param parent the parent
|
||||||
|
*/
|
||||||
|
public void setParent(InnerNode parent) {
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return this node's level in the tree.
|
||||||
|
* E.g. the root of a tree returns 0 and its children return 1
|
||||||
|
*/
|
||||||
|
public int getLevel() {
|
||||||
|
return this.level;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set this node's level in the tree.
|
||||||
|
*
|
||||||
|
* @param level the level
|
||||||
|
*/
|
||||||
|
public void setLevel(int level) {
|
||||||
|
this.level = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return this node's cost when network traffic go through it.
|
||||||
|
* E.g. the cost of going cross a switch is 1, and cost of going through a
|
||||||
|
* datacenter is 5.
|
||||||
|
* Be default the cost of leaf datanode is 0, all other inner node is 1.
|
||||||
|
*/
|
||||||
|
public int getCost() {
|
||||||
|
return this.cost;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return the leaf nodes number under this node. */
|
||||||
|
public int getNumOfLeaves() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this node is an ancestor of node <i>node</i>. Ancestor includes
|
||||||
|
* itself and parents case;
|
||||||
|
* @param node a node
|
||||||
|
* @return true if this node is an ancestor of <i>node</i>
|
||||||
|
*/
|
||||||
|
public boolean isAncestor(Node node) {
|
||||||
|
return this.getNetworkFullPath().equals(PATH_SEPARATOR_STR) ||
|
||||||
|
node.getNetworkLocation().startsWith(this.getNetworkFullPath()) ||
|
||||||
|
node.getNetworkFullPath().equalsIgnoreCase(
|
||||||
|
this.getNetworkFullPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object to) {
|
||||||
|
if (to == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this == to) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return this.toString().equals(to.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return toString().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return this node's path as its string representation
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getNetworkFullPath();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,148 @@
|
|||||||
|
/**
|
||||||
|
* 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 org.apache.hadoop.HadoopIllegalArgumentException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Network topology schema to housekeeper relevant information.
|
||||||
|
*/
|
||||||
|
public final class NodeSchema {
|
||||||
|
/**
|
||||||
|
* Network topology layer type enum definition.
|
||||||
|
*/
|
||||||
|
public enum LayerType{
|
||||||
|
ROOT("Root", NetConstants.INNER_NODE_COST_DEFAULT),
|
||||||
|
INNER_NODE("InnerNode", NetConstants.INNER_NODE_COST_DEFAULT),
|
||||||
|
LEAF_NODE("Leaf", NetConstants.NODE_COST_DEFAULT);
|
||||||
|
|
||||||
|
private final String description;
|
||||||
|
// default cost
|
||||||
|
private final int cost;
|
||||||
|
|
||||||
|
LayerType(String description, int cost) {
|
||||||
|
this.description = description;
|
||||||
|
this.cost = cost;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCost(){
|
||||||
|
return cost;
|
||||||
|
}
|
||||||
|
public static LayerType getType(String typeStr) {
|
||||||
|
for (LayerType type: LayerType.values()) {
|
||||||
|
if (typeStr.equalsIgnoreCase(type.toString())) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// default cost
|
||||||
|
private final int cost;
|
||||||
|
// layer Type, mandatory property
|
||||||
|
private final LayerType type;
|
||||||
|
// default name, can be null or ""
|
||||||
|
private final String defaultName;
|
||||||
|
// layer prefix, can be null or ""
|
||||||
|
private final String prefix;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder for NodeSchema.
|
||||||
|
*/
|
||||||
|
public static class Builder {
|
||||||
|
private int cost = -1;
|
||||||
|
private LayerType type;
|
||||||
|
private String defaultName;
|
||||||
|
private String prefix;
|
||||||
|
|
||||||
|
public Builder setCost(int nodeCost) {
|
||||||
|
this.cost = nodeCost;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setPrefix(String nodePrefix) {
|
||||||
|
this.prefix = nodePrefix;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setType(LayerType nodeType) {
|
||||||
|
this.type = nodeType;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setDefaultName(String nodeDefaultName) {
|
||||||
|
this.defaultName = nodeDefaultName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NodeSchema build() {
|
||||||
|
if (type == null) {
|
||||||
|
throw new HadoopIllegalArgumentException("Type is mandatory for a " +
|
||||||
|
"network topology node layer definition");
|
||||||
|
}
|
||||||
|
if (cost == -1) {
|
||||||
|
cost = type.getCost();
|
||||||
|
}
|
||||||
|
return new NodeSchema(type, cost, prefix, defaultName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
* @param type layer type
|
||||||
|
* @param cost layer's default cost
|
||||||
|
* @param prefix layer's prefix
|
||||||
|
* @param defaultName layer's default name is if specified
|
||||||
|
*/
|
||||||
|
public NodeSchema(LayerType type, int cost, String prefix,
|
||||||
|
String defaultName) {
|
||||||
|
this.type = type;
|
||||||
|
this.cost = cost;
|
||||||
|
this.prefix = prefix;
|
||||||
|
this.defaultName = defaultName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean matchPrefix(String name) {
|
||||||
|
if (name == null || name.isEmpty() || prefix == null || prefix.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return name.trim().toLowerCase().startsWith(prefix.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
public LayerType getType() {
|
||||||
|
return this.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPrefix() {
|
||||||
|
return this.prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDefaultName() {
|
||||||
|
return this.defaultName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCost() {
|
||||||
|
return this.cost;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,388 @@
|
|||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* <p>
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* <p>
|
||||||
|
* 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 org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
import org.w3c.dom.Text;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.hadoop.hdds.scm.net.NodeSchema.LayerType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Network topology layer schema loading tool that loads user defined network
|
||||||
|
* layer schema data from a XML configuration file.
|
||||||
|
*/
|
||||||
|
public final class NodeSchemaLoader {
|
||||||
|
private static final Logger LOG
|
||||||
|
= LoggerFactory.getLogger(NodeSchemaLoader.class);
|
||||||
|
private static final String CONFIGURATION_TAG = "configuration";
|
||||||
|
private static final String LAYOUT_VERSION_TAG = "layoutversion";
|
||||||
|
private static final String TOPOLOGY_TAG = "topology";
|
||||||
|
private static final String TOPOLOGY_PATH = "path";
|
||||||
|
private static final String TOPOLOGY_ENFORCE_PREFIX = "enforceprefix";
|
||||||
|
private static final String LAYERS_TAG = "layers";
|
||||||
|
private static final String LAYER_TAG = "layer";
|
||||||
|
private static final String LAYER_ID = "id";
|
||||||
|
private static final String LAYER_TYPE = "type";
|
||||||
|
private static final String LAYER_COST = "cost";
|
||||||
|
private static final String LAYER_PREFIX = "prefix";
|
||||||
|
private static final String LAYER_DEFAULT_NAME = "default";
|
||||||
|
|
||||||
|
private static final int LAYOUT_VERSION = 1;
|
||||||
|
private volatile static NodeSchemaLoader instance = null;
|
||||||
|
private NodeSchemaLoader() {}
|
||||||
|
|
||||||
|
public static NodeSchemaLoader getInstance() {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new NodeSchemaLoader();
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to house keep the result of parsing a network topology schema file.
|
||||||
|
*/
|
||||||
|
public static class NodeSchemaLoadResult {
|
||||||
|
private List<NodeSchema> schemaList;
|
||||||
|
private boolean enforcePrefix;
|
||||||
|
|
||||||
|
NodeSchemaLoadResult(List<NodeSchema> schemaList, boolean enforcePrefix) {
|
||||||
|
this.schemaList = schemaList;
|
||||||
|
this.enforcePrefix = enforcePrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnforePrefix() {
|
||||||
|
return enforcePrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<NodeSchema> getSchemaList() {
|
||||||
|
return schemaList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load user defined network layer schemas from a XML configuration file.
|
||||||
|
* @param schemaFilePath path of schema file
|
||||||
|
* @return all valid node schemas defined in schema file
|
||||||
|
*/
|
||||||
|
public NodeSchemaLoadResult loadSchemaFromFile(String schemaFilePath)
|
||||||
|
throws IllegalArgumentException {
|
||||||
|
try {
|
||||||
|
File schemaFile = new File(schemaFilePath);
|
||||||
|
if (!schemaFile.exists()) {
|
||||||
|
String msg = "Network topology layer schema file " + schemaFilePath +
|
||||||
|
" is not found.";
|
||||||
|
LOG.warn(msg);
|
||||||
|
throw new IllegalArgumentException(msg);
|
||||||
|
}
|
||||||
|
return loadSchema(schemaFile);
|
||||||
|
} catch (ParserConfigurationException | IOException | SAXException e) {
|
||||||
|
throw new IllegalArgumentException("Fail to load network topology node"
|
||||||
|
+ " schema file: " + schemaFilePath + " , error:" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load network topology layer schemas from a XML configuration file.
|
||||||
|
* @param schemaFile schema file
|
||||||
|
* @return all valid node schemas defined in schema file
|
||||||
|
* @throws ParserConfigurationException ParserConfigurationException happen
|
||||||
|
* @throws IOException no such schema file
|
||||||
|
* @throws SAXException xml file has some invalid elements
|
||||||
|
* @throws IllegalArgumentException xml file content is logically invalid
|
||||||
|
*/
|
||||||
|
private NodeSchemaLoadResult loadSchema(File schemaFile) throws
|
||||||
|
ParserConfigurationException, SAXException, IOException {
|
||||||
|
LOG.info("Loading network topology layer schema file " + schemaFile);
|
||||||
|
// Read and parse the schema file.
|
||||||
|
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||||
|
dbf.setIgnoringComments(true);
|
||||||
|
DocumentBuilder builder = dbf.newDocumentBuilder();
|
||||||
|
Document doc = builder.parse(schemaFile);
|
||||||
|
Element root = doc.getDocumentElement();
|
||||||
|
|
||||||
|
if (!CONFIGURATION_TAG.equals(root.getTagName())) {
|
||||||
|
throw new IllegalArgumentException("Bad network topology layer schema " +
|
||||||
|
"configuration file: top-level element not <" + CONFIGURATION_TAG +
|
||||||
|
">");
|
||||||
|
}
|
||||||
|
NodeSchemaLoadResult schemaList;
|
||||||
|
if (root.getElementsByTagName(LAYOUT_VERSION_TAG).getLength() == 1) {
|
||||||
|
if (loadLayoutVersion(root) == LAYOUT_VERSION) {
|
||||||
|
if (root.getElementsByTagName(LAYERS_TAG).getLength() == 1) {
|
||||||
|
Map<String, NodeSchema> schemas = loadLayersSection(root);
|
||||||
|
if (root.getElementsByTagName(TOPOLOGY_TAG).getLength() == 1) {
|
||||||
|
schemaList = loadTopologySection(root, schemas);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Bad network topology layer " +
|
||||||
|
"schema configuration file: no or multiple <" + TOPOLOGY_TAG +
|
||||||
|
"> element");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Bad network topology layer schema"
|
||||||
|
+ " configuration file: no or multiple <" + LAYERS_TAG +
|
||||||
|
">element");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("The parse failed because of bad "
|
||||||
|
+ LAYOUT_VERSION_TAG + " value, expected:" + LAYOUT_VERSION);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Bad network topology layer schema " +
|
||||||
|
"configuration file: no or multiple <" + LAYOUT_VERSION_TAG +
|
||||||
|
"> elements");
|
||||||
|
}
|
||||||
|
return schemaList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load layoutVersion from root element in the XML configuration file.
|
||||||
|
* @param root root element
|
||||||
|
* @return layout version
|
||||||
|
*/
|
||||||
|
private int loadLayoutVersion(Element root) {
|
||||||
|
int layoutVersion;
|
||||||
|
Text text = (Text) root.getElementsByTagName(LAYOUT_VERSION_TAG)
|
||||||
|
.item(0).getFirstChild();
|
||||||
|
if (text != null) {
|
||||||
|
String value = text.getData().trim();
|
||||||
|
try {
|
||||||
|
layoutVersion = Integer.parseInt(value);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new IllegalArgumentException("Bad " + LAYOUT_VERSION_TAG +
|
||||||
|
" value " + value + " is found. It should be an integer.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Value of <" + LAYOUT_VERSION_TAG +
|
||||||
|
"> is null");
|
||||||
|
}
|
||||||
|
return layoutVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load layers from root element in the XML configuration file.
|
||||||
|
* @param root root element
|
||||||
|
* @return A map of node schemas with layer ID and layer schema
|
||||||
|
*/
|
||||||
|
private Map<String, NodeSchema> loadLayersSection(Element root) {
|
||||||
|
NodeList elements = root.getElementsByTagName(LAYER_TAG);
|
||||||
|
Map<String, NodeSchema> schemas = new HashMap<String, NodeSchema>();
|
||||||
|
for (int i = 0; i < elements.getLength(); i++) {
|
||||||
|
Node node = elements.item(i);
|
||||||
|
if (node instanceof Element) {
|
||||||
|
Element element = (Element) node;
|
||||||
|
if (LAYER_TAG.equals(element.getTagName())) {
|
||||||
|
String layerId = element.getAttribute(LAYER_ID);
|
||||||
|
NodeSchema schema = parseLayerElement(element);
|
||||||
|
if (!schemas.containsValue(schema)) {
|
||||||
|
schemas.put(layerId, schema);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Repetitive layer in network " +
|
||||||
|
"topology node schema configuration file: " + layerId);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Bad element in network topology "
|
||||||
|
+ "node schema configuration file: " + element.getTagName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Integrity check, only one ROOT and one LEAF is allowed
|
||||||
|
boolean foundRoot = false;
|
||||||
|
boolean foundLeaf = false;
|
||||||
|
for(NodeSchema schema: schemas.values()) {
|
||||||
|
if (schema.getType() == LayerType.ROOT) {
|
||||||
|
if (foundRoot) {
|
||||||
|
throw new IllegalArgumentException("Multiple ROOT layers are found" +
|
||||||
|
" in network topology schema configuration file");
|
||||||
|
} else {
|
||||||
|
foundRoot = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (schema.getType() == LayerType.LEAF_NODE) {
|
||||||
|
if (foundLeaf) {
|
||||||
|
throw new IllegalArgumentException("Multiple LEAF layers are found" +
|
||||||
|
" in network topology schema configuration file");
|
||||||
|
} else {
|
||||||
|
foundLeaf = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!foundRoot) {
|
||||||
|
throw new IllegalArgumentException("No ROOT layer is found" +
|
||||||
|
" in network topology schema configuration file");
|
||||||
|
}
|
||||||
|
if (!foundLeaf) {
|
||||||
|
throw new IllegalArgumentException("No LEAF layer is found" +
|
||||||
|
" in network topology schema configuration file");
|
||||||
|
}
|
||||||
|
return schemas;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load network topology from root element in the XML configuration file and
|
||||||
|
* sort node schemas according to the topology path.
|
||||||
|
* @param root root element
|
||||||
|
* @param schemas schema map
|
||||||
|
* @return all valid node schemas defined in schema file
|
||||||
|
*/
|
||||||
|
private NodeSchemaLoadResult loadTopologySection(Element root,
|
||||||
|
Map<String, NodeSchema> schemas) {
|
||||||
|
NodeList elements = root.getElementsByTagName(TOPOLOGY_TAG)
|
||||||
|
.item(0).getChildNodes();
|
||||||
|
List<NodeSchema> schemaList = new ArrayList<NodeSchema>();
|
||||||
|
boolean enforecePrefix = false;
|
||||||
|
for (int i = 0; i < elements.getLength(); i++) {
|
||||||
|
Node node = elements.item(i);
|
||||||
|
if (node instanceof Element) {
|
||||||
|
Element element = (Element) node;
|
||||||
|
String tagName = element.getTagName();
|
||||||
|
// Get the nonnull text value.
|
||||||
|
Text text = (Text) element.getFirstChild();
|
||||||
|
String value;
|
||||||
|
if (text != null) {
|
||||||
|
value = text.getData().trim();
|
||||||
|
if (value.isEmpty()) {
|
||||||
|
// Element with empty value is ignored
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Value of <" + tagName
|
||||||
|
+ "> is null");
|
||||||
|
}
|
||||||
|
if (TOPOLOGY_PATH.equals(tagName)) {
|
||||||
|
if(value.startsWith(NetConstants.PATH_SEPARATOR_STR)) {
|
||||||
|
value = value.substring(1, value.length());
|
||||||
|
}
|
||||||
|
String[] layerIDs = value.split(NetConstants.PATH_SEPARATOR_STR);
|
||||||
|
if (layerIDs == null || layerIDs.length != schemas.size()) {
|
||||||
|
throw new IllegalArgumentException("Topology path depth doesn't "
|
||||||
|
+ "match layer element numbers");
|
||||||
|
}
|
||||||
|
for (int j = 0; j < layerIDs.length; j++) {
|
||||||
|
if (schemas.get(layerIDs[j]) == null) {
|
||||||
|
throw new IllegalArgumentException("No layer found for id " +
|
||||||
|
layerIDs[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (schemas.get(layerIDs[0]).getType() != LayerType.ROOT) {
|
||||||
|
throw new IllegalArgumentException("Topology path doesn't start "
|
||||||
|
+ "with ROOT layer");
|
||||||
|
}
|
||||||
|
if (schemas.get(layerIDs[layerIDs.length -1]).getType() !=
|
||||||
|
LayerType.LEAF_NODE) {
|
||||||
|
throw new IllegalArgumentException("Topology path doesn't end "
|
||||||
|
+ "with LEAF layer");
|
||||||
|
}
|
||||||
|
for (int j = 0; j < layerIDs.length; j++) {
|
||||||
|
schemaList.add(schemas.get(layerIDs[j]));
|
||||||
|
}
|
||||||
|
} else if (TOPOLOGY_ENFORCE_PREFIX.equalsIgnoreCase(tagName)) {
|
||||||
|
enforecePrefix = Boolean.parseBoolean(value);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Unsupported Element <" +
|
||||||
|
tagName + ">");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Integrity check
|
||||||
|
if (enforecePrefix) {
|
||||||
|
// Every InnerNode should have prefix defined
|
||||||
|
for (NodeSchema schema: schemas.values()) {
|
||||||
|
if (schema.getType() == LayerType.INNER_NODE &&
|
||||||
|
schema.getPrefix() == null) {
|
||||||
|
throw new IllegalArgumentException("There is layer without prefix " +
|
||||||
|
"defined while prefix is enforced.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new NodeSchemaLoadResult(schemaList, enforecePrefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a layer from a layer element in the XML configuration file.
|
||||||
|
* @param element network topology node layer element
|
||||||
|
* @return ECSchema
|
||||||
|
*/
|
||||||
|
private NodeSchema parseLayerElement(Element element) {
|
||||||
|
NodeList fields = element.getChildNodes();
|
||||||
|
LayerType type = null;
|
||||||
|
int cost = 0;
|
||||||
|
String prefix = null;
|
||||||
|
String defaultName = null;
|
||||||
|
for (int i = 0; i < fields.getLength(); i++) {
|
||||||
|
Node fieldNode = fields.item(i);
|
||||||
|
if (fieldNode instanceof Element) {
|
||||||
|
Element field = (Element) fieldNode;
|
||||||
|
String tagName = field.getTagName();
|
||||||
|
// Get the nonnull text value.
|
||||||
|
Text text = (Text) field.getFirstChild();
|
||||||
|
String value;
|
||||||
|
if (text != null) {
|
||||||
|
value = text.getData().trim();
|
||||||
|
if (value.isEmpty()) {
|
||||||
|
// Element with empty value is ignored
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (LAYER_COST.equalsIgnoreCase(tagName)) {
|
||||||
|
cost = Integer.parseInt(value);
|
||||||
|
if (cost < 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Cost should be positive number or 0");
|
||||||
|
}
|
||||||
|
} else if (LAYER_TYPE.equalsIgnoreCase(tagName)) {
|
||||||
|
type = NodeSchema.LayerType.getType(value);
|
||||||
|
if (type == null) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Unsupported layer type:" + value);
|
||||||
|
}
|
||||||
|
} else if (LAYER_PREFIX.equalsIgnoreCase(tagName)) {
|
||||||
|
prefix = value;
|
||||||
|
} else if (LAYER_DEFAULT_NAME.equalsIgnoreCase(tagName)) {
|
||||||
|
defaultName = value;
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Unsupported Element <" + tagName
|
||||||
|
+ ">");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// type is a mandatory property
|
||||||
|
if (type == null) {
|
||||||
|
throw new IllegalArgumentException("Missing type Element");
|
||||||
|
}
|
||||||
|
return new NodeSchema(type, cost, prefix, defaultName);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,136 @@
|
|||||||
|
/**
|
||||||
|
* 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 org.apache.hadoop.hdds.scm.ScmConfigKeys;
|
||||||
|
import org.apache.hadoop.hdds.scm.net.NodeSchemaLoader.NodeSchemaLoadResult;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/** The class manages all network topology schemas. */
|
||||||
|
|
||||||
|
public final class NodeSchemaManager {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(
|
||||||
|
NodeSchemaManager.class);
|
||||||
|
|
||||||
|
// All schema saved and sorted from ROOT to LEAF node
|
||||||
|
private List<NodeSchema> allSchema;
|
||||||
|
// enforcePrefix only applies to INNER_NODE
|
||||||
|
private boolean enforcePrefix;
|
||||||
|
// max level, includes ROOT level
|
||||||
|
private int maxLevel = -1;
|
||||||
|
|
||||||
|
private volatile static NodeSchemaManager instance = null;
|
||||||
|
|
||||||
|
private NodeSchemaManager() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NodeSchemaManager getInstance() {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new NodeSchemaManager();
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void init(Configuration conf) {
|
||||||
|
/**
|
||||||
|
* Load schemas from network topology schema configuration file
|
||||||
|
*/
|
||||||
|
String schemaFile = conf.get(
|
||||||
|
ScmConfigKeys.OZONE_SCM_NETWORK_TOPOLOGY_SCHEMA_FILE,
|
||||||
|
ScmConfigKeys.OZONE_SCM_NETWORK_TOPOLOGY_SCHEMA_FILE_DEFAULT);
|
||||||
|
|
||||||
|
NodeSchemaLoadResult result;
|
||||||
|
try {
|
||||||
|
result = NodeSchemaLoader.getInstance().loadSchemaFromFile(schemaFile);
|
||||||
|
allSchema = result.getSchemaList();
|
||||||
|
enforcePrefix = result.isEnforePrefix();
|
||||||
|
maxLevel = allSchema.size();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
String msg = "Fail to load schema file:" + schemaFile
|
||||||
|
+ ", error:" + e.getMessage();
|
||||||
|
LOG.error(msg);
|
||||||
|
throw new RuntimeException(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public void init(NodeSchema[] schemas, boolean enforce) {
|
||||||
|
allSchema = new ArrayList<>();
|
||||||
|
allSchema.addAll(Arrays.asList(schemas));
|
||||||
|
enforcePrefix = enforce;
|
||||||
|
maxLevel = schemas.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxLevel() {
|
||||||
|
return maxLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCost(int level) {
|
||||||
|
Preconditions.checkArgument(level <= maxLevel &&
|
||||||
|
level >= (NetConstants.ROOT_LEVEL));
|
||||||
|
return allSchema.get(level - NetConstants.ROOT_LEVEL).getCost();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a incomplete network path, return its complete network path if
|
||||||
|
* possible. E.g. input is 'node1', output is '/rack-default/node1' if this
|
||||||
|
* schema manages ROOT, RACK and LEAF, with prefix defined and enforce prefix
|
||||||
|
* enabled.
|
||||||
|
*
|
||||||
|
* @param path the incomplete input path
|
||||||
|
* @return complete path, null if cannot carry out complete action or action
|
||||||
|
* failed
|
||||||
|
*/
|
||||||
|
public String complete(String path) {
|
||||||
|
if (!enforcePrefix) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String normalizedPath = NetUtils.normalize(path);
|
||||||
|
String[] subPath = normalizedPath.split(NetConstants.PATH_SEPARATOR_STR);
|
||||||
|
if ((subPath.length) == maxLevel) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
StringBuffer newPath = new StringBuffer(NetConstants.ROOT);
|
||||||
|
// skip the ROOT and LEAF layer
|
||||||
|
int i, j;
|
||||||
|
for (i = 1, j = 1; i < subPath.length && j < (allSchema.size() - 1);) {
|
||||||
|
if (allSchema.get(j).matchPrefix(subPath[i])) {
|
||||||
|
newPath.append(NetConstants.PATH_SEPARATOR_STR + subPath[i]);
|
||||||
|
i++;
|
||||||
|
j++;
|
||||||
|
} else {
|
||||||
|
newPath.append(allSchema.get(j).getDefaultName());
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i == (subPath.length - 1)) {
|
||||||
|
newPath.append(NetConstants.PATH_SEPARATOR_STR + subPath[i]);
|
||||||
|
return newPath.toString();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
/**
|
||||||
|
The network topology supported by Ozone.
|
||||||
|
*/
|
@ -0,0 +1,68 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
<!--
|
||||||
|
This is the default for network topology configuration. It defines
|
||||||
|
level prefix key name, level default cost and max levels.
|
||||||
|
-->
|
||||||
|
<configuration>
|
||||||
|
<!-- The version of network topology configuration file format, it must be an integer -->
|
||||||
|
<layoutversion>1</layoutversion>
|
||||||
|
<layers>
|
||||||
|
<layer id="datacenter">
|
||||||
|
<prefix></prefix>
|
||||||
|
<cost>1</cost>
|
||||||
|
<type>Root</type>
|
||||||
|
</layer>
|
||||||
|
<!-- layer id is only used as the reference internally in this document -->
|
||||||
|
<layer id="rack">
|
||||||
|
<!-- prefix of the name of this layer. For example, if the prefix is "dc", then every
|
||||||
|
name in this layer should start with "dc", such as "dc1", "dc2", otherwise NetworkTopology
|
||||||
|
class should report error when add a node path which does't follow this rule. This field
|
||||||
|
is case insensitive. It is optional and can have empty or "" value, in all these cases
|
||||||
|
prefix check will not be enforced.
|
||||||
|
-->
|
||||||
|
<prefix>rack</prefix>
|
||||||
|
<!-- The default cost of this layer, an positive integer or 0. Can be override by the
|
||||||
|
"${cost}" value in specific path. This field is also optional. When it's not defined,
|
||||||
|
it's value is default "1".
|
||||||
|
-->
|
||||||
|
<cost>1</cost>
|
||||||
|
<!-- Layer type, optional field, case insensitive, default value InnerNode.
|
||||||
|
Current value range : {Root, InnerNode, Leaf}
|
||||||
|
Leaf node can only appear in the end of the "path" field of the "topology" section.
|
||||||
|
Root node is a special node. It doesn't have name. It's represented by "/" at the beginning of the path.
|
||||||
|
-->
|
||||||
|
<type>InnerNode</type>
|
||||||
|
<!-- default name if this layer is missed. Only apply to InnerNode. Ignored for Leaf node and Root. -->
|
||||||
|
<default>/default-rack</default>
|
||||||
|
</layer>
|
||||||
|
<layer id="node">
|
||||||
|
<prefix></prefix>
|
||||||
|
<cost>0</cost>
|
||||||
|
<type>Leaf</type>
|
||||||
|
</layer>
|
||||||
|
</layers>
|
||||||
|
<topology>
|
||||||
|
<path>/datacenter/rack/node</path>
|
||||||
|
<!-- When this field is true, each InnerNode layer should has its prefix defined with not empty value,
|
||||||
|
otherwise the content is not valid. Default value is false.
|
||||||
|
-->
|
||||||
|
<enforceprefix>false</enforceprefix>
|
||||||
|
</topology>
|
||||||
|
</configuration>
|
@ -0,0 +1,74 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
<!--
|
||||||
|
This is the default for network topology configuration. It defines level
|
||||||
|
prefix key name, level default cost and max levels.
|
||||||
|
-->
|
||||||
|
<configuration>
|
||||||
|
<!-- The version of network topology configuration file format, it must be an integer -->
|
||||||
|
<layoutversion>1</layoutversion>
|
||||||
|
<layers>
|
||||||
|
<layer id="datacenter">
|
||||||
|
<prefix></prefix>
|
||||||
|
<cost>1</cost>
|
||||||
|
<type>Root</type>
|
||||||
|
</layer>
|
||||||
|
<!-- layer id is only used as the reference internally in this document -->
|
||||||
|
<layer id="rack">
|
||||||
|
<!-- prefix of the name of this layer. For example, if the prefix is "dc", then every
|
||||||
|
name in this layer should start with "dc", such as "dc1", "dc2", otherwise
|
||||||
|
NetworkTopology class should report error when add a node path which does't follow this
|
||||||
|
rule. This field is case insensitive. It is optional is and can have empty or "" value, in
|
||||||
|
all these cases prefix check will not be enforced.
|
||||||
|
-->
|
||||||
|
<prefix>rack</prefix>
|
||||||
|
<!-- The default cost of this layer, an positive integer or 0. Can be override by the
|
||||||
|
"${cost}" value in specific path. This field is also optional. When it's not defined,
|
||||||
|
it's value is default "1".
|
||||||
|
-->
|
||||||
|
<cost>1</cost>
|
||||||
|
<!-- Layer type, optional field, case insensitive, default value InnerNode.
|
||||||
|
Current value range : {Root, InnerNode, Leaf}
|
||||||
|
Leaf node can only appear in the end of the "path" field of the "topology" section.
|
||||||
|
Root node is a special node. It doesn't have name. It's represented by "/" at the beginning of the path.
|
||||||
|
-->
|
||||||
|
<type>InnerNode</type>
|
||||||
|
<!-- default name if this layer is missed. Only apply to InnerNode. Ignored for Leaf node and Root -->
|
||||||
|
<default>/default-rack</default>
|
||||||
|
</layer>
|
||||||
|
<layer id="nodegroup">
|
||||||
|
<prefix>ng</prefix>
|
||||||
|
<cost>1</cost>
|
||||||
|
<type>InnerNode</type>
|
||||||
|
<default>/default-nodegroup</default>
|
||||||
|
</layer>
|
||||||
|
<layer id="node">
|
||||||
|
<prefix></prefix>
|
||||||
|
<cost>0</cost>
|
||||||
|
<type>Leaf</type>
|
||||||
|
</layer>
|
||||||
|
</layers>
|
||||||
|
<topology>
|
||||||
|
<path>/datacenter/rack/nodegroup/node</path>
|
||||||
|
<!-- When this field is true, each InnerNode layer should has its prefix defined with not empty value,
|
||||||
|
otherwise the content is not valid. Default value is false.
|
||||||
|
-->
|
||||||
|
<enforceprefix>false</enforceprefix>
|
||||||
|
</topology>
|
||||||
|
</configuration>
|
@ -2269,4 +2269,11 @@
|
|||||||
ozone.metadata.dirs.
|
ozone.metadata.dirs.
|
||||||
</description>
|
</description>
|
||||||
</property>
|
</property>
|
||||||
|
<property>
|
||||||
|
<name>ozone.scm.network.topology.schema.file</name>
|
||||||
|
<value>network-topology-default.xm</value>
|
||||||
|
<tag>OZONE, MANAGEMENT</tag>
|
||||||
|
<description>The schema file defines the ozone network topology.
|
||||||
|
</description>
|
||||||
|
</property>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
@ -0,0 +1,922 @@
|
|||||||
|
/**
|
||||||
|
* 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 org.apache.hadoop.conf.Configuration;
|
||||||
|
import static org.apache.hadoop.hdds.scm.net.NetConstants.ROOT;
|
||||||
|
import static org.apache.hadoop.hdds.scm.net.NetConstants.PATH_SEPARATOR_STR;
|
||||||
|
import static org.apache.hadoop.hdds.scm.net.NetConstants.ROOT_SCHEMA;
|
||||||
|
import static org.apache.hadoop.hdds.scm.net.NetConstants.REGION_SCHEMA;
|
||||||
|
import static org.apache.hadoop.hdds.scm.net.NetConstants.DATACENTER_SCHEMA;
|
||||||
|
import static org.apache.hadoop.hdds.scm.net.NetConstants.RACK_SCHEMA;
|
||||||
|
import static org.apache.hadoop.hdds.scm.net.NetConstants.NODEGROUP_SCHEMA;
|
||||||
|
import static org.apache.hadoop.hdds.scm.net.NetConstants.LEAF_SCHEMA;
|
||||||
|
|
||||||
|
import org.apache.hadoop.hdds.scm.ScmConfigKeys;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.Timeout;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
import static org.junit.Assume.assumeTrue;
|
||||||
|
import org.junit.runners.Parameterized;
|
||||||
|
import org.junit.runners.Parameterized.Parameters;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
/** Test the network topology functions. */
|
||||||
|
@RunWith(Parameterized.class)
|
||||||
|
public class TestNetworkTopologyImpl {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(
|
||||||
|
TestNetworkTopologyImpl.class);
|
||||||
|
private NetworkTopology cluster;
|
||||||
|
private Node[] dataNodes;
|
||||||
|
private Random random = new Random();
|
||||||
|
|
||||||
|
public TestNetworkTopologyImpl(NodeSchema[] schemas, Node[] nodeArray) {
|
||||||
|
NodeSchemaManager.getInstance().init(schemas, true);
|
||||||
|
cluster = new NetworkTopologyImpl(NodeSchemaManager.getInstance());
|
||||||
|
dataNodes = nodeArray;
|
||||||
|
for (int i = 0; i < dataNodes.length; i++) {
|
||||||
|
cluster.add(dataNodes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public Timeout testTimeout = new Timeout(3000000);
|
||||||
|
|
||||||
|
@Parameters
|
||||||
|
public static Collection<Object[]> setupDatanodes() {
|
||||||
|
Object[][] topologies = new Object[][]{
|
||||||
|
{new NodeSchema[] {ROOT_SCHEMA, LEAF_SCHEMA},
|
||||||
|
new Node[]{
|
||||||
|
createDatanode("1.1.1.1", "/"),
|
||||||
|
createDatanode("2.2.2.2", "/"),
|
||||||
|
createDatanode("3.3.3.3", "/"),
|
||||||
|
createDatanode("4.4.4.4", "/"),
|
||||||
|
createDatanode("5.5.5.5", "/"),
|
||||||
|
createDatanode("6.6.6.6", "/"),
|
||||||
|
createDatanode("7.7.7.7", "/"),
|
||||||
|
createDatanode("8.8.8.8", "/"),
|
||||||
|
}},
|
||||||
|
{new NodeSchema[] {ROOT_SCHEMA, RACK_SCHEMA, LEAF_SCHEMA},
|
||||||
|
new Node[]{
|
||||||
|
createDatanode("1.1.1.1", "/r1"),
|
||||||
|
createDatanode("2.2.2.2", "/r1"),
|
||||||
|
createDatanode("3.3.3.3", "/r2"),
|
||||||
|
createDatanode("4.4.4.4", "/r2"),
|
||||||
|
createDatanode("5.5.5.5", "/r2"),
|
||||||
|
createDatanode("6.6.6.6", "/r3"),
|
||||||
|
createDatanode("7.7.7.7", "/r3"),
|
||||||
|
createDatanode("8.8.8.8", "/r3"),
|
||||||
|
}},
|
||||||
|
{new NodeSchema[]
|
||||||
|
{ROOT_SCHEMA, DATACENTER_SCHEMA, RACK_SCHEMA, LEAF_SCHEMA},
|
||||||
|
new Node[]{
|
||||||
|
createDatanode("1.1.1.1", "/d1/r1"),
|
||||||
|
createDatanode("2.2.2.2", "/d1/r1"),
|
||||||
|
createDatanode("3.3.3.3", "/d1/r2"),
|
||||||
|
createDatanode("4.4.4.4", "/d1/r2"),
|
||||||
|
createDatanode("5.5.5.5", "/d1/r2"),
|
||||||
|
createDatanode("6.6.6.6", "/d2/r3"),
|
||||||
|
createDatanode("7.7.7.7", "/d2/r3"),
|
||||||
|
createDatanode("8.8.8.8", "/d2/r3"),
|
||||||
|
}},
|
||||||
|
{new NodeSchema[] {ROOT_SCHEMA, DATACENTER_SCHEMA, RACK_SCHEMA,
|
||||||
|
NODEGROUP_SCHEMA, LEAF_SCHEMA},
|
||||||
|
new Node[]{
|
||||||
|
createDatanode("1.1.1.1", "/d1/r1/ng1"),
|
||||||
|
createDatanode("2.2.2.2", "/d1/r1/ng1"),
|
||||||
|
createDatanode("3.3.3.3", "/d1/r2/ng2"),
|
||||||
|
createDatanode("4.4.4.4", "/d1/r2/ng2"),
|
||||||
|
createDatanode("5.5.5.5", "/d1/r2/ng3"),
|
||||||
|
createDatanode("6.6.6.6", "/d2/r3/ng3"),
|
||||||
|
createDatanode("7.7.7.7", "/d2/r3/ng3"),
|
||||||
|
createDatanode("8.8.8.8", "/d2/r3/ng3"),
|
||||||
|
createDatanode("9.9.9.9", "/d3/r1/ng1"),
|
||||||
|
createDatanode("10.10.10.10", "/d3/r1/ng1"),
|
||||||
|
createDatanode("11.11.11.11", "/d3/r1/ng1"),
|
||||||
|
createDatanode("12.12.12.12", "/d3/r2/ng2"),
|
||||||
|
createDatanode("13.13.13.13", "/d3/r2/ng2"),
|
||||||
|
createDatanode("14.14.14.14", "/d4/r1/ng1"),
|
||||||
|
createDatanode("15.15.15.15", "/d4/r1/ng1"),
|
||||||
|
createDatanode("16.16.16.16", "/d4/r1/ng1"),
|
||||||
|
createDatanode("17.17.17.17", "/d4/r1/ng2"),
|
||||||
|
createDatanode("18.18.18.18", "/d4/r1/ng2"),
|
||||||
|
createDatanode("19.19.19.19", "/d4/r1/ng3"),
|
||||||
|
createDatanode("20.20.20.20", "/d4/r1/ng3"),
|
||||||
|
}},
|
||||||
|
{new NodeSchema[] {ROOT_SCHEMA, REGION_SCHEMA, DATACENTER_SCHEMA,
|
||||||
|
RACK_SCHEMA, NODEGROUP_SCHEMA, LEAF_SCHEMA},
|
||||||
|
new Node[]{
|
||||||
|
createDatanode("1.1.1.1", "/d1/rg1/r1/ng1"),
|
||||||
|
createDatanode("2.2.2.2", "/d1/rg1/r1/ng1"),
|
||||||
|
createDatanode("3.3.3.3", "/d1/rg1/r1/ng2"),
|
||||||
|
createDatanode("4.4.4.4", "/d1/rg1/r1/ng1"),
|
||||||
|
createDatanode("5.5.5.5", "/d1/rg1/r1/ng1"),
|
||||||
|
createDatanode("6.6.6.6", "/d1/rg1/r1/ng2"),
|
||||||
|
createDatanode("7.7.7.7", "/d1/rg1/r1/ng2"),
|
||||||
|
createDatanode("8.8.8.8", "/d1/rg1/r1/ng2"),
|
||||||
|
createDatanode("9.9.9.9", "/d1/rg1/r1/ng2"),
|
||||||
|
createDatanode("10.10.10.10", "/d1/rg1/r1/ng2"),
|
||||||
|
createDatanode("11.11.11.11", "/d1/rg1/r2/ng1"),
|
||||||
|
createDatanode("12.12.12.12", "/d1/rg1/r2/ng1"),
|
||||||
|
createDatanode("13.13.13.13", "/d1/rg1/r2/ng1"),
|
||||||
|
createDatanode("14.14.14.14", "/d1/rg1/r2/ng1"),
|
||||||
|
createDatanode("15.15.15.15", "/d1/rg1/r2/ng1"),
|
||||||
|
createDatanode("16.16.16.16", "/d1/rg1/r2/ng2"),
|
||||||
|
createDatanode("17.17.17.17", "/d1/rg1/r2/ng2"),
|
||||||
|
createDatanode("18.18.18.18", "/d1/rg1/r2/ng2"),
|
||||||
|
createDatanode("19.19.19.19", "/d1/rg1/r2/ng2"),
|
||||||
|
createDatanode("20.20.20.20", "/d1/rg1/r2/ng2"),
|
||||||
|
createDatanode("21.21.21.21", "/d2/rg1/r2/ng1"),
|
||||||
|
createDatanode("22.22.22.22", "/d2/rg1/r2/ng1"),
|
||||||
|
createDatanode("23.23.23.23", "/d2/rg2/r2/ng1"),
|
||||||
|
createDatanode("24.24.24.24", "/d2/rg2/r2/ng1"),
|
||||||
|
createDatanode("25.25.25.25", "/d2/rg2/r2/ng1"),
|
||||||
|
}}
|
||||||
|
};
|
||||||
|
return Arrays.asList(topologies);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testContains() {
|
||||||
|
Node nodeNotInMap = createDatanode("8.8.8.8", "/d2/r4");
|
||||||
|
for (int i=0; i < dataNodes.length; i++) {
|
||||||
|
assertTrue(cluster.contains(dataNodes[i]));
|
||||||
|
}
|
||||||
|
assertFalse(cluster.contains(nodeNotInMap));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNumOfChildren() {
|
||||||
|
assertEquals(dataNodes.length, cluster.getNumOfLeafNode(null));
|
||||||
|
assertEquals(0, cluster.getNumOfLeafNode("/switch1/node1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetNode() {
|
||||||
|
assertEquals(cluster.getNode(""), cluster.getNode(null));
|
||||||
|
assertEquals(cluster.getNode(""), cluster.getNode("/"));
|
||||||
|
assertEquals(null, cluster.getNode("/switch1/node1"));
|
||||||
|
assertEquals(null, cluster.getNode("/switch1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateInvalidTopology() {
|
||||||
|
List<NodeSchema> schemas = new ArrayList<NodeSchema>();
|
||||||
|
schemas.add(ROOT_SCHEMA);
|
||||||
|
schemas.add(RACK_SCHEMA);
|
||||||
|
schemas.add(LEAF_SCHEMA);
|
||||||
|
NodeSchemaManager.getInstance().init(schemas.toArray(new NodeSchema[0]),
|
||||||
|
true);
|
||||||
|
NetworkTopology newCluster = new NetworkTopologyImpl(
|
||||||
|
NodeSchemaManager.getInstance());
|
||||||
|
Node[] invalidDataNodes = new Node[] {
|
||||||
|
createDatanode("1.1.1.1", "/r1"),
|
||||||
|
createDatanode("2.2.2.2", "/r2"),
|
||||||
|
createDatanode("3.3.3.3", "/d1/r2")
|
||||||
|
};
|
||||||
|
newCluster.add(invalidDataNodes[0]);
|
||||||
|
newCluster.add(invalidDataNodes[1]);
|
||||||
|
try {
|
||||||
|
newCluster.add(invalidDataNodes[2]);
|
||||||
|
fail("expected InvalidTopologyException");
|
||||||
|
} catch (NetworkTopology.InvalidTopologyException e) {
|
||||||
|
assertTrue(e.getMessage().contains("Failed to add"));
|
||||||
|
assertTrue(e.getMessage().contains("Its path depth is not " +
|
||||||
|
newCluster.getMaxLevel()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInitWithConfigFile() {
|
||||||
|
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||||
|
Configuration conf = new Configuration();
|
||||||
|
try {
|
||||||
|
String filePath = classLoader.getResource(
|
||||||
|
"./networkTopologyTestFiles/good.xml").getPath();
|
||||||
|
conf.set(ScmConfigKeys.OZONE_SCM_NETWORK_TOPOLOGY_SCHEMA_FILE, filePath);
|
||||||
|
NetworkTopology newCluster = new NetworkTopologyImpl(conf);
|
||||||
|
LOG.info("network topology max level = " + newCluster.getMaxLevel());
|
||||||
|
} catch (Throwable e) {
|
||||||
|
fail("should succeed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAncestor() {
|
||||||
|
assumeTrue(cluster.getMaxLevel() > 2);
|
||||||
|
int maxLevel = cluster.getMaxLevel();
|
||||||
|
assertTrue(cluster.isSameParent(dataNodes[0], dataNodes[1]));
|
||||||
|
while(maxLevel > 1) {
|
||||||
|
assertTrue(cluster.isSameAncestor(dataNodes[0], dataNodes[1],
|
||||||
|
maxLevel - 1));
|
||||||
|
maxLevel--;
|
||||||
|
}
|
||||||
|
assertFalse(cluster.isSameParent(dataNodes[1], dataNodes[2]));
|
||||||
|
assertFalse(cluster.isSameParent(null, dataNodes[2]));
|
||||||
|
assertFalse(cluster.isSameParent(dataNodes[1], null));
|
||||||
|
assertFalse(cluster.isSameParent(null, null));
|
||||||
|
|
||||||
|
assertFalse(cluster.isSameAncestor(dataNodes[1], dataNodes[2], 0));
|
||||||
|
assertFalse(cluster.isSameAncestor(dataNodes[1], null, 1));
|
||||||
|
assertFalse(cluster.isSameAncestor(null, dataNodes[2], 1));
|
||||||
|
assertFalse(cluster.isSameAncestor(null, null, 1));
|
||||||
|
|
||||||
|
maxLevel = cluster.getMaxLevel();
|
||||||
|
assertTrue(cluster.isSameAncestor(
|
||||||
|
dataNodes[random.nextInt(cluster.getNumOfLeafNode(null))],
|
||||||
|
dataNodes[random.nextInt(cluster.getNumOfLeafNode(null))],
|
||||||
|
maxLevel - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddRemove() {
|
||||||
|
for(int i = 0; i < dataNodes.length; i++) {
|
||||||
|
cluster.remove(dataNodes[i]);
|
||||||
|
}
|
||||||
|
for(int i = 0; i < dataNodes.length; i++) {
|
||||||
|
assertFalse(cluster.contains(dataNodes[i]));
|
||||||
|
}
|
||||||
|
// no leaf nodes
|
||||||
|
assertEquals(0, cluster.getNumOfLeafNode(null));
|
||||||
|
// no inner nodes
|
||||||
|
assertEquals(0, cluster.getNumOfNodes(2));
|
||||||
|
for(int i = 0; i < dataNodes.length; i++) {
|
||||||
|
cluster.add(dataNodes[i]);
|
||||||
|
}
|
||||||
|
// Inner nodes are created automatically
|
||||||
|
assertTrue(cluster.getNumOfNodes(2) > 0);
|
||||||
|
|
||||||
|
try {
|
||||||
|
cluster.add(cluster.chooseRandom(null).getParent());
|
||||||
|
fail("Inner node can not be added manually");
|
||||||
|
} catch (Exception e) {
|
||||||
|
assertTrue(e.getMessage().startsWith(
|
||||||
|
"Not allowed to add an inner node"));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
cluster.remove(cluster.chooseRandom(null).getParent());
|
||||||
|
fail("Inner node can not be removed manually");
|
||||||
|
} catch (Exception e) {
|
||||||
|
assertTrue(e.getMessage().startsWith(
|
||||||
|
"Not allowed to remove an inner node"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetNodesWithLevel() {
|
||||||
|
int maxLevel = cluster.getMaxLevel();
|
||||||
|
try {
|
||||||
|
assertEquals(1, cluster.getNumOfNodes(0));
|
||||||
|
fail("level 0 is not supported");
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
assertTrue(e.getMessage().startsWith("Invalid level"));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
assertEquals(1, cluster.getNumOfNodes(0));
|
||||||
|
fail("level 0 is not supported");
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
assertTrue(e.getMessage().startsWith("Invalid level"));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
assertEquals(1, cluster.getNumOfNodes(maxLevel + 1));
|
||||||
|
fail("level out of scope");
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
assertTrue(e.getMessage().startsWith("Invalid level"));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
assertEquals(1, cluster.getNumOfNodes(maxLevel + 1));
|
||||||
|
fail("level out of scope");
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
assertTrue(e.getMessage().startsWith("Invalid level"));
|
||||||
|
}
|
||||||
|
// root node
|
||||||
|
assertEquals(1, cluster.getNumOfNodes(1));
|
||||||
|
assertEquals(1, cluster.getNumOfNodes(1));
|
||||||
|
// leaf nodes
|
||||||
|
assertEquals(dataNodes.length, cluster.getNumOfNodes(maxLevel));
|
||||||
|
assertEquals(dataNodes.length, cluster.getNumOfNodes(maxLevel));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testChooseRandomSimple() {
|
||||||
|
String path =
|
||||||
|
dataNodes[random.nextInt(dataNodes.length)].getNetworkFullPath();
|
||||||
|
assertEquals(path, cluster.chooseRandom(path).getNetworkFullPath());
|
||||||
|
path = path.substring(0, path.lastIndexOf(PATH_SEPARATOR_STR));
|
||||||
|
// test chooseRandom(String scope)
|
||||||
|
while (!path.equals(ROOT)) {
|
||||||
|
assertTrue(cluster.chooseRandom(path).getNetworkLocation()
|
||||||
|
.startsWith(path));
|
||||||
|
Node node = cluster.chooseRandom("~" + path);
|
||||||
|
assertTrue(!node.getNetworkLocation()
|
||||||
|
.startsWith(path));
|
||||||
|
path = path.substring(0,
|
||||||
|
path.lastIndexOf(PATH_SEPARATOR_STR));
|
||||||
|
}
|
||||||
|
assertNotNull(cluster.chooseRandom(null));
|
||||||
|
assertNotNull(cluster.chooseRandom(""));
|
||||||
|
assertNotNull(cluster.chooseRandom("/"));
|
||||||
|
assertNull(cluster.chooseRandom("~"));
|
||||||
|
assertNull(cluster.chooseRandom("~/"));
|
||||||
|
|
||||||
|
// test chooseRandom(String scope, String excludedScope)
|
||||||
|
path = dataNodes[random.nextInt(dataNodes.length)].getNetworkFullPath();
|
||||||
|
assertNull(cluster.chooseRandom(path, path));
|
||||||
|
assertNotNull(cluster.chooseRandom(null, path));
|
||||||
|
assertNotNull(cluster.chooseRandom("", path));
|
||||||
|
|
||||||
|
// test chooseRandom(String scope, Collection<Node> excludedNodes)
|
||||||
|
assertNull(cluster.chooseRandom("", Arrays.asList(dataNodes)));
|
||||||
|
assertNull(cluster.chooseRandom("/", Arrays.asList(dataNodes)));
|
||||||
|
assertNull(cluster.chooseRandom("~", Arrays.asList(dataNodes)));
|
||||||
|
assertNull(cluster.chooseRandom("~/", Arrays.asList(dataNodes)));
|
||||||
|
assertNull(cluster.chooseRandom(null, Arrays.asList(dataNodes)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Following test checks that chooseRandom works for an excluded scope.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testChooseRandomExcludedScope() {
|
||||||
|
int[] excludedNodeIndexs = {0, dataNodes.length - 1,
|
||||||
|
random.nextInt(dataNodes.length), random.nextInt(dataNodes.length)};
|
||||||
|
String scope;
|
||||||
|
Map<Node, Integer> frequency;
|
||||||
|
for (int i : excludedNodeIndexs) {
|
||||||
|
String path = dataNodes[i].getNetworkFullPath();
|
||||||
|
while (!path.equals(ROOT)) {
|
||||||
|
scope = "~" + path;
|
||||||
|
frequency = pickNodesAtRandom(100, scope, null, 0);
|
||||||
|
for (Node key : dataNodes) {
|
||||||
|
if (key.getNetworkFullPath().startsWith(path)) {
|
||||||
|
assertTrue(frequency.get(key) == 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
path = path.substring(0, path.lastIndexOf(PATH_SEPARATOR_STR));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// null excludedScope, every node should be chosen
|
||||||
|
frequency = pickNodes(100, null, null, null, 0);
|
||||||
|
for (Node key : dataNodes) {
|
||||||
|
assertTrue(frequency.get(key) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// "" excludedScope, no node will ever be chosen
|
||||||
|
frequency = pickNodes(100, "", null, null, 0);
|
||||||
|
for (Node key : dataNodes) {
|
||||||
|
assertTrue(frequency.get(key) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// "~" scope, no node will ever be chosen
|
||||||
|
scope = "~";
|
||||||
|
frequency = pickNodesAtRandom(100, scope, null, 0);
|
||||||
|
for (Node key : dataNodes) {
|
||||||
|
assertTrue(frequency.get(key) == 0);
|
||||||
|
}
|
||||||
|
// out network topology excluded scope, every node should be chosen
|
||||||
|
scope = "/city1";
|
||||||
|
frequency = pickNodes(cluster.getNumOfLeafNode(null), scope, null, null, 0);
|
||||||
|
for (Node key : dataNodes) {
|
||||||
|
assertTrue(frequency.get(key) != 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Following test checks that chooseRandom works for an excluded nodes.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testChooseRandomExcludedNode() {
|
||||||
|
Node[][] excludedNodeLists = {
|
||||||
|
{},
|
||||||
|
{dataNodes[0]},
|
||||||
|
{dataNodes[dataNodes.length - 1]},
|
||||||
|
{dataNodes[random.nextInt(dataNodes.length)]},
|
||||||
|
{dataNodes[random.nextInt(dataNodes.length)],
|
||||||
|
dataNodes[random.nextInt(dataNodes.length)]
|
||||||
|
},
|
||||||
|
{dataNodes[random.nextInt(dataNodes.length)],
|
||||||
|
dataNodes[random.nextInt(dataNodes.length)],
|
||||||
|
dataNodes[random.nextInt(dataNodes.length)],
|
||||||
|
}};
|
||||||
|
int leafNum = cluster.getNumOfLeafNode(null);
|
||||||
|
Map<Node, Integer> frequency;
|
||||||
|
for(Node[] list : excludedNodeLists) {
|
||||||
|
List<Node> excludedList = Arrays.asList(list);
|
||||||
|
int ancestorGen = 0;
|
||||||
|
while(ancestorGen < cluster.getMaxLevel()) {
|
||||||
|
frequency = pickNodesAtRandom(leafNum, null, excludedList, ancestorGen);
|
||||||
|
List<Node> ancestorList = NetUtils.getAncestorList(cluster,
|
||||||
|
excludedList, ancestorGen);
|
||||||
|
for (Node key : dataNodes) {
|
||||||
|
if (excludedList.contains(key) ||
|
||||||
|
(ancestorList.size() > 0 &&
|
||||||
|
ancestorList.stream()
|
||||||
|
.map(a -> (InnerNode) a)
|
||||||
|
.filter(a -> a.isAncestor(key))
|
||||||
|
.collect(Collectors.toList()).size() > 0)) {
|
||||||
|
assertTrue(frequency.get(key) == 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ancestorGen++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// all nodes excluded, no node will be picked
|
||||||
|
List<Node> excludedList = Arrays.asList(dataNodes);
|
||||||
|
int ancestorGen = 0;
|
||||||
|
while(ancestorGen < cluster.getMaxLevel()) {
|
||||||
|
frequency = pickNodesAtRandom(leafNum, null, excludedList, ancestorGen);
|
||||||
|
for (Node key : dataNodes) {
|
||||||
|
assertTrue(frequency.get(key) == 0);
|
||||||
|
}
|
||||||
|
ancestorGen++;
|
||||||
|
}
|
||||||
|
// out scope excluded nodes, each node will be picked
|
||||||
|
excludedList = Arrays.asList(createDatanode("1.1.1.1.", "/city1/rack1"));
|
||||||
|
ancestorGen = 0;
|
||||||
|
while(ancestorGen < cluster.getMaxLevel()) {
|
||||||
|
frequency = pickNodes(leafNum, null, excludedList, null, ancestorGen);
|
||||||
|
for (Node key : dataNodes) {
|
||||||
|
assertTrue(frequency.get(key) != 0);
|
||||||
|
}
|
||||||
|
ancestorGen++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Following test checks that chooseRandom works for excluded nodes and scope.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testChooseRandomExcludedNodeAndScope() {
|
||||||
|
int[] excludedNodeIndexs = {0, dataNodes.length - 1,
|
||||||
|
random.nextInt(dataNodes.length), random.nextInt(dataNodes.length)};
|
||||||
|
Node[][] excludedNodeLists = {
|
||||||
|
{},
|
||||||
|
{dataNodes[0]},
|
||||||
|
{dataNodes[dataNodes.length - 1]},
|
||||||
|
{dataNodes[random.nextInt(dataNodes.length)]},
|
||||||
|
{dataNodes[random.nextInt(dataNodes.length)],
|
||||||
|
dataNodes[random.nextInt(dataNodes.length)]
|
||||||
|
},
|
||||||
|
{dataNodes[random.nextInt(dataNodes.length)],
|
||||||
|
dataNodes[random.nextInt(dataNodes.length)],
|
||||||
|
dataNodes[random.nextInt(dataNodes.length)],
|
||||||
|
}};
|
||||||
|
int leafNum = cluster.getNumOfLeafNode(null);
|
||||||
|
Map<Node, Integer> frequency;
|
||||||
|
String scope;
|
||||||
|
for (int i : excludedNodeIndexs) {
|
||||||
|
String path = dataNodes[i].getNetworkFullPath();
|
||||||
|
while (!path.equals(ROOT)) {
|
||||||
|
scope = "~" + path;
|
||||||
|
int ancestorGen = 0;
|
||||||
|
while(ancestorGen < cluster.getMaxLevel()) {
|
||||||
|
for (Node[] list : excludedNodeLists) {
|
||||||
|
List<Node> excludedList = Arrays.asList(list);
|
||||||
|
frequency =
|
||||||
|
pickNodesAtRandom(leafNum, scope, excludedList, ancestorGen);
|
||||||
|
List<Node> ancestorList = NetUtils.getAncestorList(cluster,
|
||||||
|
excludedList, ancestorGen);
|
||||||
|
for (Node key : dataNodes) {
|
||||||
|
if (excludedList.contains(key) ||
|
||||||
|
key.getNetworkFullPath().startsWith(path) ||
|
||||||
|
(ancestorList.size() > 0 &&
|
||||||
|
ancestorList.stream()
|
||||||
|
.map(a -> (InnerNode) a)
|
||||||
|
.filter(a -> a.isAncestor(key))
|
||||||
|
.collect(Collectors.toList()).size() > 0)) {
|
||||||
|
assertTrue(frequency.get(key) == 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ancestorGen++;
|
||||||
|
}
|
||||||
|
path = path.substring(0, path.lastIndexOf(PATH_SEPARATOR_STR));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// all nodes excluded, no node will be picked
|
||||||
|
List<Node> excludedList = Arrays.asList(dataNodes);
|
||||||
|
for (int i : excludedNodeIndexs) {
|
||||||
|
String path = dataNodes[i].getNetworkFullPath();
|
||||||
|
while (!path.equals(ROOT)) {
|
||||||
|
scope = "~" + path;
|
||||||
|
int ancestorGen = 0;
|
||||||
|
while (ancestorGen < cluster.getMaxLevel()) {
|
||||||
|
frequency =
|
||||||
|
pickNodesAtRandom(leafNum, scope, excludedList, ancestorGen);
|
||||||
|
for (Node key : dataNodes) {
|
||||||
|
assertTrue(frequency.get(key) == 0);
|
||||||
|
}
|
||||||
|
ancestorGen++;
|
||||||
|
}
|
||||||
|
path = path.substring(0, path.lastIndexOf(PATH_SEPARATOR_STR));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no node excluded and no excluded scope, each node will be picked
|
||||||
|
int ancestorGen = 0;
|
||||||
|
while (ancestorGen < cluster.getMaxLevel()) {
|
||||||
|
frequency = pickNodes(leafNum, null, null, null, ancestorGen);
|
||||||
|
for (Node key : dataNodes) {
|
||||||
|
assertTrue(frequency.get(key) != 0);
|
||||||
|
}
|
||||||
|
ancestorGen++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Following test checks that chooseRandom works for excluded nodes, scope
|
||||||
|
* and ancestor generation.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testChooseRandomWithAffinityNode() {
|
||||||
|
int[] excludedNodeIndexs = {0, dataNodes.length - 1,
|
||||||
|
random.nextInt(dataNodes.length), random.nextInt(dataNodes.length)};
|
||||||
|
Node[][] excludedNodeLists = {
|
||||||
|
{},
|
||||||
|
{dataNodes[0]},
|
||||||
|
{dataNodes[dataNodes.length - 1]},
|
||||||
|
{dataNodes[random.nextInt(dataNodes.length)]},
|
||||||
|
{dataNodes[random.nextInt(dataNodes.length)],
|
||||||
|
dataNodes[random.nextInt(dataNodes.length)]
|
||||||
|
},
|
||||||
|
{dataNodes[random.nextInt(dataNodes.length)],
|
||||||
|
dataNodes[random.nextInt(dataNodes.length)],
|
||||||
|
dataNodes[random.nextInt(dataNodes.length)],
|
||||||
|
}};
|
||||||
|
int[] affinityNodeIndexs = {0, dataNodes.length - 1,
|
||||||
|
random.nextInt(dataNodes.length), random.nextInt(dataNodes.length)};
|
||||||
|
int leafNum = cluster.getNumOfLeafNode(null);
|
||||||
|
Map<Node, Integer> frequency;
|
||||||
|
String scope;
|
||||||
|
for (int k : affinityNodeIndexs) {
|
||||||
|
for (int i : excludedNodeIndexs) {
|
||||||
|
String path = dataNodes[i].getNetworkFullPath();
|
||||||
|
while (!path.equals(ROOT)) {
|
||||||
|
int ancestorGen = cluster.getMaxLevel() - 1;
|
||||||
|
while (ancestorGen > 0) {
|
||||||
|
for (Node[] list : excludedNodeLists) {
|
||||||
|
List<Node> excludedList = Arrays.asList(list);
|
||||||
|
frequency = pickNodes(leafNum, path, excludedList, dataNodes[k],
|
||||||
|
ancestorGen);
|
||||||
|
Node affinityAncestor = dataNodes[k].getAncestor(ancestorGen);
|
||||||
|
for (Node key : dataNodes) {
|
||||||
|
if (affinityAncestor != null) {
|
||||||
|
if (frequency.get(key) > 0) {
|
||||||
|
assertTrue(affinityAncestor.isAncestor(key));
|
||||||
|
} else if (!affinityAncestor.isAncestor(key)) {
|
||||||
|
continue;
|
||||||
|
} else if (excludedList != null &&
|
||||||
|
excludedList.contains(key)) {
|
||||||
|
continue;
|
||||||
|
} else if (path != null &&
|
||||||
|
key.getNetworkFullPath().startsWith(path)) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
fail("Node is not picked when sequentially going " +
|
||||||
|
"through ancestor node's leaf nodes. node:" +
|
||||||
|
key.getNetworkFullPath() + ", ancestor node:" +
|
||||||
|
affinityAncestor.getNetworkFullPath() +
|
||||||
|
", excludedScope: " + path + ", " + "excludedList:" +
|
||||||
|
(excludedList == null ? "" : excludedList.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ancestorGen--;
|
||||||
|
}
|
||||||
|
path = path.substring(0, path.lastIndexOf(PATH_SEPARATOR_STR));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// all nodes excluded, no node will be picked
|
||||||
|
List<Node> excludedList = Arrays.asList(dataNodes);
|
||||||
|
for (int k : affinityNodeIndexs) {
|
||||||
|
for (int i : excludedNodeIndexs) {
|
||||||
|
String path = dataNodes[i].getNetworkFullPath();
|
||||||
|
while (!path.equals(ROOT)) {
|
||||||
|
scope = "~" + path;
|
||||||
|
int ancestorGen = 0;
|
||||||
|
while (ancestorGen < cluster.getMaxLevel()) {
|
||||||
|
frequency = pickNodesAtRandom(leafNum, scope, excludedList,
|
||||||
|
dataNodes[k], ancestorGen);
|
||||||
|
for (Node key : dataNodes) {
|
||||||
|
assertTrue(frequency.get(key) == 0);
|
||||||
|
}
|
||||||
|
ancestorGen++;
|
||||||
|
}
|
||||||
|
path = path.substring(0, path.lastIndexOf(PATH_SEPARATOR_STR));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// no node excluded and no excluded scope, each node will be picked
|
||||||
|
int ancestorGen = cluster.getMaxLevel() - 1;
|
||||||
|
for (int k : affinityNodeIndexs) {
|
||||||
|
while (ancestorGen > 0) {
|
||||||
|
frequency =
|
||||||
|
pickNodes(leafNum, null, null, dataNodes[k], ancestorGen);
|
||||||
|
Node affinityAncestor = dataNodes[k].getAncestor(ancestorGen);
|
||||||
|
for (Node key : dataNodes) {
|
||||||
|
if (frequency.get(key) > 0) {
|
||||||
|
if (affinityAncestor != null) {
|
||||||
|
assertTrue(affinityAncestor.isAncestor(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ancestorGen--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// check invalid ancestor generation
|
||||||
|
try {
|
||||||
|
cluster.chooseRandom(null, null, null, dataNodes[0],
|
||||||
|
cluster.getMaxLevel());
|
||||||
|
fail("ancestor generation exceeds max level, should fail");
|
||||||
|
} catch (Exception e) {
|
||||||
|
assertTrue(e.getMessage().startsWith("ancestorGen " +
|
||||||
|
cluster.getMaxLevel() +
|
||||||
|
" exceeds this network topology acceptable level"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCost() {
|
||||||
|
// network topology with default cost
|
||||||
|
List<NodeSchema> schemas = new ArrayList<>();
|
||||||
|
schemas.add(ROOT_SCHEMA);
|
||||||
|
schemas.add(RACK_SCHEMA);
|
||||||
|
schemas.add(NODEGROUP_SCHEMA);
|
||||||
|
schemas.add(LEAF_SCHEMA);
|
||||||
|
|
||||||
|
NodeSchemaManager manager = NodeSchemaManager.getInstance();
|
||||||
|
manager.init(schemas.toArray(new NodeSchema[0]), true);
|
||||||
|
NetworkTopology newCluster =
|
||||||
|
new NetworkTopologyImpl(manager);
|
||||||
|
Node[] nodeList = new Node[] {
|
||||||
|
createDatanode("1.1.1.1", "/r1/ng1"),
|
||||||
|
createDatanode("2.2.2.2", "/r1/ng1"),
|
||||||
|
createDatanode("3.3.3.3", "/r1/ng2"),
|
||||||
|
createDatanode("4.4.4.4", "/r2/ng1"),
|
||||||
|
};
|
||||||
|
for (Node node: nodeList) {
|
||||||
|
newCluster.add(node);
|
||||||
|
}
|
||||||
|
Node outScopeNode1 = createDatanode("5.5.5.5", "/r2/ng2");
|
||||||
|
Node outScopeNode2 = createDatanode("6.6.6.6", "/r2/ng2");
|
||||||
|
assertEquals(Integer.MAX_VALUE,
|
||||||
|
newCluster.getDistanceCost(nodeList[0], null));
|
||||||
|
assertEquals(Integer.MAX_VALUE,
|
||||||
|
newCluster.getDistanceCost(null, nodeList[0]));
|
||||||
|
assertEquals(Integer.MAX_VALUE,
|
||||||
|
newCluster.getDistanceCost(outScopeNode1, nodeList[0]));
|
||||||
|
assertEquals(Integer.MAX_VALUE,
|
||||||
|
newCluster.getDistanceCost(nodeList[0], outScopeNode1));
|
||||||
|
assertEquals(Integer.MAX_VALUE,
|
||||||
|
newCluster.getDistanceCost(outScopeNode1, outScopeNode2));
|
||||||
|
|
||||||
|
assertEquals(0, newCluster.getDistanceCost(null, null));
|
||||||
|
assertEquals(0, newCluster.getDistanceCost(nodeList[0], nodeList[0]));
|
||||||
|
assertEquals(2, newCluster.getDistanceCost(nodeList[0], nodeList[1]));
|
||||||
|
assertEquals(4, newCluster.getDistanceCost(nodeList[0], nodeList[2]));
|
||||||
|
assertEquals(6, newCluster.getDistanceCost(nodeList[0], nodeList[3]));
|
||||||
|
|
||||||
|
// network topology with customized cost
|
||||||
|
schemas.clear();
|
||||||
|
schemas.add(new NodeSchema.Builder()
|
||||||
|
.setType(NodeSchema.LayerType.ROOT).setCost(5).build());
|
||||||
|
schemas.add(new NodeSchema.Builder()
|
||||||
|
.setType(NodeSchema.LayerType.INNER_NODE).setCost(3).build());
|
||||||
|
schemas.add(new NodeSchema.Builder()
|
||||||
|
.setType(NodeSchema.LayerType.INNER_NODE).setCost(1).build());
|
||||||
|
schemas.add(new NodeSchema.Builder()
|
||||||
|
.setType(NodeSchema.LayerType.LEAF_NODE).build());
|
||||||
|
manager = NodeSchemaManager.getInstance();
|
||||||
|
manager.init(schemas.toArray(new NodeSchema[0]), true);
|
||||||
|
newCluster = new NetworkTopologyImpl(manager);
|
||||||
|
for (Node node: nodeList) {
|
||||||
|
newCluster.add(node);
|
||||||
|
}
|
||||||
|
assertEquals(Integer.MAX_VALUE,
|
||||||
|
newCluster.getDistanceCost(nodeList[0], null));
|
||||||
|
assertEquals(Integer.MAX_VALUE,
|
||||||
|
newCluster.getDistanceCost(null, nodeList[0]));
|
||||||
|
assertEquals(Integer.MAX_VALUE,
|
||||||
|
newCluster.getDistanceCost(outScopeNode1, nodeList[0]));
|
||||||
|
assertEquals(Integer.MAX_VALUE,
|
||||||
|
newCluster.getDistanceCost(nodeList[0], outScopeNode1));
|
||||||
|
assertEquals(Integer.MAX_VALUE,
|
||||||
|
newCluster.getDistanceCost(outScopeNode1, outScopeNode2));
|
||||||
|
|
||||||
|
assertEquals(0, newCluster.getDistanceCost(null, null));
|
||||||
|
assertEquals(0, newCluster.getDistanceCost(nodeList[0], nodeList[0]));
|
||||||
|
assertEquals(2, newCluster.getDistanceCost(nodeList[0], nodeList[1]));
|
||||||
|
assertEquals(8, newCluster.getDistanceCost(nodeList[0], nodeList[2]));
|
||||||
|
assertEquals(18, newCluster.getDistanceCost(nodeList[0], nodeList[3]));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSortByDistanceCost() {
|
||||||
|
Node[][] nodes = {
|
||||||
|
{},
|
||||||
|
{dataNodes[0]},
|
||||||
|
{dataNodes[dataNodes.length - 1]},
|
||||||
|
{dataNodes[random.nextInt(dataNodes.length)]},
|
||||||
|
{dataNodes[random.nextInt(dataNodes.length)],
|
||||||
|
dataNodes[random.nextInt(dataNodes.length)]
|
||||||
|
},
|
||||||
|
{dataNodes[random.nextInt(dataNodes.length)],
|
||||||
|
dataNodes[random.nextInt(dataNodes.length)],
|
||||||
|
dataNodes[random.nextInt(dataNodes.length)],
|
||||||
|
},
|
||||||
|
{dataNodes[random.nextInt(dataNodes.length)],
|
||||||
|
dataNodes[random.nextInt(dataNodes.length)],
|
||||||
|
dataNodes[random.nextInt(dataNodes.length)],
|
||||||
|
dataNodes[random.nextInt(dataNodes.length)],
|
||||||
|
},
|
||||||
|
{dataNodes[random.nextInt(dataNodes.length)],
|
||||||
|
dataNodes[random.nextInt(dataNodes.length)],
|
||||||
|
dataNodes[random.nextInt(dataNodes.length)],
|
||||||
|
dataNodes[random.nextInt(dataNodes.length)],
|
||||||
|
dataNodes[random.nextInt(dataNodes.length)],
|
||||||
|
}};
|
||||||
|
Node[] readers = {null, dataNodes[0], dataNodes[dataNodes.length - 1],
|
||||||
|
dataNodes[random.nextInt(dataNodes.length)],
|
||||||
|
dataNodes[random.nextInt(dataNodes.length)],
|
||||||
|
dataNodes[random.nextInt(dataNodes.length)]
|
||||||
|
};
|
||||||
|
for (Node reader : readers) {
|
||||||
|
for (Node[] nodeList : nodes) {
|
||||||
|
int length = nodeList.length;
|
||||||
|
while (length > 0) {
|
||||||
|
cluster.sortByDistanceCost(reader, nodeList, length);
|
||||||
|
for (int i = 0; i < nodeList.length; i++) {
|
||||||
|
if ((i + 1) < nodeList.length) {
|
||||||
|
int cost1 = cluster.getDistanceCost(reader, nodeList[i]);
|
||||||
|
int cost2 = cluster.getDistanceCost(reader, nodeList[i + 1]);
|
||||||
|
assertTrue("reader:" + (reader != null ?
|
||||||
|
reader.getNetworkFullPath() : "null") +
|
||||||
|
",node1:" + nodeList[i].getNetworkFullPath() +
|
||||||
|
",node2:" + nodeList[i + 1].getNetworkFullPath() +
|
||||||
|
",cost1:" + cost1 + ",cost2:" + cost2,
|
||||||
|
cost1 == Integer.MAX_VALUE || cost1 <= cost2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
length--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort all nodes
|
||||||
|
Node[] nodeList = dataNodes.clone();
|
||||||
|
for (Node reader : readers) {
|
||||||
|
int length = nodeList.length;
|
||||||
|
while (length >= 0) {
|
||||||
|
cluster.sortByDistanceCost(reader, nodeList, length);
|
||||||
|
for (int i = 0; i < nodeList.length; i++) {
|
||||||
|
if ((i + 1) < nodeList.length) {
|
||||||
|
int cost1 = cluster.getDistanceCost(reader, nodeList[i]);
|
||||||
|
int cost2 = cluster.getDistanceCost(reader, nodeList[i + 1]);
|
||||||
|
// node can be removed when called in testConcurrentAccess
|
||||||
|
assertTrue("reader:" + (reader != null ?
|
||||||
|
reader.getNetworkFullPath() : "null") +
|
||||||
|
",node1:" + nodeList[i].getNetworkFullPath() +
|
||||||
|
",node2:" + nodeList[i + 1].getNetworkFullPath() +
|
||||||
|
",cost1:" + cost1 + ",cost2:" + cost2,
|
||||||
|
cost1 == Integer.MAX_VALUE || cost1 <= cost2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
length--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Node createDatanode(String name, String path) {
|
||||||
|
return new NodeImpl(name, path, NetConstants.NODE_COST_DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This picks a large number of nodes at random in order to ensure coverage.
|
||||||
|
*
|
||||||
|
* @param numNodes the number of nodes
|
||||||
|
* @param excludedScope the excluded scope
|
||||||
|
* @param excludedNodes the excluded node list
|
||||||
|
* @param ancestorGen the chosen node cannot share the same ancestor at
|
||||||
|
* this generation with excludedNodes
|
||||||
|
* @return the frequency that nodes were chosen
|
||||||
|
*/
|
||||||
|
private Map<Node, Integer> pickNodesAtRandom(int numNodes,
|
||||||
|
String excludedScope, Collection<Node> excludedNodes, int ancestorGen) {
|
||||||
|
Map<Node, Integer> frequency = new HashMap<Node, Integer>();
|
||||||
|
for (Node dnd : dataNodes) {
|
||||||
|
frequency.put(dnd, 0);
|
||||||
|
}
|
||||||
|
for (int j = 0; j < numNodes; j++) {
|
||||||
|
Node node = cluster.chooseRandom(excludedScope, excludedNodes,
|
||||||
|
ancestorGen);
|
||||||
|
if (node != null) {
|
||||||
|
frequency.put(node, frequency.get(node) + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG.info("Result:" + frequency);
|
||||||
|
return frequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This picks a large number of nodes at random in order to ensure coverage.
|
||||||
|
*
|
||||||
|
* @param numNodes the number of nodes
|
||||||
|
* @param excludedScope the excluded scope
|
||||||
|
* @param excludedNodes the excluded node list
|
||||||
|
* @param affinityNode the chosen node should share the same ancestor at
|
||||||
|
* generation "ancestorGen" with this node
|
||||||
|
* @param ancestorGen the chosen node cannot share the same ancestor at
|
||||||
|
* this generation with excludedNodes
|
||||||
|
* @return the frequency that nodes were chosen
|
||||||
|
*/
|
||||||
|
private Map<Node, Integer> pickNodesAtRandom(int numNodes,
|
||||||
|
String excludedScope, Collection<Node> excludedNodes, Node affinityNode,
|
||||||
|
int ancestorGen) {
|
||||||
|
Map<Node, Integer> frequency = new HashMap<Node, Integer>();
|
||||||
|
for (Node dnd : dataNodes) {
|
||||||
|
frequency.put(dnd, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int j = 0; j < numNodes; j++) {
|
||||||
|
Node node = cluster.chooseRandom("", excludedScope.substring(1),
|
||||||
|
excludedNodes, affinityNode, ancestorGen);
|
||||||
|
if (node != null) {
|
||||||
|
frequency.put(node, frequency.get(node) + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG.info("Result:" + frequency);
|
||||||
|
return frequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This picks a large amount of nodes sequentially.
|
||||||
|
*
|
||||||
|
* @param numNodes the number of nodes
|
||||||
|
* @param excludedScope the excluded scope, should not start with "~"
|
||||||
|
* @param excludedNodes the excluded node list
|
||||||
|
* @param affinityNode the chosen node should share the same ancestor at
|
||||||
|
* generation "ancestorGen" with this node
|
||||||
|
* @param ancestorGen the chosen node cannot share the same ancestor at
|
||||||
|
* this generation with excludedNodes
|
||||||
|
* @return the frequency that nodes were chosen
|
||||||
|
*/
|
||||||
|
private Map<Node, Integer> pickNodes(int numNodes, String excludedScope,
|
||||||
|
Collection<Node> excludedNodes, Node affinityNode, int ancestorGen) {
|
||||||
|
Map<Node, Integer> frequency = new HashMap<>();
|
||||||
|
for (Node dnd : dataNodes) {
|
||||||
|
frequency.put(dnd, 0);
|
||||||
|
}
|
||||||
|
excludedNodes = excludedNodes == null ? null :
|
||||||
|
excludedNodes.stream().distinct().collect(Collectors.toList());
|
||||||
|
for (int j = 0; j < numNodes; j++) {
|
||||||
|
Node node = cluster.getNode(j, null, excludedScope, excludedNodes,
|
||||||
|
affinityNode, ancestorGen);
|
||||||
|
if (node != null) {
|
||||||
|
frequency.put(node, frequency.get(node) + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.info("Result:" + frequency);
|
||||||
|
return frequency;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,103 @@
|
|||||||
|
/**
|
||||||
|
* 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 org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.Timeout;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.Parameterized;
|
||||||
|
import org.junit.runners.Parameterized.Parameters;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
/** Test the node schema loader. */
|
||||||
|
@RunWith(Parameterized.class)
|
||||||
|
public class TestNodeSchemaLoader {
|
||||||
|
private static final Logger LOG =
|
||||||
|
LoggerFactory.getLogger(TestNodeSchemaLoader.class);
|
||||||
|
private ClassLoader classLoader =
|
||||||
|
Thread.currentThread().getContextClassLoader();
|
||||||
|
|
||||||
|
public TestNodeSchemaLoader(String schemaFile, String errMsg) {
|
||||||
|
try {
|
||||||
|
String filePath = classLoader.getResource(
|
||||||
|
"./networkTopologyTestFiles/" + schemaFile).getPath();
|
||||||
|
NodeSchemaLoader.getInstance().loadSchemaFromFile(filePath);
|
||||||
|
fail("expect exceptions");
|
||||||
|
} catch (Throwable e) {
|
||||||
|
assertTrue(e.getMessage().contains(errMsg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public Timeout testTimeout = new Timeout(30000);
|
||||||
|
|
||||||
|
@Parameters
|
||||||
|
public static Collection<Object[]> getSchemaFiles() {
|
||||||
|
Object[][] schemaFiles = new Object[][]{
|
||||||
|
{"enforce-error.xml", "layer without prefix defined"},
|
||||||
|
{"invalid-cost.xml", "Cost should be positive number or 0"},
|
||||||
|
{"multiple-leaf.xml", "Multiple LEAF layers are found"},
|
||||||
|
{"multiple-root.xml", "Multiple ROOT layers are found"},
|
||||||
|
{"no-leaf.xml", "No LEAF layer is found"},
|
||||||
|
{"no-root.xml", "No ROOT layer is found"},
|
||||||
|
{"path-layers-size-mismatch.xml",
|
||||||
|
"Topology path depth doesn't match layer element numbers"},
|
||||||
|
{"path-with-id-reference-failure.xml",
|
||||||
|
"No layer found for id"},
|
||||||
|
{"unknown-layer-type.xml", "Unsupported layer type"},
|
||||||
|
{"wrong-path-order-1.xml",
|
||||||
|
"Topology path doesn't start with ROOT layer"},
|
||||||
|
{"wrong-path-order-2.xml", "Topology path doesn't end with LEAF layer"},
|
||||||
|
{"no-topology.xml", "no or multiple <topology> element"},
|
||||||
|
{"multiple-topology.xml", "no or multiple <topology> element"},
|
||||||
|
{"invalid-version.xml", "Bad layoutversion value"},
|
||||||
|
};
|
||||||
|
return Arrays.asList(schemaFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGood() {
|
||||||
|
try {
|
||||||
|
String filePath = classLoader.getResource(
|
||||||
|
"./networkTopologyTestFiles/good.xml").getPath();
|
||||||
|
NodeSchemaLoader.getInstance().loadSchemaFromFile(filePath);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
fail("should succeed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNotExist() {
|
||||||
|
String filePath = classLoader.getResource(
|
||||||
|
"./networkTopologyTestFiles/good.xml").getPath() + ".backup";
|
||||||
|
try {
|
||||||
|
NodeSchemaLoader.getInstance().loadSchemaFromFile(filePath);
|
||||||
|
fail("should fail");
|
||||||
|
} catch (Throwable e) {
|
||||||
|
assertTrue(e.getMessage().contains("file " + filePath + " is not found"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,101 @@
|
|||||||
|
/**
|
||||||
|
* 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 org.apache.hadoop.conf.Configuration;
|
||||||
|
import org.apache.hadoop.hdds.scm.ScmConfigKeys;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.Timeout;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import static org.apache.hadoop.hdds.scm.net.NetConstants.DEFAULT_NODEGROUP;
|
||||||
|
import static org.apache.hadoop.hdds.scm.net.NetConstants.DEFAULT_RACK;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
/** Test the node schema loader. */
|
||||||
|
public class TestNodeSchemaManager {
|
||||||
|
private static final Logger LOG =
|
||||||
|
LoggerFactory.getLogger(TestNodeSchemaManager.class);
|
||||||
|
private ClassLoader classLoader =
|
||||||
|
Thread.currentThread().getContextClassLoader();
|
||||||
|
private NodeSchemaManager manager;
|
||||||
|
private Configuration conf;
|
||||||
|
|
||||||
|
public TestNodeSchemaManager() {
|
||||||
|
conf = new Configuration();
|
||||||
|
String filePath = classLoader.getResource(
|
||||||
|
"./networkTopologyTestFiles/good.xml").getPath();
|
||||||
|
conf.set(ScmConfigKeys.OZONE_SCM_NETWORK_TOPOLOGY_SCHEMA_FILE, filePath);
|
||||||
|
manager = NodeSchemaManager.getInstance();
|
||||||
|
manager.init(conf);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public Timeout testTimeout = new Timeout(30000);
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testFailure1() {
|
||||||
|
manager.getCost(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testFailure2() {
|
||||||
|
manager.getCost(manager.getMaxLevel() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPass() {
|
||||||
|
assertEquals(4, manager.getMaxLevel());
|
||||||
|
for (int i = 1; i <= manager.getMaxLevel(); i++) {
|
||||||
|
assertTrue(manager.getCost(i) == 1 || manager.getCost(i) == 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInitFailure() {
|
||||||
|
String filePath = classLoader.getResource(
|
||||||
|
"./networkTopologyTestFiles/good.xml").getPath() + ".backup";
|
||||||
|
conf.set(ScmConfigKeys.OZONE_SCM_NETWORK_TOPOLOGY_SCHEMA_FILE, filePath);
|
||||||
|
try {
|
||||||
|
manager.init(conf);
|
||||||
|
fail("should fail");
|
||||||
|
} catch (Throwable e) {
|
||||||
|
assertTrue(e.getMessage().contains("Fail to load schema file:" +
|
||||||
|
filePath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testComplete() {
|
||||||
|
// successful complete action
|
||||||
|
String path = "/node1";
|
||||||
|
assertEquals(DEFAULT_RACK + DEFAULT_NODEGROUP + path,
|
||||||
|
manager.complete(path));
|
||||||
|
assertEquals("/rack" + DEFAULT_NODEGROUP + path,
|
||||||
|
manager.complete("/rack" + path));
|
||||||
|
assertEquals(DEFAULT_RACK + "/nodegroup" + path,
|
||||||
|
manager.complete("/nodegroup" + path));
|
||||||
|
|
||||||
|
// failed complete action
|
||||||
|
assertEquals(null, manager.complete("/dc" + path));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
<configuration>
|
||||||
|
<layoutversion>1</layoutversion>
|
||||||
|
<layers>
|
||||||
|
<layer id="datacenter">
|
||||||
|
<prefix></prefix>
|
||||||
|
<cost>1</cost>
|
||||||
|
<type>ROOT</type>
|
||||||
|
</layer>
|
||||||
|
<layer id="rack">
|
||||||
|
<prefix>rack</prefix>
|
||||||
|
<cost>1</cost>
|
||||||
|
<type>InnerNode</type>
|
||||||
|
</layer>
|
||||||
|
<layer id="nodegroup">
|
||||||
|
<prefix></prefix>
|
||||||
|
<cost>1</cost>
|
||||||
|
<type>InnerNode</type>
|
||||||
|
</layer>
|
||||||
|
<layer id="node">
|
||||||
|
<prefix></prefix>
|
||||||
|
<cost>0</cost>
|
||||||
|
<type>Leaf</type>
|
||||||
|
</layer>
|
||||||
|
</layers>
|
||||||
|
<topology>
|
||||||
|
<path>/datacenter/rack/nodegroup/node</path>
|
||||||
|
<enforceprefix>true</enforceprefix>
|
||||||
|
</topology>
|
||||||
|
</configuration>
|
@ -0,0 +1,49 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
<configuration>
|
||||||
|
<layoutversion>1</layoutversion>
|
||||||
|
<layers>
|
||||||
|
<layer id="datacenter">
|
||||||
|
<prefix></prefix>
|
||||||
|
<cost>1</cost>
|
||||||
|
<type>Root</type>
|
||||||
|
</layer>
|
||||||
|
<layer id="rack">
|
||||||
|
<prefix>rack</prefix>
|
||||||
|
<cost>1</cost>
|
||||||
|
<type>InnerNode</type>
|
||||||
|
<default>/default-rack</default>
|
||||||
|
</layer>
|
||||||
|
<layer id="nodegroup">
|
||||||
|
<prefix>nodegroup</prefix>
|
||||||
|
<cost>1</cost>
|
||||||
|
<type>InnerNode</type>
|
||||||
|
<default>/default-nodegroup</default>
|
||||||
|
</layer>
|
||||||
|
<layer id="node">
|
||||||
|
<prefix></prefix>
|
||||||
|
<cost>0</cost>
|
||||||
|
<type>Leaf</type>
|
||||||
|
</layer>
|
||||||
|
</layers>
|
||||||
|
<topology>
|
||||||
|
<path>/datacenter/rack/nodegroup/node</path>
|
||||||
|
<enforceprefix>true</enforceprefix>
|
||||||
|
</topology>
|
||||||
|
</configuration>
|
@ -0,0 +1,43 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
<configuration>
|
||||||
|
<layoutversion>1</layoutversion>
|
||||||
|
<layers>
|
||||||
|
<layer id="datacenter">
|
||||||
|
<prefix></prefix>
|
||||||
|
<cost>1</cost>
|
||||||
|
<type>ROOT</type>
|
||||||
|
</layer>
|
||||||
|
<layer id="rack">
|
||||||
|
<prefix>rack</prefix>
|
||||||
|
<cost>-1</cost>
|
||||||
|
<type>InnerNode</type>
|
||||||
|
<default>default-rack</default>
|
||||||
|
</layer>
|
||||||
|
<layer id="node">
|
||||||
|
<prefix></prefix>
|
||||||
|
<cost>0</cost>
|
||||||
|
<type>leaf</type>
|
||||||
|
</layer>
|
||||||
|
</layers>
|
||||||
|
<topology>
|
||||||
|
<path>/datacenter/rack/node</path>
|
||||||
|
<enforceprefix>false</enforceprefix>
|
||||||
|
</topology>
|
||||||
|
</configuration>
|
@ -0,0 +1,43 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
<configuration>
|
||||||
|
<layoutversion>a</layoutversion>
|
||||||
|
<layers>
|
||||||
|
<layer id="datacenter">
|
||||||
|
<prefix></prefix>
|
||||||
|
<cost>1</cost>
|
||||||
|
<type>ROOT</type>
|
||||||
|
</layer>
|
||||||
|
<layer id="rack">
|
||||||
|
<prefix>rack</prefix>
|
||||||
|
<cost>-1</cost>
|
||||||
|
<type>InnerNode</type>
|
||||||
|
<default>default-rack</default>
|
||||||
|
</layer>
|
||||||
|
<layer id="node">
|
||||||
|
<prefix></prefix>
|
||||||
|
<cost>0</cost>
|
||||||
|
<type>leaf</type>
|
||||||
|
</layer>
|
||||||
|
</layers>
|
||||||
|
<topology>
|
||||||
|
<path>/datacenter/rack/node</path>
|
||||||
|
<enforceprefix>false</enforceprefix>
|
||||||
|
</topology>
|
||||||
|
</configuration>
|
@ -0,0 +1,43 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
<configuration>
|
||||||
|
<layoutversion>1</layoutversion>
|
||||||
|
<layers>
|
||||||
|
<layer id="datacenter">
|
||||||
|
<prefix></prefix>
|
||||||
|
<cost>1</cost>
|
||||||
|
<type>ROOT</type>
|
||||||
|
</layer>
|
||||||
|
<layer id="rack">
|
||||||
|
<prefix>rack</prefix>
|
||||||
|
<cost>1</cost>
|
||||||
|
<type>Leaf</type>
|
||||||
|
<default>default-rack</default>
|
||||||
|
</layer>
|
||||||
|
<layer id="node">
|
||||||
|
<prefix></prefix>
|
||||||
|
<cost>0</cost>
|
||||||
|
<type>Leaf</type>
|
||||||
|
</layer>
|
||||||
|
</layers>
|
||||||
|
<topology>
|
||||||
|
<path>/datacenter/rack/node</path>
|
||||||
|
<enforceprefix>false</enforceprefix>
|
||||||
|
</topology>
|
||||||
|
</configuration>
|
@ -0,0 +1,43 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
<configuration>
|
||||||
|
<layoutversion>1</layoutversion>
|
||||||
|
<layers>
|
||||||
|
<layer id="datacenter">
|
||||||
|
<prefix></prefix>
|
||||||
|
<cost>1</cost>
|
||||||
|
<type>ROOT</type>
|
||||||
|
</layer>
|
||||||
|
<layer id="rack">
|
||||||
|
<prefix>rack</prefix>
|
||||||
|
<cost>1</cost>
|
||||||
|
<type>ROOT</type>
|
||||||
|
<default>default-rack</default>
|
||||||
|
</layer>
|
||||||
|
<layer id="node">
|
||||||
|
<prefix></prefix>
|
||||||
|
<cost>0</cost>
|
||||||
|
<type>Leaf</type>
|
||||||
|
</layer>
|
||||||
|
</layers>
|
||||||
|
<topology>
|
||||||
|
<path>/datacenter/rack/node</path>
|
||||||
|
<enforceprefix>false</enforceprefix>
|
||||||
|
</topology>
|
||||||
|
</configuration>
|
@ -0,0 +1,47 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
<configuration>
|
||||||
|
<layoutversion>1</layoutversion>
|
||||||
|
<layers>
|
||||||
|
<layer id="datacenter">
|
||||||
|
<prefix></prefix>
|
||||||
|
<cost>1</cost>
|
||||||
|
<type>ROOT</type>
|
||||||
|
</layer>
|
||||||
|
<layer id="rack">
|
||||||
|
<prefix>rack</prefix>
|
||||||
|
<cost>1</cost>
|
||||||
|
<type>InnerNode</type>
|
||||||
|
<default>default-rack</default>
|
||||||
|
</layer>
|
||||||
|
<layer id="node">
|
||||||
|
<prefix></prefix>
|
||||||
|
<cost>0</cost>
|
||||||
|
<type>Leaf</type>
|
||||||
|
</layer>
|
||||||
|
</layers>
|
||||||
|
<topology>
|
||||||
|
<path>/datacenter/rack/node</path>
|
||||||
|
<enforceprefix>false</enforceprefix>
|
||||||
|
</topology>
|
||||||
|
<topology>
|
||||||
|
<path>/datacenter/rack/node</path>
|
||||||
|
<enforceprefix>false</enforceprefix>
|
||||||
|
</topology>
|
||||||
|
</configuration>
|
@ -0,0 +1,43 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
<configuration>
|
||||||
|
<layoutversion>1</layoutversion>
|
||||||
|
<layers>
|
||||||
|
<layer id="datacenter">
|
||||||
|
<prefix></prefix>
|
||||||
|
<cost>1</cost>
|
||||||
|
<type>ROOT</type>
|
||||||
|
</layer>
|
||||||
|
<layer id="rack">
|
||||||
|
<prefix>rack</prefix>
|
||||||
|
<cost>1</cost>
|
||||||
|
<type>InnerNode</type>
|
||||||
|
<default>default-rack</default>
|
||||||
|
</layer>
|
||||||
|
<layer id="node">
|
||||||
|
<prefix></prefix>
|
||||||
|
<cost>0</cost>
|
||||||
|
<type>InnerNode</type>
|
||||||
|
</layer>
|
||||||
|
</layers>
|
||||||
|
<topology>
|
||||||
|
<path>/datacenter/rack/node</path>
|
||||||
|
<enforceprefix>false</enforceprefix>
|
||||||
|
</topology>
|
||||||
|
</configuration>
|
@ -0,0 +1,43 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
<configuration>
|
||||||
|
<layoutversion>1</layoutversion>
|
||||||
|
<layers>
|
||||||
|
<layer id="datacenter">
|
||||||
|
<prefix></prefix>
|
||||||
|
<cost>1</cost>
|
||||||
|
<type>InnerNode</type>
|
||||||
|
</layer>
|
||||||
|
<layer id="rack">
|
||||||
|
<prefix>rack</prefix>
|
||||||
|
<cost>1</cost>
|
||||||
|
<type>InnerNode</type>
|
||||||
|
<default>default-rack</default>
|
||||||
|
</layer>
|
||||||
|
<layer id="node">
|
||||||
|
<prefix></prefix>
|
||||||
|
<cost>0</cost>
|
||||||
|
<type>Leaf</type>
|
||||||
|
</layer>
|
||||||
|
</layers>
|
||||||
|
<topology>
|
||||||
|
<path>/datacenter/rack/node</path>
|
||||||
|
<enforceprefix>false</enforceprefix>
|
||||||
|
</topology>
|
||||||
|
</configuration>
|
@ -0,0 +1,39 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
<configuration>
|
||||||
|
<layoutversion>1</layoutversion>
|
||||||
|
<layers>
|
||||||
|
<layer id="datacenter">
|
||||||
|
<prefix></prefix>
|
||||||
|
<cost>1</cost>
|
||||||
|
<type>ROOT</type>
|
||||||
|
</layer>
|
||||||
|
<layer id="rack">
|
||||||
|
<prefix>rack</prefix>
|
||||||
|
<cost>1</cost>
|
||||||
|
<type>InnerNode</type>
|
||||||
|
<default>default-rack</default>
|
||||||
|
</layer>
|
||||||
|
<layer id="node">
|
||||||
|
<prefix></prefix>
|
||||||
|
<cost>0</cost>
|
||||||
|
<type>LEAF</type>
|
||||||
|
</layer>
|
||||||
|
</layers>
|
||||||
|
</configuration>
|
@ -0,0 +1,43 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
<configuration>
|
||||||
|
<layoutversion>1</layoutversion>
|
||||||
|
<layers>
|
||||||
|
<layer id="datacenter">
|
||||||
|
<prefix></prefix>
|
||||||
|
<cost>1</cost>
|
||||||
|
<type>ROOT</type>
|
||||||
|
</layer>
|
||||||
|
<layer id="rack">
|
||||||
|
<prefix>rack</prefix>
|
||||||
|
<cost>1</cost>
|
||||||
|
<type>InnerNode</type>
|
||||||
|
<default>default-rack</default>
|
||||||
|
</layer>
|
||||||
|
<layer id="node">
|
||||||
|
<prefix></prefix>
|
||||||
|
<cost>0</cost>
|
||||||
|
<type>Leaf</type>
|
||||||
|
</layer>
|
||||||
|
</layers>
|
||||||
|
<topology>
|
||||||
|
<path>/datacenter/node</path>
|
||||||
|
<enforceprefix>false</enforceprefix>
|
||||||
|
</topology>
|
||||||
|
</configuration>
|
@ -0,0 +1,43 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
<configuration>
|
||||||
|
<layoutversion>1</layoutversion>
|
||||||
|
<layers>
|
||||||
|
<layer id="datacenter">
|
||||||
|
<prefix></prefix>
|
||||||
|
<cost>1</cost>
|
||||||
|
<type>ROOT</type>
|
||||||
|
</layer>
|
||||||
|
<layer id="rack">
|
||||||
|
<prefix>rack</prefix>
|
||||||
|
<cost>1</cost>
|
||||||
|
<type>InnerNode</type>
|
||||||
|
<default>default-rack</default>
|
||||||
|
</layer>
|
||||||
|
<layer id="node">
|
||||||
|
<prefix></prefix>
|
||||||
|
<cost>0</cost>
|
||||||
|
<type>Leaf</type>
|
||||||
|
</layer>
|
||||||
|
</layers>
|
||||||
|
<topology>
|
||||||
|
<path>/datacenter/room/node</path>
|
||||||
|
<enforceprefix>false</enforceprefix>
|
||||||
|
</topology>
|
||||||
|
</configuration>
|
@ -0,0 +1,43 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
<configuration>
|
||||||
|
<layoutversion>1</layoutversion>
|
||||||
|
<layers>
|
||||||
|
<layer id="datacenter">
|
||||||
|
<prefix></prefix>
|
||||||
|
<cost>1</cost>
|
||||||
|
<type>ROOT</type>
|
||||||
|
</layer>
|
||||||
|
<layer id="rack">
|
||||||
|
<prefix>rack</prefix>
|
||||||
|
<cost>1</cost>
|
||||||
|
<type>InnerNode</type>
|
||||||
|
<default>default-rack</default>
|
||||||
|
</layer>
|
||||||
|
<layer id="node">
|
||||||
|
<prefix></prefix>
|
||||||
|
<cost>0</cost>
|
||||||
|
<type>leaves</type>
|
||||||
|
</layer>
|
||||||
|
</layers>
|
||||||
|
<topology>
|
||||||
|
<path>/datacenter/rack/node</path>
|
||||||
|
<enforceprefix>false</enforceprefix>
|
||||||
|
</topology>
|
||||||
|
</configuration>
|
@ -0,0 +1,43 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
<configuration>
|
||||||
|
<layoutversion>1</layoutversion>
|
||||||
|
<layers>
|
||||||
|
<layer id="datacenter">
|
||||||
|
<prefix></prefix>
|
||||||
|
<cost>1</cost>
|
||||||
|
<type>ROOT</type>
|
||||||
|
</layer>
|
||||||
|
<layer id="rack">
|
||||||
|
<prefix>rack</prefix>
|
||||||
|
<cost>1</cost>
|
||||||
|
<type>InnerNode</type>
|
||||||
|
<default>default-rack</default>
|
||||||
|
</layer>
|
||||||
|
<layer id="node">
|
||||||
|
<prefix></prefix>
|
||||||
|
<cost>0</cost>
|
||||||
|
<type>Leaf</type>
|
||||||
|
</layer>
|
||||||
|
</layers>
|
||||||
|
<topology>
|
||||||
|
<path>/rack/datacenter/node</path>
|
||||||
|
<enforceprefix>false</enforceprefix>
|
||||||
|
</topology>
|
||||||
|
</configuration>
|
@ -0,0 +1,43 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
<configuration>
|
||||||
|
<layoutversion>1</layoutversion>
|
||||||
|
<layers>
|
||||||
|
<layer id="datacenter">
|
||||||
|
<prefix></prefix>
|
||||||
|
<cost>1</cost>
|
||||||
|
<type>ROOT</type>
|
||||||
|
</layer>
|
||||||
|
<layer id="rack">
|
||||||
|
<prefix>rack</prefix>
|
||||||
|
<cost>1</cost>
|
||||||
|
<type>InnerNode</type>
|
||||||
|
<default>default-rack</default>
|
||||||
|
</layer>
|
||||||
|
<layer id="node">
|
||||||
|
<prefix></prefix>
|
||||||
|
<cost>0</cost>
|
||||||
|
<type>Leaf</type>
|
||||||
|
</layer>
|
||||||
|
</layers>
|
||||||
|
<topology>
|
||||||
|
<path>/datacenter/node/rack</path>
|
||||||
|
<enforceprefix>false</enforceprefix>
|
||||||
|
</topology>
|
||||||
|
</configuration>
|
@ -90,6 +90,8 @@ run cp "${ROOT}/hadoop-ozone/dist/src/main/conf/dn-audit-log4j2.properties" "etc
|
|||||||
run cp "${ROOT}/hadoop-ozone/dist/src/main/conf/scm-audit-log4j2.properties" "etc/hadoop"
|
run cp "${ROOT}/hadoop-ozone/dist/src/main/conf/scm-audit-log4j2.properties" "etc/hadoop"
|
||||||
run cp "${ROOT}/hadoop-ozone/dist/src/main/conf/ozone-site.xml" "etc/hadoop"
|
run cp "${ROOT}/hadoop-ozone/dist/src/main/conf/ozone-site.xml" "etc/hadoop"
|
||||||
run cp -f "${ROOT}/hadoop-ozone/dist/src/main/conf/log4j.properties" "etc/hadoop"
|
run cp -f "${ROOT}/hadoop-ozone/dist/src/main/conf/log4j.properties" "etc/hadoop"
|
||||||
|
run cp "${ROOT}/hadoop-hdds/common/src/main/resources/network-topology-default.xml" "etc/hadoop"
|
||||||
|
run cp "${ROOT}/hadoop-hdds/common/src/main/resources/network-topology-nodegroup.xml" "etc/hadoop"
|
||||||
run cp "${ROOT}/hadoop-common-project/hadoop-common/src/main/bin/hadoop" "bin/"
|
run cp "${ROOT}/hadoop-common-project/hadoop-common/src/main/bin/hadoop" "bin/"
|
||||||
run cp "${ROOT}/hadoop-common-project/hadoop-common/src/main/bin/hadoop.cmd" "bin/"
|
run cp "${ROOT}/hadoop-common-project/hadoop-common/src/main/bin/hadoop.cmd" "bin/"
|
||||||
run cp "${ROOT}/hadoop-ozone/common/src/main/bin/ozone" "bin/"
|
run cp "${ROOT}/hadoop-ozone/common/src/main/bin/ozone" "bin/"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user