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

Public Instance Methods

any_failed?() click to toggle source

Are there any failed resources in this transaction?

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

Find all of the changed resources.

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

Is this resource tagged appropriately?

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

Should this resource be skipped?

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

Wraps application run state check to flag need to interrupt processing

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

The tags we should be checking.

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

Private Instance Methods

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

Apply all changes for a resource

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

Evaluate a single resource.

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

Does this resource have any failed dependencies?

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

Should we ignore tags?

    # File lib/puppet/transaction.rb
353 def ignore_tags?
354   ! @catalog.host_config?
355 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
374 def prefetch(provider_class, resources)
375   type_name = provider_class.resource_type.name
376   return if @prefetched_providers[type_name][provider_class.name] ||
377     @prefetch_failed_providers[type_name][provider_class.name]
378   Puppet.debug { "Prefetching #{provider_class.name} resources for #{type_name}" }
379   begin
380     provider_class.prefetch(resources)
381   rescue LoadError, StandardError => detail
382     #TRANSLATORS `prefetch` is a function name and should not be translated
383     message = _("Could not prefetch %{type_name} provider '%{name}': %{detail}") % { type_name: type_name, name: provider_class.name, detail: detail }
384     Puppet.log_exception(detail, message)
385     @prefetch_failed_providers[type_name][provider_class.name] = true
386   end
387   @prefetched_providers[type_name][provider_class.name] = true
388 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
333 def propagate_failure(resource)
334 
335   provider_class = resource.provider.class
336   s = resource_status(resource)
337   if prefetch_failed_providers[resource.type][provider_class.name] && !s.nil?
338     message = _("Prefetch failed for %{type_name} provider '%{name}'") % { type_name: resource.type, name: provider_class.name }
339     s.fail_with_event(message)
340   end
341 
342   failed = Set.new
343   relationship_graph.direct_dependencies_of(resource).each do |dep|
344     s = resource_status(dep)
345     next if s.nil?
346     failed.merge(s.failed_dependencies) if s.dependency_failed?
347     failed.add(dep) if s.failed? || s.failed_to_restart?
348   end
349   resource_status(resource).failed_dependencies = failed.to_a
350 end
resolve_resource(resource) click to toggle source

These two methods are only made public to enable the existing spec tests to run under rspec 3 (apparently rspec 2 didn't enforce access controls?). Please do not treat these as part of a public API. Possible future improvement: rewrite to not require access to private methods.

    # File lib/puppet/transaction.rb
469 def resolve_resource(resource)
470   return unless catalog.host_config?
471 
472   deferred_validate = false
473 
474   resource.eachparameter do |param|
475     if param.value.instance_of?(Puppet::Pops::Evaluator::DeferredValue)
476       # Puppet::Parameter#value= triggers validation and munging. Puppet::Property#value=
477       # overrides the method, but also triggers validation and munging, since we're
478       # setting the desired/should value.
479       resolved = param.value.resolve
480       # resource.notice("Resolved deferred value to #{resolved}")
481       param.value = resolved
482       deferred_validate = true
483     end
484   end
485 
486   if deferred_validate
487     resource.validate_resource
488   end
489 end
resources_by_provider(type_name, provider_name) click to toggle source
    # File lib/puppet/transaction.rb
357 def resources_by_provider(type_name, provider_name)
358   unless @resources_by_provider
359     @resources_by_provider = Hash.new { |h, k| h[k] = Hash.new { |h1, k1| h1[k1] = {} } }
360 
361     @catalog.vertices.each do |resource|
362       if resource.class.attrclass(:provider)
363         prov = resource.provider && resource.provider.class.name
364         @resources_by_provider[resource.type][prov][resource.name] = resource
365       end
366     end
367   end
368 
369   @resources_by_provider[type_name][provider_name] || {}
370 end
scheduled?(resource) click to toggle source

Is the resource currently scheduled?

    # File lib/puppet/transaction.rb
395 def scheduled?(resource)
396   self.ignoreschedules || resource_harness.scheduled?(resource)
397 end
skip_tags?(resource) click to toggle source
    # File lib/puppet/transaction.rb
451 def skip_tags?(resource)
452   return false if ignore_tags?
453   return false if skip_tags.empty?
454 
455   resource.tagged?(*skip_tags)
456 end
split_qualified_tags?() click to toggle source
    # File lib/puppet/transaction.rb
458 def split_qualified_tags?
459   false
460 end