mirror of
				https://github.com/containers/podman.git
				synced 2025-10-25 18:25:59 +08:00 
			
		
		
		
	 b0526caa93
			
		
	
	b0526caa93
	
	
	
		
			
			Instead of looping to find containers with no dependencies, maintain a map of them and remove entries as we add dependency edges. Signed-off-by: Matthew Heon <matthew.heon@gmail.com> Closes: #557 Approved by: rhatdan
		
			
				
	
	
		
			172 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			172 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package libpod
 | |
| 
 | |
| import (
 | |
| 	"github.com/pkg/errors"
 | |
| 	"github.com/sirupsen/logrus"
 | |
| )
 | |
| 
 | |
| type containerNode struct {
 | |
| 	id         string
 | |
| 	container  *Container
 | |
| 	dependsOn  []*containerNode
 | |
| 	dependedOn []*containerNode
 | |
| }
 | |
| 
 | |
| type containerGraph struct {
 | |
| 	nodes              map[string]*containerNode
 | |
| 	noDepNodes         []*containerNode
 | |
| 	notDependedOnNodes map[string]*containerNode
 | |
| }
 | |
| 
 | |
| func buildContainerGraph(ctrs []*Container) (*containerGraph, error) {
 | |
| 	graph := new(containerGraph)
 | |
| 	graph.nodes = make(map[string]*containerNode)
 | |
| 	graph.notDependedOnNodes = make(map[string]*containerNode)
 | |
| 
 | |
| 	// Start by building all nodes, with no edges
 | |
| 	for _, ctr := range ctrs {
 | |
| 		ctrNode := new(containerNode)
 | |
| 		ctrNode.id = ctr.ID()
 | |
| 		ctrNode.container = ctr
 | |
| 
 | |
| 		graph.nodes[ctr.ID()] = ctrNode
 | |
| 		graph.notDependedOnNodes[ctr.ID()] = ctrNode
 | |
| 	}
 | |
| 
 | |
| 	// Now add edges based on dependencies
 | |
| 	for _, node := range graph.nodes {
 | |
| 		deps := node.container.Dependencies()
 | |
| 		for _, dep := range deps {
 | |
| 			// Get the dep's node
 | |
| 			depNode, ok := graph.nodes[dep]
 | |
| 			if !ok {
 | |
| 				return nil, errors.Wrapf(ErrNoSuchCtr, "container %s depends on container %s not found in input list", node.id, dep)
 | |
| 			}
 | |
| 
 | |
| 			// Add the dependent node to the node's dependencies
 | |
| 			// And add the node to the dependent node's dependedOn
 | |
| 			node.dependsOn = append(node.dependsOn, depNode)
 | |
| 			depNode.dependedOn = append(depNode.dependedOn, node)
 | |
| 
 | |
| 			// The dependency now has something depending on it
 | |
| 			delete(graph.notDependedOnNodes, dep)
 | |
| 		}
 | |
| 
 | |
| 		// Maintain a list of nodes with no dependencies
 | |
| 		// (no edges coming from them)
 | |
| 		if len(deps) == 0 {
 | |
| 			graph.noDepNodes = append(graph.noDepNodes, node)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Need to do cycle detection
 | |
| 	// We cannot start or stop if there are cyclic dependencies
 | |
| 	cycle, err := detectCycles(graph)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	} else if cycle {
 | |
| 		return nil, errors.Wrapf(ErrInternal, "cycle found in container dependency graph")
 | |
| 	}
 | |
| 
 | |
| 	return graph, nil
 | |
| }
 | |
| 
 | |
| // Detect cycles in a container graph using Tarjan's strongly connected
 | |
| // components algorithm
 | |
| // Return true if a cycle is found, false otherwise
 | |
| func detectCycles(graph *containerGraph) (bool, error) {
 | |
| 	type nodeInfo struct {
 | |
| 		index   int
 | |
| 		lowLink int
 | |
| 		onStack bool
 | |
| 	}
 | |
| 
 | |
| 	index := 0
 | |
| 
 | |
| 	nodes := make(map[string]*nodeInfo)
 | |
| 	stack := make([]*containerNode, 0, len(graph.nodes))
 | |
| 
 | |
| 	var strongConnect func(*containerNode) (bool, error)
 | |
| 	strongConnect = func(node *containerNode) (bool, error) {
 | |
| 		logrus.Debugf("Strongconnecting node %s", node.id)
 | |
| 
 | |
| 		info := new(nodeInfo)
 | |
| 		info.index = index
 | |
| 		info.lowLink = index
 | |
| 		index = index + 1
 | |
| 
 | |
| 		nodes[node.id] = info
 | |
| 
 | |
| 		stack = append(stack, node)
 | |
| 
 | |
| 		info.onStack = true
 | |
| 
 | |
| 		logrus.Debugf("Pushed %s onto stack", node.id)
 | |
| 
 | |
| 		// Work through all nodes we point to
 | |
| 		for _, successor := range node.dependsOn {
 | |
| 			if _, ok := nodes[successor.id]; !ok {
 | |
| 				logrus.Debugf("Recursing to successor node %s", successor.id)
 | |
| 
 | |
| 				cycle, err := strongConnect(successor)
 | |
| 				if err != nil {
 | |
| 					return false, err
 | |
| 				} else if cycle {
 | |
| 					return true, nil
 | |
| 				}
 | |
| 
 | |
| 				successorInfo := nodes[successor.id]
 | |
| 				if successorInfo.lowLink < info.lowLink {
 | |
| 					info.lowLink = successorInfo.lowLink
 | |
| 				}
 | |
| 			} else {
 | |
| 				successorInfo := nodes[successor.id]
 | |
| 				if successorInfo.index < info.lowLink && successorInfo.onStack {
 | |
| 					info.lowLink = successorInfo.index
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if info.lowLink == info.index {
 | |
| 			l := len(stack)
 | |
| 			if l == 0 {
 | |
| 				return false, errors.Wrapf(ErrInternal, "empty stack in detectCycles")
 | |
| 			}
 | |
| 
 | |
| 			// Pop off the stack
 | |
| 			topOfStack := stack[l-1]
 | |
| 			stack = stack[:l-1]
 | |
| 
 | |
| 			// Popped item is no longer on the stack, mark as such
 | |
| 			topInfo, ok := nodes[topOfStack.id]
 | |
| 			if !ok {
 | |
| 				return false, errors.Wrapf(ErrInternal, "error finding node info for %s", topOfStack.id)
 | |
| 			}
 | |
| 			topInfo.onStack = false
 | |
| 
 | |
| 			logrus.Debugf("Finishing node %s. Popped %s off stack", node.id, topOfStack.id)
 | |
| 
 | |
| 			// If the top of the stack is not us, we have found a
 | |
| 			// cycle
 | |
| 			if topOfStack.id != node.id {
 | |
| 				return true, nil
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return false, nil
 | |
| 	}
 | |
| 
 | |
| 	for id, node := range graph.nodes {
 | |
| 		if _, ok := nodes[id]; !ok {
 | |
| 			cycle, err := strongConnect(node)
 | |
| 			if err != nil {
 | |
| 				return false, err
 | |
| 			} else if cycle {
 | |
| 				return true, nil
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return false, nil
 | |
| }
 |