diff options
-rw-r--r-- | lib/puppet/pgraph.rb | 170 | ||||
-rw-r--r-- | lib/puppet/relationship.rb | 51 | ||||
-rw-r--r-- | lib/puppet/transaction.rb | 8 | ||||
-rw-r--r-- | lib/puppet/type/component.rb | 19 | ||||
-rw-r--r-- | lib/puppet/util/graph.rb | 23 | ||||
-rwxr-xr-x | test/lib/puppettest.rb | 6 | ||||
-rw-r--r-- | test/lib/puppettest/graph.rb | 40 | ||||
-rw-r--r-- | test/other/pgraph.rb | 77 | ||||
-rw-r--r-- | test/other/relationship.rb | 48 | ||||
-rwxr-xr-x | test/other/transactions.rb | 2 | ||||
-rwxr-xr-x | test/types/component.rb | 49 | ||||
-rwxr-xr-x | test/util/graph.rb | 34 |
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}, |