Java tutorial
/*********************************************************************************************************************** * Copyright (C) 2010-2013 by the Stratosphere project (http://stratosphere.eu) * * Licensed 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 eu.stratosphere.nephele.multicast; import java.io.BufferedReader; import java.io.DataInputStream; import java.io.FileInputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import eu.stratosphere.configuration.GlobalConfiguration; import eu.stratosphere.nephele.execution.ExecutionState; import eu.stratosphere.nephele.executiongraph.ExecutionEdge; import eu.stratosphere.nephele.executiongraph.ExecutionGate; import eu.stratosphere.nephele.executiongraph.ExecutionGraph; import eu.stratosphere.nephele.executiongraph.ExecutionVertex; import eu.stratosphere.nephele.instance.InstanceConnectionInfo; import eu.stratosphere.nephele.io.channels.ChannelID; import eu.stratosphere.nephele.jobgraph.JobID; import eu.stratosphere.nephele.jobmanager.JobManager; import eu.stratosphere.nephele.jobmanager.scheduler.AbstractScheduler; import eu.stratosphere.nephele.protocols.ChannelLookupProtocol; import eu.stratosphere.nephele.taskmanager.bytebuffered.ConnectionInfoLookupResponse; /** * The MulticastManager is responsible for the creation and storage of application-layer multicast trees used to * broadcast records to multiple target vertices. * */ public final class MulticastManager implements ChannelLookupProtocol { /** * The log object used to report errors and warnings. */ private static final Log LOG = LogFactory.getLog(JobManager.class); /** * Indicates if the arrangement of nodes within the overlay-tree should be randomized or not. If set to false, * arrangement of the same set of receiver nodes is guaranteed to be the same */ private final boolean randomized; /** * Indicates if the tree should be constructed with a given topology stored in a file. */ private final boolean useHardCodedTree; /** * File containing the hard-coded tree topology, if desired should contain node names (e.g. hostnames) with * corresponding children per line. * For example, a line "vm1.local vm2.local vm3.local" would result in vm1.local connecting to vm2.local and * vm3.local as children no further checking for connectivity of the given topology is done! */ private final String hardCodedTreeFilePath; /** * Indicates the desired branching of the generated multicast-tree. 0 means unicast transmisison, 1 sequential tree, * 2 binomial tree, 3+ clustered tree */ private final int treeBranching; /** * Reference to the scheduler. */ private final AbstractScheduler scheduler; /** * Map caching already computed multicast forwarding tables. */ private final Map<ChannelID, MulticastForwardingTable> cachedTrees = new HashMap<ChannelID, MulticastForwardingTable>(); /** * Constructs a new multicast manager. * * @param scheduler * reference to the scheduler */ public MulticastManager(final AbstractScheduler scheduler) { this.scheduler = scheduler; this.randomized = GlobalConfiguration.getBoolean("multicast.randomize", false); this.treeBranching = GlobalConfiguration.getInteger("multicast.branching", 1); this.useHardCodedTree = GlobalConfiguration.getBoolean("multicast.usehardcodedtree", false); this.hardCodedTreeFilePath = GlobalConfiguration.getString("multicast.hardcodedtreefile", null); } /** * Retrieves all recipients of a data for the given <code>sourceChannelID</code>. Returns both local recipients as * well as next-hop remote instances within the multicast-tree. * * @param caller * the {@link InstanceConnectionInfo} object of the task manager which calls this method * @param jobID * the ID of the job the channel ID belongs to * @param sourceChannelID * the ID of the channel to resolve * @return the lookup response containing the connection info and a return code */ public synchronized ConnectionInfoLookupResponse lookupConnectionInfo(final InstanceConnectionInfo caller, final JobID jobID, final ChannelID sourceChannelID) { if (LOG.isInfoEnabled()) { LOG.info("Receiving multicast receiver request from " + caller + " channel ID: " + sourceChannelID); } // Check if the tree is already created and cached if (this.cachedTrees.containsKey(sourceChannelID)) { LOG.info("Replying with cached entry..."); return cachedTrees.get(sourceChannelID).getConnectionInfo(caller); } else { // No tree exists, so we assume that this is the sending node initiating a multicast if (!checkIfAllTargetVerticesReady(caller, jobID, sourceChannelID)) { LOG.info("Received multicast request but not all receivers ready."); return ConnectionInfoLookupResponse.createReceiverNotReady(); } // Receivers are up and running.. extract tree nodes... LinkedList<TreeNode> treeNodes = extractTreeNodes(caller, jobID, sourceChannelID, this.randomized); // Do we want to use a hard-coded tree topology? if (this.useHardCodedTree) { LOG.info("Creating a hard-coded tree topology from file: " + hardCodedTreeFilePath); cachedTrees.put(sourceChannelID, createHardCodedTree(treeNodes)); return cachedTrees.get(sourceChannelID).getConnectionInfo(caller); } // Otherwise we create a default tree and put it into the tree-cache cachedTrees.put(sourceChannelID, createDefaultTree(treeNodes, this.treeBranching)); return cachedTrees.get(sourceChannelID).getConnectionInfo(caller); } } /** * Returns and removes the TreeNode which is closest to the given indicator. * * @param indicator * @param nodes * @return */ private TreeNode pollClosestNode(final TreeNode indicator, final LinkedList<TreeNode> nodes) { TreeNode closestnode = getClosestNode(indicator, nodes); nodes.remove(closestnode); return closestnode; } /** * Returns the TreeNode which is closest to the given indicator Node. Proximity is determined * either using topology-information (if given), penalty information (if given) or it returns * the first node in the list. * * @param indicator * @param nodes * @return */ private TreeNode getClosestNode(final TreeNode indicator, final LinkedList<TreeNode> nodes) { if (indicator == null) { return nodes.getFirst(); } TreeNode closestNode = null; for (TreeNode n : nodes) { if (closestNode == null || n.getDistance(indicator) < closestNode.getDistance(indicator)) { closestNode = n; } } return closestNode; } /** * This method creates a tree with an arbitrary fan out (two means binary tree). * If topology information or penalties are available, it considers that. * If fanout is set to 1, it creates a sequential tree. * if fanout is set to Integer.MAXVALUE, it creates a unicast tree. * * @param nodes * @param fanout * @return */ private MulticastForwardingTable createDefaultTree(LinkedList<TreeNode> nodes, int fanout) { // Store nodes that already have a parent, but no children LinkedList<TreeNode> connectedNodes = new LinkedList<TreeNode>(); final TreeNode rootnode = nodes.pollFirst(); TreeNode actualnode = rootnode; while (nodes.size() > 0) { // We still have unconnected nodes... for (int i = 0; i < fanout; i++) { if (nodes.size() > 0) { // pick the closest one and attach to actualnode TreeNode child = pollClosestNode(actualnode, nodes); actualnode.addChild(child); // The child is now connected and can be used as forwarder in the next iteration.. connectedNodes.add(child); } else { break; } } // OK.. take the next node to attach children to it.. // TODO: Optimization? "pollBest()" ? actualnode = connectedNodes.pollFirst(); } LOG.info("created multicast tree with following topology:\n" + rootnode.printTree()); return rootnode.createForwardingTable(); } /** * Reads a hard-coded tree topology from file and creates a tree according to the hard-coded * topology from the file. * * @param nodes * @return */ private MulticastForwardingTable createHardCodedTree(LinkedList<TreeNode> nodes) { try { FileInputStream fstream = new FileInputStream(this.hardCodedTreeFilePath); DataInputStream in = new DataInputStream(fstream); BufferedReader br = new BufferedReader(new InputStreamReader(in)); String strLine; while ((strLine = br.readLine()) != null) { String[] values = strLine.split(" "); String actualhostname = values[0]; for (TreeNode n : nodes) { if (n.toString().equals(actualhostname)) { // we found the node.. connect the children for (int i = 1; i < values.length; i++) { for (TreeNode childnode : nodes) { if (childnode.toString().equals(values[i])) { n.addChild(childnode); } } } } } } br.close(); // First node is root.. create tree. easy return nodes.getFirst().createForwardingTable(); } catch (Exception e) { System.out.println("Error reading hard-coded topology file for multicast tree: " + e.getMessage()); return null; } } /** * Checks, if all target vertices for multicast transmisison are ready. If vertices are in state ASSIGNED, it will * deploy those vertices. * * @param caller * @param jobID * @param sourceChannelID * @return */ private boolean checkIfAllTargetVerticesReady(InstanceConnectionInfo caller, JobID jobID, ChannelID sourceChannelID) { final ExecutionGraph eg = this.scheduler.getExecutionGraphByID(jobID); final ExecutionEdge outputChannel = eg.getEdgeByID(sourceChannelID); final ExecutionGate broadcastGate = outputChannel.getOutputGate(); List<ExecutionVertex> verticesToDeploy = null; // get all broadcast output channels final int numberOfOutputChannels = broadcastGate.getNumberOfEdges(); for (int i = 0; i < numberOfOutputChannels; ++i) { final ExecutionEdge c = broadcastGate.getEdge(i); if (c.isBroadcast()) { final ExecutionVertex targetVertex = c.getInputGate().getVertex(); if (targetVertex.getExecutionState() == ExecutionState.ASSIGNED) { if (verticesToDeploy == null) { verticesToDeploy = new ArrayList<ExecutionVertex>(); } verticesToDeploy.add(targetVertex); } else { if (targetVertex.getExecutionState() != ExecutionState.RUNNING && targetVertex.getExecutionState() != ExecutionState.FINISHING) { return false; } } } } if (verticesToDeploy != null) { this.scheduler.deployAssignedVertices(verticesToDeploy); return false; } return true; } /** * Returns a list of (physical) Nodes (=hosts) within the multicast tree. Each node contains the local ChannelIDs, * records * must be forwarded to. The first node in the List is the only multicast sender. * * @param sourceChannelID * @return */ private LinkedList<TreeNode> extractTreeNodes(final InstanceConnectionInfo source, final JobID jobID, final ChannelID sourceChannelID, final boolean randomize) { final ExecutionGraph eg = this.scheduler.getExecutionGraphByID(jobID); final ExecutionEdge outputChannel = eg.getEdgeByID(sourceChannelID); final ExecutionGate broadcastGate = outputChannel.getOutputGate(); final LinkedList<ExecutionEdge> outputChannels = new LinkedList<ExecutionEdge>(); // Get all broadcast output channels final int numberOfOutputChannels = broadcastGate.getNumberOfEdges(); for (int i = 0; i < numberOfOutputChannels; ++i) { final ExecutionEdge c = broadcastGate.getEdge(i); if (c.isBroadcast()) { outputChannels.add(c); } } final LinkedList<TreeNode> treeNodes = new LinkedList<TreeNode>(); LinkedList<ChannelID> actualLocalTargets = new LinkedList<ChannelID>(); int firstConnectionID = 0; // search for local targets for the tree node for (Iterator<ExecutionEdge> iter = outputChannels.iterator(); iter.hasNext();) { final ExecutionEdge actualOutputChannel = iter.next(); // the connection ID should not be needed for the root node (as it is not set as remote receiver) // but in order to maintain consistency, it also gets the connectionID of the first channel pointing to it firstConnectionID = actualOutputChannel.getConnectionID(); final ExecutionVertex targetVertex = actualOutputChannel.getInputGate().getVertex(); // is the target vertex running on the same instance? if (targetVertex.getAllocatedResource().getInstance().getInstanceConnectionInfo().equals(source)) { actualLocalTargets.add(actualOutputChannel.getInputChannelID()); iter.remove(); } } // create sender node (root) with source instance TreeNode actualNode = new TreeNode( eg.getVertexByChannelID(sourceChannelID).getAllocatedResource().getInstance(), source, firstConnectionID, actualLocalTargets); treeNodes.add(actualNode); // now we have the root-node.. lets extract all other nodes LinkedList<TreeNode> receiverNodes = new LinkedList<TreeNode>(); while (outputChannels.size() > 0) { final ExecutionEdge firstChannel = outputChannels.pollFirst(); // each receiver nodes' endpoint is associated with the connection ID // of the first channel pointing to this node. final int connectionID = firstChannel.getConnectionID(); final ExecutionVertex firstTarget = firstChannel.getInputGate().getVertex(); final InstanceConnectionInfo actualInstance = firstTarget.getAllocatedResource().getInstance() .getInstanceConnectionInfo(); actualLocalTargets = new LinkedList<ChannelID>(); // add first local target actualLocalTargets.add(firstChannel.getInputChannelID()); // now we iterate through the remaining channels to find other local targets... for (Iterator<ExecutionEdge> iter = outputChannels.iterator(); iter.hasNext();) { final ExecutionEdge actualOutputChannel = iter.next(); final ExecutionVertex actualTarget = actualOutputChannel.getInputGate().getVertex(); // is the target vertex running on the same instance? if (actualTarget.getAllocatedResource().getInstance().getInstanceConnectionInfo() .equals(actualInstance)) { actualLocalTargets.add(actualOutputChannel.getInputChannelID()); iter.remove(); } } // end for // create tree node for current instance actualNode = new TreeNode(firstTarget.getAllocatedResource().getInstance(), actualInstance, connectionID, actualLocalTargets); receiverNodes.add(actualNode); } // end while // Do we want to shuffle the receiver nodes? // Only randomize the receivers, as the sender (the first one) has to stay the same if (randomize) { Collections.shuffle(receiverNodes); } else { // Sort Tree Nodes according to host name.. Collections.sort(receiverNodes); } treeNodes.addAll(receiverNodes); return treeNodes; } }