Class Puppet::Transaction
In: lib/puppet/transaction.rb
Parent: Object

Methods

Included Modules

Puppet::Util

Attributes

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] 

Public Class methods

this should only be called by a Puppet::Type::Component resource now and it should only receive an array

[Source]

     # 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

Public Instance methods

Add some additional times for reporting

[Source]

    # 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.

[Source]

    # 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?

[Source]

    # 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.

[Source]

     # 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.

[Source]

     # 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.

[Source]

     # 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

Do any necessary cleanup. If we don‘t get rid of the graphs, the contained resources might never get cleaned up.

[Source]

     # File lib/puppet/transaction.rb, line 155
155:     def cleanup
156:         if defined? @generated
157:             relationship_graph.remove_resource(*@generated)
158:         end
159:     end

Copy an important relationships from the parent to the newly-generated child resource.

[Source]

     # 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?

[Source]

     # 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.

[Source]

     # 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.

[Source]

     # 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.

[Source]

     # 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.

[Source]

     # 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?

[Source]

     # 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.

[Source]

     # 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.

[Source]

     # 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?

[Source]

     # 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?

[Source]

     # 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.

[Source]

     # 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.

[Source]

     # 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

[Source]

     # File lib/puppet/transaction.rb, line 514
514:     def relationship_graph
515:         catalog.relationship_graph
516:     end

[Source]

     # 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.

[Source]

     # 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?

[Source]

     # File lib/puppet/transaction.rb, line 596
596:     def scheduled?(resource)
597:         self.ignoreschedules or resource.scheduled?
598:     end

Send off the transaction report.

[Source]

     # 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.

[Source]

     # 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?

[Source]

     # 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

The tags we should be checking.

[Source]

     # File lib/puppet/transaction.rb, line 628
628:     def tags
629:         unless defined? @tags
630:             tags = Puppet[:tags]
631:             if tags.nil? or tags == ""
632:                 @tags = []
633:             else
634:                 @tags = tags.split(/\s*,\s*/)
635:             end
636:         end
637:         
638:         @tags
639:     end

[Source]

     # 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?

[Source]

     # 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.

[Source]

     # 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

[Source]

     # File lib/puppet/transaction.rb, line 724
724:     def triggered(resource, method)
725:         @triggered[resource][method] += 1
726:     end

[Source]

     # File lib/puppet/transaction.rb, line 728
728:     def triggered?(resource, method)
729:         @triggered[resource][method]
730:     end

[Validate]