| Class | Puppet::Node::Catalog |
| In: |
lib/puppet/node/catalog.rb
|
| Parent: | Puppet::PGraph |
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.
| edgelist_class | [RW] | We need the ability to set this externally, so we can yaml-dump the catalog. |
| extraction_format | [R] | How we should extract the catalog for sending to the client. |
| from_cache | [RW] | Whether this catalog was retrieved from the cache, which affects whether it is written back out again. |
| host_config | [RW] | 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. |
| is_relationship_graph | [RW] | Whether this graph is another catalog‘s relationship graph. We don‘t want to accidentally create a relationship graph for another relationship graph. |
| name | [RW] | The host name this is a catalog for. |
| retrieval_duration | [RW] | How long this catalog took to retrieve. Used for reporting stats. |
| version | [RW] | The catalog version. Used for testing whether a catalog is up to date. |
# File lib/puppet/node/catalog.rb, line 290
290: def initialize(name = nil)
291: super()
292: @name = name if name
293: @extraction_format ||= :transportable
294: @classes = []
295: @resource_table = {}
296: @transient_resources = []
297: @applying = false
298: @relationship_graph = nil
299:
300: @aliases = Hash.new { |hash, key| hash[key] = [] }
301:
302: if block_given?
303: yield(self)
304: finalize()
305: end
306: end
Add one or more resources to our graph and to our resource table.
# File lib/puppet/node/catalog.rb, line 61
61: def add_resource(*resources)
62: resources.each do |resource|
63: unless resource.respond_to?(:ref)
64: raise ArgumentError, "Can only add objects that respond to :ref"
65: end
66:
67: fail_unless_unique(resource)
68:
69: ref = resource.ref
70:
71: @resource_table[ref] = resource
72:
73: # If the name and title differ, set up an alias
74: #self.alias(resource, resource.name) if resource.respond_to?(:name) and resource.respond_to?(:title) and resource.name != resource.title
75: if resource.respond_to?(:name) and resource.respond_to?(:title) and resource.name != resource.title
76: self.alias(resource, resource.name) if resource.isomorphic?
77: end
78:
79: resource.catalog = self if resource.respond_to?(:catalog=) and ! is_relationship_graph
80:
81: add_vertex(resource)
82: end
83: end
Create an alias for a resource.
# File lib/puppet/node/catalog.rb, line 86
86: def alias(resource, name)
87: #set $1
88: resource.ref =~ /^(.+)\[/
89:
90: newref = "%s[%s]" % [$1 || resource.class.name, name]
91:
92: # LAK:NOTE It's important that we directly compare the references,
93: # because sometimes an alias is created before the resource is
94: # added to the catalog, so comparing inside the below if block
95: # isn't sufficient.
96: return if newref == resource.ref
97: if existing = @resource_table[newref]
98: return if existing == resource
99: raise(ArgumentError, "Cannot alias %s to %s; resource %s already exists" % [resource.ref, name, newref])
100: end
101: @resource_table[newref] = resource
102: @aliases[resource.ref] << newref
103: 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
# File lib/puppet/node/catalog.rb, line 111
111: def apply(options = {})
112: @applying = true
113:
114: Puppet::Util::Storage.load if host_config?
115: transaction = Puppet::Transaction.new(self)
116:
117: transaction.tags = options[:tags] if options[:tags]
118: transaction.ignoreschedules = true if options[:ignoreschedules]
119:
120: transaction.addtimes :config_retrieval => @retrieval_duration
121:
122:
123: begin
124: transaction.evaluate
125: rescue Puppet::Error => detail
126: Puppet.err "Could not apply complete catalog: %s" % detail
127: rescue => detail
128: puts detail.backtrace if Puppet[:trace]
129: Puppet.err "Got an uncaught exception of type %s: %s" % [detail.class, detail]
130: ensure
131: # Don't try to store state unless we're a host config
132: # too recursive.
133: Puppet::Util::Storage.store if host_config?
134: end
135:
136: yield transaction if block_given?
137:
138: transaction.send_report if host_config and (Puppet[:report] or Puppet[:summarize])
139:
140: return transaction
141: ensure
142: @applying = false
143: cleanup()
144: transaction.cleanup if defined? transaction and transaction
145: end
Are we in the middle of applying the catalog?
# File lib/puppet/node/catalog.rb, line 148
148: def applying?
149: @applying
150: end
# File lib/puppet/node/catalog.rb, line 152
152: def clear(remove_resources = true)
153: super()
154: # We have to do this so that the resources clean themselves up.
155: @resource_table.values.each { |resource| resource.remove } if remove_resources
156: @resource_table.clear
157:
158: if defined?(@relationship_graph) and @relationship_graph
159: @relationship_graph.clear(false)
160: @relationship_graph = nil
161: end
162: end
Create an implicit resource, meaning that it will lose out to any explicitly defined resources. This method often returns nil.
The quirk of this method is that it's not possible to create
an implicit resource before an explicit resource of the same name, because all explicit resources are created before any generate() methods are called on the individual resources. Thus, this method can safely just check if an explicit resource already exists and toss this implicit resource if so.
# File lib/puppet/node/catalog.rb, line 177
177: def create_implicit_resource(type, options)
178: unless options.include?(:implicit)
179: options[:implicit] = true
180: end
181:
182: # This will return nil if an equivalent explicit resource already exists.
183: # When resource classes no longer retain references to resource instances,
184: # this will need to be modified to catch that conflict and discard
185: # implicit resources.
186: if resource = create_resource(type, options)
187: resource.implicit = true
188:
189: return resource
190: else
191: return nil
192: end
193: end
Create a new resource and register it in the catalog.
# File lib/puppet/node/catalog.rb, line 196
196: def create_resource(type, options)
197: unless klass = Puppet::Type.type(type)
198: raise ArgumentError, "Unknown resource type %s" % type
199: end
200: return unless resource = klass.create(options)
201:
202: @transient_resources << resource if applying?
203: add_resource(resource)
204: if @relationship_graph
205: @relationship_graph.add_resource(resource) unless @relationship_graph.resource(resource.ref)
206: end
207: resource
208: end
Turn our catalog graph into whatever the client is expecting.
# File lib/puppet/node/catalog.rb, line 219
219: def extract
220: send("extract_to_%s" % extraction_format)
221: end
Create the traditional TransBuckets and TransObjects from our catalog graph. This will hopefully be deprecated soon.
# File lib/puppet/node/catalog.rb, line 225
225: def extract_to_transportable
226: top = nil
227: current = nil
228: buckets = {}
229:
230: unless main = vertices.find { |res| res.type == "Class" and res.title == :main }
231: raise Puppet::DevError, "Could not find 'main' class; cannot generate catalog"
232: end
233:
234: # Create a proc for examining edges, which we'll use to build our tree
235: # of TransBuckets and TransObjects.
236: bucket = nil
237: walk(main, :out) do |source, target|
238: # The sources are always non-builtins.
239: unless tmp = buckets[source.to_s]
240: if tmp = buckets[source.to_s] = source.to_trans
241: bucket = tmp
242: else
243: # This is because virtual resources return nil. If a virtual
244: # container resource contains realized resources, we still need to get
245: # to them. So, we keep a reference to the last valid bucket
246: # we returned and use that if the container resource is virtual.
247: end
248: end
249: bucket = tmp || bucket
250: if child = target.to_trans
251: unless bucket
252: raise "No bucket created for %s" % source
253: end
254: bucket.push child
255:
256: # It's important that we keep a reference to any TransBuckets we've created, so
257: # we don't create multiple buckets for children.
258: unless target.builtin?
259: buckets[target.to_s] = child
260: end
261: end
262: end
263:
264: # Retrieve the bucket for the top-level scope and set the appropriate metadata.
265: unless result = buckets[main.to_s]
266: # This only happens when the catalog is entirely empty.
267: result = buckets[main.to_s] = main.to_trans
268: end
269:
270: result.classes = classes
271:
272: # Clear the cache to encourage the GC
273: buckets.clear
274: return result
275: end
Make sure we support the requested extraction format.
# File lib/puppet/node/catalog.rb, line 211
211: def extraction_format=(value)
212: unless respond_to?("extract_to_%s" % value)
213: raise ArgumentError, "Invalid extraction format %s" % value
214: end
215: @extraction_format = value
216: end
# File lib/puppet/node/catalog.rb, line 286
286: def host_config?
287: host_config || false
288: end
Make the default objects necessary for function.
# File lib/puppet/node/catalog.rb, line 309
309: def make_default_resources
310: # We have to add the resources to the catalog, or else they won't get cleaned up after
311: # the transaction.
312:
313: # First create the default scheduling objects
314: Puppet::Type.type(:schedule).mkdefaultschedules.each { |res| add_resource(res) unless resource(res.ref) }
315:
316: # And filebuckets
317: if bucket = Puppet::Type.type(:filebucket).mkdefaultbucket
318: add_resource(bucket)
319: end
320: end
Create a graph of all of the relationships in our catalog.
# File lib/puppet/node/catalog.rb, line 323
323: def relationship_graph
324: raise(Puppet::DevError, "Tried get a relationship graph for a relationship graph") if self.is_relationship_graph
325:
326: unless defined? @relationship_graph and @relationship_graph
327: # It's important that we assign the graph immediately, because
328: # the debug messages below use the relationships in the
329: # relationship graph to determine the path to the resources
330: # spitting out the messages. If this is not set,
331: # then we get into an infinite loop.
332: @relationship_graph = Puppet::Node::Catalog.new
333: @relationship_graph.host_config = host_config?
334: @relationship_graph.is_relationship_graph = true
335:
336: # First create the dependency graph
337: self.vertices.each do |vertex|
338: @relationship_graph.add_vertex vertex
339: vertex.builddepends.each do |edge|
340: @relationship_graph.add_edge(edge)
341: end
342: end
343:
344: # Lastly, add in any autorequires
345: @relationship_graph.vertices.each do |vertex|
346: vertex.autorequire.each do |edge|
347: unless @relationship_graph.edge?(edge.source, edge.target) # don't let automatic relationships conflict with manual ones.
348: unless @relationship_graph.edge?(edge.target, edge.source)
349: vertex.debug "Autorequiring %s" % [edge.source]
350: @relationship_graph.add_edge(edge)
351: else
352: vertex.debug "Skipping automatic relationship with %s" % (edge.source == vertex ? edge.target : edge.source)
353: end
354: end
355: end
356: end
357:
358: @relationship_graph.write_graph(:relationships)
359:
360: # Then splice in the container information
361: @relationship_graph.splice!(self, Puppet::Type::Component)
362:
363: @relationship_graph.write_graph(:expanded_relationships)
364: end
365: @relationship_graph
366: 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.
# File lib/puppet/node/catalog.rb, line 371
371: def remove_resource(*resources)
372: resources.each do |resource|
373: @resource_table.delete(resource.ref)
374: @aliases[resource.ref].each { |res_alias| @resource_table.delete(res_alias) }
375: @aliases[resource.ref].clear
376: remove_vertex!(resource) if vertex?(resource)
377: @relationship_graph.remove_vertex!(resource) if @relationship_graph and @relationship_graph.vertex?(resource)
378: resource.remove
379: end
380: end
Look a resource up by its reference (e.g., File[/etc/passwd]).
# File lib/puppet/node/catalog.rb, line 383
383: def resource(type, title = nil)
384: # Always create a resource reference, so that it always canonizes how we
385: # are referring to them.
386: if title
387: ref = Puppet::ResourceReference.new(type, title).to_s
388: else
389: # If they didn't provide a title, then we expect the first
390: # argument to be of the form 'Class[name]', which our
391: # Reference class canonizes for us.
392: ref = Puppet::ResourceReference.new(nil, type).to_s
393: end
394: if resource = @resource_table[ref]
395: return resource
396: elsif defined?(@relationship_graph) and @relationship_graph
397: @relationship_graph.resource(ref)
398: end
399: end
Convert our catalog into a RAL catalog.
# File lib/puppet/node/catalog.rb, line 407
407: def to_ral
408: to_catalog :to_type
409: end
Turn our parser catalog into a transportable catalog.
# File lib/puppet/node/catalog.rb, line 412
412: def to_transportable
413: to_catalog :to_transobject
414: end
LAK:NOTE We cannot yaml-dump the class in the edgelist_class, because classes cannot be dumped by default, nor does yaml-dumping # the edge-labels work at this point (I don‘t know why).
Neither of these matters right now, but I suppose it could at some point.
We also have to have the vertex_dict dumped after the resource table, because yaml can‘t seem to handle the output of yaml-dumping the vertex_dict.
# File lib/puppet/node/catalog.rb, line 437
437: def to_yaml_properties
438: props = instance_variables.reject { |v| %w{@edgelist_class @edge_labels @vertex_dict}.include?(v) }
439: props << "@vertex_dict"
440: props
441: end
Produce the graph files if requested.
# File lib/puppet/node/catalog.rb, line 417
417: def write_graph(name)
418: # We only want to graph the main host catalog.
419: return unless host_config?
420:
421: return unless Puppet[:graph]
422:
423: Puppet.settings.use(:graphing)
424:
425: file = File.join(Puppet[:graphdir], "%s.dot" % name.to_s)
426: File.open(file, "w") { |f|
427: f.puts to_dot("name" => name.to_s.capitalize)
428: }
429: end