From cf2f59a3d4359b914fc3925552b290ade9ddfda0 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Fri, 7 Oct 2016 16:20:39 -0700 Subject: [PATCH 1/2] Move graph related code to fluent core --- .../resources/fluentcore/dag/DAGNode.java | 126 ++++++++++++ .../resources/fluentcore/dag/DAGraph.java | 193 ++++++++++++++++++ .../resources/fluentcore/dag/Graph.java | 180 ++++++++++++++++ .../resources/fluentcore/dag/Node.java | 70 +++++++ .../resources/fluentcore/dag/TaskGroup.java | 66 ++++++ .../fluentcore/dag/TaskGroupBase.java | 101 +++++++++ .../resources/fluentcore/dag/TaskItem.java | 31 +++ .../resources/fluentcore/dag/DAGraphTest.java | 147 +++++++++++++ .../fluentcore/dag/DAGraphTests.java | 154 ++++++++++++++ 9 files changed, 1068 insertions(+) create mode 100644 azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/dag/DAGNode.java create mode 100644 azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/dag/DAGraph.java create mode 100644 azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/dag/Graph.java create mode 100644 azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/dag/Node.java create mode 100644 azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/dag/TaskGroup.java create mode 100644 azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/dag/TaskGroupBase.java create mode 100644 azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/dag/TaskItem.java create mode 100644 azure-mgmt-resources/src/test/java/com/microsoft/azure/management/resources/fluentcore/dag/DAGraphTest.java create mode 100644 azure-mgmt-resources/src/test/java/com/microsoft/azure/management/resources/fluentcore/dag/DAGraphTests.java diff --git a/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/dag/DAGNode.java b/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/dag/DAGNode.java new file mode 100644 index 0000000000000..8a4c7625eeeea --- /dev/null +++ b/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/dag/DAGNode.java @@ -0,0 +1,126 @@ +/** + * + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * + */ + +package com.microsoft.azure.management.resources.fluentcore.dag; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +/** + * The type representing node in a {@link DAGraph}. + * + * @param the type of the data stored in the node + */ +public class DAGNode extends Node { + private List dependentKeys; + private int toBeResolved; + private boolean isPreparer; + private ReentrantLock lock; + + /** + * Creates a DAG node. + * + * @param key unique id of the node + * @param data data to be stored in the node + */ + public DAGNode(String key, T data) { + super(key, data); + dependentKeys = new ArrayList<>(); + lock = new ReentrantLock(); + } + + /** + * @return the lock to be used while performing thread safe operation on this node. + */ + public ReentrantLock lock() { + return this.lock; + } + + /** + * @return a list of keys of nodes in {@link DAGraph} those are dependents on this node + */ + List dependentKeys() { + return Collections.unmodifiableList(this.dependentKeys); + } + + /** + * Mark the node identified by the given key as dependent of this node. + * + * @param key the id of the dependent node + */ + public void addDependent(String key) { + this.dependentKeys.add(key); + } + + /** + * @return a list of keys of nodes in {@link DAGraph} that this node depends on + */ + public List dependencyKeys() { + return this.children(); + } + + /** + * Mark the node identified by the given key as this node's dependency. + * + * @param dependencyKey the id of the dependency node + */ + public void addDependency(String dependencyKey) { + super.addChild(dependencyKey); + } + + /** + * @return true if this node has any dependency + */ + public boolean hasDependencies() { + return this.hasChildren(); + } + + /** + * Mark or un-mark this node as preparer. + * + * @param isPreparer true if this node needs to be marked as preparer, false otherwise. + */ + public void setPreparer(boolean isPreparer) { + this.isPreparer = isPreparer; + } + + /** + * @return true if this node is marked as preparer + */ + public boolean isPreparer() { + return isPreparer; + } + + /** + * Initialize the node so that traversal can be performed on the parent DAG. + */ + public void initialize() { + this.toBeResolved = this.dependencyKeys().size(); + this.dependentKeys.clear(); + } + + /** + * @return true if all dependencies of this node are ready to be consumed + */ + boolean hasAllResolved() { + return toBeResolved == 0; + } + + /** + * Reports that one of this node's dependency has been resolved and ready to be consumed. + * + * @param dependencyKey the id of the dependency node + */ + void reportResolved(String dependencyKey) { + if (toBeResolved == 0) { + throw new RuntimeException("invalid state - " + this.key() + ": The dependency '" + dependencyKey + "' is already reported or there is no such dependencyKey"); + } + toBeResolved--; + } +} diff --git a/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/dag/DAGraph.java b/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/dag/DAGraph.java new file mode 100644 index 0000000000000..80ec33d20630b --- /dev/null +++ b/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/dag/DAGraph.java @@ -0,0 +1,193 @@ +/** + * + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * + */ + +package com.microsoft.azure.management.resources.fluentcore.dag; + +import java.util.Map; +import java.util.concurrent.ConcurrentLinkedQueue; + +/** + * Type representing a DAG (directed acyclic graph). + *

+ * each node in a DAG is represented by {@link DAGNode} + * + * @param the type of the data stored in the graph nodes + * @param the type of the nodes in the graph + */ +public class DAGraph> extends Graph { + private ConcurrentLinkedQueue queue; + private boolean hasParent; + private U rootNode; + + /** + * Creates a new DAG. + * + * @param rootNode the root node of this DAG + */ + public DAGraph(U rootNode) { + this.rootNode = rootNode; + this.queue = new ConcurrentLinkedQueue<>(); + this.rootNode.setPreparer(true); + this.addNode(rootNode); + } + + /** + * @return true if this DAG is merged with another DAG and hence has a parent + */ + public boolean hasParent() { + return hasParent; + } + + /** + * Checks whether the given node is root node of this DAG. + * + * @param node the node {@link DAGNode} to be checked + * @return true if the given node is root node + */ + public boolean isRootNode(U node) { + return this.rootNode == node; + } + + /** + * @return true if this dag is the preparer responsible for + * preparing the DAG for traversal. + */ + public boolean isPreparer() { + return this.rootNode.isPreparer(); + } + + /** + * Merge this DAG with another DAG. + *

+ * This will mark this DAG as a child DAG, the dependencies of nodes in this DAG will be merged + * with (copied to) the parent DAG + * + * @param parent the parent DAG + */ + public void merge(DAGraph parent) { + this.hasParent = true; + parent.rootNode.addDependency(this.rootNode.key()); + for (Map.Entry entry: graph.entrySet()) { + String key = entry.getKey(); + if (!parent.graph.containsKey(key)) { + parent.graph.put(key, entry.getValue()); + } + } + } + + /** + * Prepares this DAG for traversal using getNext method, each call to getNext returns next node + * in the DAG with no dependencies. + */ + public void prepare() { + if (isPreparer()) { + for (U node : graph.values()) { + // Prepare each node for traversal + node.initialize(); + if (!this.isRootNode(node)) { + // Mark other sub-DAGs as non-preparer + node.setPreparer(false); + } + } + initializeDependentKeys(); + initializeQueue(); + } + } + + /** + * Gets next node in the DAG which has no dependency or all of it's dependencies are resolved and + * ready to be consumed. + * + * @return next node or null if all the nodes have been explored or no node is available at this moment. + */ + public U getNext() { + String nextItemKey = queue.poll(); + if (nextItemKey == null) { + return null; + } + return graph.get(nextItemKey); + } + + /** + * Gets the data stored in a graph node with a given key. + * + * @param key the key of the node + * @return the value stored in the node + */ + public T getNodeData(String key) { + return graph.get(key).data(); + } + + /** + * Reports that a node is resolved hence other nodes depends on it can consume it. + * + * @param completed the node ready to be consumed + */ + public void reportedCompleted(U completed) { + completed.setPreparer(true); + String dependency = completed.key(); + for (String dependentKey : graph.get(dependency).dependentKeys()) { + DAGNode dependent = graph.get(dependentKey); + dependent.lock().lock(); + try { + dependent.reportResolved(dependency); + if (dependent.hasAllResolved()) { + queue.add(dependent.key()); + } + } finally { + dependent.lock().unlock(); + } + } + } + + /** + * Initializes dependents of all nodes. + *

+ * The DAG will be explored in DFS order and all node's dependents will be identified, + * this prepares the DAG for traversal using getNext method, each call to getNext returns next node + * in the DAG with no dependencies. + */ + private void initializeDependentKeys() { + visit(new Visitor() { + @Override + public void visitNode(U node) { + if (node.dependencyKeys().isEmpty()) { + return; + } + + String dependentKey = node.key(); + for (String dependencyKey : node.dependencyKeys()) { + graph.get(dependencyKey) + .addDependent(dependentKey); + } + } + + @Override + public void visitEdge(String fromKey, String toKey, EdgeType edgeType) { + if (edgeType == EdgeType.BACK) { + throw new IllegalStateException("Detected circular dependency: " + findPath(fromKey, toKey)); + } + } + }); + } + + /** + * Initializes the queue that tracks the next set of nodes with no dependencies or + * whose dependencies are resolved. + */ + private void initializeQueue() { + this.queue.clear(); + for (Map.Entry entry: graph.entrySet()) { + if (!entry.getValue().hasDependencies()) { + this.queue.add(entry.getKey()); + } + } + if (queue.isEmpty()) { + throw new RuntimeException("Found circular dependency"); + } + } +} diff --git a/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/dag/Graph.java b/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/dag/Graph.java new file mode 100644 index 0000000000000..de5ca0ec8a12d --- /dev/null +++ b/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/dag/Graph.java @@ -0,0 +1,180 @@ +/** + * + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * + */ + +package com.microsoft.azure.management.resources.fluentcore.dag; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Type representing a directed graph data structure. + *

+ * Each node in a graph is represented by {@link Node} + * + * @param the type of the data stored in the graph's nodes + * @param the type of the nodes in the graph + */ +public class Graph> { + protected Map graph; + private Set visited; + private Integer time; + private Map entryTime; + private Map exitTime; + private Map parent; + private Set processed; + + /** + * Creates a directed graph. + */ + public Graph() { + this.graph = new HashMap<>(); + this.visited = new HashSet<>(); + this.time = 0; + this.entryTime = new HashMap<>(); + this.exitTime = new HashMap<>(); + this.parent = new HashMap<>(); + this.processed = new HashSet<>(); + } + + /** + * Adds a node to this graph. + * + * @param node the node + */ + public void addNode(U node) { + graph.put(node.key(), node); + } + + /** + * @return all nodes in the graph. + */ + public Collection getNodes() { + return graph.values(); + } + + /** + * Perform DFS visit in this graph. + *

+ * The directed graph will be traversed in DFS order and the visitor will be notified as + * search explores each node and edge. + * + * @param visitor the graph visitor + */ + public void visit(Visitor visitor) { + for (Map.Entry> item : graph.entrySet()) { + if (!visited.contains(item.getKey())) { + this.dfs(visitor, item.getValue()); + } + } + visited.clear(); + time = 0; + entryTime.clear(); + exitTime.clear(); + parent.clear(); + processed.clear(); + } + + private void dfs(Visitor visitor, Node node) { + visitor.visitNode(node); + + String fromKey = node.key(); + visited.add(fromKey); + time++; + entryTime.put(fromKey, time); + for (String toKey : node.children()) { + if (!visited.contains(toKey)) { + parent.put(toKey, fromKey); + visitor.visitEdge(fromKey, toKey, edgeType(fromKey, toKey)); + this.dfs(visitor, this.graph.get(toKey)); + } else { + visitor.visitEdge(fromKey, toKey, edgeType(fromKey, toKey)); + } + } + time++; + exitTime.put(fromKey, time); + processed.add(fromKey); + } + + private EdgeType edgeType(String fromKey, String toKey) { + if (parent.containsKey(toKey) && parent.get(toKey).equals(fromKey)) { + return EdgeType.TREE; + } + + if (visited.contains(toKey) && !processed.contains(toKey)) { + return EdgeType.BACK; + } + + if (processed.contains(toKey) && entryTime.containsKey(toKey) && entryTime.containsKey(fromKey)) { + if (entryTime.get(toKey) > entryTime.get(fromKey)) { + return EdgeType.FORWARD; + } + + if (entryTime.get(toKey) < entryTime.get(fromKey)) { + return EdgeType.CROSS; + } + } + + throw new IllegalStateException("Internal Error: Unable to locate the edge type {" + fromKey + ", " + toKey + "}"); + } + + protected String findPath(String start, String end) { + if (start.equals(end)) { + return start; + } else { + return findPath(start, parent.get(end)) + " -> " + end; + } + } + + /** + * The edge types in a graph. + */ + enum EdgeType { + /** + * An edge (u, v) is a tree edge if v is visited the first time. + */ + TREE, + /** + * An edge (u, v) is a forward edge if v is descendant of u. + */ + FORWARD, + /** + * An edge (u, v) is a back edge if v is ancestor of u. + */ + BACK, + /** + * An edge (u, v) is a cross edge if v is neither ancestor or descendant of u. + */ + CROSS + } + + /** + * Represents a visitor to be implemented by the consumer who want to visit the + * graph's nodes in DFS order by calling visit method. + * + * @param the type of the node + */ + interface Visitor { + /** + * visit a node. + * + * @param node the node to visited + */ + void visitNode(U node); + + /** + * visit an edge. + * + * @param fromKey key of the from node + * @param toKey key of the to node + * @param edgeType the edge type + */ + void visitEdge(String fromKey, String toKey, EdgeType edgeType); + } +} diff --git a/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/dag/Node.java b/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/dag/Node.java new file mode 100644 index 0000000000000..20e960c24ac18 --- /dev/null +++ b/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/dag/Node.java @@ -0,0 +1,70 @@ +/** + * + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * + */ + +package com.microsoft.azure.management.resources.fluentcore.dag; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Type represents a node in a {@link Graph}. + * + * @param the type of the data stored in the node + */ +public class Node { + private String key; + private T data; + private List children; + + /** + * Creates a graph node. + * + * @param key unique id of the node + * @param data data to be stored in the node + */ + public Node(String key, T data) { + this.key = key; + this.data = data; + this.children = new ArrayList<>(); + } + + /** + * @return this node's unique id + */ + public String key() { + return this.key; + } + + /** + * @return data stored in this node + */ + public T data() { + return data; + } + + /** + * @return true if this node has any children + */ + public boolean hasChildren() { + return !this.children.isEmpty(); + } + + /** + * @return children (neighbours) of this node + */ + public List children() { + return Collections.unmodifiableList(this.children); + } + + /** + * @param childKey add a child (neighbour) of this node + */ + public void addChild(String childKey) { + this.children.add(childKey); + } +} diff --git a/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/dag/TaskGroup.java b/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/dag/TaskGroup.java new file mode 100644 index 0000000000000..4c39ac0c3c9b3 --- /dev/null +++ b/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/dag/TaskGroup.java @@ -0,0 +1,66 @@ +/** + * + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * + */ + +package com.microsoft.azure.management.resources.fluentcore.dag; + +import rx.Observable; + +/** + * Represents a group of related tasks. + *

+ * each task in a group is represented by {@link TaskItem} + * + * @param the type of result of tasks in the group + * @param the task type + */ +public interface TaskGroup> { + /** + * Gets underlying directed acyclic graph structure that stores tasks in the group and + * dependency information between them. + * + * @return the dag + */ + DAGraph> dag(); + + /** + * Merges this task group with parent task group. + *

+ * once merged, calling execute in the parent group will executes the task in this + * group as well. + * + * @param parentTaskGroup task group + */ + void merge(TaskGroup parentTaskGroup); + + /** + * @return true if the group is responsible for preparing execution of original task in + * this group and all tasks belong other task group it composes. + */ + boolean isPreparer(); + + /** + * Prepare the graph for execution. + */ + void prepare(); + + /** + * Executes the tasks in the group asynchronously. + * + * @return the handle to the REST call + */ + Observable executeAsync(); + + /** + * Gets the result of execution of a task in the group. + *

+ * this method can null if the task has not yet been executed + * + * @param taskId the task id + * @return the task result + */ + T taskResult(String taskId); +} diff --git a/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/dag/TaskGroupBase.java b/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/dag/TaskGroupBase.java new file mode 100644 index 0000000000000..45fdc9f000d52 --- /dev/null +++ b/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/dag/TaskGroupBase.java @@ -0,0 +1,101 @@ +/** + * + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * + */ + +package com.microsoft.azure.management.resources.fluentcore.dag; + +import rx.Observable; +import rx.functions.Func1; + +import java.util.ArrayList; +import java.util.List; + +/** + * The base implementation of TaskGroup interface. + * + * @param the result type of the tasks in the group + * @param the task item + */ +public abstract class TaskGroupBase> + implements TaskGroup { + /** + * Stores the tasks in this group and their dependency information. + */ + private DAGraph> dag; + + /** + * Creates TaskGroupBase. + * + * @param rootTaskItemId the id of the root task in this task group + * @param rootTaskItem the root task + */ + public TaskGroupBase(String rootTaskItemId, U rootTaskItem) { + this.dag = new DAGraph<>(new DAGNode<>(rootTaskItemId, rootTaskItem)); + } + + @Override + public DAGraph> dag() { + return dag; + } + + @Override + public boolean isPreparer() { + return dag.isPreparer(); + } + + @Override + public void merge(TaskGroup parentTaskGroup) { + dag.merge(parentTaskGroup.dag()); + } + + @Override + public void prepare() { + if (isPreparer()) { + dag.prepare(); + } + } + + @Override + public Observable executeAsync() { + DAGNode nextNode = dag.getNext(); + final List> observables = new ArrayList<>(); + while (nextNode != null) { + final DAGNode thisNode = nextNode; + T cachedResult = nextNode.data().result(); + if (cachedResult != null && !this.dag().isRootNode(nextNode)) { + observables.add(Observable.just(cachedResult) + .flatMap(new Func1>() { + @Override + public Observable call(T t) { + dag().reportedCompleted(thisNode); + return executeAsync(); + } + }) + ); + } else { + observables.add(nextNode.data().executeAsync() + .flatMap(new Func1>() { + @Override + public Observable call(T t) { + dag().reportedCompleted(thisNode); + if (dag().isRootNode(thisNode)) { + return Observable.just(t); + } else { + return executeAsync(); + } + } + })); + } + nextNode = dag.getNext(); + } + return Observable.merge(observables); + } + + @Override + public T taskResult(String taskId) { + return dag.getNodeData(taskId).result(); + } +} diff --git a/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/dag/TaskItem.java b/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/dag/TaskItem.java new file mode 100644 index 0000000000000..afe0546ac79b3 --- /dev/null +++ b/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/dag/TaskItem.java @@ -0,0 +1,31 @@ +/** + * + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * + */ + +package com.microsoft.azure.management.resources.fluentcore.dag; + +import rx.Observable; + +/** + * Type representing a task in a task group {@link TaskGroup}. + * + * @param the task result type + */ +public interface TaskItem { + /** + * @return the result of the task execution + */ + U result(); + + /** + * Executes the task asynchronously. + *

+ * once executed the result will be available through result getter + * + * @return the handle of the REST call + */ + Observable executeAsync(); +} diff --git a/azure-mgmt-resources/src/test/java/com/microsoft/azure/management/resources/fluentcore/dag/DAGraphTest.java b/azure-mgmt-resources/src/test/java/com/microsoft/azure/management/resources/fluentcore/dag/DAGraphTest.java new file mode 100644 index 0000000000000..5a27a25e93d02 --- /dev/null +++ b/azure-mgmt-resources/src/test/java/com/microsoft/azure/management/resources/fluentcore/dag/DAGraphTest.java @@ -0,0 +1,147 @@ +package com.microsoft.azure.management.resources.fluentcore.dag; + +import com.microsoft.azure.DAGNode; +import com.microsoft.azure.DAGraph; +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +public class DAGraphTest { + @Test + public void testDAGraphGetNext() { + /** + * |-------->[D]------>[B]-----------[A] + * | ^ ^ + * | | | + * [F]------->[E]-------| | + * | | | + * | |------->[G]----->[C]---- + * | + * |-------->[H]-------------------->[I] + */ + List expectedOrder = new ArrayList<>(); + expectedOrder.add("A"); expectedOrder.add("I"); + expectedOrder.add("B"); expectedOrder.add("C"); expectedOrder.add("H"); + expectedOrder.add("D"); expectedOrder.add("G"); + expectedOrder.add("E"); + expectedOrder.add("F"); + + DAGNode nodeA = new DAGNode<>("A", "dataA"); + DAGNode nodeI = new DAGNode<>("I", "dataI"); + + DAGNode nodeB = new DAGNode<>("B", "dataB"); + nodeB.addDependency(nodeA.key()); + + DAGNode nodeC = new DAGNode<>("C", "dataC"); + nodeC.addDependency(nodeA.key()); + + DAGNode nodeH = new DAGNode<>("H", "dataH"); + nodeH.addDependency(nodeI.key()); + + DAGNode nodeG = new DAGNode<>("G", "dataG"); + nodeG.addDependency(nodeC.key()); + + DAGNode nodeE = new DAGNode<>("E", "dataE"); + nodeE.addDependency(nodeB.key()); + nodeE.addDependency(nodeG.key()); + + DAGNode nodeD = new DAGNode<>("D", "dataD"); + nodeD.addDependency(nodeB.key()); + + + DAGNode nodeF = new DAGNode<>("F", "dataF"); + nodeF.addDependency(nodeD.key()); + nodeF.addDependency(nodeE.key()); + nodeF.addDependency(nodeH.key()); + + DAGraph> dag = new DAGraph<>(nodeF); + dag.addNode(nodeA); + dag.addNode(nodeB); + dag.addNode(nodeC); + dag.addNode(nodeD); + dag.addNode(nodeE); + dag.addNode(nodeG); + dag.addNode(nodeH); + dag.addNode(nodeI); + + dag.prepare(); + DAGNode nextNode = dag.getNext(); + int i = 0; + while (nextNode != null) { + Assert.assertEquals(nextNode.key(), expectedOrder.get(i)); + dag.reportedCompleted(nextNode); + nextNode = dag.getNext(); + i++; + } + + System.out.println("done"); + } + + @Test + public void testGraphMerge() { + /** + * |-------->[D]------>[B]-----------[A] + * | ^ ^ + * | | | + * [F]------->[E]-------| | + * | | | + * | |------->[G]----->[C]---- + * | + * |-------->[H]-------------------->[I] + */ + List expectedOrder = new ArrayList<>(); + expectedOrder.add("A"); expectedOrder.add("I"); + expectedOrder.add("B"); expectedOrder.add("C"); expectedOrder.add("H"); + expectedOrder.add("D"); expectedOrder.add("G"); + expectedOrder.add("E"); + expectedOrder.add("F"); + + DAGraph> graphA = createGraph("A"); + DAGraph> graphI = createGraph("I"); + + DAGraph> graphB = createGraph("B"); + graphA.merge(graphB); + + DAGraph> graphC = createGraph("C"); + graphA.merge(graphC); + + DAGraph> graphH = createGraph("H"); + graphI.merge(graphH); + + DAGraph> graphG = createGraph("G"); + graphC.merge(graphG); + + DAGraph> graphE = createGraph("E"); + graphB.merge(graphE); + graphG.merge(graphE); + + DAGraph> graphD = createGraph("D"); + graphB.merge(graphD); + + DAGraph> graphF = createGraph("F"); + graphD.merge(graphF); + graphE.merge(graphF); + graphH.merge(graphF); + + DAGraph> dag = graphF; + dag.prepare(); + + DAGNode nextNode = dag.getNext(); + int i = 0; + while (nextNode != null) { + Assert.assertEquals(expectedOrder.get(i), nextNode.key()); + // Process the node + dag.reportedCompleted(nextNode); + nextNode = dag.getNext(); + i++; + } + } + + private DAGraph> createGraph(String resourceName) { + DAGNode node = new DAGNode<>(resourceName, "data" + resourceName); + DAGraph> graph = new DAGraph<>(node); + return graph; + } +} diff --git a/azure-mgmt-resources/src/test/java/com/microsoft/azure/management/resources/fluentcore/dag/DAGraphTests.java b/azure-mgmt-resources/src/test/java/com/microsoft/azure/management/resources/fluentcore/dag/DAGraphTests.java new file mode 100644 index 0000000000000..7b4c77d7de9c9 --- /dev/null +++ b/azure-mgmt-resources/src/test/java/com/microsoft/azure/management/resources/fluentcore/dag/DAGraphTests.java @@ -0,0 +1,154 @@ +/** + * + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * + */ + +package com.microsoft.azure.management.resources.fluentcore.dag; + +import com.microsoft.azure.DAGNode; +import com.microsoft.azure.DAGraph; +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +public class DAGraphTests { + @Test + public void testDAGraphGetNext() { + /** + * |-------->[D]------>[B]-----------[A] + * | ^ ^ + * | | | + * [F]------->[E]-------| | + * | | | + * | |------->[G]----->[C]---- + * | + * |-------->[H]-------------------->[I] + */ + List expectedOrder = new ArrayList<>(); + expectedOrder.add("A"); expectedOrder.add("I"); + expectedOrder.add("B"); expectedOrder.add("C"); expectedOrder.add("H"); + expectedOrder.add("D"); expectedOrder.add("G"); + expectedOrder.add("E"); + expectedOrder.add("F"); + + DAGNode nodeA = new DAGNode<>("A", "dataA"); + DAGNode nodeI = new DAGNode<>("I", "dataI"); + + DAGNode nodeB = new DAGNode<>("B", "dataB"); + nodeB.addDependency(nodeA.key()); + + DAGNode nodeC = new DAGNode<>("C", "dataC"); + nodeC.addDependency(nodeA.key()); + + DAGNode nodeH = new DAGNode<>("H", "dataH"); + nodeH.addDependency(nodeI.key()); + + DAGNode nodeG = new DAGNode<>("G", "dataG"); + nodeG.addDependency(nodeC.key()); + + DAGNode nodeE = new DAGNode<>("E", "dataE"); + nodeE.addDependency(nodeB.key()); + nodeE.addDependency(nodeG.key()); + + DAGNode nodeD = new DAGNode<>("D", "dataD"); + nodeD.addDependency(nodeB.key()); + + + DAGNode nodeF = new DAGNode<>("F", "dataF"); + nodeF.addDependency(nodeD.key()); + nodeF.addDependency(nodeE.key()); + nodeF.addDependency(nodeH.key()); + + DAGraph> dag = new DAGraph<>(nodeF); + dag.addNode(nodeA); + dag.addNode(nodeB); + dag.addNode(nodeC); + dag.addNode(nodeD); + dag.addNode(nodeE); + dag.addNode(nodeG); + dag.addNode(nodeH); + dag.addNode(nodeI); + + dag.prepare(); + DAGNode nextNode = dag.getNext(); + int i = 0; + while (nextNode != null) { + Assert.assertEquals(nextNode.key(), expectedOrder.get(i)); + dag.reportedCompleted(nextNode); + nextNode = dag.getNext(); + i++; + } + + System.out.println("done"); + } + + @Test + public void testGraphMerge() { + /** + * |-------->[D]------>[B]-----------[A] + * | ^ ^ + * | | | + * [F]------->[E]-------| | + * | | | + * | |------->[G]----->[C]---- + * | + * |-------->[H]-------------------->[I] + */ + List expectedOrder = new ArrayList<>(); + expectedOrder.add("A"); expectedOrder.add("I"); + expectedOrder.add("B"); expectedOrder.add("C"); expectedOrder.add("H"); + expectedOrder.add("D"); expectedOrder.add("G"); + expectedOrder.add("E"); + expectedOrder.add("F"); + + DAGraph> graphA = createGraph("A"); + DAGraph> graphI = createGraph("I"); + + DAGraph> graphB = createGraph("B"); + graphA.merge(graphB); + + DAGraph> graphC = createGraph("C"); + graphA.merge(graphC); + + DAGraph> graphH = createGraph("H"); + graphI.merge(graphH); + + DAGraph> graphG = createGraph("G"); + graphC.merge(graphG); + + DAGraph> graphE = createGraph("E"); + graphB.merge(graphE); + graphG.merge(graphE); + + DAGraph> graphD = createGraph("D"); + graphB.merge(graphD); + + DAGraph> graphF = createGraph("F"); + graphD.merge(graphF); + graphE.merge(graphF); + graphH.merge(graphF); + + DAGraph> dag = graphF; + dag.prepare(); + + DAGNode nextNode = dag.getNext(); + int i = 0; + while (nextNode != null) { + Assert.assertEquals(expectedOrder.get(i), nextNode.key()); + // Process the node + dag.reportedCompleted(nextNode); + nextNode = dag.getNext(); + i++; + } + } + + private DAGraph> createGraph(String resourceName) { + DAGNode node = new DAGNode<>(resourceName, "data" + resourceName); + DAGraph> graph = new DAGraph<>(node); + return graph; + } +} From 78fb5349acd881d748121020edb34df1d712c7b5 Mon Sep 17 00:00:00 2001 From: Jianghao Lu Date: Fri, 7 Oct 2016 16:41:45 -0700 Subject: [PATCH 2/2] Add package info --- .../resources/fluentcore/dag/package-info.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/dag/package-info.java diff --git a/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/dag/package-info.java b/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/dag/package-info.java new file mode 100644 index 0000000000000..e56ce01f26084 --- /dev/null +++ b/azure-mgmt-resources/src/main/java/com/microsoft/azure/management/resources/fluentcore/dag/package-info.java @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. + +/** + * This package contains the graph related classes that are useful + * for performing cloud requests in parallel. + */ +package com.microsoft.azure.management.resources.fluentcore.dag;