summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/puppet/pgraph.rb170
-rw-r--r--lib/puppet/relationship.rb51
-rw-r--r--lib/puppet/transaction.rb8
-rw-r--r--lib/puppet/type/component.rb19
-rw-r--r--lib/puppet/util/graph.rb23
-rwxr-xr-xtest/lib/puppettest.rb6
-rw-r--r--test/lib/puppettest/graph.rb40
-rw-r--r--test/other/pgraph.rb77
-rw-r--r--test/other/relationship.rb48
-rwxr-xr-xtest/other/transactions.rb2
-rwxr-xr-xtest/types/component.rb49
-rwxr-xr-xtest/util/graph.rb34
12 files changed, 472 insertions, 55 deletions
diff --git a/lib/puppet/pgraph.rb b/lib/puppet/pgraph.rb
new file mode 100644
index 000000000..fba93d20c
--- /dev/null
+++ b/lib/puppet/pgraph.rb
@@ -0,0 +1,170 @@
+#!/usr/bin/env ruby
+#
+# Created by Luke A. Kanies on 2006-11-24.
+# Copyright (c) 2006. All rights reserved.
+
+require 'puppet/gratr/digraph'
+require 'puppet/gratr/import'
+require 'puppet/gratr/dot'
+require 'puppet/relationship'
+
+# This class subclasses a graph class in order to handle relationships
+# among resources.
+class Puppet::PGraph < GRATR::Digraph
+ # Collect all of the targets for the list of events. Basically just iterates
+ # over the sources of the events and returns all of the targets of them.
+ def collect_targets(events)
+ events.collect do |event|
+ source = event.source
+ start = source
+
+ # Get all of the edges that this vertex points at
+ adjacent(source, :direction => :out, :type => :edges).find_all do |edge|
+ edge.match?(event.event)
+ end.collect { |event|
+ target = event.target
+ if target.respond_to?(:ref)
+ source.info "Scheduling %s of %s" %
+ [event.callback, target.ref]
+ end
+ target
+ }
+ end.flatten
+ end
+
+ # The dependencies for a given resource.
+ def dependencies(resource)
+ tree_from_vertex(resource, :dfs).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)
+ leaves = tree.keys.find_all { |c| adjacent(c, :direction => :out).empty? }
+ return leaves
+ 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)
+ vertices.each do |vertex|
+ # Go through each vertex and replace the edges with edges
+ # to the leaves instead
+ next unless vertex.is_a?(type)
+
+ leaves = other.leaves(vertex)
+ next if leaves.empty?
+
+ # First create new edges for each of the :in edges
+ adjacent(vertex, :direction => :in).each do |up|
+ leaves.each do |leaf|
+ add_edge!(up, leaf)
+ if cyclic?
+ raise ArgumentError,
+ "%s => %s results in a loop" %
+ [up, leaf]
+ end
+ end
+ end
+
+ # Then for each of the out edges
+ adjacent(vertex, :direction => :out).each do |down|
+ leaves.each do |leaf|
+ add_edge!(leaf, down)
+ if cyclic?
+ raise ArgumentError,
+ "%s => %s results in a loop" %
+ [leaf, down]
+ end
+ end
+ end
+
+ # And finally, remove the vertex entirely.
+ remove_vertex!(vertex)
+ end
+ end
+
+ # Trigger any subscriptions to a child. This does an upwardly recursive
+ # search -- it triggers the passed object, but also the object's parent
+ # and so on up the tree.
+ def trigger(child)
+ obj = child
+ callbacks = Hash.new { |hash, key| hash[key] = [] }
+ sources = Hash.new { |hash, key| hash[key] = [] }
+
+ trigged = []
+ while obj
+ if @targets.include?(obj)
+ callbacks.clear
+ sources.clear
+ @targets[obj].each do |event, sub|
+ # Collect all of the subs for each callback
+ callbacks[sub.callback] << sub
+
+ # And collect the sources for logging
+ sources[event.source] << sub.callback
+ end
+
+ sources.each do |source, callbacklist|
+ obj.debug "%s[%s] results in triggering %s" %
+ [source.class.name, source.name, callbacklist.join(", ")]
+ end
+
+ callbacks.each do |callback, subs|
+ message = "Triggering '%s' from %s dependencies" %
+ [callback, subs.length]
+ obj.notice message
+ # At this point, just log failures, don't try to react
+ # to them in any way.
+ begin
+ obj.send(callback)
+ @resourcemetrics[:restarted] += 1
+ rescue => detail
+ obj.err "Failed to call %s on %s: %s" %
+ [callback, obj, detail]
+
+ @resourcemetrics[:failed_restarts] += 1
+
+ if Puppet[:debug]
+ puts detail.backtrace
+ end
+ end
+
+ # And then add an event for it.
+ trigged << Puppet::Event.new(
+ :event => :triggered,
+ :transaction => self,
+ :source => obj,
+ :message => message
+ )
+
+ triggered(obj, callback)
+ end
+ end
+
+ obj = obj.parent
+ end
+
+ if trigged.empty?
+ return nil
+ else
+ return trigged
+ end
+ end
+
+ def to_jpg(name)
+ gv = vertices()
+ Dir.chdir("/Users/luke/Desktop/pics") do
+ induced_subgraph(gv).write_to_graphic_file('jpg', name)
+ end
+ end
+end
+
+# $Id$
diff --git a/lib/puppet/relationship.rb b/lib/puppet/relationship.rb
new file mode 100644
index 000000000..cbd15b2af
--- /dev/null
+++ b/lib/puppet/relationship.rb
@@ -0,0 +1,51 @@
+#!/usr/bin/env ruby
+#
+# Created by Luke A. Kanies on 2006-11-24.
+# Copyright (c) 2006. All rights reserved.
+
+require 'puppet/gratr'
+
+# subscriptions are permanent associations determining how different
+# objects react to an event
+
+class Puppet::Relationship < GRATR::Edge
+ # Return the callback
+ def callback
+ label[:callback]
+ end
+
+ # Return our event.
+ def event
+ label[:event]
+ end
+
+ def initialize(source, target, label = nil)
+ if label
+ unless label.is_a?(Hash)
+ raise Puppet::DevError, "The label must be a hash"
+ end
+
+ if label[:event] and label[:event] != :NONE and ! label[:callback]
+ raise Puppet::DevError, "You must pass a callback for non-NONE events"
+ end
+ else
+ label = {}
+ end
+
+ super(source, target, label)
+ end
+
+ # Does the passed event match our event? This is where the meaning
+ # of :NONE comes from.
+ def match?(event)
+ if event == :NONE or self.event == :NONE
+ return false
+ elsif self.event == :ALL_EVENTS or event == :ALL_EVENTS or event == self.event
+ return true
+ else
+ return false
+ end
+ end
+end
+
+# $Id$
diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb
index a7ea48a19..1551acbb6 100644
--- a/lib/puppet/transaction.rb
+++ b/lib/puppet/transaction.rb
@@ -29,7 +29,7 @@ class Transaction
# First make sure there are no failed dependencies
child.eachdependency do |dep|
skip = false
- if @failures[dep] > 0
+ if fails = failed?(dep)
child.notice "Dependency %s[%s] has %s failures" %
[dep.class.name, dep.name, @failures[dep]]
skip = true
@@ -209,7 +209,11 @@ class Transaction
# Determine whether a given object has failed.
def failed?(obj)
- @failures[obj] > 0
+ if @failures[obj] > 0
+ return @failures[obj]
+ else
+ return false
+ end
end
# this should only be called by a Puppet::Container object now
diff --git a/lib/puppet/type/component.rb b/lib/puppet/type/component.rb
index c159ccc83..3165d9e11 100644
--- a/lib/puppet/type/component.rb
+++ b/lib/puppet/type/component.rb
@@ -5,6 +5,7 @@
require 'puppet'
require 'puppet/type'
require 'puppet/transaction'
+require 'puppet/pgraph'
module Puppet
newtype(:component) do
@@ -149,6 +150,24 @@ module Puppet
end
}
end
+
+ # Convert to a graph object with all of the container info.
+ def to_graph
+ graph = Puppet::PGraph.new
+
+ delver = proc do |obj|
+ obj.each do |child|
+ if child.is_a?(Puppet::Type)
+ graph.add_edge!(obj, child)
+ delver.call(child)
+ end
+ end
+ end
+
+ delver.call(self)
+
+ return graph
+ end
def to_s
return "component(%s)" % self.title
diff --git a/lib/puppet/util/graph.rb b/lib/puppet/util/graph.rb
index c32d15cb4..d9e122c7b 100644
--- a/lib/puppet/util/graph.rb
+++ b/lib/puppet/util/graph.rb
@@ -2,18 +2,7 @@
# Copyright (c) 2006. All rights reserved.
require 'puppet'
-require 'puppet/gratr/digraph'
-require 'puppet/gratr/import'
-require 'puppet/gratr/dot'
-
-class GRATR::Digraph
- # Determine all of the leaf nodes below a given vertex.
- def leaves(vertex, type = :dfs)
- tree = tree_from_vertex(vertex, type)
- leaves = tree.keys.find_all { |c| adjacent(c, :direction => :out).empty? }
- return leaves
- end
-end
+require 'puppet/pgraph'
# A module that handles the small amount of graph stuff in Puppet.
module Puppet::Util::Graph
@@ -23,7 +12,7 @@ module Puppet::Util::Graph
def to_graph(graph = nil, &block)
# Allow our calling function to send in a graph, so that we
# can call this recursively with one graph.
- graph ||= GRATR::Digraph.new
+ graph ||= Puppet::PGraph.new
self.each do |child|
unless block_given? and ! yield(child)
@@ -45,14 +34,6 @@ module Puppet::Util::Graph
graph
end
-
- def to_jpg(graph = nil)
- graph ||= to_graph
- gv = graph.vertices
- Dir.chdir("/Users/luke/Desktop/pics") do
- graph.induced_subgraph(gv).write_to_graphic_file('jpg', 'graph')
- end
- end
end
# $Id$ \ No newline at end of file
diff --git a/test/lib/puppettest.rb b/test/lib/puppettest.rb
index 05d59a3a7..6e3d8cd4c 100755
--- a/test/lib/puppettest.rb
+++ b/test/lib/puppettest.rb
@@ -11,9 +11,11 @@ module PuppetTest
# the parent of that dir.
def basedir(*list)
unless defined? @@basedir
- case $0
- when /rake_test_loader/
+ case
+ when $0 =~ /rake_test_loader/
@@basedir = File.dirname(Dir.getwd)
+ when ENV['BASEDIR']
+ @@basedir = ENV['BASEDIR']
else
dir = nil
app = $0.sub /^\.\//, ""
diff --git a/test/lib/puppettest/graph.rb b/test/lib/puppettest/graph.rb
new file mode 100644
index 000000000..a6d0069cc
--- /dev/null
+++ b/test/lib/puppettest/graph.rb
@@ -0,0 +1,40 @@
+#!/usr/bin/env ruby
+#
+# Created by Luke A. Kanies on 2006-11-24.
+# Copyright (c) 2006. All rights reserved.
+
+require 'puppet/util/graph'
+
+class Container
+ include Puppet::Util::Graph
+ include Enumerable
+ attr_accessor :name
+ def each
+ @children.each do |c| yield c end
+ end
+
+ def initialize(name, ary)
+ @name = name
+ @children = ary
+ end
+
+ def push(*ary)
+ ary.each { |c| @children.push(c)}
+ end
+
+ def to_s
+ @name
+ end
+end
+
+module PuppetTest::Graph
+ def build_tree
+ one = Container.new("one", %w{a b})
+ two = Container.new("two", ["c", "d"])
+ middle = Container.new("middle", ["e", "f", two])
+ top = Container.new("top", ["g", "h", middle, one])
+ return one, two, middle, top
+ end
+end
+
+# $Id$ \ No newline at end of file
diff --git a/test/other/pgraph.rb b/test/other/pgraph.rb
new file mode 100644
index 000000000..c3baa7722
--- /dev/null
+++ b/test/other/pgraph.rb
@@ -0,0 +1,77 @@
+#!/usr/bin/env ruby
+#
+# Created by Luke Kanies on 2006-11-16.
+# Copyright (c) 2006. All rights reserved.
+
+$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/
+
+require 'puppettest'
+require 'puppettest/graph'
+
+class TestPGraph < Test::Unit::TestCase
+ include PuppetTest
+ include PuppetTest::Graph
+
+ def test_collect_targets
+ graph = Puppet::PGraph.new
+
+ event = Puppet::Event.new(:source => "a", :event => :yay)
+ none = Puppet::Event.new(:source => "a", :event => :NONE)
+
+ graph.add_edge!("a", "b", :event => :yay)
+
+ # Try it for the trivial case of one target and a matching event
+ assert_equal(["b"], graph.collect_targets([event]))
+
+ # Make sure we get nothing with a different event
+ assert_equal([], graph.collect_targets([none]))
+
+ # Set up multiple targets and make sure we get them all back
+ graph.add_edge!("a", "c", :event => :yay)
+ assert_equal(["b", "c"].sort, graph.collect_targets([event]).sort)
+ assert_equal([], graph.collect_targets([none]))
+ end
+
+ def test_dependencies
+ graph = Puppet::PGraph.new
+
+ graph.add_edge!("a", "b")
+ graph.add_edge!("a", "c")
+ graph.add_edge!("b", "d")
+
+ assert_equal(%w{b c d}.sort, graph.dependencies("a").sort)
+ assert_equal(%w{d}.sort, graph.dependencies("b").sort)
+ assert_equal([].sort, graph.dependencies("c").sort)
+ end
+
+ # Test that we can take a containment graph and rearrange it by dependencies
+ def test_splice
+ one, two, middle, top = build_tree
+ contgraph = top.to_graph
+
+ # Now make a dependency graph
+ deps = Puppet::PGraph.new
+
+ contgraph.vertices.each do |v|
+ deps.add_vertex(v)
+ end
+
+ {one => two, "f" => "c", "h" => middle}.each do |source, target|
+ deps.add_edge!(source, target)
+ end
+
+ deps.to_jpg("deps-before")
+
+ deps.splice!(contgraph, Container)
+
+ assert(! deps.cyclic?, "Created a cyclic graph")
+
+ nons = deps.vertices.find_all { |v| ! v.is_a?(String) }
+ assert(nons.empty?,
+ "still contain non-strings %s" % nons.inspect)
+
+ deps.to_jpg("deps-after")
+ end
+end
+
+# $Id$ \ No newline at end of file
diff --git a/test/other/relationship.rb b/test/other/relationship.rb
new file mode 100644
index 000000000..c6f8eb763
--- /dev/null
+++ b/test/other/relationship.rb
@@ -0,0 +1,48 @@
+#!/usr/bin/env ruby
+#
+# Created by Luke A. Kanies on 2006-11-24.
+# Copyright (c) 2006. All rights reserved.
+
+$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/
+
+require 'puppettest'
+require 'puppet/relationship'
+
+class TestRelationship < Test::Unit::TestCase
+ include PuppetTest
+
+ def test_initialize
+ rel = Puppet::Relationship
+
+ [["source", "target", "label"],
+ ["source", "target", {:event => :nothing}]
+ ].each do |ary|
+ # Make sure the label is required
+ assert_raise(Puppet::DevError) do
+ rel.new(*ary)
+ end
+ end
+ end
+
+ def test_attributes
+ rel = Puppet::Relationship
+
+ i = nil
+ assert_nothing_raised do
+ i = rel.new "source", "target", :event => :yay, :callback => :boo
+ end
+
+ assert_equal(:yay, i.event, "event was not retrieved")
+ assert_equal(:boo, i.callback, "callback was not retrieved")
+
+ # Now try it with nil values
+ assert_nothing_raised("failed to create with no event or callback") {
+ i = rel.new "source", "target"
+ }
+
+ assert_nil(i.event, "event was not nil")
+ assert_nil(i.callback, "callback was not nil")
+ end
+end
+
+# $Id$ \ No newline at end of file
diff --git a/test/other/transactions.rb b/test/other/transactions.rb
index ee4b901cb..985e9a0c5 100755
--- a/test/other/transactions.rb
+++ b/test/other/transactions.rb
@@ -461,3 +461,5 @@ class TestTransactions < Test::Unit::TestCase
end
end
end
+
+# $Id$ \ No newline at end of file
diff --git a/test/types/component.rb b/test/types/component.rb
index 78f184eed..28d6748a0 100755
--- a/test/types/component.rb
+++ b/test/types/component.rb
@@ -12,6 +12,8 @@ class TestComponent < Test::Unit::TestCase
def setup
super
@@used = {}
+ @type = Puppet::Type::Component
+ @file = Puppet::Type.type(:file)
end
def randnum(limit)
@@ -103,6 +105,53 @@ class TestComponent < Test::Unit::TestCase
}
}
end
+
+ def treefile(name)
+ @file.create :path => "/tmp/#{name}", :mode => 0755
+ end
+
+ def treecomp(name)
+ @type.create :name => name, :type => "yay"
+ end
+
+ def treenode(name, *children)
+ comp = treecomp name
+ children.each do |c|
+ if c.is_a?(String)
+ comp.push treefile(c)
+ else
+ comp.push c
+ end
+ end
+ return comp
+ end
+
+ def mktree
+ one = treenode("one", "a", "b")
+ two = treenode("two", "c", "d")
+ middle = treenode("middle", "e", "f", two)
+ top = treenode("top", "g", "h", middle, one)
+
+ return one, two, middle, top
+ end
+
+ def test_to_graph
+ one, two, middle, top = mktree
+
+ graph = nil
+ assert_nothing_raised do
+ graph = top.to_graph
+ end
+
+ assert(graph.is_a?(Puppet::PGraph), "result is not a pgraph")
+
+ [one, two, middle, top].each do |comp|
+ comp.each do |child|
+ assert(graph.edge?(comp, child),
+ "Did not create edge from %s => %s" % [comp.name, child.name])
+ end
+ end
+ end
def test_correctsorting
tmpfile = tempfile()
diff --git a/test/util/graph.rb b/test/util/graph.rb
index 0a331f0e9..1df294c77 100755
--- a/test/util/graph.rb
+++ b/test/util/graph.rb
@@ -6,32 +6,12 @@
$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/
require 'puppettest'
+require 'puppettest/graph'
require 'puppet/util/graph'
class TestUtilGraph < Test::Unit::TestCase
include PuppetTest
-
- class Container
- include Puppet::Util::Graph
- include Enumerable
- attr_accessor :name
- def each
- @children.each do |c| yield c end
- end
-
- def initialize(name, ary)
- @name = name
- @children = ary
- end
-
- def push(*ary)
- ary.each { |c| @children.push(c)}
- end
-
- def to_s
- @name
- end
- end
+ include PuppetTest::Graph
def test_to_graph
children = %w{a b c d}
@@ -54,13 +34,7 @@ class TestUtilGraph < Test::Unit::TestCase
end
def test_recursive_to_graph
- one = Container.new("one", %w{a b})
-
- two = Container.new("two", ["c", "d"])
-
- middle = Container.new("middle", ["e", "f", two])
-
- top = Container.new("top", ["g", "h", middle, one])
+ one, two, middle, top = build_tree
graph = nil
assert_nothing_raised do
@@ -77,7 +51,7 @@ class TestUtilGraph < Test::Unit::TestCase
end
end
- top.to_jpg(graph)
+ graph.to_jpg("graph")
# Now make sure we correctly retrieve the leaves from each container
{top => %w{a b c d e f g h},