class Transaction

the class that actually walks our resource/property tree, collects the changes, and performs them

@api private

Attributes

catalog[RW]
event_manager[R]

Routes and stores any events and subscriptions.

for_network_device[RW]
ignoreschedules[RW]
persistence[R]

@!attribute [r] persistence

@return [Puppet::Transaction::Persistence] persistence object for cross
   transaction storage.
prefetch_failed_providers[R]
prefetched_providers[R]
report[R]

The report, once generated.

resource_harness[R]

Handles most of the actual interacting with resources

Public Class Methods

new(catalog, report, prioritizer) click to toggle source
   # File lib/puppet/transaction.rb
41 def initialize(catalog, report, prioritizer)
42   @catalog = catalog
43 
44   @persistence = Puppet::Transaction::Persistence.new
45 
46   @report = report || Puppet::Transaction::Report.new(catalog.version, catalog.environment)
47 
48   @prioritizer = prioritizer
49 
50   @report.add_times(:config_retrieval, @catalog.retrieval_duration || 0)
51 
52   @event_manager = Puppet::Transaction::EventManager.new(self)
53 
54   @resource_harness = Puppet::Transaction::ResourceHarness.new(self)
55 
56   @prefetched_providers = Hash.new { |h,k| h[k] = {} }
57 
58   @prefetch_failed_providers = Hash.new { |h,k| h[k] = {} }
59 
60   # With merge_dependency_warnings, notify and warn about class dependency failures ... just once per class. TJK 2019-09-09
61   @merge_dependency_warnings = Puppet[:merge_dependency_warnings]
62   @failed_dependencies_already_notified = Set.new()
63   @failed_class_dependencies_already_notified = Set.new()
64   @failed_class_dependencies_already_warned = Set.new()
65 end

Public Instance Methods

any_failed?() click to toggle source

Are there any failed resources in this transaction?

    # File lib/puppet/transaction.rb
214 def any_failed?
215   report.resource_statuses.values.detect { |status|
216     status.failed? || status.failed_to_restart?
217   }
218 end
changed?() click to toggle source

Find all of the changed resources.

    # File lib/puppet/transaction.rb
221 def changed?
222   report.resource_statuses.values.find_all { |status| status.changed }.collect { |status| catalog.resource(status.resource) }
223 end
evaluate(&block) click to toggle source

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
 97 def evaluate(&block)
 98   block ||= method(:eval_resource)
 99   generator = AdditionalResourceGenerator.new(@catalog, nil, @prioritizer)
100   @catalog.vertices.each { |resource| generator.generate_additional_resources(resource) }
101 
102   perform_pre_run_checks
103 
104   persistence.load if persistence.enabled?(catalog)
105 
106   Puppet.info _("Applying configuration version '%{version}'") % { version: catalog.version } if catalog.version
107 
108   continue_while = lambda { !stop_processing? }
109 
110   post_evalable_providers = Set.new
111   pre_process = lambda do |resource|
112     prov_class = resource.provider.class
113     post_evalable_providers << prov_class if prov_class.respond_to?(:post_resource_eval)
114 
115     prefetch_if_necessary(resource)
116 
117     # If we generated resources, we don't know what they are now
118     # blocking, so we opt to recompute it, rather than try to track every
119     # change that would affect the number.
120     relationship_graph.clear_blockers if generator.eval_generate(resource)
121   end
122 
123   providerless_types = []
124   overly_deferred_resource_handler = lambda do |resource|
125     # We don't automatically assign unsuitable providers, so if there
126     # is one, it must have been selected by the user.
127     return if missing_tags?(resource)
128     if resource.provider
129       resource.err _("Provider %{name} is not functional on this host") % { name: resource.provider.class.name }
130     else
131       providerless_types << resource.type
132     end
133 
134     resource_status(resource).failed = true
135   end
136 
137   canceled_resource_handler = lambda do |resource|
138     resource_status(resource).skipped = true
139     resource.debug "Transaction canceled, skipping"
140   end
141 
142   teardown = lambda do
143     # Just once per type. No need to punish the user.
144     providerless_types.uniq.each do |type|
145       Puppet.err _("Could not find a suitable provider for %{type}") % { type: type }
146     end
147 
148     post_evalable_providers.each do |provider|
149       begin
150         provider.post_resource_eval
151       rescue => detail
152         Puppet.log_exception(detail, _("post_resource_eval failed for provider %{provider}") % { provider: provider })
153       end
154     end
155 
156     persistence.save if persistence.enabled?(catalog)
157   end
158 
159   # Graph cycles are returned as an array of arrays
160   # - outer array is an array of cycles
161   # - each inner array is an array of resources involved in a cycle
162   # Short circuit resource evaluation if we detect cycle(s) in the graph. Mark
163   # each corresponding resource as failed in the report before we fail to
164   # ensure accurate reporting.
165   graph_cycle_handler = lambda do |cycles|
166     cycles.flatten.uniq.each do |resource|
167       # We add a failed resource event to the status to ensure accurate
168       # reporting through the event manager.
169       resource_status(resource).fail_with_event(_('resource is part of a dependency cycle'))
170     end
171     raise Puppet::Error, _('One or more resource dependency cycles detected in graph')
172   end
173 
174   # Generate the relationship graph, set up our generator to use it
175   # for eval_generate, then kick off our traversal.
176   generator.relationship_graph = relationship_graph
177   progress = 0
178   relationship_graph.traverse(:while => continue_while,
179                               :pre_process => pre_process,
180                               :overly_deferred_resource_handler => overly_deferred_resource_handler,
181                               :canceled_resource_handler => canceled_resource_handler,
182                               :graph_cycle_handler => graph_cycle_handler,
183                               :teardown => teardown) do |resource|
184     progress += 1
185     if resource.is_a?(Puppet::Type::Component)
186       Puppet.warning _("Somehow left a component in the relationship graph")
187     else
188       if Puppet[:evaltrace] && @catalog.host_config?
189         resource.info _("Starting to evaluate the resource (%{progress} of %{total})") % { progress: progress, total: relationship_graph.size }
190       end
191       seconds = thinmark { block.call(resource) }
192       resource.info _("Evaluated in %{seconds} seconds") % { seconds: "%0.2f" % seconds } if Puppet[:evaltrace] && @catalog.host_config?
193     end
194   end
195 
196   # if one or more resources has attempted and failed to generate resources,
197   # report it
198   if generator.resources_failed_to_generate
199     report.resources_failed_to_generate = true
200   end
201 
202   # mark the end of transaction evaluate.
203   report.transaction_completed = true
204 
205   Puppet.debug { "Finishing transaction #{object_id}" }
206 end
missing_tags?(resource) click to toggle source

