diff --git a/lib/puppet/pgraph.rb b/lib/puppet/pgraph.rb index d988ff36a..a8ff952ed 100644 --- a/lib/puppet/pgraph.rb +++ b/lib/puppet/pgraph.rb @@ -1,122 +1,122 @@ #!/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 # This is the type used for splicing. attr_accessor :container_type def clear @vertex_dict.clear if defined? @edge_number @edge_number.clear end 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 # Collect all of the edges that the passed events match. Returns # an array of edges. def matching_edges(events) events.collect do |event| source = 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.each { |edge| target = edge.target if target.respond_to?(:ref) source.info "Scheduling %s of %s" % [edge.callback, target.ref] 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) 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) if leaves.empty? remove_vertex!(vertex) next end # First create new edges for each of the :in edges adjacent(vertex, :direction => :in, :type => :edges).each do |edge| leaves.each do |leaf| add_edge!(edge.source, leaf, edge.label) 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, :type => :edges).each do |edge| leaves.each do |leaf| add_edge!(leaf, edge.target, edge.label) 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 # For some reason, unconnected vertices do not show up in # this graph. - def to_jpg(name) + def to_jpg(path, name) gv = vertices() - Dir.chdir("/Users/luke/Desktop/pics") do + Dir.chdir(path) do induced_subgraph(gv).write_to_graphic_file('jpg', name) end end end # $Id$ diff --git a/lib/puppet/type/resources.rb b/lib/puppet/type/resources.rb new file mode 100644 index 000000000..7e81a08b3 --- /dev/null +++ b/lib/puppet/type/resources.rb @@ -0,0 +1,68 @@ +# Created by Luke Kanies on 2006-12-12. +# Copyright (c) 2006. All rights reserved. + +require 'puppet' + +Puppet::Type.newtype(:resources) do + @doc = "This is a metatype that can manage other resource types. Any + metaparams specified here will be passed on to any generated resources, + so you can purge umanaged resources but set ``noop`` to true so the + purging is only logged and does not actually happen." + + + newparam(:name) do + desc "The name of the type to be managed." + + validate do |name| + unless Puppet::Type.type(name) + raise ArgumentError, "Could not find resource type '%s'" % name + end + end + end + + newparam(:purge, :boolean => true) do + desc "Purge unmanaged resources. This will delete any resource + that is not specified in your configuration + and is not required by any specified resources." + + newvalues(:true, :false) + + validate do |value| + if [:true, true, "true"].include?(value) + unless @parent.resource_type.respond_to?(:list) + raise ArgumentError, "Purging resources of type %s is not supported" % @parent[:name] + end + unless @parent.resource_type.validstate?(:ensure) + raise ArgumentError, "Purging is only supported on types that accept 'ensure'" + end + end + end + end + + # Generate any new resources we need to manage. + def generate + resource_type.list.find_all do |resource| + ! resource.managed? + end.each do |resource| + begin + resource[:ensure] = :absent + rescue ArgumentError, Parse::Error => detail + err "The 'ensure' attribute on %s resources does not accept 'absent' as a value" % + [self[:name]] + return [] + end + end + end + + def resource_type + unless defined? @resource_type + unless type = Puppet::Type.type(self[:name]) + raise Puppet::DevError, "Could not find resource type" + end + @resource_type = type + end + @resource_type + end +end + +# $Id$ \ No newline at end of file diff --git a/test/other/dsl.rb b/test/other/dsl.rb index 50eac4781..b36cca745 100755 --- a/test/other/dsl.rb +++ b/test/other/dsl.rb @@ -1,217 +1,218 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppet' require 'puppet/dsl' require 'puppet/autoload' require 'puppettest' class TestDSL < Test::Unit::TestCase include PuppetTest include Puppet::DSL def teardown Puppet::Aspect.clear end def test_aspect a = nil assert_nothing_raised do a = aspect :yaytest do end end assert_equal(a, Puppet::Aspect[:yaytest]) # Now make a child aspect b = nil assert_nothing_raised do b = aspect :child, :inherits => :yaytest do end end assert(b.child_of?(a), "Parentage not set up correctly") assert(b.child_of?(:yaytest), "Parentage not set up for symbols") # Now make another subclass c = nil assert_nothing_raised do c = aspect :kid, :inherits => :child do end end assert(c.child_of?(b), "Parentage not set up correctly") assert(c.child_of?(a), "Parentage is not inherited") # Lastly, make a separate aspect x = nil assert_nothing_raised do x = aspect :other do end end assert(! x.child_of?(a), "Parentage came from nowhere") assert(! x.child_of?(b), "Parentage came from nowhere") assert(! x.child_of?(c), "Parentage came from nowhere") # Make sure we can specify the name or the aspect y = nil assert_nothing_raised do x = aspect :naming, :inherits => a do end end assert(x.child_of?(a), "Parentage not set up correctly") # And make sure the parent must exist z = nil assert_raise(RuntimeError) do z = aspect :noparent, :inherits => :nosuchaspect do end end assert(x.child_of?(a), "Parentage not set up correctly") end def test_evaluate parent = child = nil parenteval = childeval = nil assert_nothing_raised do parent = aspect :parent do if parenteval raise "parent already evaluated" end parenteval = true end child = aspect :child, :inherits => parent do if childeval raise "child already evaluated" end childeval = true end end assert_nothing_raised do parent.evaluate() end assert(parenteval, "Parent was not evaluated") assert(parent.evaluated?, "parent was not considered evaluated") # Make sure evaluating twice silently does nothing assert_nothing_raised do parent.evaluate() end # Now evaluate the child assert_nothing_raised do child.evaluate end assert(childeval, "child was not evaluated") assert(child.evaluated?, "child was not considered evaluated") # Now reset them both parenteval = childeval = nil parent.evaluated = false child.evaluated = false # evaluate the child assert_nothing_raised do child.evaluate end # and make sure both get evaluated assert(parenteval, "Parent was not evaluated") assert(parent.evaluated?, "parent was not considered evaluated") assert(childeval, "child was not evaluated") assert(child.evaluated?, "child was not considered evaluated") end def test_acquire evalled = false a = aspect :test do evalled = true end assert_nothing_raised do acquire :test end assert(evalled, "Did not evaluate aspect") assert_nothing_raised do acquire :test end end def test_newresource filetype = Puppet::Type.type(:file) path = tempfile() a = aspect :testing resource = nil assert_nothing_raised do resource = a.newresource filetype, path, :content => "yay", :mode => "640" end assert_instance_of(Puppet::Parser::Resource, resource) assert_equal("yay", resource[:content]) assert_equal("640", resource[:mode]) assert_equal(:testing, resource.source.name) # Now try exporting our aspect assert_nothing_raised do a.evaluate end result = nil assert_nothing_raised do result = a.export end assert_equal([resource], result) # Then try the DSL export assert_nothing_raised do result = export end assert_instance_of(Puppet::TransBucket, result) # And just for kicks, test applying everything assert_nothing_raised do apply() end assert(FileTest.exists?(path), "File did not get created") assert_equal("yay", File.read(path)) end def test_typemethods Puppet::Type.loadall filetype = Puppet::Type.type(:file) path = tempfile() a = aspect :testing Puppet::Type.eachtype do |type| + next if type.name.to_s =~ /test/ assert(a.respond_to?(type.name), "Aspects do not have a %s method" % type.name) end file = nil assert_nothing_raised do file = a.file path, :content => "yay", :mode => "640" end assert_instance_of(Puppet::Parser::Resource, file) end end # $Id$ diff --git a/test/other/pgraph.rb b/test/other/pgraph.rb index 88f753131..1ef853cc4 100644 --- a/test/other/pgraph.rb +++ b/test/other/pgraph.rb @@ -1,128 +1,126 @@ #!/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 Edge = Puppet::Relationship def test_clear graph = Puppet::PGraph.new graph.add_edge!("a", "b") graph.add_vertex! "c" assert_nothing_raised do graph.clear end assert(graph.vertices.empty?, "Still have vertices after clear") assert(graph.edges.empty?, "still have edges after clear") end def test_matching_edges graph = Puppet::PGraph.new event = Puppet::Event.new(:source => "a", :event => :yay) none = Puppet::Event.new(:source => "a", :event => :NONE) edges = {} edges["a/b"] = Edge["a", "b", {:event => :yay, :callback => :refresh}] edges["a/c"] = Edge["a", "c", {:event => :yay, :callback => :refresh}] graph.add_edge!(edges["a/b"]) # Try it for the trivial case of one target and a matching event assert_equal([edges["a/b"]], graph.matching_edges([event])) # Make sure we get nothing with a different event assert_equal([], graph.matching_edges([none])) # Set up multiple targets and make sure we get them all back graph.add_edge!(edges["a/c"]) assert_equal([edges["a/b"], edges["a/c"]].sort, graph.matching_edges([event]).sort) assert_equal([], graph.matching_edges([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 empty = Container.new("empty", []) # Also, add an empty container to top top.push empty contgraph = top.to_graph # Now add a couple of child files, so that we can test whether all containers # get spliced, rather than just components. # Now make a dependency graph deps = Puppet::PGraph.new contgraph.vertices.each do |v| deps.add_vertex(v) end # We have to specify a relationship to our empty container, else it never makes it # into the dep graph in the first place. {one => two, "f" => "c", "h" => middle, "c" => empty}.each do |source, target| deps.add_edge!(source, target, :callback => :refresh) end - deps.to_jpg("deps-before") - deps.splice!(contgraph, Container) assert(! deps.cyclic?, "Created a cyclic graph") # Now make sure the containers got spliced correctly. contgraph.leaves(middle).each do |leaf| assert(deps.edge?("h", leaf), "no edge for h => %s" % leaf) end one.each do |oobj| two.each do |tobj| assert(deps.edge?(oobj, tobj), "no %s => %s edge" % [oobj, tobj]) end end # Make sure there are no container objects remaining c = deps.vertices.find_all { |v| v.is_a?(Container) } assert(c.empty?, "Still have containers %s" % c.inspect) 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") deps.edges.each do |edge| assert_equal({:callback => :refresh}, edge.label, "Label was not copied on splice") end end # Make sure empty containers are also removed def test_empty_splice end end -# $Id$ \ No newline at end of file +# $Id$ diff --git a/test/other/transactions.rb b/test/other/transactions.rb index 67a2daea9..4190ada3a 100755 --- a/test/other/transactions.rb +++ b/test/other/transactions.rb @@ -1,772 +1,771 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppet' require 'puppettest' require 'puppettest/support/resources' # $Id$ class TestTransactions < Test::Unit::TestCase include PuppetTest::FileTesting include PuppetTest::Support::Resources def mkgenerator(&block) # Create a bogus type that generates new instances with shorter type = Puppet::Type.newtype(:generator) do newparam(:name, :namevar => true) end if block type.class_eval(&block) end cleanup do Puppet::Type.rmtype(:generator) end return type end def test_reports path1 = tempfile() path2 = tempfile() objects = [] objects << Puppet::Type.newfile( :path => path1, :content => "yayness" ) objects << Puppet::Type.newfile( :path => path2, :content => "booness" ) trans = assert_events([:file_created, :file_created], *objects) report = nil assert_nothing_raised { report = trans.report } # First test the report logs assert(report.logs.length > 0, "Did not get any report logs") report.logs.each do |obj| assert_instance_of(Puppet::Log, obj) end # Then test the metrics metrics = report.metrics assert(metrics, "Did not get any metrics") assert(metrics.length > 0, "Did not get any metrics") assert(metrics.has_key?("resources"), "Did not get object metrics") assert(metrics.has_key?("changes"), "Did not get change metrics") metrics.each do |name, metric| assert_instance_of(Puppet::Metric, metric) end end def test_prefetch # Create a type just for testing prefetch name = :prefetchtesting $prefetched = false type = Puppet::Type.newtype(name) do newparam(:name) {} end cleanup do Puppet::Type.rmtype(name) end # Now create a provider type.provide(:prefetch) do def self.prefetch $prefetched = true end end # Now create an instance inst = type.create :name => "yay" # Create a transaction trans = Puppet::Transaction.new(newcomp(inst)) # Make sure prefetch works assert_nothing_raised do trans.prefetch end assert_equal(true, $prefetched, "type prefetch was not called") # Now make sure it gets called from within evaluate() $prefetched = false assert_nothing_raised do trans.evaluate end assert_equal(true, $prefetched, "evaluate did not call prefetch") end def test_refreshes_generate_events path = tempfile() firstpath = tempfile() secondpath = tempfile() file = Puppet::Type.newfile(:title => "file", :path => path, :content => "yayness") first = Puppet::Type.newexec(:title => "first", :command => "/bin/echo first > #{firstpath}", :subscribe => [:file, path], :refreshonly => true ) second = Puppet::Type.newexec(:title => "second", :command => "/bin/echo second > #{secondpath}", :subscribe => [:exec, "first"], :refreshonly => true ) assert_apply(file, first, second) assert(FileTest.exists?(secondpath), "Refresh did not generate an event") end unless %x{groups}.chomp.split(/ /).length > 1 $stderr.puts "You must be a member of more than one group to test transactions" else def ingroup(gid) require 'etc' begin group = Etc.getgrgid(gid) rescue => detail puts "Could not retrieve info for group %s: %s" % [gid, detail] return nil end return @groups.include?(group.name) end def setup super @groups = %x{groups}.chomp.split(/ /) unless @groups.length > 1 p @groups raise "You must be a member of more than one group to test this" end end def newfile(hash = {}) tmpfile = tempfile() File.open(tmpfile, "w") { |f| f.puts rand(100) } # XXX now, because os x apparently somehow allows me to make a file # owned by a group i'm not a member of, i have to verify that # the file i just created is owned by one of my groups # grrr unless ingroup(File.stat(tmpfile).gid) Puppet.info "Somehow created file in non-member group %s; fixing" % File.stat(tmpfile).gid require 'etc' firstgr = @groups[0] unless firstgr.is_a?(Integer) str = Etc.getgrnam(firstgr) firstgr = str.gid end File.chown(nil, firstgr, tmpfile) end hash[:name] = tmpfile assert_nothing_raised() { return Puppet.type(:file).create(hash) } end def newservice assert_nothing_raised() { return Puppet.type(:service).create( :name => "sleeper", :type => "init", :path => exampledir("root/etc/init.d"), :hasstatus => true, :check => [:ensure] ) } end def newexec(file) assert_nothing_raised() { return Puppet.type(:exec).create( :name => "touch %s" % file, :path => "/bin:/usr/bin:/sbin:/usr/sbin", :returns => 0 ) } end # modify a file and then roll the modifications back def test_filerollback transaction = nil file = newfile() states = {} check = [:group,:mode] file[:check] = check assert_nothing_raised() { file.retrieve } assert_nothing_raised() { check.each { |state| assert(file[state]) states[state] = file[state] } } component = newcomp("file",file) require 'etc' groupname = Etc.getgrgid(File.stat(file.name).gid).name assert_nothing_raised() { # Find a group that it's not set to group = @groups.find { |group| group != groupname } unless group raise "Could not find suitable group" end file[:group] = group file[:mode] = "755" } trans = assert_events([:file_changed, :file_changed], component) file.retrieve assert_rollback_events(trans, [:file_changed, :file_changed], "file") assert_nothing_raised() { file.retrieve } states.each { |state,value| assert_equal( value,file.is(state), "File %s remained %s" % [state, file.is(state)] ) } end # start a service, and then roll the modification back # Disabled, because it wasn't really worth the effort. def disabled_test_servicetrans transaction = nil service = newservice() component = newcomp("service",service) assert_nothing_raised() { service[:ensure] = 1 } service.retrieve assert(service.insync?, "Service did not start") system("ps -ef | grep ruby") trans = assert_events([:service_started], component) service.retrieve assert_rollback_events(trans, [:service_stopped], "service") end # test that services are correctly restarted and that work is done # in the right order def test_refreshing transaction = nil file = newfile() execfile = File.join(tmpdir(), "exectestingness") exec = newexec(execfile) states = {} check = [:group,:mode] file[:check] = check file[:group] = @groups[0] assert_apply(file) @@tmpfiles << execfile component = newcomp("both",file,exec) # 'subscribe' expects an array of arrays exec[:subscribe] = [[file.class.name,file.name]] exec[:refreshonly] = true assert_nothing_raised() { file.retrieve exec.retrieve } check.each { |state| states[state] = file[state] } assert_nothing_raised() { file[:mode] = "755" } trans = assert_events([:file_changed, :triggered], component) assert(FileTest.exists?(execfile), "Execfile does not exist") File.unlink(execfile) assert_nothing_raised() { file[:group] = @groups[1] } trans = assert_events([:file_changed, :triggered], component) assert(FileTest.exists?(execfile), "Execfile does not exist") end # Verify that one component requiring another causes the contained # resources in the requiring component to get refreshed. def test_refresh_across_two_components transaction = nil file = newfile() execfile = File.join(tmpdir(), "exectestingness2") @@tmpfiles << execfile exec = newexec(execfile) states = {} check = [:group,:mode] file[:check] = check file[:group] = @groups[0] assert_apply(file) fcomp = newcomp("file",file) ecomp = newcomp("exec",exec) component = newcomp("both",fcomp,ecomp) # 'subscribe' expects an array of arrays #component[:require] = [[file.class.name,file.name]] ecomp[:subscribe] = fcomp exec[:refreshonly] = true trans = assert_events([], component) assert_nothing_raised() { file[:group] = @groups[1] file[:mode] = "755" } trans = assert_events([:file_changed, :file_changed, :triggered], component) end # Make sure that multiple subscriptions get triggered. def test_multisubs path = tempfile() file1 = tempfile() file2 = tempfile() file = Puppet.type(:file).create( :path => path, :ensure => "file" ) exec1 = Puppet.type(:exec).create( :path => ENV["PATH"], :command => "touch %s" % file1, :refreshonly => true, :subscribe => [:file, path] ) exec2 = Puppet.type(:exec).create( :path => ENV["PATH"], :command => "touch %s" % file2, :refreshonly => true, :subscribe => [:file, path] ) assert_apply(file, exec1, exec2) assert(FileTest.exists?(file1), "File 1 did not get created") assert(FileTest.exists?(file2), "File 2 did not get created") end # Make sure that a failed trigger doesn't result in other events not # getting triggered. def test_failedrefreshes path = tempfile() newfile = tempfile() file = Puppet.type(:file).create( :path => path, :ensure => "file" ) svc = Puppet.type(:service).create( :name => "thisservicedoesnotexist", :subscribe => [:file, path] ) exec = Puppet.type(:exec).create( :path => ENV["PATH"], :command => "touch %s" % newfile, :logoutput => true, :refreshonly => true, :subscribe => [:file, path] ) assert_apply(file, svc, exec) assert(FileTest.exists?(path), "File did not get created") assert(FileTest.exists?(newfile), "Refresh file did not get created") end # Make sure that unscheduled and untagged objects still respond to events def test_unscheduled_and_untagged_response Puppet::Type.type(:schedule).mkdefaultschedules Puppet[:ignoreschedules] = false file = Puppet.type(:file).create( :name => tempfile(), :ensure => "file" ) fname = tempfile() exec = Puppet.type(:exec).create( :name => "touch %s" % fname, :path => "/usr/bin:/bin", :schedule => "monthly", :subscribe => ["file", file.name] ) comp = newcomp(file,exec) comp.finalize # Run it once assert_apply(comp) assert(FileTest.exists?(fname), "File did not get created") assert(!exec.scheduled?, "Exec is somehow scheduled") # Now remove it, so it can get created again File.unlink(fname) file[:content] = "some content" assert_events([:file_changed, :triggered], comp) assert(FileTest.exists?(fname), "File did not get recreated") # Now remove it, so it can get created again File.unlink(fname) # And tag our exec exec.tag("testrun") # And our file, so it runs file.tag("norun") Puppet[:tags] = "norun" file[:content] = "totally different content" assert(! file.insync?, "Uh, file is in sync?") assert_events([:file_changed, :triggered], comp) assert(FileTest.exists?(fname), "File did not get recreated") end def test_failed_reqs_mean_no_run exec = Puppet::Type.type(:exec).create( :command => "/bin/mkdir /this/path/cannot/possibly/exit", :title => "mkdir" ) file1 = Puppet::Type.type(:file).create( :title => "file1", :path => tempfile(), :require => exec, :ensure => :file ) file2 = Puppet::Type.type(:file).create( :title => "file2", :path => tempfile(), :require => file1, :ensure => :file ) comp = newcomp(exec, file1, file2) comp.finalize assert_apply(comp) assert(! FileTest.exists?(file1[:path]), "File got created even tho its dependency failed") assert(! FileTest.exists?(file2[:path]), "File got created even tho its deep dependency failed") end end def f(n) Puppet::Type.type(:file)["/tmp/#{n.to_s}"] end def test_relationship_graph one, two, middle, top = mktree {one => two, "f" => "c", "h" => middle}.each do |source, target| if source.is_a?(String) source = f(source) end if target.is_a?(String) target = f(target) end target[:require] = source end trans = Puppet::Transaction.new(top) graph = nil assert_nothing_raised do graph = trans.relationship_graph end assert_instance_of(Puppet::PGraph, graph, "Did not get relationship graph") # Make sure all of the components are gone comps = graph.vertices.find_all { |v| v.is_a?(Puppet::Type::Component)} assert(comps.empty?, "Deps graph still contains components") # It must be reversed because of how topsort works sorted = graph.topsort.reverse # Now make sure the appropriate edges are there and are in the right order assert(graph.dependencies(f(:f)).include?(f(:c)), "c not marked a dep of f") assert(sorted.index(f(:c)) < sorted.index(f(:f)), "c is not before f") one.each do |o| two.each do |t| assert(graph.dependencies(o).include?(t), "%s not marked a dep of %s" % [t.ref, o.ref]) assert(sorted.index(t) < sorted.index(o), "%s is not before %s" % [t.ref, o.ref]) end end trans.resources.leaves(middle).each do |child| assert(graph.dependencies(f(:h)).include?(child), "%s not marked a dep of h" % [child.ref]) assert(sorted.index(child) < sorted.index(f(:h)), "%s is not before h" % child.ref) end # Lastly, make sure our 'g' vertex made it into the relationship # graph, since it's not involved in any relationships. assert(graph.vertex?(f(:g)), "Lost vertexes with no relations") # Now make the reversal graph and make sure all of the vertices made it into that reverse = graph.reversal %w{a b c d e f g h}.each do |letter| file = f(letter) assert(reverse.vertex?(file), "%s did not make it into reversal" % letter) end - graph.to_jpg("normal_relations") end # Test pre-evaluation generation def test_generate mkgenerator() do def generate ret = [] if title.length > 1 ret << self.class.create(:title => title[0..-2]) else return nil end ret end end yay = Puppet::Type.newgenerator :title => "yay" rah = Puppet::Type.newgenerator :title => "rah" comp = newcomp(yay, rah) trans = comp.evaluate assert_nothing_raised do trans.generate end %w{ya ra y r}.each do |name| assert(trans.resources.vertex?(Puppet::Type.type(:generator)[name]), "Generated %s was not a vertex" % name) end # Now make sure that cleanup gets rid of those generated types. assert_nothing_raised do trans.cleanup end %w{ya ra y r}.each do |name| assert(!trans.resources.vertex?(Puppet::Type.type(:generator)[name]), "Generated vertex %s was not removed from graph" % name) assert_nil(Puppet::Type.type(:generator)[name], "Generated vertex %s was not removed from class" % name) end end # Test mid-evaluation generation. def test_eval_generate $evaluated = [] type = mkgenerator() do def eval_generate ret = [] if title.length > 1 ret << self.class.create(:title => title[0..-2]) else return nil end ret end def evaluate $evaluated << self.title return [] end end yay = Puppet::Type.newgenerator :title => "yay" rah = Puppet::Type.newgenerator :title => "rah", :subscribe => yay comp = newcomp(yay, rah) trans = comp.evaluate trans.prepare # Now apply the resources, and make sure they appropriately generate # things. assert_nothing_raised("failed to apply yay") do trans.eval_resource(yay) end ya = type["ya"] assert(ya, "Did not generate ya") assert(trans.relgraph.vertex?(ya), "Did not add ya to rel_graph") # Now make sure the appropriate relationships were added assert(trans.relgraph.edge?(yay, ya), "parent was not required by child") assert(trans.relgraph.edge?(ya, rah), "rah was not subscribed to ya") # And make sure the relationship is a subscription with a callback, # not just a require. assert_equal({:callback => :refresh, :event => :ALL_EVENTS}, trans.relgraph[Puppet::Relationship.new(ya, rah)], "The label was not retained") # Now make sure it in turn eval_generates appropriately assert_nothing_raised("failed to apply yay") do trans.eval_resource(type["ya"]) end %w{y}.each do |name| res = type[name] assert(res, "Did not generate %s" % name) assert(trans.relgraph.vertex?(res), "Did not add %s to rel_graph" % name) end assert_nothing_raised("failed to eval_generate with nil response") do trans.eval_resource(type["y"]) end assert(trans.relgraph.edge?(yay, ya), "no edge was created for ya => yay") assert_nothing_raised("failed to apply rah") do trans.eval_resource(rah) end ra = type["ra"] assert(ra, "Did not generate ra") assert(trans.relgraph.vertex?(ra), "Did not add ra to rel_graph" % name) # Now make sure this generated resource has the same relationships as the generating # resource assert(trans.relgraph.edge?(yay, ra), "yay is not required by ra") assert(trans.relgraph.edge?(ya, ra), "ra is not subscribed to ya") # And make sure the relationship is a subscription with a callback, # not just a require. assert_equal({:callback => :refresh, :event => :ALL_EVENTS}, trans.relgraph[Puppet::Relationship.new(ya, ra)], "The label was not retained") # Now make sure that cleanup gets rid of those generated types. assert_nothing_raised do trans.cleanup end %w{ya ra y r}.each do |name| assert(!trans.relgraph.vertex?(type[name]), "Generated vertex %s was not removed from graph" % name) assert_nil(type[name], "Generated vertex %s was not removed from class" % name) end # Now, start over and make sure that everything gets evaluated. trans = comp.evaluate $evaluated.clear assert_nothing_raised do trans.evaluate end assert_equal(%w{yay ya y rah ra r}, $evaluated, "Not all resources were evaluated or not in the right order") end def test_tags res = Puppet::Type.newfile :path => tempfile() comp = newcomp(res) # Make sure they default to none assert_equal([], comp.evaluate.tags) # Make sure we get the main tags Puppet[:tags] = %w{this is some tags} assert_equal(%w{this is some tags}, comp.evaluate.tags) # And make sure they get processed correctly Puppet[:tags] = ["one", "two,three", "four"] assert_equal(%w{one two three four}, comp.evaluate.tags) # lastly, make sure we can override them trans = comp.evaluate trans.tags = ["one", "two,three", "four"] assert_equal(%w{one two three four}, comp.evaluate.tags) end def test_tagged? res = Puppet::Type.newfile :path => tempfile() comp = newcomp(res) trans = comp.evaluate assert(trans.tagged?(res), "tagged? defaulted to false") # Now set some tags trans.tags = %w{some tags} # And make sure it's false assert(! trans.tagged?(res), "matched invalid tags") # Set ignoretags and make sure it sticks trans.ignoretags = true assert(trans.tagged?(res), "tags were not ignored") # Now make sure we actually correctly match tags res[:tag] = "mytag" trans.ignoretags = false trans.tags = %w{notag} assert(! trans.tagged?(res), "tags incorrectly matched") trans.tags = %w{mytag yaytag} assert(trans.tagged?(res), "tags should have matched") end # Make sure events propagate down the relationship graph appropriately. def test_trigger end end -# $Id$ \ No newline at end of file +# $Id$ diff --git a/test/types/resources.rb b/test/types/resources.rb new file mode 100755 index 000000000..f8a2f6640 --- /dev/null +++ b/test/types/resources.rb @@ -0,0 +1,129 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2006-12-12. +# Copyright (c) 2006. All rights reserved. + +$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ + +require 'puppettest' + +class TestResources < Test::Unit::TestCase + include PuppetTest + + def add_purge_lister + # Now define the list method + class << @purgetype + def list + $purgemembers.values + end + end + end + + def mk_purger(managed = false) + @purgenum ||= 0 + @purgenum += 1 + obj = @purgetype.create :name => "purger%s" % @purgenum + $purgemembers[obj[:name]] = obj + if managed + obj[:fake] = "testing" + end + obj + end + + def mkpurgertype + # Create a purgeable type + $purgemembers = {} + @purgetype = Puppet::Type.newtype(:purgetest) do + newparam(:name, :namevar => true) {} + newstate(:ensure) do + newvalue(:absent) do + $purgemembers[@parent[:name]] = @parent + end + newvalue(:present) do + $purgemembers.delete(@parent[:name]) + end + end + newstate(:fake) do + def sync + :faked + end + end + end + cleanup do + Puppet::Type.rmtype(:purgetest) + end + end + + def setup + super + @type = Puppet::Type.type(:resources) + end + + def test_initialize + assert(@type, "Could not retrieve resources type") + # Make sure we can't create them for types that don't exist + assert_raise(ArgumentError) do + @type.create :name => "thereisnotypewiththisname" + end + + # Now make sure it works for a normal type + usertype = nil + assert_nothing_raised do + usertype = @type.create :name => "user" + end + assert(usertype, "did not create user resource type") + assert_equal(Puppet::Type.type(:user), usertype.resource_type, + "resource_type was not set correctly") + end + + def test_purge + # Create a purgeable type + mkpurgertype + + purger = nil + assert_nothing_raised do + purger = @type.create :name => "purgetest" + end + assert(purger, "did not get purger manager") + + # Make sure we throw an error, because the purger type does + # not support listing. + + # It should work when we set it to false + assert_nothing_raised do + purger[:purge] = false + end + # but not true + assert_raise(ArgumentError) do + purger[:purge] = true + end + add_purge_lister() + + assert_equal($purgemembers.values.sort, @purgetype.list.sort) + + # and it should now succeed + assert_nothing_raised do + purger[:purge] = true + end + assert(purger.purge?, "purge boolean was not enabled") + + # Okay, now let's try doing some purging, yo + managed = [] + unmanned = [] + 3.times { managed << mk_purger(true) } # 3 managed + 3.times { unmanned << mk_purger(false) } # 3 unmanaged + + managed.each do |m| + assert(m.managed?, "managed resource was not considered managed") + end + unmanned.each do |u| + assert(! u.managed?, "unmanaged resource was considered managed") + end + + # Now make sure the generate method only finds the unmanaged resources + assert_equal(unmanned.collect { |r| r.title }.sort, purger.generate.collect { |r| r.title }, + "Did not return correct purge list") + end +end + +# $Id$ \ No newline at end of file diff --git a/test/util/graph.rb b/test/util/graph.rb index 1df294c77..7d43ed6f9 100755 --- a/test/util/graph.rb +++ b/test/util/graph.rb @@ -1,109 +1,107 @@ #!/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' require 'puppet/util/graph' class TestUtilGraph < Test::Unit::TestCase include PuppetTest include PuppetTest::Graph def test_to_graph children = %w{a b c d} list = Container.new("yay", children) graph = nil assert_nothing_raised do graph = list.to_graph end assert(graph.vertices.include?(list), "wtf?") ([list] + children).each do |thing| assert(graph.vertex?(thing), "%s is not a vertex" % thing) end children.each do |child| assert(graph.edge?(list, child), "%s/%s was not added as an edge" % ["yay", child]) end end def test_recursive_to_graph one, two, middle, top = build_tree graph = nil assert_nothing_raised do graph = top.to_graph end (%w{a b c d e f g h} + [one, two, middle, top]).each do |v| assert(graph.vertex?(v), "%s is not a vertex" % v) end [one, two, middle, top].each do |con| con.each do |child| assert(graph.edge?(con, child), "%s/%s is not an edge" % [con, child]) end end - 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}, one => %w{a b}, two => %w{c d}, middle => %w{c d e f}}.each do |cont, list| leaves = nil assert_nothing_raised do leaves = graph.leaves(cont) end leaves = leaves.sort assert_equal(list.sort, leaves.sort, "Got incorrect leaf list for %s" % cont.name) %w{a b c d e f g h}.each do |letter| unless list.include?(letter) assert(!leaves.include?(letter), "incorrectly got %s as a leaf of %s" % [letter, cont.to_s]) end end end end def test_to_graph_with_block middle = Container.new "middle", ["c", "d", 3, 4] top = Container.new "top", ["a", "b", middle, 1, 2] graph = nil assert_nothing_raised() { graph = top.to_graph { |c| c.is_a?(String) or c.is_a?(Container) } } %w{a b c d}.each do |child| assert(graph.vertex?(child), "%s was not added as a vertex" % child) end [1, 2, 3, 4].each do |child| assert(! graph.vertex?(child), "%s is a vertex" % child) end end def test_cyclic_graphs one = Container.new "one", %w{a b} two = Container.new "two", %w{c d} one.push(two) two.push(one) assert_raise(Puppet::Error, "did not fail on cyclic graph") do one.to_graph end end end -# $Id$ \ No newline at end of file +# $Id$