# Created by Luke A. Kanies on 2006-11-24.
# Copyright (c) 2006. All rights reserved.
require 'puppet/external/gratr/digraph'
require 'puppet/external/gratr/import'
require 'puppet/external/gratr/dot'
require 'puppet/relationship'
# This class subclasses a graph class in order to handle relationships
# among resources.
class Puppet::PGraph < GRATR::Digraph
# This is the type used for splicing.
attr_accessor :container_type
include Puppet::Util
def add_edge!(*args)
@reversal = nil
super
end
def add_vertex!(*args)
@reversal = nil
super
end
def clear
@vertex_dict.clear
if defined? @edge_number
@edge_number.clear
end
end
# Make sure whichever edge has a label keeps the label
def copy_label(source, target, label)
# 'require' relationships will not have a label,
# and all 'subscribe' relationships have the same
# label, at least for now.
# Labels default to {}, so we can't just test for nil.
newlabel = label || {}
oldlabel = edge_label(source, target) || {}
if ! newlabel.empty? and oldlabel.empty?
edge_label_set(source, target, label)
# We should probably check to see if the labels both exist
# and don't match, but we'd just throw an error which the user
# couldn't do anyting about.
end
end
# Which resources a given resource depends upon.
def dependents(resource)
tree_from_vertex2(resource).keys
end
# Which resources depend upon the given resource.
def dependencies(resource)
# Cache the reversal graph, because it's somewhat expensive
# to create.
unless defined? @reversal and @reversal
@reversal = reversal
end
# Strangely, it's significantly faster to search a reversed
# tree in the :out direction than to search a normal tree
# in the :in direction.
@reversal.tree_from_vertex2(resource, :out).keys
#tree_from_vertex2(resource, :in).keys
end
# Override this method to use our class instead.
def edge_class()
Puppet::Relationship
end
# Determine all of the leaf nodes below a given vertex.
def leaves(vertex, type = :dfs)
tree = tree_from_vertex(vertex, type)
l = tree.keys.find_all { |c| adjacent(c, :direction => :out).empty? }
return l
end
# Collect all of the edges that the passed events match. Returns
# an array of edges.
def matching_edges(events, base = nil)
events.collect do |event|
source = base || event.source
unless vertex?(source)
Puppet.warning "Got an event from invalid vertex %s" % source.ref
next
end
# Get all of the edges that this vertex should forward events
# to, which is the same thing as saying all edges directly below
# This vertex in the graph.
adjacent(source, :direction => :out, :type => :edges).find_all do |edge|
edge.match?(event.event)
end
end.flatten
end
# Take container information from another graph and use it
# to replace any container vertices with their respective leaves.
# This creates direct relationships where there were previously
# indirect relationships through the containers.
def splice!(other, type)
# We have to get the container list via a topological sort on the
# configuration graph, because otherwise containers that contain
# other containers will add those containers back into the
# graph. We could get a similar affect by only setting relationships
# to container leaves, but that would result in many more
# relationships.
containers = other.topsort.find_all { |v| v.is_a?(type) and vertex?(v) }
containers.each do |container|
# Get the list of children from the other graph.
children = other.adjacent(container, :direction => :out)
# Just remove the container if it's empty.
if children.empty?
remove_vertex!(container)
next
end
# First create new edges for each of the :in edges
[:in, :out].each do |dir|
edges = adjacent(container, :direction => dir, :type => :edges)
edges.each do |edge|
children.each do |child|
if dir == :in
s = edge.source
t = child
else
s = child
t = edge.target
end
# We don't want to add multiple copies of the
# same edge, but we *do* want to make sure we
# keep labels around.
# XXX This will *not* work when we support multiple
# types of labels, and only works now because
# you can only do simple subscriptions.
if edge?(s, t)
copy_label(s, t, edge.label)
next
end
add_edge!(s, t, edge.label)
end
# Now get rid of the edge, so remove_vertex! works correctly.
remove_edge!(edge)
Puppet.debug "%s: %s => %s: %s" % [container,
edge.source, edge.target, edge?(edge)]
end
end
remove_vertex!(container)
end
end
# For some reason, unconnected vertices do not show up in
# this graph.
def to_jpg(path, name)
gv = vertices()
Dir.chdir(path) do
induced_subgraph(gv).write_to_graphic_file('jpg', name)
end
end
# Replace the default method, because we want to throw errors on back edges,
# not just skip them.
def topsort(start = nil, &block)
result = []
go = true
cycles = []
back = Proc.new { |e|
cycles << e
go = false
}
push = Proc.new { |v| result.unshift(v) if go}
start ||= vertices[0]
dfs({:exit_vertex => push, :back_edge => back, :start => start})
if block_given?
result.each {|v| yield(v) }
end
if cycles.length > 0
msg = "Found cycles in the following relationships:"
cycles.each { |edge| msg += " %s => %s" % [edge.source, edge.target] }
raise Puppet::Error, msg
end
return result
end
# A different way of walking a tree, and a much faster way than the
# one that comes with GRATR.
def tree_from_vertex2(start, direction = :out)
predecessor={}
walk(start, direction) do |parent, child|
predecessor[child] = parent
end
predecessor
end
# A support method for tree_from_vertex2. Just walk the tree and pass
# the parents and children.
def walk(source, direction, &block)
adjacent(source, :direction => direction).each do |target|
yield source, target
walk(target, direction, &block)
end
end
end
# $Id: pgraph.rb 2521 2007-05-17 20:57:24Z luke $
syntax highlighted by Code2HTML, v. 0.9.1