diff options
Diffstat (limited to 'lib/puppet/node')
-rw-r--r-- | lib/puppet/node/catalog.rb | 508 |
1 files changed, 0 insertions, 508 deletions
diff --git a/lib/puppet/node/catalog.rb b/lib/puppet/node/catalog.rb deleted file mode 100644 index bc522cc1e..000000000 --- a/lib/puppet/node/catalog.rb +++ /dev/null @@ -1,508 +0,0 @@ -require 'puppet/indirector' -require 'puppet/simple_graph' -require 'puppet/transaction' - -require 'puppet/util/cacher' - -require 'puppet/util/tagging' - -# This class models a node catalog. It is the thing -# meant to be passed from server to client, and it contains all -# of the information in the catalog, including the resources -# and the relationships between them. -class Puppet::Node::Catalog < Puppet::SimpleGraph - class DuplicateResourceError < Puppet::Error; end - - extend Puppet::Indirector - indirects :catalog, :terminus_class => :compiler - - include Puppet::Util::Tagging - include Puppet::Util::Cacher::Expirer - - # The host name this is a catalog for. - attr_accessor :name - - # The catalog version. Used for testing whether a catalog - # is up to date. - attr_accessor :version - - # How long this catalog took to retrieve. Used for reporting stats. - attr_accessor :retrieval_duration - - # How we should extract the catalog for sending to the client. - attr_reader :extraction_format - - # Whether this is a host catalog, which behaves very differently. - # In particular, reports are sent, graphs are made, and state is - # stored in the state database. If this is set incorrectly, then you often - # end up in infinite loops, because catalogs are used to make things - # that the host catalog needs. - attr_accessor :host_config - - # Whether this catalog was retrieved from the cache, which affects - # whether it is written back out again. - attr_accessor :from_cache - - # Add classes to our class list. - def add_class(*classes) - classes.each do |klass| - @classes << klass - end - - # Add the class names as tags, too. - tag(*classes) - end - - # Add one or more resources to our graph and to our resource table. - # This is actually a relatively complicated method, because it handles multiple - # aspects of Catalog behaviour: - # * Add the resource to the resource table - # * Add the resource to the resource graph - # * Add the resource to the relationship graph - # * Add any aliases that make sense for the resource (e.g., name != title) - def add_resource(*resources) - resources.each do |resource| - unless resource.respond_to?(:ref) - raise ArgumentError, "Can only add objects that respond to :ref, not instances of %s" % resource.class - end - end.find_all { |resource| fail_or_skip_unless_unique(resource) }.each do |resource| - ref = resource.ref - - @transient_resources << resource if applying? - @resource_table[ref] = resource - - # If the name and title differ, set up an alias - #self.alias(resource, resource.name) if resource.respond_to?(:name) and resource.respond_to?(:title) and resource.name != resource.title - if resource.respond_to?(:name) and resource.respond_to?(:title) and resource.name != resource.title - self.alias(resource, resource.name) if resource.isomorphic? - end - - resource.catalog = self if resource.respond_to?(:catalog=) - - add_vertex(resource) - - if @relationship_graph - @relationship_graph.add_vertex(resource) - end - - yield(resource) if block_given? - end - end - - # Create an alias for a resource. - def alias(resource, name) - #set $1 - resource.ref =~ /^(.+)\[/ - - newref = "%s[%s]" % [$1 || resource.class.name, name] - - # LAK:NOTE It's important that we directly compare the references, - # because sometimes an alias is created before the resource is - # added to the catalog, so comparing inside the below if block - # isn't sufficient. - return if newref == resource.ref - if existing = @resource_table[newref] - return if existing == resource - raise(ArgumentError, "Cannot alias %s to %s; resource %s already exists" % [resource.ref, name, newref]) - end - @resource_table[newref] = resource - @aliases[resource.ref] << newref - end - - # Apply our catalog to the local host. Valid options - # are: - # :tags - set the tags that restrict what resources run - # during the transaction - # :ignoreschedules - tell the transaction to ignore schedules - # when determining the resources to run - def apply(options = {}) - @applying = true - - # Expire all of the resource data -- this ensures that all - # data we're operating against is entirely current. - expire() - - Puppet::Util::Storage.load if host_config? - transaction = Puppet::Transaction.new(self) - - transaction.tags = options[:tags] if options[:tags] - transaction.ignoreschedules = true if options[:ignoreschedules] - - transaction.addtimes :config_retrieval => @retrieval_duration - - - begin - transaction.evaluate - rescue Puppet::Error => detail - Puppet.err "Could not apply complete catalog: %s" % detail - rescue => detail - puts detail.backtrace if Puppet[:trace] - Puppet.err "Got an uncaught exception of type %s: %s" % [detail.class, detail] - ensure - # Don't try to store state unless we're a host config - # too recursive. - Puppet::Util::Storage.store if host_config? - end - - yield transaction if block_given? - - transaction.send_report if host_config and (Puppet[:report] or Puppet[:summarize]) - - return transaction - ensure - @applying = false - cleanup() - transaction.cleanup if defined? transaction and transaction - end - - # Are we in the middle of applying the catalog? - def applying? - @applying - end - - def clear(remove_resources = true) - super() - # We have to do this so that the resources clean themselves up. - @resource_table.values.each { |resource| resource.remove } if remove_resources - @resource_table.clear - - if defined?(@relationship_graph) and @relationship_graph - @relationship_graph.clear - @relationship_graph = nil - end - end - - def classes - @classes.dup - end - - # Create a new resource and register it in the catalog. - def create_resource(type, options) - unless klass = Puppet::Type.type(type) - raise ArgumentError, "Unknown resource type %s" % type - end - return unless resource = klass.create(options) - - add_resource(resource) - resource - end - - def expired?(ts) - if applying? - return super - else - return true - end - end - - # Make sure we support the requested extraction format. - def extraction_format=(value) - unless respond_to?("extract_to_%s" % value) - raise ArgumentError, "Invalid extraction format %s" % value - end - @extraction_format = value - end - - # Turn our catalog graph into whatever the client is expecting. - def extract - send("extract_to_%s" % extraction_format) - end - - # Create the traditional TransBuckets and TransObjects from our catalog - # graph. This will hopefully be deprecated soon. - def extract_to_transportable - top = nil - current = nil - buckets = {} - - unless main = vertices.find { |res| res.type == "Class" and res.title == :main } - raise Puppet::DevError, "Could not find 'main' class; cannot generate catalog" - end - - # Create a proc for examining edges, which we'll use to build our tree - # of TransBuckets and TransObjects. - bucket = nil - walk(main, :out) do |source, target| - # The sources are always non-builtins. - unless tmp = buckets[source.to_s] - if tmp = buckets[source.to_s] = source.to_trans - bucket = tmp - else - # This is because virtual resources return nil. If a virtual - # container resource contains realized resources, we still need to get - # to them. So, we keep a reference to the last valid bucket - # we returned and use that if the container resource is virtual. - end - end - bucket = tmp || bucket - if child = target.to_trans - unless bucket - raise "No bucket created for %s" % source - end - bucket.push child - - # It's important that we keep a reference to any TransBuckets we've created, so - # we don't create multiple buckets for children. - unless target.builtin? - buckets[target.to_s] = child - end - end - end - - # Retrieve the bucket for the top-level scope and set the appropriate metadata. - unless result = buckets[main.to_s] - # This only happens when the catalog is entirely empty. - result = buckets[main.to_s] = main.to_trans - end - - result.classes = classes - - # Clear the cache to encourage the GC - buckets.clear - return result - end - - # Make sure all of our resources are "finished". - def finalize - make_default_resources - - @resource_table.values.each { |resource| resource.finish } - - write_graph(:resources) - end - - def host_config? - host_config || false - end - - def initialize(name = nil) - super() - @name = name if name - @extraction_format ||= :transportable - @classes = [] - @resource_table = {} - @transient_resources = [] - @applying = false - @relationship_graph = nil - - @aliases = Hash.new { |hash, key| hash[key] = [] } - - if block_given? - yield(self) - finalize() - end - end - - # Make the default objects necessary for function. - def make_default_resources - # We have to add the resources to the catalog, or else they won't get cleaned up after - # the transaction. - - # First create the default scheduling objects - Puppet::Type.type(:schedule).mkdefaultschedules.each { |res| add_resource(res) unless resource(res.ref) } - - # And filebuckets - if bucket = Puppet::Type.type(:filebucket).mkdefaultbucket - add_resource(bucket) unless resource(bucket.ref) - end - end - - # Create a graph of all of the relationships in our catalog. - def relationship_graph - unless defined? @relationship_graph and @relationship_graph - # It's important that we assign the graph immediately, because - # the debug messages below use the relationships in the - # relationship graph to determine the path to the resources - # spitting out the messages. If this is not set, - # then we get into an infinite loop. - @relationship_graph = Puppet::SimpleGraph.new - - # First create the dependency graph - self.vertices.each do |vertex| - @relationship_graph.add_vertex vertex - vertex.builddepends.each do |edge| - @relationship_graph.add_edge(edge) - end - end - - # Lastly, add in any autorequires - @relationship_graph.vertices.each do |vertex| - vertex.autorequire(self).each do |edge| - unless @relationship_graph.edge?(edge.source, edge.target) # don't let automatic relationships conflict with manual ones. - unless @relationship_graph.edge?(edge.target, edge.source) - vertex.debug "Autorequiring %s" % [edge.source] - @relationship_graph.add_edge(edge) - else - vertex.debug "Skipping automatic relationship with %s" % (edge.source == vertex ? edge.target : edge.source) - end - end - end - end - - @relationship_graph.write_graph(:relationships) if host_config? - - # Then splice in the container information - @relationship_graph.splice!(self, Puppet::Type::Component) - - @relationship_graph.write_graph(:expanded_relationships) if host_config? - end - @relationship_graph - end - - # Remove the resource from our catalog. Notice that we also call - # 'remove' on the resource, at least until resource classes no longer maintain - # references to the resource instances. - def remove_resource(*resources) - resources.each do |resource| - @resource_table.delete(resource.ref) - @aliases[resource.ref].each { |res_alias| @resource_table.delete(res_alias) } - @aliases[resource.ref].clear - remove_vertex!(resource) if vertex?(resource) - @relationship_graph.remove_vertex!(resource) if @relationship_graph and @relationship_graph.vertex?(resource) - resource.remove - end - end - - # Look a resource up by its reference (e.g., File[/etc/passwd]). - def resource(type, title = nil) - # Always create a resource reference, so that it always canonizes how we - # are referring to them. - if title - ref = Puppet::Resource::Reference.new(type, title).to_s - else - # If they didn't provide a title, then we expect the first - # argument to be of the form 'Class[name]', which our - # Reference class canonizes for us. - ref = Puppet::Resource::Reference.new(nil, type).to_s - end - @resource_table[ref] - end - - # Return an array of all resources. - def resources - @resource_table.keys - end - - # Convert our catalog into a RAL catalog. - def to_ral - to_catalog :to_type - end - - # Turn our parser catalog into a transportable catalog. - def to_transportable - to_catalog :to_transobject - end - - # Produce the graph files if requested. - def write_graph(name) - # We only want to graph the main host catalog. - return unless host_config? - - super - end - - private - - def cleanup - unless @transient_resources.empty? - remove_resource(*@transient_resources) - @transient_resources.clear - @relationship_graph = nil - end - - # Expire any cached data the resources are keeping. - expire() - end - - # Verify that the given resource isn't defined elsewhere. - def fail_or_skip_unless_unique(resource) - # Short-curcuit the common case, - return resource unless existing_resource = @resource_table[resource.ref] - - if resource.implicit? - resource.debug "Generated resource conflicts with explicit resource; ignoring generated resource" - return nil - elsif old = resource(resource.ref) and old.implicit? - # The existing resource is implicit; remove it and replace it with - # the new one. - old.debug "Replacing with new resource" - remove_resource(old) - return resource - end - - # If we've gotten this far, it's a real conflict - - # Either it's a defined type, which are never - # isomorphic, or it's a non-isomorphic type, so - # we should throw an exception. - msg = "Duplicate definition: %s is already defined" % resource.ref - - if existing_resource.file and existing_resource.line - msg << " in file %s at line %s" % - [existing_resource.file, existing_resource.line] - end - - if resource.line or resource.file - msg << "; cannot redefine" - end - - raise DuplicateResourceError.new(msg) - end - - # An abstracted method for converting one catalog into another type of catalog. - # This pretty much just converts all of the resources from one class to another, using - # a conversion method. - def to_catalog(convert) - result = self.class.new(self.name) - - map = {} - vertices.each do |resource| - next if resource.respond_to?(:virtual?) and resource.virtual? - - #This is hackity hack for 1094 - #Aliases aren't working in the ral catalog because the current instance of the resource - #has a reference to the catalog being converted. . . So, give it a reference to the new one - #problem solved. . . - if resource.is_a?(Puppet::TransObject) - resource = resource.dup - resource.catalog = result - elsif resource.is_a?(Puppet::Parser::Resource) - resource = resource.to_transobject - resource.catalog = result - end - - newres = resource.send(convert) - - # We can't guarantee that resources don't munge their names - # (like files do with trailing slashes), so we have to keep track - # of what a resource got converted to. - map[resource.ref] = newres - - result.add_resource newres - end - - message = convert.to_s.gsub "_", " " - edges.each do |edge| - # Skip edges between virtual resources. - next if edge.source.respond_to?(:virtual?) and edge.source.virtual? - next if edge.target.respond_to?(:virtual?) and edge.target.virtual? - - unless source = map[edge.source.ref] - raise Puppet::DevError, "Could not find resource %s when converting %s resources" % [edge.source.ref, message] - end - - unless target = map[edge.target.ref] - raise Puppet::DevError, "Could not find resource %s when converting %s resources" % [edge.target.ref, message] - end - - result.add_edge(source, target, edge.label) - end - - map.clear - - result.add_class(*self.classes) - result.tag(*self.tags) - - return result - end -end |