diff options
author | luke <luke@980ebf18-57e1-0310-9a29-db15c13687c0> | 2006-12-01 04:11:11 +0000 |
---|---|---|
committer | luke <luke@980ebf18-57e1-0310-9a29-db15c13687c0> | 2006-12-01 04:11:11 +0000 |
commit | f3a0c488b884b59df790fce0192dd1a2d735ad7c (patch) | |
tree | 2efc2f2398d9ed2df6a8cb72a0db9ee01035433f | |
parent | a7354d0e3bd77625de9da5f5846bfefbc6d88121 (diff) | |
download | puppet-f3a0c488b884b59df790fce0192dd1a2d735ad7c.tar.gz puppet-f3a0c488b884b59df790fce0192dd1a2d735ad7c.tar.xz puppet-f3a0c488b884b59df790fce0192dd1a2d735ad7c.zip |
Most of the graphing work is now done. I have also added the generator work in transactions, but I need to migrate files to using it. Until that migration is done, files will not work correctly for many cases.
git-svn-id: https://reductivelabs.com/svn/puppet/trunk@1896 980ebf18-57e1-0310-9a29-db15c13687c0
-rw-r--r-- | lib/puppet/metatype/container.rb | 20 | ||||
-rw-r--r-- | lib/puppet/metatype/manager.rb | 8 | ||||
-rw-r--r-- | lib/puppet/metatype/metaparams.rb | 87 | ||||
-rw-r--r-- | lib/puppet/metatype/relationships.rb | 142 | ||||
-rw-r--r-- | lib/puppet/pgraph.rb | 125 | ||||
-rw-r--r-- | lib/puppet/relationship.rb | 18 | ||||
-rw-r--r-- | lib/puppet/transaction.rb | 179 | ||||
-rw-r--r-- | lib/puppet/type.rb | 2 | ||||
-rw-r--r-- | lib/puppet/type/component.rb | 3 | ||||
-rwxr-xr-x | test/lib/puppettest/support/resources.rb | 37 | ||||
-rwxr-xr-x | test/other/overrides.rb | 10 | ||||
-rw-r--r-- | test/other/pgraph.rb | 41 | ||||
-rwxr-xr-x | test/other/relationships.rb | 216 | ||||
-rwxr-xr-x | test/other/transactions.rb | 136 | ||||
-rwxr-xr-x | test/types/component.rb | 30 | ||||
-rwxr-xr-x | test/types/manager.rb | 44 | ||||
-rwxr-xr-x | test/types/type.rb | 49 |
17 files changed, 659 insertions, 488 deletions
diff --git a/lib/puppet/metatype/container.rb b/lib/puppet/metatype/container.rb index 3f970b7d9..9ed587a4c 100644 --- a/lib/puppet/metatype/container.rb +++ b/lib/puppet/metatype/container.rb @@ -78,26 +78,6 @@ class Puppet::Type hash.clear end - - if rmdeps - Puppet::Event::Subscription.dependencies(self).each { |dep| - #info "Deleting dependency %s" % dep - #begin - # self.unsubscribe(dep) - #rescue - # # ignore failed unsubscribes - #end - dep.delete - } - Puppet::Event::Subscription.subscribers(self).each { |dep| - #info "Unsubscribing from %s" % dep - begin - dep.unsubscribe(self) - rescue - # ignore failed unsubscribes - end - } - end self.class.delete(self) if defined? @parent and @parent diff --git a/lib/puppet/metatype/manager.rb b/lib/puppet/metatype/manager.rb index 996d9a3cc..d2749b87d 100644 --- a/lib/puppet/metatype/manager.rb +++ b/lib/puppet/metatype/manager.rb @@ -106,6 +106,14 @@ module Manager klass end + + # Remove an existing defined type. Largely used for testing. + def rmtype(name) + # Then create the class. + klass = rmclass(name, + :hash => @types + ) + end # Return a Type instance by name. def type(name) diff --git a/lib/puppet/metatype/metaparams.rb b/lib/puppet/metatype/metaparams.rb index e09bbc6d5..f28103a87 100644 --- a/lib/puppet/metatype/metaparams.rb +++ b/lib/puppet/metatype/metaparams.rb @@ -92,6 +92,47 @@ class Puppet::Type } end end + + # We've got four relationship metaparameters, so this method is used + # to reduce code duplication between them. + def store_relationship(param, values) + # We need to support values passed in as an array or as a + # resource reference. + result = [] + + # 'values' could be an array or a reference. If it's an array, + # it could be an array of references or an array of arrays. + if values.is_a?(Puppet::Type) + result << [values.class.name, values.title] + else + unless values.is_a?(Array) + devfail "Relationships must be resource references" + end + if values[0].is_a?(String) or values[0].is_a?(Symbol) + # we're a type/title array reference + values[0] = symbolize(values[0]) + result << values + else + # we're an array of stuff + values.each do |value| + if value.is_a?(Puppet::Type) + result << [value.class.name, value.title] + elsif value.is_a?(Array) + value[0] = symbolize(value[0]) + result << value + else + devfail "Invalid relationship %s" % value.inspect + end + end + end + end + + if existing = self[param] + result = existing + result + end + + result + end # For each object we require, subscribe to all events that it generates. We # might reduce the level of subscription eventually, but for now... @@ -133,17 +174,7 @@ class Puppet::Type # Take whatever dependencies currently exist and add these. # Note that this probably doesn't behave correctly with unsubscribe. munge do |requires| - # We need to be two arrays deep... - unless requires.is_a?(Array) - requires = [requires] - end - unless requires[0].is_a?(Array) - requires = [requires] - end - if values = @parent[:require] - requires = values + requires - end - requires + @parent.store_relationship(:require, requires) end end @@ -167,11 +198,7 @@ class Puppet::Type " munge do |requires| - if values = @parent[:subscribe] - requires = values + requires - end - requires - # @parent.handledepends(requires, :ALL_EVENTS, :refresh) + @parent.store_relationship(:subscribe, requires) end end @@ -295,21 +322,9 @@ class Puppet::Type This will restart the sshd service if the sshd config file changes.} - # Take whatever dependencies currently exist and add these. munge do |notifies| - # We need to be two arrays deep... - unless notifies.is_a?(Array) - notifies = [notifies] - end - unless notifies[0].is_a?(Array) - notifies = [notifies] - end - if values = @parent[:notify] - notifies = values + notifies - end - notifies + @parent.store_relationship(:notify, notifies) end - end newmetaparam(:before) do @@ -331,21 +346,9 @@ class Puppet::Type This will make sure all of the files are up to date before the make command is run.} - # Take whatever dependencies currently exist and add these. munge do |notifies| - # We need to be two arrays deep... - unless notifies.is_a?(Array) - notifies = [notifies] - end - unless notifies[0].is_a?(Array) - notifies = [notifies] - end - if values = @parent[:notify] - notifies = values + notifies - end - notifies + @parent.store_relationship(:before, notifies) end - end end # Puppet::Type diff --git a/lib/puppet/metatype/relationships.rb b/lib/puppet/metatype/relationships.rb index 714ed7690..5f2471460 100644 --- a/lib/puppet/metatype/relationships.rb +++ b/lib/puppet/metatype/relationships.rb @@ -18,6 +18,7 @@ class Puppet::Type # Figure out of there are any objects we can automatically add as # dependencies. def autorequire + reqs = [] self.class.eachautorequire { |type, block| # Ignore any types we can't find, although that would be a bit odd. next unless typeobj = Puppet.type(type) @@ -32,74 +33,48 @@ class Puppet::Type list.each { |dep| obj = nil # Support them passing objects directly, to save some effort. - if dep.is_a? Puppet::Type - type = dep.class.name - obj = dep - - # Now change our dependency to just the string, instead of - # the object itself. - dep = dep.title - else + unless dep.is_a? Puppet::Type # Skip autorequires that we aren't managing - unless obj = typeobj[dep] + unless dep = typeobj[dep] next end end - - # Skip autorequires that we already require - next if self.requires?(obj) - - debug "Autorequiring %s %s" % [obj.class.name, obj.title] - self[:require] = [type, dep] + + debug "Autorequiring %s" % [dep.ref] + reqs << Puppet::Relationship[dep, self] } - - #self.info reqs.inspect - #self[:require] = reqs } + + return reqs end - # Build the dependencies associated with an individual object. + # Build the dependencies associated with an individual object. :in + # relationships are specified by the event-receivers, and :out + # relationships are specified by the event generator. This + # way 'source' and 'target' are consistent terms in both edges + # and events -- that is, an event targets edges whose source matches + # the event's source. Note that the direction of the relationship + # doesn't actually mean anything until you start using events -- + # the same information is present regardless. def builddepends # Handle the requires - if self[:require] - self.handledepends(self[:require], :NONE, nil, true) - end - - # And the subscriptions - if self[:subscribe] - self.handledepends(self[:subscribe], :ALL_EVENTS, :refresh, true) - end - - if self[:notify] - self.handledepends(self[:notify], :ALL_EVENTS, :refresh, false) - end - - if self[:before] - self.handledepends(self[:before], :NONE, nil, false) - end - end - - # return all objects that we depend on - def eachdependency - Puppet::Event::Subscription.dependencies(self).each { |dep| - yield dep.source - } - end - - # return all objects subscribed to the current object - def eachsubscriber - Puppet::Event::Subscription.subscribers(self).each { |sub| - yield sub.target - } + {:require => [:NONE, nil, :in], + :subscribe => [:ALL_EVENTS, :refresh, :in], + :notify => [:ALL_EVENTS, :refresh, :out], + :before => [:NONE, nil, :out]}.collect do |type, args| + if self[type] + handledepends(self[type], *args) + end + end.flatten.reject { |r| r.nil? } end - def handledepends(requires, event, method, up) + def handledepends(requires, event, method, direction) # Requires are specified in the form of [type, name], so they're always # an array. But we want them to be an array of arrays. unless requires[0].is_a?(Array) requires = [requires] end - requires.each { |rname| + requires.collect { |rname| # we just have a name and a type, and we need to convert it # to an object... type = nil @@ -115,9 +90,9 @@ class Puppet::Type end self.debug("subscribes to %s" % [object]) - # Are we requiring them, or vice versa? - source = target = nil - if up + # Are we requiring them, or vice versa? See the builddepends + # method for further docs on this. + if direction == :in source = object target = self else @@ -129,61 +104,30 @@ class Puppet::Type # we store the method to call when a given subscription is # triggered, but the source object decides whether subargs = { - :event => event, - :source => source, - :target => target + :event => event } - if method and target.respond_to?(method) + if method subargs[:callback] = method end - Puppet::Event::Subscription.new(subargs) + rel = Puppet::Relationship.new(source, target, subargs) } end - def requires?(object) - req = false - self.eachdependency { |dep| - if dep == object - req = true - break - end - } - - return req - end - - def subscribe(hash) - hash[:source] = self - Puppet::Event::Subscription.new(hash) - - # add to the correct area - #@subscriptions.push sub - end - - def subscribesto?(object) - sub = false - self.eachsubscriber { |o| - if o == object - sub = true - break - end - } - - return sub - end - # Unsubscribe from a given object, possibly with a specific event. def unsubscribe(object, event = nil) - Puppet::Event::Subscription.dependencies(self).find_all { |sub| - if event - sub.match?(event) - else - sub.source == object + # First look through our own relationship params + [:require, :subscribe].each do |param| + if values = self[param] + newvals = values.reject { |d| + d == [object.class.name, object.title] + } + if newvals.length != values.length + self.delete(param) + self[param] = newvals + end end - }.each { |sub| - sub.delete - } + end end # we've received an event diff --git a/lib/puppet/pgraph.rb b/lib/puppet/pgraph.rb index fba93d20c..292e25073 100644 --- a/lib/puppet/pgraph.rb +++ b/lib/puppet/pgraph.rb @@ -11,27 +11,6 @@ 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 @@ -49,6 +28,32 @@ class Puppet::PGraph < GRATR::Digraph 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 @@ -63,9 +68,9 @@ class Puppet::PGraph < GRATR::Digraph next if leaves.empty? # First create new edges for each of the :in edges - adjacent(vertex, :direction => :in).each do |up| + adjacent(vertex, :direction => :in, :type => :edges).each do |edge| leaves.each do |leaf| - add_edge!(up, leaf) + add_edge!(edge.source, leaf, edge.label) if cyclic? raise ArgumentError, "%s => %s results in a loop" % @@ -75,9 +80,9 @@ class Puppet::PGraph < GRATR::Digraph end # Then for each of the out edges - adjacent(vertex, :direction => :out).each do |down| + adjacent(vertex, :direction => :out, :type => :edges).each do |edge| leaves.each do |leaf| - add_edge!(leaf, down) + add_edge!(leaf, edge.target, edge.label) if cyclic? raise ArgumentError, "%s => %s results in a loop" % @@ -89,76 +94,10 @@ class Puppet::PGraph < GRATR::Digraph # 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 + # For some reason, unconnected vertices do not show up in + # this graph. def to_jpg(name) gv = vertices() Dir.chdir("/Users/luke/Desktop/pics") do diff --git a/lib/puppet/relationship.rb b/lib/puppet/relationship.rb index cbd15b2af..879beeab7 100644 --- a/lib/puppet/relationship.rb +++ b/lib/puppet/relationship.rb @@ -11,15 +11,23 @@ require 'puppet/gratr' class Puppet::Relationship < GRATR::Edge # Return the callback def callback - label[:callback] + if label + label[:callback] + else + nil + end end # Return our event. def event - label[:event] + if label + label[:event] + else + nil + end end - def initialize(source, target, label = nil) + def initialize(source, target, label = {}) if label unless label.is_a?(Hash) raise Puppet::DevError, "The label must be a hash" @@ -46,6 +54,10 @@ class Puppet::Relationship < GRATR::Edge return false end end + + def ref + "%s => %s" % [source.ref, target.ref] + end end # $Id$ diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb index 1551acbb6..6835b1b19 100644 --- a/lib/puppet/transaction.rb +++ b/lib/puppet/transaction.rb @@ -1,4 +1,4 @@ -# the class that actually walks our object/state tree, collects the changes, +# the class that actually walks our resource/state tree, collects the changes, # and performs them require 'puppet' @@ -6,13 +6,14 @@ require 'puppet/statechange' module Puppet class Transaction - attr_accessor :component, :objects, :tags, :ignoreschedules, :ignoretags + attr_accessor :component, :resources, :tags, :ignoreschedules, :ignoretags + attr_accessor :relgraph include Puppet::Util Puppet.config.setdefaults(:transaction, - :tags => ["", "Tags to use to find objects. If this is set, then - only objects tagged with the specified tags will be applied. + :tags => ["", "Tags to use to find resources. If this is set, then + only resources tagged with the specified tags will be applied. Values must be comma-separated."] ) @@ -26,8 +27,13 @@ class Transaction # Apply all changes for a child, returning a list of the events # generated. def apply(child) - # First make sure there are no failed dependencies - child.eachdependency do |dep| + child.info "applying" + # First make sure there are no failed dependencies. To do this, + # we check for failures in any of the vertexes above us. It's not + # enough to check the immediate dependencies, which is why we use + # a tree from the reversed graph. + p @relgraph.vertices.collect { |v| v.ref } + @relgraph.reversal.tree_from_vertex(child, :dfs).keys.each do |dep| skip = false if fails = failed?(dep) child.notice "Dependency %s[%s] has %s failures" % @@ -76,7 +82,7 @@ class Transaction # event if they want events = [change.forward].flatten.reject { |e| e.nil? } rescue => detail - if Puppet[:debug] + if Puppet[:trace] puts detail.backtrace end change.state.err "change from %s to %s failed: %s" % @@ -111,37 +117,13 @@ class Transaction childevents end - # Find all of the changed objects. + # Find all of the changed resources. def changed? @changes.find_all { |change| change.changed }.collect { |change| change.state.parent }.uniq end - # Collect all of the targets for the list of events. This is an unintuitive - # method because it recurses up through the source the event. - def collecttargets(events) - events.each do |event| - source = event.source - start = source - - while source - Puppet::Event::Subscription.targets_of(event, source) do |sub| - if target = sub.target - start.info "Scheduling %s of %s[%s]" % - [sub.callback, sub.target.class.name, sub.target.title] - @targets[sub.target][event] = sub - else - raise Puppet::DevError, - "Failed to find a target for %s" % sub.inspect - end - end - - source = source.parent - end - end - end - # This method does all the actual work of running a transaction. It # collects all of the changes, executes them, and responds to any # necessary events. @@ -163,9 +145,17 @@ class Transaction Puppet::Log.newdestination(@report) prefetch() + + # Now add any dynamically generated resources + generate() + + # Create a relationship graph from our resource graph + @relgraph = relationship_graph + + @relgraph.to_jpg("relations") begin - allevents = @objects.collect { |child| + allevents = @relgraph.topsort.collect { |child| events = [] if (self.ignoretags or tags.nil? or child.tagged?(tags)) if self.ignoreschedules or child.scheduled? @@ -176,7 +166,7 @@ class Transaction events = apply(child) end - # Keep track of how long we spend in each type of object + # Keep track of how long we spend in each type of resource @timemetrics[child.class.name] += seconds else child.debug "Not scheduled" @@ -191,7 +181,9 @@ class Transaction end # Collect the targets of any subscriptions to those events - collecttargets(events) + @relgraph.matching_edges(events).each do |edge| + @targets[edge.target] << edge + end # And return the events for collection events @@ -207,7 +199,7 @@ class Transaction allevents end - # Determine whether a given object has failed. + # Determine whether a given resource has failed. def failed?(obj) if @failures[obj] > 0 return @failures[obj] @@ -215,40 +207,63 @@ class Transaction return false end end + + # Collect any dynamically generated resources. + def generate + list = @resources.vertices + newlist = [] + while ! list.empty? + list.each do |resource| + if resource.respond_to?(:generate) + made = resource.generate + unless made.is_a?(Array) + made = [made] + end + made.each do |res| + @resources.add_vertex!(res) + newlist << res + end + end + end + list.clear + list = newlist + newlist = [] + end + end - # this should only be called by a Puppet::Container object now + # this should only be called by a Puppet::Type::Component resource now # and it should only receive an array - def initialize(objects) - @objects = objects + def initialize(resources) + @resources = resources.to_graph @resourcemetrics = { - :total => @objects.length, - :out_of_sync => 0, # The number of objects that had changes - :applied => 0, # The number of objects fixed - :skipped => 0, # The number of objects skipped - :restarted => 0, # The number of objects triggered - :failed_restarts => 0, # The number of objects that fail a trigger - :scheduled => 0 # The number of objects scheduled + :total => @resources.vertices.length, + :out_of_sync => 0, # The number of resources that had changes + :applied => 0, # The number of resources fixed + :skipped => 0, # The number of resources skipped + :restarted => 0, # The number of resources triggered + :failed_restarts => 0, # The number of resources that fail a trigger + :scheduled => 0 # The number of resources scheduled } # Metrics for distributing times across the different types. @timemetrics = Hash.new(0) - # The number of objects that were triggered in this run + # The number of resources that were triggered in this run @triggered = Hash.new { |hash, key| hash[key] = Hash.new(0) } # Targets of being triggered. @targets = Hash.new do |hash, key| - hash[key] = {} + hash[key] = [] end # The changes we're performing @changes = [] - # The objects that have failed and the number of failures each. This - # is used for skipping objects because of failed dependencies. + # The resources that have failed and the number of failures each. This + # is used for skipping resources because of failed dependencies. @failures = Hash.new do |h, key| h[key] = 0 end @@ -259,7 +274,7 @@ class Transaction # Prefetch any providers that support it. We don't support prefetching # types, just providers. def prefetch - @objects.collect { |obj| + @resources.collect { |obj| if pro = obj.provider pro.class else @@ -272,6 +287,33 @@ class Transaction end end end + + # Create a graph of all of the relationships in our resource graph. + def relationship_graph + graph = Puppet::PGraph.new + + # First create the dependency graph + @resources.vertices.each do |vertex| + graph.add_vertex!(vertex) + vertex.builddepends.each do |edge| + graph.add_edge!(edge) + end + end + + # Then splice in the container information + graph.splice!(@resources, Puppet::Type::Component) + + # Lastly, add in any autorequires + graph.vertices.each do |vertex| + vertex.autorequire.each do |edge| + unless graph.edge?(edge) + graph.add_edge!(edge) + end + end + end + + return graph + end # Generate a transaction report. def report @@ -295,10 +337,10 @@ class Transaction end end - # Add all of the metrics related to object count and status + # Add all of the metrics related to resource count and status @report.newmetric(:resources, @resourcemetrics) - # Record the relative time spent in each object. + # Record the relative time spent in each resource. @report.newmetric(:time, @timemetrics) # Then all of the change-related metrics @@ -325,19 +367,21 @@ class Transaction events = change.backward rescue => detail Puppet.err("%s rollback failed: %s" % [change,detail]) - if Puppet[:debug] + if Puppet[:trace] puts detail.backtrace end next # at this point, we would normally do error handling # but i haven't decided what to do for that yet - # so just record that a sync failed for a given object + # so just record that a sync failed for a given resource #@@failures[change.state.parent] += 1 # this still could get hairy; what if file contents changed, # but a chmod failed? how would i handle that error? dern end - - collecttargets(events) if events + + @relgraph.matching_edges(events).each do |edge| + @targets[edge.target] << edge + end # Now check to see if there are any events for this child. # Kind of hackish, since going backwards goes a change at a @@ -350,7 +394,7 @@ class Transaction end # Trigger any subscriptions to a child. This does an upwardly recursive - # search -- it triggers the passed object, but also the object's parent + # search -- it triggers the passed resource, but also the resource's parent # and so on up the tree. def trigger(child) obj = child @@ -362,12 +406,15 @@ class Transaction if @targets.include?(obj) callbacks.clear sources.clear - @targets[obj].each do |event, sub| + @targets[obj].each do |edge| + # Some edges don't have callbacks + next unless edge.callback + # Collect all of the subs for each callback - callbacks[sub.callback] << sub + callbacks[edge.callback] << edge # And collect the sources for logging - sources[event.source] << sub.callback + sources[edge.source] << edge.callback end sources.each do |source, callbacklist| @@ -390,7 +437,7 @@ class Transaction @resourcemetrics[:failed_restarts] += 1 - if Puppet[:debug] + if Puppet[:trace] puts detail.backtrace end end @@ -417,12 +464,12 @@ class Transaction end end - def triggered(object, method) - @triggered[object][method] += 1 + def triggered(resource, method) + @triggered[resource][method] += 1 end - def triggered?(object, method) - @triggered[object][method] + def triggered?(resource, method) + @triggered[resource][method] end end end diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb index 1adeb1366..19de99b7c 100644 --- a/lib/puppet/type.rb +++ b/lib/puppet/type.rb @@ -301,8 +301,6 @@ class Type < Puppet::Element # Set up all of our autorequires. def finish - self.autorequire - # Scheduling has to be done when the whole config is instantiated, so # that file order doesn't matter in finding them. self.schedule diff --git a/lib/puppet/type/component.rb b/lib/puppet/type/component.rb index 3165d9e11..cadd586c8 100644 --- a/lib/puppet/type/component.rb +++ b/lib/puppet/type/component.rb @@ -79,7 +79,7 @@ module Puppet # this also won't work with scheduling, but eh def evaluate self.finalize unless self.finalized? - transaction = Puppet::Transaction.new(self.flatten) + transaction = Puppet::Transaction.new(self) transaction.component = self return transaction end @@ -102,7 +102,6 @@ module Puppet end unless finished.has_key?(object) object.finish - object.builddepends finished[object] = true end end diff --git a/test/lib/puppettest/support/resources.rb b/test/lib/puppettest/support/resources.rb new file mode 100755 index 000000000..45d89c5fb --- /dev/null +++ b/test/lib/puppettest/support/resources.rb @@ -0,0 +1,37 @@ +#!/usr/bin/env ruby +# +# Created by Luke A. Kanies on 2006-11-29. +# Copyright (c) 2006. All rights reserved. + +module PuppetTest::Support::Resources + def treefile(name) + Puppet::Type.type(:file).create :path => "/tmp/#{name}", :mode => 0755 + end + + def treecomp(name) + Puppet::Type::Component.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 +end + +# $Id$
\ No newline at end of file diff --git a/test/other/overrides.rb b/test/other/overrides.rb index e25670291..2bc443980 100755 --- a/test/other/overrides.rb +++ b/test/other/overrides.rb @@ -27,6 +27,7 @@ class TestOverrides < Test::Unit::TestCase basefile = File.join(basedir, "file") assert_nothing_raised("Could not create base obj") { baseobj = Puppet.type(:file).create( + :title => "base", :path => basedir, :recurse => true, :mode => "755" @@ -38,23 +39,20 @@ class TestOverrides < Test::Unit::TestCase subfile = File.join(subdir, "file") assert_nothing_raised("Could not create sub obj") { subobj = Puppet.type(:file).create( + :title => "sub", :path => subdir, :recurse => true, :mode => "644" ) } - comp = newcomp("overrides", baseobj, subobj) - assert_nothing_raised("Could not eval component") { - trans = comp.evaluate - trans.evaluate - } + assert_apply(baseobj, subobj) assert(File.stat(basefile).mode & 007777 == 0755) assert(File.stat(subfile).mode & 007777 == 0644) end - def test_zdeepoverride + def test_deepoverride basedir = File.join(tmpdir(), "deepoverridetesting") mksubdirs(basedir, 10) diff --git a/test/other/pgraph.rb b/test/other/pgraph.rb index c3baa7722..adf290b34 100644 --- a/test/other/pgraph.rb +++ b/test/other/pgraph.rb @@ -12,24 +12,31 @@ class TestPGraph < Test::Unit::TestCase include PuppetTest include PuppetTest::Graph - def test_collect_targets + Edge = Puppet::Relationship + + def test_matching_edges 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) + 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(["b"], graph.collect_targets([event])) + assert_equal([edges["a/b"]], graph.matching_edges([event])) # Make sure we get nothing with a different event - assert_equal([], graph.collect_targets([none])) + assert_equal([], graph.matching_edges([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])) + 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 @@ -49,6 +56,9 @@ class TestPGraph < Test::Unit::TestCase one, two, middle, top = build_tree 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 @@ -57,7 +67,7 @@ class TestPGraph < Test::Unit::TestCase end {one => two, "f" => "c", "h" => middle}.each do |source, target| - deps.add_edge!(source, target) + deps.add_edge!(source, target, :callback => :refresh) end deps.to_jpg("deps-before") @@ -66,11 +76,26 @@ class TestPGraph < Test::Unit::TestCase 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 + 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 end diff --git a/test/other/relationships.rb b/test/other/relationships.rb index 893ef1ff4..e9c1d1c9c 100755 --- a/test/other/relationships.rb +++ b/test/other/relationships.rb @@ -15,37 +15,141 @@ class TestRelationships < Test::Unit::TestCase ) } end + + def check_relationship(sources, targets, out, refresher) + if out + deps = sources.builddepends + sources = [sources] + else + deps = targets.builddepends + targets = [targets] + end + assert_instance_of(Array, deps) + assert(! deps.empty?, "Did not receive any relationships") + + deps.each do |edge| + assert_instance_of(Puppet::Relationship, edge) + end + + sources.each do |source| + targets.each do |target| + edge = deps.find { |e| e.source == source and e.target == target } + assert(edge, "Could not find edge for %s => %s" % + [source.ref, target.ref]) + + if refresher + assert_equal(:ALL_EVENTS, edge.event) + assert_equal(:refresh, edge.callback) + else + assert_equal(:NONE, edge.event) + assert_nil(edge.callback, "Got a callback with no events") + end + end + end + end - def test_simplerel - file1 = newfile() - file2 = newfile() - assert_nothing_raised { - file1[:require] = [file2.class.name, file2.name] - } - - comp = newcomp(file1, file2) - comp.finalize - deps = [] - assert_nothing_raised { - file1.eachdependency { |obj| - deps << obj - } - } - - assert_equal(1, deps.length, "Did not get dependency") - - assert_nothing_raised { - file1.unsubscribe(file2) - } - - deps = [] - assert_nothing_raised { - file1.eachdependency { |obj| - deps << obj - } - } - - assert_equal(0, deps.length, "Still have dependency") + # Make sure our various metaparams work correctly. We're just checking + # here whether they correctly set up the callbacks and the direction of + # the relationship. + def test_relationship_metaparams + out = {:require => false, :subscribe => false, + :notify => true, :before => true} + refreshers = [:subscribe, :notify] + [:require, :subscribe, :notify, :before].each do |param| + # Create three files to generate our events and three + # execs to receive them + files = [] + execs = [] + 3.times do |i| + files << Puppet::Type.newfile( + :title => "file#{i}", + :path => tempfile(), + :ensure => :file + ) + + path = tempfile() + execs << Puppet::Type.newexec( + :title => "notifytest#{i}", + :path => "/usr/bin:/bin", + :command => "touch #{path}", + :refreshonly => true + ) + end + + # Add our first relationship + if out[param] + files[0][param] = execs[0] + sources = files[0] + targets = [execs[0]] + else + execs[0][param] = files[0] + sources = [files[0]] + targets = execs[0] + end + check_relationship(sources, targets, out[param], refreshers.include?(param)) + + # Now add another relationship + if out[param] + files[0][param] = execs[1] + targets << execs[1] + assert_equal(targets.collect { |t| [t.class.name, t.title]}, + files[0][param], "Incorrect target list") + else + execs[0][param] = files[1] + sources << files[1] + assert_equal(sources.collect { |t| [t.class.name, t.title]}, + execs[0][param], "Incorrect source list") + end + check_relationship(sources, targets, out[param], refreshers.include?(param)) + + Puppet::Type.allclear + end + end + + def test_store_relationship + file = Puppet::Type.newfile :path => tempfile(), :mode => 0755 + execs = [] + 3.times do |i| + execs << Puppet::Type.newexec(:title => "yay#{i}", :command => "/bin/echo yay") + end + + # First try it with one object, specified as a reference and an array + result = nil + [execs[0], [:exec, "yay0"], ["exec", "yay0"]].each do |target| + assert_nothing_raised do + result = file.send(:store_relationship, :require, target) + end + + assert_equal([[:exec, "yay0"]], result) + end + + # Now try it with multiple objects + symbols = execs.collect { |e| [e.class.name, e.title] } + strings = execs.collect { |e| [e.class.name.to_s, e.title] } + [execs, symbols, strings].each do |target| + assert_nothing_raised do + result = file.send(:store_relationship, :require, target) + end + + assert_equal(symbols, result) + end + + # Make sure we can mix it up, even though this shouldn't happen + assert_nothing_raised do + result = file.send(:store_relationship, :require, [execs[0], [execs[1].class.name, execs[1].title]]) + end + + assert_equal([[:exec, "yay0"], [:exec, "yay1"]], result) + + # Finally, make sure that new results get added to old. The only way + # to get rid of relationships is to delete the parameter. + file[:require] = execs[0] + + assert_nothing_raised do + result = file.send(:store_relationship, :require, [execs[1], execs[2]]) + end + + assert_equal(symbols, result) end def test_newsub @@ -107,33 +211,31 @@ class TestRelationships < Test::Unit::TestCase assert(! sub.match?(:ALL_EVENTS), "ALL_EVENTS matched") assert(! sub.match?(:NONE), "matched :NONE") end - - def test_deletingsubs - file1 = newfile() - file2 = newfile() - - file1[:subscribe] = [:file, file2.name] - - comp = newcomp(file1, file2) - comp.finalize - - assert(file1.requires?(file2)) - - assert_nothing_raised { - file1.unsubscribe(file2) - } - assert(!file1.requires?(file2)) - - # Now readd it, so we can use 'remove' - file1[:subscribe] = [:file, file2.name] - comp.finalize - - assert_nothing_raised { - file1.remove - } - - assert(!comp.include?(file1)) - assert(!file2.subscribesto?(file1)) + + def test_autorequire + # We know that execs autorequire their cwd, so we'll use that + path = tempfile() + + file = Puppet::Type.newfile(:title => "myfile", :path => path, + :ensure => :directory) + exec = Puppet::Type.newexec(:title => "myexec", :cwd => path, + :command => "/bin/echo") + + reqs = nil + assert_nothing_raised do + reqs = exec.autorequire + end + assert_equal([Puppet::Relationship[file, exec]], reqs) + + # Now make sure that these relationships are added to the transaction's + # relgraph + trans = Puppet::Transaction.new(newcomp(file, exec)) + assert_nothing_raised do + trans.evaluate + end + + graph = trans.relgraph + assert(graph.edge?(file, exec), "autorequire edge was not created") end end diff --git a/test/other/transactions.rb b/test/other/transactions.rb index 985e9a0c5..c143b3a0c 100755 --- a/test/other/transactions.rb +++ b/test/other/transactions.rb @@ -4,11 +4,13 @@ $:.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 test_reports path1 = tempfile() @@ -59,6 +61,10 @@ class TestTransactions < Test::Unit::TestCase type = Puppet::Type.newtype(name) do newparam(:name) {} end + + cleanup do + Puppet::Type.rmtype(name) + end # Now create a provider type.provide(:prefetch) do @@ -69,10 +75,9 @@ class TestTransactions < Test::Unit::TestCase # Now create an instance inst = type.create :name => "yay" - - + # Create a transaction - trans = Puppet::Transaction.new([inst]) + trans = Puppet::Transaction.new(newcomp(inst)) # Make sure prefetch works assert_nothing_raised do @@ -94,7 +99,7 @@ class TestTransactions < Test::Unit::TestCase path = tempfile() firstpath = tempfile() secondpath = tempfile() - file = Puppet::Type.newfile(:path => path, :content => "yayness") + file = Puppet::Type.newfile(:title => "file", :path => path, :content => "yayness") first = Puppet::Type.newexec(:title => "first", :command => "/bin/echo first > #{firstpath}", :subscribe => [:file, path], @@ -299,7 +304,9 @@ class TestTransactions < Test::Unit::TestCase assert(FileTest.exists?(execfile), "Execfile does not exist") end - def test_refreshAcrossTwoComponents + # 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") @@ -318,7 +325,7 @@ class TestTransactions < Test::Unit::TestCase # 'subscribe' expects an array of arrays #component[:require] = [[file.class.name,file.name]] - ecomp[:subscribe] = [[fcomp.class.name,fcomp.name]] + ecomp[:subscribe] = fcomp exec[:refreshonly] = true trans = assert_events([], component) @@ -329,7 +336,6 @@ class TestTransactions < Test::Unit::TestCase } trans = assert_events([:file_changed, :file_changed, :triggered], component) - end # Make sure that multiple subscriptions get triggered. @@ -386,7 +392,7 @@ class TestTransactions < Test::Unit::TestCase end # Make sure that unscheduled and untagged objects still respond to events - def test_unscheduledanduntaggedresponse + def test_unscheduled_and_untagged_response Puppet::Type.type(:schedule).mkdefaultschedules Puppet[:ignoreschedules] = false file = Puppet.type(:file).create( @@ -444,21 +450,127 @@ class TestTransactions < Test::Unit::TestCase :title => "mkdir" ) - file = Puppet::Type.type(:file).create( + file1 = Puppet::Type.type(:file).create( + :title => "file1", :path => tempfile(), - :require => ["exec", "mkdir"], + :require => exec, :ensure => :file ) - comp = newcomp(exec, 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?(file[:path]), + 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") + + graph.to_jpg("normal_relations") end + + def test_generate + # Create a bogus type that generates new instances with shorter + Puppet::Type.newtype(:generator) do + newparam(:name, :namevar => true) + + def generate + ret = [] + if title.length > 1 + ret << self.class.create(:title => title[0..-2]) + end + ret + end + end + cleanup do + Puppet::Type.rmtype(:generator) + 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 end end diff --git a/test/types/component.rb b/test/types/component.rb index 28d6748a0..3fdc7bfe2 100755 --- a/test/types/component.rb +++ b/test/types/component.rb @@ -4,6 +4,7 @@ $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppet' require 'puppettest' +require 'puppettest/support/resources' # $Id$ @@ -105,35 +106,6 @@ 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 diff --git a/test/types/manager.rb b/test/types/manager.rb new file mode 100755 index 000000000..f4353c128 --- /dev/null +++ b/test/types/manager.rb @@ -0,0 +1,44 @@ +#!/usr/bin/env ruby +# +# Created by Luke A. Kanies on 2006-11-29. +# Copyright (c) 2006. All rights reserved. + +$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ + +require 'puppet' +require 'puppettest' + +class TestTypeManager < Test::Unit::TestCase + include PuppetTest + + class FakeManager + extend Puppet::MetaType::Manager + def self.clear + @types = {} + end + end + + def teardown + super + FakeManager.clear + end + + # Make sure we can remove defined types + def test_rmtype + assert_nothing_raised { + FakeManager.newtype :testing do + newparam(:name, :namevar => true) + end + } + assert(FakeManager.type(:testing), "Did not get fake type") + + assert_nothing_raised do + FakeManager.rmtype(:testing) + end + + assert_nil(FakeManager.type(:testing), "Type was not removed") + assert(! defined?(FakeManager::Testing), "Constant was not removed") + end +end + +# $Id$
\ No newline at end of file diff --git a/test/types/type.rb b/test/types/type.rb index 4be929ae9..821a9524b 100755 --- a/test/types/type.rb +++ b/test/types/type.rb @@ -411,55 +411,6 @@ end "newparam method got replaced by newtype") end - def test_notify_metaparam - file = Puppet::Type.newfile( - :path => tempfile(), - :notify => ["exec", "notifytest"], - :ensure => :file - ) - - path = tempfile() - exec = Puppet::Type.newexec( - :title => "notifytest", - :path => "/usr/bin:/bin", - :command => "touch #{path}", - :refreshonly => true - ) - - assert_apply(file, exec) - - assert(exec.requires?(file), - "Notify did not correctly set up the requirement chain.") - - assert(FileTest.exists?(path), - "Exec path did not get created.") - end - - def test_before_metaparam - file = Puppet::Type.newfile( - :path => tempfile(), - :before => ["exec", "beforetest"], - :content => "yaytest" - ) - - path = tempfile() - exec = Puppet::Type.newexec( - :title => "beforetest", - :command => "/bin/cp #{file[:path]} #{path}" - ) - - assert_apply(file, exec) - - assert(exec.requires?(file), - "Before did not correctly set up the requirement chain.") - - assert(FileTest.exists?(path), - "Exec path did not get created.") - - assert_equal("yaytest", File.read(path), - "Exec did not correctly copy file.") - end - def test_newstate_options # Create a type with a fake provider providerclass = Class.new do |