summaryrefslogtreecommitdiffstats
path: root/lib/puppet
diff options
context:
space:
mode:
authorPaul Berry <paul@puppetlabs.com>2010-12-17 10:59:12 -0800
committerPaul Berry <paul@puppetlabs.com>2010-12-17 11:02:04 -0800
commit7fff7808e25491a5ea1e207b8de3ade0c4f95f4f (patch)
tree454a6f212d7b79aff983450b8f667ed5ea1d4d14 /lib/puppet
parent519e3857c7edf0d007dd3d4924d27ae552a9b457 (diff)
downloadpuppet-7fff7808e25491a5ea1e207b8de3ade0c4f95f4f.tar.gz
puppet-7fff7808e25491a5ea1e207b8de3ade0c4f95f4f.tar.xz
puppet-7fff7808e25491a5ea1e207b8de3ade0c4f95f4f.zip
(#5408) Reworked ResourceHarness so that code is clearer and all behaviors are tested
This patch removes the Puppet::Transaction::Change class and replaces it with a method, Puppet::Transaction::ResourceHarness#apply_parameter. The new code is shorter, more thoroughly unit tested, and addresses known bugs in the interaction between auditing and performing changes. This code does not address drawbacks in the report output (for example a resource is still flagged as changed even if it merely contains audit information); those will be addressed in a follow-up patch.
Diffstat (limited to 'lib/puppet')
-rw-r--r--lib/puppet/resource/status.rb8
-rw-r--r--lib/puppet/transaction.rb1
-rw-r--r--lib/puppet/transaction/change.rb75
-rw-r--r--lib/puppet/transaction/resource_harness.rb143
4 files changed, 97 insertions, 130 deletions
diff --git a/lib/puppet/resource/status.rb b/lib/puppet/resource/status.rb
index 2bdfbbfef..9240a06d7 100644
--- a/lib/puppet/resource/status.rb
+++ b/lib/puppet/resource/status.rb
@@ -4,13 +4,13 @@ module Puppet
include Puppet::Util::Tagging
include Puppet::Util::Logging
- ATTRIBUTES = [:resource, :node, :version, :file, :line, :current_values, :skipped_reason, :status, :evaluation_time, :change_count]
- attr_accessor *ATTRIBUTES
+ attr_accessor :resource, :node, :version, :file, :line, :current_values, :skipped_reason, :status, :evaluation_time
STATES = [:skipped, :failed, :failed_to_restart, :restarted, :changed, :out_of_sync, :scheduled]
attr_accessor *STATES
attr_reader :source_description, :default_log_level, :time, :resource
+ attr_reader :change_count
# Provide a boolean method for each of the states.
STATES.each do |attr|
@@ -29,6 +29,9 @@ module Puppet
if event.status == 'failure'
self.failed = true
end
+ @change_count += 1
+ @changed = true
+ @out_of_sync = true
end
def events
@@ -38,6 +41,7 @@ module Puppet
def initialize(resource)
@source_description = resource.path
@resource = resource.to_s
+ @change_count = 0
[:file, :line, :version].each do |attr|
send(attr.to_s + "=", resource.send(attr))
diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb
index 2d49062dd..4db971477 100644
--- a/lib/puppet/transaction.rb
+++ b/lib/puppet/transaction.rb
@@ -6,7 +6,6 @@ require 'puppet/util/tagging'
require 'puppet/application'
class Puppet::Transaction
- require 'puppet/transaction/change'
require 'puppet/transaction/event'
require 'puppet/transaction/event_manager'
require 'puppet/transaction/resource_harness'
diff --git a/lib/puppet/transaction/change.rb b/lib/puppet/transaction/change.rb
deleted file mode 100644
index d57ac1917..000000000
--- a/lib/puppet/transaction/change.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-require 'puppet/transaction'
-require 'puppet/transaction/event'
-
-# Handle all of the work around performing an actual change,
-# including calling 'sync' on the properties and producing events.
-class Puppet::Transaction::Change
- attr_accessor :is, :should, :property, :proxy, :auditing, :old_audit_value
-
- def auditing?
- auditing
- end
-
- def initialize(property, currentvalue)
- @property = property
- @is = currentvalue
-
- @should = property.should
-
- @changed = false
- end
-
- def apply
- event = property.event
- event.previous_value = is
- event.desired_value = should
- event.historical_value = old_audit_value
-
- if auditing? and old_audit_value != is
- event.message = "audit change: previously recorded value #{property.is_to_s(old_audit_value)} has been changed to #{property.is_to_s(is)}"
- event.status = "audit"
- event.audited = true
- brief_audit_message = " (previously recorded value was #{property.is_to_s(old_audit_value)})"
- else
- brief_audit_message = ""
- end
-
- if property.insync?(is)
- # nothing happens
- elsif noop?
- event.message = "is #{property.is_to_s(is)}, should be #{property.should_to_s(should)} (noop)#{brief_audit_message}"
- event.status = "noop"
- else
- property.sync
- event.message = [ property.change_to_s(is, should), brief_audit_message ].join
- event.status = "success"
- end
- event
- rescue => detail
- puts detail.backtrace if Puppet[:trace]
- event.status = "failure"
-
- event.message = "change from #{property.is_to_s(is)} to #{property.should_to_s(should)} failed: #{detail}"
- event
- ensure
- event.send_log
- end
-
- # Is our property noop? This is used for generating special events.
- def noop?
- @property.noop
- end
-
- # The resource that generated this change. This is used for handling events,
- # and the proxy resource is used for generated resources, since we can't
- # send an event to a resource we don't have a direct relationship with. If we
- # have a proxy resource, then the events will be considered to be from
- # that resource, rather than us, so the graph resolution will still work.
- def resource
- self.proxy || @property.resource
- end
-
- def to_s
- "change #{@property.change_to_s(@is, @should)}"
- end
-end
diff --git a/lib/puppet/transaction/resource_harness.rb b/lib/puppet/transaction/resource_harness.rb
index c978e5545..cb9a193b9 100644
--- a/lib/puppet/transaction/resource_harness.rb
+++ b/lib/puppet/transaction/resource_harness.rb
@@ -7,22 +7,15 @@ class Puppet::Transaction::ResourceHarness
attr_reader :transaction
def allow_changes?(resource)
- return true unless resource.purging? and resource.deleting?
- return true unless deps = relationship_graph.dependents(resource) and ! deps.empty? and deps.detect { |d| ! d.deleting? }
-
- deplabel = deps.collect { |r| r.ref }.join(",")
- plurality = deps.length > 1 ? "":"s"
- resource.warning "#{deplabel} still depend#{plurality} on me -- not purging"
- false
- end
-
- def apply_changes(status, changes)
- changes.each do |change|
- status << change.apply
-
- cache(change.property.resource, change.property.name, change.is) if change.auditing?
+ if resource.purging? and resource.deleting? and deps = relationship_graph.dependents(resource) \
+ and ! deps.empty? and deps.detect { |d| ! d.deleting? }
+ deplabel = deps.collect { |r| r.ref }.join(",")
+ plurality = deps.length > 1 ? "":"s"
+ resource.warning "#{deplabel} still depend#{plurality} on me -- not purging"
+ false
+ else
+ true
end
- status.changed = true
end
# Used mostly for scheduling and auditing at this point.
@@ -35,66 +28,112 @@ class Puppet::Transaction::ResourceHarness
Puppet::Util::Storage.cache(resource)[name] = value
end
- def changes_to_perform(status, resource)
+ def perform_changes(resource)
current = resource.retrieve_resource
cache resource, :checked, Time.now
return [] if ! allow_changes?(resource)
- audited = copy_audited_parameters(resource, current)
+ current_values = current.to_hash
+ historical_values = Puppet::Util::Storage.cache(resource).dup
+ desired_values = resource.to_resource.to_hash
+ audited_params = (resource[:audit] || []).map { |p| p.to_sym }
+ synced_params = []
- if param = resource.parameter(:ensure)
- return [] if absent_and_not_being_created?(current, param)
- unless ensure_is_insync?(current, param)
- audited.keys.reject{|name| name == :ensure}.each do |name|
- resource.parameter(name).notice "audit change: previously recorded value #{audited[name]} has been changed to #{current[param]}"
- cache(resource, name, current[param])
+ # Record the current state in state.yml.
+ audited_params.each do |param|
+ cache(resource, param, current_values[param])
+ end
+
+ # Update the machine state & create logs/events
+ events = []
+ ensure_param = resource.parameter(:ensure)
+ if desired_values[:ensure] && !ensure_param.insync?(current_values[:ensure])
+ events << apply_parameter(ensure_param, current_values[:ensure], audited_params.include?(:ensure), historical_values[:ensure])
+ synced_params << :ensure
+ elsif current_values[:ensure] != :absent
+ work_order = resource.properties # Note: only the resource knows what order to apply changes in
+ work_order.each do |param|
+ if !param.insync?(current_values[param.name])
+ events << apply_parameter(param, current_values[param.name], audited_params.include?(param.name), historical_values[param.name])
+ synced_params << param.name
end
- return [Puppet::Transaction::Change.new(param, current[:ensure])]
end
- return [] if ensure_should_be_absent?(current, param)
end
- resource.properties.reject { |param| param.name == :ensure }.select do |param|
- (audited.include?(param.name) && audited[param.name] != current[param.name]) || (param.should != nil && !param_is_insync?(current, param))
- end.collect do |param|
- change = Puppet::Transaction::Change.new(param, current[param.name])
- change.auditing = true if audited.include?(param.name)
- change.old_audit_value = audited[param.name]
- change
+ # Add more events to capture audit results
+ audited_params.each do |param_name|
+ if historical_values.include?(param_name)
+ if historical_values[param_name] != current_values[param_name] && !synced_params.include?(param_name)
+ event = create_change_event(resource.parameter(param_name), current_values[param_name], true, historical_values[param_name])
+ event.send_log
+ events << event
+ end
+ else
+ resource.property(param_name).notice "audit change: newly-recorded value #{current_values[param_name]}"
+ end
end
+
+ events
end
- def copy_audited_parameters(resource, current)
- return {} unless audit = resource[:audit]
- audit = Array(audit).collect { |p| p.to_sym }
- audited = {}
- audit.find_all do |param|
- if value = cached(resource, param)
- audited[param] = value
- else
- resource.property(param).notice "audit change: newly-recorded recorded value #{current[param]}"
- cache(resource, param, current[param])
- end
+ def create_change_event(property, current_value, do_audit, historical_value)
+ event = property.event
+ event.previous_value = current_value
+ event.desired_value = property.should
+ event.historical_value = historical_value
+
+ if do_audit and historical_value != current_value
+ event.message = "audit change: previously recorded value #{property.is_to_s(historical_value)} has been changed to #{property.is_to_s(current_value)}"
+ event.status = "audit"
+ event.audited = true
end
- audited
+ event
+ end
+
+ def apply_parameter(property, current_value, do_audit, historical_value)
+ event = create_change_event(property, current_value, do_audit, historical_value)
+
+ if event.audited && historical_value
+ brief_audit_message = " (previously recorded value was #{property.is_to_s(historical_value)})"
+ else
+ brief_audit_message = ""
+ end
+
+ if property.noop
+ event.message = "current_value #{property.is_to_s(current_value)}, should be #{property.should_to_s(property.should)} (noop)#{brief_audit_message}"
+ event.status = "noop"
+ else
+ property.sync
+ event.message = [ property.change_to_s(current_value, property.should), brief_audit_message ].join
+ event.status = "success"
+ end
+ event
+ rescue => detail
+ puts detail.backtrace if Puppet[:trace]
+ event.status = "failure"
+
+ event.message = "change from #{property.is_to_s(current_value)} to #{property.should_to_s(property.should)} failed: #{detail}"
+ event
+ ensure
+ event.send_log
end
def evaluate(resource)
start = Time.now
status = Puppet::Resource::Status.new(resource)
- if changes = changes_to_perform(status, resource) and ! changes.empty?
- status.out_of_sync = true
- status.change_count = changes.length
- apply_changes(status, changes)
- if ! resource.noop?
- cache(resource, :synced, Time.now)
- resource.flush if resource.respond_to?(:flush)
- end
+ perform_changes(resource).each do |event|
+ status << event
end
+
+ if status.changed? && ! resource.noop?
+ cache(resource, :synced, Time.now)
+ resource.flush if resource.respond_to?(:flush)
+ end
+
return status
rescue => detail
resource.fail "Could not create resource status: #{detail}" unless status