Is this resource tagged appropriately?

    # File lib/puppet/transaction.rb
442 def missing_tags?(resource)
443   return false if ignore_tags?
444   return false if tags.empty?
445 
446   not resource.tagged?(*tags)
447 end
perform_pre_run_checks() click to toggle source

Invoke the pre_run_check hook in every resource in the catalog. This should (only) be called by Transaction#evaluate before applying the catalog.

@see Puppet::Transaction#evaluate @see Puppet::Type#pre_run_check @raise [Puppet::Error] If any pre-run checks failed. @return [void]

   # File lib/puppet/transaction.rb
75 def perform_pre_run_checks
76   prerun_errors = {}
77 
78   @catalog.vertices.each do |res|
79     begin
80       res.pre_run_check
81     rescue Puppet::Error => detail
82       prerun_errors[res] = detail
83     end
84   end
85 
86   unless prerun_errors.empty?
87     prerun_errors.each do |res, detail|
88       res.log_exception(detail)
89     end
90     raise Puppet::Error, _("Some pre-run checks failed")
91   end
92 end
prefetch_if_necessary(resource) click to toggle source
    # File lib/puppet/transaction.rb
244 def prefetch_if_necessary(resource)
245   provider_class = resource.provider.class
246   if !provider_class.respond_to?(:prefetch) or
247       prefetched_providers[resource.type][provider_class.name] or
248       prefetch_failed_providers[resource.type][provider_class.name]
249     return
250   end
251 
252   resources = resources_by_provider(resource.type, provider_class.name)
253 
254   if provider_class == resource.class.defaultprovider
255     providerless_resources = resources_by_provider(resource.type, nil)
256     providerless_resources.values.each {|res| res.provider = provider_class.name}
257     resources.merge! providerless_resources
258   end
259 
260   prefetch(provider_class, resources)
261 end
relationship_graph() click to toggle source
    # File lib/puppet/transaction.rb
225 def relationship_graph
226   catalog.relationship_graph(@prioritizer)
227 end
resource_status(resource) click to toggle source
    # File lib/puppet/transaction.rb
229 def resource_status(resource)
230   report.resource_statuses[resource.to_s] || add_resource_status(Puppet::Resource::Status.new(resource))
231 end
skip?(resource) click to toggle source

Should this resource be skipped?

    # File lib/puppet/transaction.rb
