| Class | Puppet::Transaction |
| In: |
lib/puppet/transaction.rb
|
| Parent: | Object |
| catalog | [RW] | |
| component | [RW] | |
| configurator | [RW] | |
| events | [R] | The list of events generated in this transaction. |
| ignoreschedules | [RW] | |
| report | [R] | The report, once generated. |
| sorted_resources | [RW] |
this should only be called by a Puppet::Type::Component resource now and it should only receive an array
# File lib/puppet/transaction.rb, line 431
431: def initialize(resources)
432: if resources.is_a?(Puppet::Node::Catalog)
433: @catalog = resources
434: elsif resources.is_a?(Puppet::PGraph)
435: raise "Transactions should get catalogs now, not PGraph"
436: else
437: raise "Transactions require catalogs"
438: end
439:
440: @resourcemetrics = {
441: :total => @catalog.vertices.length,
442: :out_of_sync => 0, # The number of resources that had changes
443: :applied => 0, # The number of resources fixed
444: :skipped => 0, # The number of resources skipped
445: :restarted => 0, # The number of resources triggered
446: :failed_restarts => 0, # The number of resources that fail a trigger
447: :scheduled => 0 # The number of resources scheduled
448: }
449:
450: # Metrics for distributing times across the different types.
451: @timemetrics = Hash.new(0)
452:
453: # The number of resources that were triggered in this run
454: @triggered = Hash.new { |hash, key|
455: hash[key] = Hash.new(0)
456: }
457:
458: # Targets of being triggered.
459: @targets = Hash.new do |hash, key|
460: hash[key] = []
461: end
462:
463: # The changes we're performing
464: @changes = []
465:
466: # The resources that have failed and the number of failures each. This
467: # is used for skipping resources because of failed dependencies.
468: @failures = Hash.new do |h, key|
469: h[key] = 0
470: end
471:
472: @report = Report.new
473: @count = 0
474: end
Add some additional times for reporting
# File lib/puppet/transaction.rb, line 23
23: def addtimes(hash)
24: hash.each do |name, num|
25: @timemetrics[name] = num
26: end
27: end
Check to see if we should actually allow processing, but this really only matters when a resource is getting deleted.
# File lib/puppet/transaction.rb, line 31
31: def allow_processing?(resource, changes)
32: # If a resource is going to be deleted but it still has
33: # dependencies, then don't delete it unless it's implicit or the
34: # dependency is itself being deleted.
35: if resource.purging? and resource.deleting?
36: if deps = relationship_graph.dependents(resource) and ! deps.empty? and deps.detect { |d| ! d.deleting? }
37: resource.warning "%s still depend%s on me -- not purging" %
38: [deps.collect { |r| r.ref }.join(","), deps.length > 1 ? "":"s"]
39: return false
40: end
41: end
42:
43: return true
44: end
Are there any failed resources in this transaction?
# File lib/puppet/transaction.rb, line 47
47: def any_failed?
48: failures = @failures.inject(0) { |failures, array| failures += array[1]; failures }
49: if failures > 0
50: failures
51: else
52: false
53: end
54: end
Apply all changes for a resource, returning a list of the events generated.
# File lib/puppet/transaction.rb, line 58
58: def apply(resource)
59: begin
60: changes = resource.evaluate
61: rescue => detail
62: if Puppet[:trace]
63: puts detail.backtrace
64: end
65:
66: resource.err "Failed to retrieve current state of resource: %s" % detail
67:
68: # Mark that it failed
69: @failures[resource] += 1
70:
71: # And then return
72: return []
73: end
74:
75: changes = [changes] unless changes.is_a?(Array)
76:
77: if changes.length > 0
78: @resourcemetrics[:out_of_sync] += 1
79: end
80:
81: return [] if changes.empty? or ! allow_processing?(resource, changes)
82:
83: resourceevents = apply_changes(resource, changes)
84:
85: # If there were changes and the resource isn't in noop mode...
86: unless changes.empty? or resource.noop
87: # Record when we last synced
88: resource.cache(:synced, Time.now)
89:
90: # Flush, if appropriate
91: if resource.respond_to?(:flush)
92: resource.flush
93: end
94:
95: # And set a trigger for refreshing this resource if it's a
96: # self-refresher
97: if resource.self_refresh? and ! resource.deleting?
98: # Create an edge with this resource as both the source and
99: # target. The triggering method treats these specially for
100: # logging.
101: events = resourceevents.collect { |e| e.name }
102: set_trigger(Puppet::Relationship.new(resource, resource, :callback => :refresh, :event => events))
103: end
104: end
105:
106: resourceevents
107: end
Apply each change in turn.
# File lib/puppet/transaction.rb, line 110
110: def apply_changes(resource, changes)
111: changes.collect { |change|
112: @changes << change
113: @count += 1
114: events = nil
115: begin
116: # use an array, so that changes can return more than one
117: # event if they want
118: events = [change.forward].flatten.reject { |e| e.nil? }
119: rescue => detail
120: if Puppet[:trace]
121: puts detail.backtrace
122: end
123: change.property.err "change from %s to %s failed: %s" %
124: [change.property.is_to_s(change.is), change.property.should_to_s(change.should), detail]
125: @failures[resource] += 1
126: next
127: # FIXME this should support using onerror to determine
128: # behaviour; or more likely, the client calling us
129: # should do so
130: end
131:
132: # Mark that our change happened, so it can be reversed
133: # if we ever get to that point
134: unless events.nil? or (events.is_a?(Array) and (events.empty?) or events.include?(:noop))
135: change.changed = true
136: @resourcemetrics[:applied] += 1
137: end
138:
139: events
140: }.flatten.reject { |e| e.nil? }
141: end
Find all of the changed resources.
# File lib/puppet/transaction.rb, line 144
144: def changed?
145: @changes.find_all { |change| change.changed }.collect { |change|
146: unless change.property.resource
147: raise "No resource for %s" % change.inspect
148: end
149: change.property.resource
150: }.uniq
151: end
Copy an important relationships from the parent to the newly-generated child resource.
# File lib/puppet/transaction.rb, line 163
163: def copy_relationships(resource, children)
164: depthfirst = resource.depthfirst?
165:
166: children.each do |gen_child|
167: if depthfirst
168: edge = [gen_child, resource]
169: else
170: edge = [resource, gen_child]
171: end
172: relationship_graph.add_resource(gen_child) unless relationship_graph.resource(gen_child.ref)
173:
174: unless relationship_graph.edge?(edge[1], edge[0])
175: relationship_graph.add_edge(*edge)
176: else
177: resource.debug "Skipping automatic relationship to %s" % gen_child
178: end
179: end
180: end
Are we deleting this resource?
# File lib/puppet/transaction.rb, line 183
183: def deleting?(changes)
184: changes.detect { |change|
185: change.property.name == :ensure and change.should == :absent
186: }
187: end
See if the resource generates new resources at evaluation time.
# File lib/puppet/transaction.rb, line 190
190: def eval_generate(resource)
191: if resource.respond_to?(:eval_generate)
192: begin
193: children = resource.eval_generate
194: rescue => detail
195: if Puppet[:trace]
196: puts detail.backtrace
197: end
198: resource.err "Failed to generate additional resources during transaction: %s" %
199: detail
200: return nil
201: end
202:
203: if children
204: children.each { |child| child.finish }
205: @generated += children
206: return children
207: end
208: end
209: end
Evaluate a single resource.
# File lib/puppet/transaction.rb, line 212
212: def eval_resource(resource, checkskip = true)
213: events = []
214:
215: if resource.is_a?(Puppet::Type::Component)
216: raise Puppet::DevError, "Got a component to evaluate"
217: end
218:
219: if checkskip and skip?(resource)
220: @resourcemetrics[:skipped] += 1
221: else
222: @resourcemetrics[:scheduled] += 1
223:
224: changecount = @changes.length
225:
226: # We need to generate first regardless, because the recursive
227: # actions sometimes change how the top resource is applied.
228: children = eval_generate(resource)
229:
230: if children and resource.depthfirst?
231: children.each do |child|
232: # The child will never be skipped when the parent isn't
233: events += eval_resource(child, false)
234: end
235: end
236:
237: # Perform the actual changes
238: seconds = thinmark do
239: events += apply(resource)
240: end
241:
242: if children and ! resource.depthfirst?
243: children.each do |child|
244: events += eval_resource(child, false)
245: end
246: end
247:
248: # Create a child/parent relationship. We do this after everything else because
249: # we want explicit relationships to be able to override automatic relationships,
250: # including this one.
251: if children
252: copy_relationships(resource, children)
253: end
254:
255: # A bit of hackery here -- if skipcheck is true, then we're the
256: # top-level resource. If that's the case, then make sure all of
257: # the changes list this resource as a proxy. This is really only
258: # necessary for rollback, since we know the generating resource
259: # during forward changes.
260: if children and checkskip
261: @changes[changecount..-1].each { |change| change.proxy = resource }
262: end
263:
264: # Keep track of how long we spend in each type of resource
265: @timemetrics[resource.class.name] += seconds
266: end
267:
268: # Check to see if there are any events for this resource
269: if triggedevents = trigger(resource)
270: events += triggedevents
271: end
272:
273: # Collect the targets of any subscriptions to those events. We pass
274: # the parent resource in so it will override the source in the events,
275: # since eval_generated children can't have direct relationships.
276: relationship_graph.matching_edges(events, resource).each do |orig_edge|
277: # We have to dup the label here, else we modify the original edge label,
278: # which affects whether a given event will match on the next run, which is,
279: # of course, bad.
280: edge = orig_edge.class.new(orig_edge.source, orig_edge.target)
281: label = orig_edge.label.dup
282: label[:event] = events.collect { |e| e.name }
283: edge.label = label
284: set_trigger(edge)
285: end
286:
287: # And return the events for collection
288: events
289: 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.
# File lib/puppet/transaction.rb, line 294
294: def evaluate
295: @count = 0
296:
297: # Start logging.
298: Puppet::Util::Log.newdestination(@report)
299:
300: prepare()
301:
302: begin
303: allevents = @sorted_resources.collect { |resource|
304: if resource.is_a?(Puppet::Type::Component)
305: Puppet.warning "Somehow left a component in the relationship graph"
306: next
307: end
308: ret = nil
309: seconds = thinmark do
310: ret = eval_resource(resource)
311: end
312:
313: if Puppet[:evaltrace] and @catalog.host_config?
314: resource.info "Evaluated in %0.2f seconds" % seconds
315: end
316: ret
317: }.flatten.reject { |e| e.nil? }
318: ensure
319: # And then close the transaction log.
320: Puppet::Util::Log.close(@report)
321: end
322:
323: Puppet.debug "Finishing transaction %s with %s changes" %
324: [self.object_id, @count]
325:
326: @events = allevents
327: allevents
328: end
Determine whether a given resource has failed.
# File lib/puppet/transaction.rb, line 331
331: def failed?(obj)
332: if @failures[obj] > 0
333: return @failures[obj]
334: else
335: return false
336: end
337: end
Does this resource have any failed dependencies?
# File lib/puppet/transaction.rb, line 340
340: def failed_dependencies?(resource)
341: # First make sure there are no failed dependencies. To do this,
342: # we check for failures in any of the vertexes above us. It's not
343: # enough to check the immediate dependencies, which is why we use
344: # a tree from the reversed graph.
345: skip = false
346: deps = relationship_graph.dependencies(resource)
347: deps.each do |dep|
348: if fails = failed?(dep)
349: resource.notice "Dependency %s[%s] has %s failures" %
350: [dep.class.name, dep.name, @failures[dep]]
351: skip = true
352: end
353: end
354:
355: return skip
356: end
Collect any dynamically generated resources.
# File lib/puppet/transaction.rb, line 359
359: def generate
360: list = @catalog.vertices
361:
362: # Store a list of all generated resources, so that we can clean them up
363: # after the transaction closes.
364: @generated = []
365:
366: newlist = []
367: while ! list.empty?
368: list.each do |resource|
369: if resource.respond_to?(:generate)
370: begin
371: made = resource.generate
372: rescue => detail
373: resource.err "Failed to generate additional resources: %s" %
374: detail
375: end
376: next unless made
377: unless made.is_a?(Array)
378: made = [made]
379: end
380: made.uniq!
381: made.each do |res|
382: @catalog.add_resource(res)
383: res.catalog = catalog
384: newlist << res
385: @generated << res
386: res.finish
387: end
388: end
389: end
390: list.clear
391: list = newlist
392: newlist = []
393: end
394: end
Generate a transaction report.
# File lib/puppet/transaction.rb, line 397
397: def generate_report
398: @resourcemetrics[:failed] = @failures.find_all do |name, num|
399: num > 0
400: end.length
401:
402: # Get the total time spent
403: @timemetrics[:total] = @timemetrics.inject(0) do |total, vals|
404: total += vals[1]
405: total
406: end
407:
408: # Add all of the metrics related to resource count and status
409: @report.newmetric(:resources, @resourcemetrics)
410:
411: # Record the relative time spent in each resource.
412: @report.newmetric(:time, @timemetrics)
413:
414: # Then all of the change-related metrics
415: @report.newmetric(:changes,
416: :total => @changes.length
417: )
418:
419: @report.time = Time.now
420:
421: return @report
422: end
Should we ignore tags?
# File lib/puppet/transaction.rb, line 425
425: def ignore_tags?
426: ! (@catalog.host_config? or Puppet[:name] == "puppet")
427: end
Is this resource tagged appropriately?
# File lib/puppet/transaction.rb, line 647
647: def missing_tags?(resource)
648: return false if self.ignore_tags? or tags.empty?
649: return true unless resource.tagged?(tags)
650: end
Prefetch any providers that support it. We don‘t support prefetching types, just providers.
# File lib/puppet/transaction.rb, line 478
478: def prefetch
479: prefetchers = {}
480: @catalog.vertices.each do |resource|
481: if provider = resource.provider and provider.class.respond_to?(:prefetch)
482: prefetchers[provider.class] ||= {}
483: prefetchers[provider.class][resource.title] = resource
484: end
485: end
486:
487: # Now call prefetch, passing in the resources so that the provider instances can be replaced.
488: prefetchers.each do |provider, resources|
489: Puppet.debug "Prefetching %s resources for %s" % [provider.name, provider.resource_type.name]
490: begin
491: provider.prefetch(resources)
492: rescue => detail
493: if Puppet[:trace]
494: puts detail.backtrace
495: end
496: Puppet.err "Could not prefetch %s provider '%s': %s" % [provider.resource_type.name, provider.name, detail]
497: end
498: end
499: end
Prepare to evaluate the resources in a transaction.
# File lib/puppet/transaction.rb, line 502
502: def prepare
503: # Now add any dynamically generated resources
504: generate()
505:
506: # Then prefetch. It's important that we generate and then prefetch,
507: # so that any generated resources also get prefetched.
508: prefetch()
509:
510: # This will throw an error if there are cycles in the graph.
511: @sorted_resources = relationship_graph.topsort
512: end
# File lib/puppet/transaction.rb, line 514
514: def relationship_graph
515: catalog.relationship_graph
516: end
# File lib/puppet/transaction.rb, line 544
544: def reportclient
545: unless defined? @reportclient
546: @reportclient = Puppet::Network::Client.report.new(
547: :Server => Puppet[:reportserver]
548: )
549: end
550:
551: @reportclient
552: end
Roll all completed changes back.
# File lib/puppet/transaction.rb, line 555
555: def rollback
556: @targets.clear
557: @triggered.clear
558: allevents = @changes.reverse.collect { |change|
559: # skip changes that were never actually run
560: unless change.changed
561: Puppet.debug "%s was not changed" % change.to_s
562: next
563: end
564: begin
565: events = change.backward
566: rescue => detail
567: Puppet.err("%s rollback failed: %s" % [change,detail])
568: if Puppet[:trace]
569: puts detail.backtrace
570: end
571: next
572: # at this point, we would normally do error handling
573: # but i haven't decided what to do for that yet
574: # so just record that a sync failed for a given resource
575: #@@failures[change.property.parent] += 1
576: # this still could get hairy; what if file contents changed,
577: # but a chmod failed? how would i handle that error? dern
578: end
579:
580: # FIXME This won't work right now.
581: relationship_graph.matching_edges(events).each do |edge|
582: @targets[edge.target] << edge
583: end
584:
585: # Now check to see if there are any events for this child.
586: # Kind of hackish, since going backwards goes a change at a
587: # time, not a child at a time.
588: trigger(change.property.resource)
589:
590: # And return the events for collection
591: events
592: }.flatten.reject { |e| e.nil? }
593: end
Is the resource currently scheduled?
# File lib/puppet/transaction.rb, line 596
596: def scheduled?(resource)
597: self.ignoreschedules or resource.scheduled?
598: end
Send off the transaction report.
# File lib/puppet/transaction.rb, line 519
519: def send_report
520: begin
521: report = generate_report()
522: rescue => detail
523: Puppet.err "Could not generate report: %s" % detail
524: return
525: end
526:
527: if Puppet[:rrdgraph] == true
528: report.graph()
529: end
530:
531: if Puppet[:summarize]
532: puts report.summary
533: end
534:
535: if Puppet[:report]
536: begin
537: reportclient().report(report)
538: rescue => detail
539: Puppet.err "Reporting failed: %s" % detail
540: end
541: end
542: end
Set an edge to be triggered when we evaluate its target.
# File lib/puppet/transaction.rb, line 601
601: def set_trigger(edge)
602: return unless method = edge.callback
603: return unless edge.target.respond_to?(method)
604: if edge.target.respond_to?(:ref)
605: unless edge.source == edge.target
606: edge.source.info "Scheduling %s of %s" % [edge.callback, edge.target.ref]
607: end
608: end
609: @targets[edge.target] << edge
610: end
Should this resource be skipped?
# File lib/puppet/transaction.rb, line 613
613: def skip?(resource)
614: skip = false
615: if missing_tags?(resource)
616: resource.debug "Not tagged with %s" % tags.join(", ")
617: elsif ! scheduled?(resource)
618: resource.debug "Not scheduled"
619: elsif failed_dependencies?(resource)
620: resource.warning "Skipping because of failed dependencies"
621: else
622: return false
623: end
624: return true
625: end
# File lib/puppet/transaction.rb, line 641
641: def tags=(tags)
642: tags = [tags] unless tags.is_a?(Array)
643: @tags = tags
644: end
Are there any edges that target this resource?
# File lib/puppet/transaction.rb, line 653
653: def targeted?(resource)
654: # The default value is a new array so we have to test the length of it.
655: @targets.include?(resource) and @targets[resource].length > 0
656: end
Trigger any subscriptions to a child. This does an upwardly recursive search — it triggers the passed resource, but also the resource‘s parent and so on up the tree.
# File lib/puppet/transaction.rb, line 661
661: def trigger(resource)
662: return nil unless targeted?(resource)
663: callbacks = Hash.new { |hash, key| hash[key] = [] }
664:
665: trigged = []
666: @targets[resource].each do |edge|
667: # Collect all of the subs for each callback
668: callbacks[edge.callback] << edge
669: end
670:
671: callbacks.each do |callback, subs|
672: noop = true
673: subs.each do |edge|
674: if edge.event.nil? or ! edge.event.include?(:noop)
675: noop = false
676: end
677: end
678:
679: if noop
680: resource.notice "Would have triggered %s from %s dependencies" %
681: [callback, subs.length]
682:
683: # And then add an event for it.
684: return [Puppet::Transaction::Event.new(:noop, resource)]
685: end
686:
687: if subs.length == 1 and subs[0].source == resource
688: message = "Refreshing self"
689: else
690: message = "Triggering '%s' from %s dependencies" %
691: [callback, subs.length]
692: end
693: resource.notice message
694:
695: # At this point, just log failures, don't try to react
696: # to them in any way.
697: begin
698: resource.send(callback)
699: @resourcemetrics[:restarted] += 1
700: rescue => detail
701: resource.err "Failed to call %s on %s: %s" %
702: [callback, resource, detail]
703:
704: @resourcemetrics[:failed_restarts] += 1
705:
706: if Puppet[:trace]
707: puts detail.backtrace
708: end
709: end
710:
711: # And then add an event for it.
712: trigged << Puppet::Transaction::Event.new(:triggered, resource)
713:
714: triggered(resource, callback)
715: end
716:
717: if trigged.empty?
718: return nil
719: else
720: return trigged
721: end
722: end
# File lib/puppet/transaction.rb, line 724
724: def triggered(resource, method)
725: @triggered[resource][method] += 1
726: end