398 def skip?(resource)
399   if skip_tags?(resource)
400     resource.debug "Skipping with skip tags #{skip_tags.join(", ")}"
401   elsif missing_tags?(resource)
402     resource.debug "Not tagged with #{tags.join(", ")}"
403   elsif ! scheduled?(resource)
404     resource.debug "Not scheduled"
405   elsif failed_dependencies?(resource)
406     # When we introduced the :whit into the graph, to reduce the combinatorial
407     # explosion of edges, we also ended up reporting failures for containers
408     # like class and stage.  This is undesirable; while just skipping the
409     # output isn't perfect, it is RC-safe. --daniel 2011-06-07
410     # With merge_dependency_warnings, warn about class dependency failures ... just once per class. TJK 2019-09-09
411     unless resource.class == Puppet::Type.type(:whit)
412       if @merge_dependency_warnings && resource.parent && failed_dependencies?(resource.parent)
413         ps = resource_status(resource.parent)
414         ps.failed_dependencies.find_all { |d| !(@failed_class_dependencies_already_warned.include?(d.ref)) }.each do |dep|
415           resource.parent.warning _("Skipping resources in class because of failed class dependencies")
416           @failed_class_dependencies_already_warned.add(dep.ref)
417         end
418       else
419         resource.warning _("Skipping because of failed dependencies")
420       end
421     end
422   elsif resource_status(resource).failed? && @prefetch_failed_providers[resource.type][resource.provider.class.name]
423     #Do not try to evaluate a resource with a known failed provider
424     resource.warning _("Skipping because provider prefetch failed")
425   elsif resource.virtual?
426     resource.debug "Skipping because virtual"
427   elsif !host_and_device_resource?(resource) && resource.appliable_to_host? && for_network_device
428     resource.debug "Skipping host resources because running on a device"
429   elsif !host_and_device_resource?(resource) && resource.appliable_to_device? && !for_network_device
430     resource.debug "Skipping device resources because running on a posix host"
431   else
432     return false
433   end
434   true
435 end
skip_tags() click to toggle source
    # File lib/puppet/transaction.rb
240 def skip_tags
241   @skip_tags ||= Puppet::Util::SkipTags.new(Puppet[:skip_tags]).tags
242 end
stop_processing?() click to toggle source

Wraps application run state check to flag need to interrupt processing

    # File lib/puppet/transaction.rb
209 def stop_processing?
210   Puppet::Application.stop_requested? && catalog.host_config?
211 end
tags() click to toggle source

The tags we should be checking.

Calls superclass method Puppet::Util::Tagging#tags
    # File lib/puppet/transaction.rb
234 def tags
235   self.tags = Puppet[:tags] unless defined?(@tags)
236 
237   super
238 end

Private Instance Methods

add_resource_status(status) click to toggle source
    # File lib/puppet/transaction.rb
388 def add_resource_status(status)
389   report.add_resource_status(status)
390 end
apply(resource, ancestor = nil) click to toggle source

Apply all changes for a resource

    # File lib/puppet/transaction.rb
266 def apply(resource, ancestor = nil)
267   status = resource_harness.evaluate(resource)
268   add_resource_status(status)
269   ancestor ||= resource
270   if !(status.failed? || status.failed_to_restart?)
271     event_manager.queue_events(ancestor, status.events)
272   end
273 rescue => detail
274   resource.err _("Could not evaluate: %{detail}") % { detail: detail }
275 end
eval_resource(resource, ancestor = nil) click to toggle source

Evaluate a single resource.

    # File lib/puppet/transaction.rb
278 def eval_resource(resource, ancestor = nil)
279   propagate_failure(resource)
280   if skip?(resource)
281     resource_status(resource).skipped = true
282     resource.debug("Resource is being skipped, unscheduling all events")
283     event_manager.dequeue_all_events_for_resource(resource)
284     persistence.copy_skipped(resource.ref)
285   else
286     resource_status(resource).scheduled = true
287     apply(resource, ancestor)
288     event_manager.process_events(resource)
289   end
290 end
failed_dependencies?(resource) click to toggle source

Does this resource have any failed dependencies?

    # File lib/puppet/transaction.rb
293 def failed_dependencies?(resource)
294   # When we introduced the :whit into the graph, to reduce the combinatorial
295   # explosion of edges, we also ended up reporting failures for containers
296   # like class and stage.  This is undesirable; while just skipping the
297   # output isn't perfect, it is RC-safe. --daniel 2011-06-07
298   is_puppet_class = resource.class == Puppet::Type.type(:whit)
299   # With merge_dependency_warnings, notify about class dependency failures ... just once per class. TJK 2019-09-09
300   s = resource_status(resource)
301   if s && s.dependency_failed?
302     if @merge_dependency_warnings && is_puppet_class
303       # Notes: Puppet::Resource::Status.failed_dependencies() is an Array of Puppet::Resource(s) and
304       #        Puppet::Resource.ref() calls Puppet::Resource.to_s() which is: "#{type}[#{title}]" and
305       #        Puppet::Resource.resource_status(resource) calls Puppet::Resource.to_s()
306       class_dependencies_to_be_notified = (s.failed_dependencies.map(&:ref) - @failed_class_dependencies_already_notified.to_a)
307       class_dependencies_to_be_notified.each do |dep_ref|
308         resource.notice _("Class dependency %{dep} has failures: %{status}") % { dep: dep_ref, status: resource_status(dep_ref).failed }
309       end
310       @failed_class_dependencies_already_notified.merge(class_dependencies_to_be_notified)
311     else
312       unless @merge_dependency_warnings || is_puppet_class
313         s.failed_dependencies.find_all { |d| !(@failed_dependencies_already_notified.include?(d.ref)) }.each do |dep|
314           resource.notice _("Dependency %{dep} has failures: %{status}") % { dep: dep, status: resource_status(dep).failed }
315           @failed_dependencies_already_notified.add(dep.ref)
316         end
317       end
318     end
319   end
320 
321   s && s.dependency_failed?
322 end
host_and_device_resource?(resource) click to toggle source
    # File lib/puppet/transaction.rb
437 def host_and_device_resource?(resource)
438   resource.appliable_to_host? && resource.appliable_to_device?
439 end
ignore_tags?() click to toggle source

Should we ignore tags?

    # File lib/puppet/transaction.rb
351 def ignore_tags?
352   ! @catalog.host_config?
353 end
prefetch(provider_class, resources) click to toggle source

Prefetch any providers that support it, yo. We don't support prefetching types, just providers.

    # File lib/puppet/transaction.rb
372 def prefetch(provider_class, resources)
373   type_name = provider_class.resource_type.name
374   return if @prefetched_providers[type_name][provider_class.name] ||
375     @prefetch_failed_providers[type_name][provider_class.name]
376   Puppet.debug { "Prefetching #{provider_class.name} resources for #{type_name}" }
377   begin
378     provider_class.prefetch(resources)
379   rescue LoadError, StandardError => detail
380     #TRANSLATORS `prefetch` is a function name and should not be translated
381     message = _("Could not prefetch %{type_name} provider '%{name}': %{detail}") % { type_name: type_name, name: provider_class.name, detail: detail }
382     Puppet.log_exception(detail, message)
383     @prefetch_failed_providers[type_name][provider_class.name] = true
384   end
385   @prefetched_providers[type_name][provider_class.name] = true
386 end
propagate_failure(resource) click to toggle source

We need to know if a resource has any failed dependencies before we try to process it. We keep track of this by keeping a list on each resource of the failed dependencies, and incrementally computing it as the union of the failed dependencies of each first-order dependency. We have to do this as-we-go instead of up-front at failure time because the graph may be mutated as we walk it.

    # File lib/puppet/transaction.rb
331 def propagate_failure(resource)
332 
333   provider_class = resource.provider.class
334   s = resource_status(resource)
335   if prefetch_failed_providers[resource.type][provider_class.name] && !s.nil?
336     message = _("Prefetch failed for %{type_name} provider '%{name}'") % { type_name: resource.type, name: provider_class.name }
337     s.fail_with_event(message)
338   end
339 
340   failed = Set.new
341   relationship_graph.direct_dependencies_of(resource).each do |dep|
342     s = resource_status(dep)
343     next if s.nil?
344     failed.merge(s.failed_dependencies) if s.dependency_failed?
345     failed.add(dep) if s.failed? || s.failed_to_restart?
346   end
347   resource_status(resource).failed_dependencies = failed.to_a
348 end
resources_by_provider(type_name, provider_name) click to toggle source
    # File lib/puppet/transaction.rb
355 def resources_by_provider(type_name, provider_name)
356   unless @resources_by_provider
357     @resources_by_provider = Hash.new { |h, k| h[k] = Hash.new { |h1, k1| h1[k1] = {} } }
358 
359     @catalog.vertices.each do |resource|
360       if resource.class.attrclass(:provider)
361         prov = resource.provider && resource.provider.class.name
362         @resources_by_provider[resource.type][prov][resource.name] = resource
363       end
364     end
365   end
366 
367   @resources_by_provider[type_name][provider_name] || {}
368 end
scheduled?(resource) click to toggle source

Is the resource currently scheduled?

    # File lib/puppet/transaction.rb
393 def scheduled?(resource)
394   self.ignoreschedules || resource_harness.scheduled?(resource)
395 end
skip_tags?(resource) click to toggle source
    # File lib/puppet/transaction.rb
449 def skip_tags?(resource)
450   return false if ignore_tags?
451   return false if skip_tags.empty?
452 
453   resource.tagged?(*skip_tags)
454 end
split_qualified_tags?() click to toggle source
    # File lib/puppet/transaction.rb
456 def split_qualified_tags?
457   false
458 end