diff --git a/lib/puppet/resource/status.rb b/lib/puppet/resource/status.rb index 43d3f1644..b138c6520 100644 --- a/lib/puppet/resource/status.rb +++ b/lib/puppet/resource/status.rb @@ -1,67 +1,69 @@ module Puppet class Resource class Status include Puppet::Util::Tagging include Puppet::Util::Logging attr_accessor :resource, :node, :file, :line, :current_values, :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, :out_of_sync_count # Provide a boolean method for each of the states. STATES.each do |attr| define_method("#{attr}?") do !! send(attr) end end def <<(event) add_event(event) self end def add_event(event) @events << event if event.status == 'failure' self.failed = true elsif event.status == 'success' @change_count += 1 @changed = true end if event.status != 'audit' @out_of_sync_count += 1 @out_of_sync = true end end def events @events end def initialize(resource) @source_description = resource.path @resource = resource.to_s @change_count = 0 @out_of_sync_count = 0 + @changed = false + @out_of_sync = false [:file, :line].each do |attr| send(attr.to_s + "=", resource.send(attr)) end tag(*resource.tags) @time = Time.now @events = [] end private def log_source source_description end end end end diff --git a/lib/puppet/transaction/event.rb b/lib/puppet/transaction/event.rb index e3537bb08..b9fa5c38b 100644 --- a/lib/puppet/transaction/event.rb +++ b/lib/puppet/transaction/event.rb @@ -1,61 +1,62 @@ require 'puppet/transaction' require 'puppet/util/tagging' require 'puppet/util/logging' # A simple struct for storing what happens on the system. class Puppet::Transaction::Event include Puppet::Util::Tagging include Puppet::Util::Logging ATTRIBUTES = [:name, :resource, :property, :previous_value, :desired_value, :historical_value, :status, :message, :file, :line, :source_description, :audited] attr_accessor *ATTRIBUTES attr_writer :tags attr_accessor :time attr_reader :default_log_level EVENT_STATUSES = %w{noop success failure audit} def initialize(*args) + @audited = false options = args.last.is_a?(Hash) ? args.pop : ATTRIBUTES.inject({}) { |hash, attr| hash[attr] = args.pop; hash } options.each { |attr, value| send(attr.to_s + "=", value) unless value.nil? } @time = Time.now end def property=(prop) @property = prop.to_s end def resource=(res) if res.respond_to?(:[]) and level = res[:loglevel] @default_log_level = level end @resource = res.to_s end def send_log super(log_level, message) end def status=(value) raise ArgumentError, "Event status can only be #{EVENT_STATUSES.join(', ')}" unless EVENT_STATUSES.include?(value) @status = value end def to_s message end private # If it's a failure, use 'err', else use either the resource's log level (if available) # or 'notice'. def log_level status == "failure" ? :err : (@default_log_level || :notice) end # Used by the Logging module def log_source source_description || property || resource end end diff --git a/spec/unit/resource/status_spec.rb b/spec/unit/resource/status_spec.rb index 22d5fb4f9..3d9a84152 100755 --- a/spec/unit/resource/status_spec.rb +++ b/spec/unit/resource/status_spec.rb @@ -1,138 +1,138 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/resource/status' describe Puppet::Resource::Status do before do @resource = Puppet::Type.type(:file).new :path => "/my/file" @status = Puppet::Resource::Status.new(@resource) end [:node, :file, :line, :current_values, :status, :evaluation_time].each do |attr| it "should support #{attr}" do @status.send(attr.to_s + "=", "foo") @status.send(attr).should == "foo" end end [:skipped, :failed, :restarted, :failed_to_restart, :changed, :out_of_sync, :scheduled].each do |attr| it "should support #{attr}" do @status.send(attr.to_s + "=", "foo") @status.send(attr).should == "foo" end it "should have a boolean method for determining whehter it was #{attr}" do @status.send(attr.to_s + "=", "foo") @status.should send("be_#{attr}") end end it "should accept a resource at initialization" do Puppet::Resource::Status.new(@resource).resource.should_not be_nil end it "should set its source description to the resource's path" do @resource.expects(:path).returns "/my/path" Puppet::Resource::Status.new(@resource).source_description.should == "/my/path" end [:file, :line].each do |attr| it "should copy the resource's #{attr}" do @resource.expects(attr).returns "foo" Puppet::Resource::Status.new(@resource).send(attr).should == "foo" end end it "should copy the resource's tags" do @resource.expects(:tags).returns %w{foo bar} Puppet::Resource::Status.new(@resource).tags.should == %w{foo bar} end it "should always convert the resource to a string" do @resource.expects(:to_s).returns "foo" Puppet::Resource::Status.new(@resource).resource.should == "foo" end it "should support tags" do Puppet::Resource::Status.ancestors.should include(Puppet::Util::Tagging) end it "should create a timestamp at its creation time" do @status.time.should be_instance_of(Time) end describe "when sending logs" do before do Puppet::Util::Log.stubs(:new) end it "should set the tags to the event tags" do Puppet::Util::Log.expects(:new).with { |args| args[:tags] == %w{one two} } @status.stubs(:tags).returns %w{one two} @status.send_log :notice, "my message" end [:file, :line].each do |attr| it "should pass the #{attr}" do Puppet::Util::Log.expects(:new).with { |args| args[attr] == "my val" } @status.send(attr.to_s + "=", "my val") @status.send_log :notice, "my message" end end it "should use the source description as the source" do Puppet::Util::Log.expects(:new).with { |args| args[:source] == "my source" } @status.stubs(:source_description).returns "my source" @status.send_log :notice, "my message" end end it "should support adding events" do event = Puppet::Transaction::Event.new(:name => :foobar) @status.add_event(event) @status.events.should == [event] end it "should use '<<' to add events" do event = Puppet::Transaction::Event.new(:name => :foobar) (@status << event).should equal(@status) @status.events.should == [event] end it "should count the number of successful events and set changed" do 3.times{ @status << Puppet::Transaction::Event.new(:status => 'success') } @status.change_count.should == 3 @status.changed.should == true @status.out_of_sync.should == true end it "should not start with any changes" do @status.change_count.should == 0 - @status.changed.should be_false - @status.out_of_sync.should be_false + @status.changed.should == false + @status.out_of_sync.should == false end it "should not treat failure, audit, or noop events as changed" do ['failure', 'audit', 'noop'].each do |s| @status << Puppet::Transaction::Event.new(:status => s) end @status.change_count.should == 0 - @status.changed.should be_false + @status.changed.should == false end it "should not treat audit events as out of sync" do @status << Puppet::Transaction::Event.new(:status => 'audit') @status.out_of_sync_count.should == 0 - @status.out_of_sync.should be_false + @status.out_of_sync.should == false end ['failure', 'noop', 'success'].each do |event_status| it "should treat #{event_status} events as out of sync" do 3.times do @status << Puppet::Transaction::Event.new(:status => event_status) end @status.out_of_sync_count.should == 3 @status.out_of_sync.should == true end end end diff --git a/spec/unit/transaction/event_spec.rb b/spec/unit/transaction/event_spec.rb index 4a3e6bad5..2776be8d7 100755 --- a/spec/unit/transaction/event_spec.rb +++ b/spec/unit/transaction/event_spec.rb @@ -1,108 +1,114 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/transaction/event' describe Puppet::Transaction::Event do - [:previous_value, :desired_value, :property, :resource, :name, :message, :file, :line, :tags].each do |attr| + [:previous_value, :desired_value, :property, :resource, :name, :message, :file, :line, :tags, :audited].each do |attr| it "should support #{attr}" do event = Puppet::Transaction::Event.new event.send(attr.to_s + "=", "foo") event.send(attr).should == "foo" end end it "should always convert the property to a string" do Puppet::Transaction::Event.new(:property => :foo).property.should == "foo" end it "should always convert the resource to a string" do Puppet::Transaction::Event.new(:resource => :foo).resource.should == "foo" end it "should produce the message when converted to a string" do event = Puppet::Transaction::Event.new event.expects(:message).returns "my message" event.to_s.should == "my message" end it "should support 'status'" do event = Puppet::Transaction::Event.new event.status = "success" event.status.should == "success" end it "should fail if the status is not to 'audit', 'noop', 'success', or 'failure" do event = Puppet::Transaction::Event.new lambda { event.status = "foo" }.should raise_error(ArgumentError) end it "should support tags" do Puppet::Transaction::Event.ancestors.should include(Puppet::Util::Tagging) end it "should create a timestamp at its creation time" do Puppet::Transaction::Event.new.time.should be_instance_of(Time) end + describe "audit property" do + it "should default to false" do + Puppet::Transaction::Event.new.audited.should == false + end + end + describe "when sending logs" do before do Puppet::Util::Log.stubs(:new) end it "should set the level to the resources's log level if the event status is 'success' and a resource is available" do resource = stub 'resource' resource.expects(:[]).with(:loglevel).returns :myloglevel Puppet::Util::Log.expects(:create).with { |args| args[:level] == :myloglevel } Puppet::Transaction::Event.new(:status => "success", :resource => resource).send_log end it "should set the level to 'notice' if the event status is 'success' and no resource is available" do Puppet::Util::Log.expects(:new).with { |args| args[:level] == :notice } Puppet::Transaction::Event.new(:status => "success").send_log end it "should set the level to 'notice' if the event status is 'noop'" do Puppet::Util::Log.expects(:new).with { |args| args[:level] == :notice } Puppet::Transaction::Event.new(:status => "noop").send_log end it "should set the level to 'err' if the event status is 'failure'" do Puppet::Util::Log.expects(:new).with { |args| args[:level] == :err } Puppet::Transaction::Event.new(:status => "failure").send_log end it "should set the 'message' to the event log" do Puppet::Util::Log.expects(:new).with { |args| args[:message] == "my message" } Puppet::Transaction::Event.new(:message => "my message").send_log end it "should set the tags to the event tags" do Puppet::Util::Log.expects(:new).with { |args| args[:tags] == %w{one two} } Puppet::Transaction::Event.new(:tags => %w{one two}).send_log end [:file, :line].each do |attr| it "should pass the #{attr}" do Puppet::Util::Log.expects(:new).with { |args| args[attr] == "my val" } Puppet::Transaction::Event.new(attr => "my val").send_log end end it "should use the source description as the source if one is set" do Puppet::Util::Log.expects(:new).with { |args| args[:source] == "/my/param" } Puppet::Transaction::Event.new(:source_description => "/my/param", :resource => "Foo[bar]", :property => "foo").send_log end it "should use the property as the source if one is available and no source description is set" do Puppet::Util::Log.expects(:new).with { |args| args[:source] == "foo" } Puppet::Transaction::Event.new(:resource => "Foo[bar]", :property => "foo").send_log end it "should use the property as the source if one is available and no property or source description is set" do Puppet::Util::Log.expects(:new).with { |args| args[:source] == "Foo[bar]" } Puppet::Transaction::Event.new(:resource => "Foo[bar]").send_log end end end diff --git a/spec/unit/transaction/resource_harness_spec.rb b/spec/unit/transaction/resource_harness_spec.rb index 387deca61..771c7b4b0 100755 --- a/spec/unit/transaction/resource_harness_spec.rb +++ b/spec/unit/transaction/resource_harness_spec.rb @@ -1,348 +1,348 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet_spec/files' require 'puppet/transaction/resource_harness' describe Puppet::Transaction::ResourceHarness do include PuppetSpec::Files before do @transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new) @resource = Puppet::Type.type(:file).new :path => "/my/file" @harness = Puppet::Transaction::ResourceHarness.new(@transaction) @current_state = Puppet::Resource.new(:file, "/my/file") @resource.stubs(:retrieve).returns @current_state @status = Puppet::Resource::Status.new(@resource) Puppet::Resource::Status.stubs(:new).returns @status end it "should accept a transaction at initialization" do harness = Puppet::Transaction::ResourceHarness.new(@transaction) harness.transaction.should equal(@transaction) end it "should delegate to the transaction for its relationship graph" do @transaction.expects(:relationship_graph).returns "relgraph" Puppet::Transaction::ResourceHarness.new(@transaction).relationship_graph.should == "relgraph" end describe "when evaluating a resource" do it "should create and return a resource status instance for the resource" do @harness.evaluate(@resource).should be_instance_of(Puppet::Resource::Status) end it "should fail if no status can be created" do Puppet::Resource::Status.expects(:new).raises ArgumentError lambda { @harness.evaluate(@resource) }.should raise_error end it "should retrieve the current state of the resource" do @resource.expects(:retrieve).returns @current_state @harness.evaluate(@resource) end it "should mark the resource as failed and return if the current state cannot be retrieved" do @resource.expects(:retrieve).raises ArgumentError @harness.evaluate(@resource).should be_failed end it "should store the resource's evaluation time in the resource status" do @harness.evaluate(@resource).evaluation_time.should be_instance_of(Float) end end describe "when applying changes" do [false, true].each do |noop_mode|; describe (noop_mode ? "in noop mode" : "in normal mode") do [nil, '750'].each do |machine_state|; describe (machine_state ? "with a file initially present" : "with no file initially present") do [nil, '750', '755'].each do |yaml_mode| [nil, :file, :absent].each do |yaml_ensure|; describe "with mode=#{yaml_mode.inspect} and ensure=#{yaml_ensure.inspect} stored in state.yml" do [false, true].each do |auditing_ensure| [false, true].each do |auditing_mode| auditing = [] auditing.push(:mode) if auditing_mode auditing.push(:ensure) if auditing_ensure [nil, :file, :absent].each do |ensure_property| # what we set "ensure" to in the manifest [nil, '750', '755'].each do |mode_property| # what we set "mode" to in the manifest manifest_settings = {} manifest_settings[:audit] = auditing if !auditing.empty? manifest_settings[:ensure] = ensure_property if ensure_property manifest_settings[:mode] = mode_property if mode_property describe "with manifest settings #{manifest_settings.inspect}" do; it "should behave properly" do # Set up preconditions test_file = tmpfile('foo') if machine_state File.open(test_file, 'w', machine_state.to_i(8)).close end Puppet[:noop] = noop_mode params = { :path => test_file, :backup => false } params.merge!(manifest_settings) resource = Puppet::Type.type(:file).new params @harness.cache(resource, :mode, yaml_mode) if yaml_mode @harness.cache(resource, :ensure, yaml_ensure) if yaml_ensure resource.expects(:err).never # make sure no exceptions get swallowed status = @harness.evaluate(resource) # do the thing # check that the state of the machine has been properly updated expected_logs = [] if auditing_mode @harness.cached(resource, :mode).should == (machine_state || :absent) else @harness.cached(resource, :mode).should == yaml_mode end if auditing_ensure @harness.cached(resource, :ensure).should == (machine_state ? :file : :absent) else @harness.cached(resource, :ensure).should == yaml_ensure end if ensure_property == :file file_would_be_there_if_not_noop = true elsif ensure_property == nil file_would_be_there_if_not_noop = machine_state != nil else # ensure_property == :absent file_would_be_there_if_not_noop = false end file_should_be_there = noop_mode ? machine_state != nil : file_would_be_there_if_not_noop File.exists?(test_file).should == file_should_be_there if file_should_be_there if noop_mode expected_file_mode = machine_state else expected_file_mode = mode_property || machine_state end if !expected_file_mode # we didn't specify a mode and the file was created, so mode comes from umode else file_mode = File.stat(test_file).mode & 0777 file_mode.should == expected_file_mode.to_i(8) end end # Test log output for the "mode" parameter previously_recorded_mode_already_logged = false if machine_state && file_would_be_there_if_not_noop && mode_property && machine_state != mode_property if noop_mode what_happened = "current_value #{machine_state}, should be #{mode_property} (noop)" else what_happened = "mode changed '#{machine_state}' to '#{mode_property}'" end if auditing_mode && yaml_mode && yaml_mode != machine_state previously_recorded_mode_already_logged = true expected_logs << "notice: /#{resource}/mode: #{what_happened} (previously recorded value was #{yaml_mode})" else expected_logs << "notice: /#{resource}/mode: #{what_happened}" end end if @harness.cached(resource, :mode) && @harness.cached(resource, :mode) != yaml_mode if yaml_mode unless previously_recorded_mode_already_logged expected_logs << "notice: /#{resource}/mode: audit change: previously recorded value #{yaml_mode} has been changed to #{@harness.cached(resource, :mode)}" end else expected_logs << "notice: /#{resource}/mode: audit change: newly-recorded value #{@harness.cached(resource, :mode)}" end end # Test log output for the "ensure" parameter previously_recorded_ensure_already_logged = false if file_would_be_there_if_not_noop != (machine_state != nil) if noop_mode what_happened = "current_value #{machine_state ? 'file' : 'absent'}, should be #{file_would_be_there_if_not_noop ? 'file' : 'absent'} (noop)" else what_happened = file_would_be_there_if_not_noop ? 'created' : 'removed' end if auditing_ensure && yaml_ensure && yaml_ensure != (machine_state ? :file : :absent) previously_recorded_ensure_already_logged = true expected_logs << "notice: /#{resource}/ensure: #{what_happened} (previously recorded value was #{yaml_ensure})" else expected_logs << "notice: /#{resource}/ensure: #{what_happened}" end end if @harness.cached(resource, :ensure) && @harness.cached(resource, :ensure) != yaml_ensure if yaml_ensure unless previously_recorded_ensure_already_logged expected_logs << "notice: /#{resource}/ensure: audit change: previously recorded value #{yaml_ensure} has been changed to #{@harness.cached(resource, :ensure)}" end else expected_logs << "notice: /#{resource}/ensure: audit change: newly-recorded value #{@harness.cached(resource, :ensure)}" end end # Actually check the logs. @logs.map {|l| "#{l.level}: #{l.source}: #{l.message}"}.should =~ expected_logs # All the log messages should show up as events except the "newly-recorded" ones. expected_event_logs = @logs.reject {|l| l.message =~ /newly-recorded/ } status.events.map {|e| e.message}.should =~ expected_event_logs.map {|l| l.message } # Check change count - this is the number of changes that actually occurred. expected_change_count = 0 if (machine_state != nil) != file_should_be_there expected_change_count = 1 elsif machine_state != nil if expected_file_mode != machine_state expected_change_count = 1 end end status.change_count.should == expected_change_count # Check out of sync count - this is the number # of changes that would have occurred in # non-noop mode. expected_out_of_sync_count = 0 if (machine_state != nil) != file_would_be_there_if_not_noop expected_out_of_sync_count = 1 elsif machine_state != nil if mode_property != nil && mode_property != machine_state expected_out_of_sync_count = 1 end end if !noop_mode expected_out_of_sync_count.should == expected_change_count end status.out_of_sync_count.should == expected_out_of_sync_count # Check legacy summary fields - status.changed.should == (expected_change_count == 0 ? nil : true) - status.out_of_sync.should == (expected_out_of_sync_count == 0 ? nil : true) + status.changed.should == (expected_change_count != 0) + status.out_of_sync.should == (expected_out_of_sync_count != 0) # Check the :synced field on state.yml - synced_should_be_set = !noop_mode && status.changed != nil + synced_should_be_set = !noop_mode && status.changed (@harness.cached(resource, :synced) != nil).should == synced_should_be_set end; end end end end end end; end end end; end end; end it "should not apply changes if allow_changes?() returns false" do test_file = tmpfile('foo') resource = Puppet::Type.type(:file).new :path => test_file, :backup => false, :ensure => :file resource.expects(:err).never # make sure no exceptions get swallowed @harness.expects(:allow_changes?).with(resource).returns false status = @harness.evaluate(resource) File.exists?(test_file).should == false end end describe "when determining whether the resource can be changed" do before do @resource.stubs(:purging?).returns true @resource.stubs(:deleting?).returns true end it "should be true if the resource is not being purged" do @resource.expects(:purging?).returns false @harness.should be_allow_changes(@resource) end it "should be true if the resource is not being deleted" do @resource.expects(:deleting?).returns false @harness.should be_allow_changes(@resource) end it "should be true if the resource has no dependents" do @harness.relationship_graph.expects(:dependents).with(@resource).returns [] @harness.should be_allow_changes(@resource) end it "should be true if all dependents are being deleted" do dep = stub 'dependent', :deleting? => true @harness.relationship_graph.expects(:dependents).with(@resource).returns [dep] @resource.expects(:purging?).returns true @harness.should be_allow_changes(@resource) end it "should be false if the resource's dependents are not being deleted" do dep = stub 'dependent', :deleting? => false, :ref => "myres" @resource.expects(:warning) @harness.relationship_graph.expects(:dependents).with(@resource).returns [dep] @harness.should_not be_allow_changes(@resource) end end describe "when finding the schedule" do before do @catalog = Puppet::Resource::Catalog.new @resource.catalog = @catalog end it "should warn and return nil if the resource has no catalog" do @resource.catalog = nil @resource.expects(:warning) @harness.schedule(@resource).should be_nil end it "should return nil if the resource specifies no schedule" do @harness.schedule(@resource).should be_nil end it "should fail if the named schedule cannot be found" do @resource[:schedule] = "whatever" @resource.expects(:fail) @harness.schedule(@resource) end it "should return the named schedule if it exists" do sched = Puppet::Type.type(:schedule).new(:name => "sched") @catalog.add_resource(sched) @resource[:schedule] = "sched" @harness.schedule(@resource).to_s.should == sched.to_s end end describe "when determining if a resource is scheduled" do before do @catalog = Puppet::Resource::Catalog.new @resource.catalog = @catalog @status = Puppet::Resource::Status.new(@resource) end it "should return true if 'ignoreschedules' is set" do Puppet[:ignoreschedules] = true @resource[:schedule] = "meh" @harness.should be_scheduled(@status, @resource) end it "should return true if the resource has no schedule set" do @harness.should be_scheduled(@status, @resource) end it "should return the result of matching the schedule with the cached 'checked' time if a schedule is set" do t = Time.now @harness.expects(:cached).with(@resource, :checked).returns(t) sched = Puppet::Type.type(:schedule).new(:name => "sched") @catalog.add_resource(sched) @resource[:schedule] = "sched" sched.expects(:match?).with(t.to_i).returns "feh" @harness.scheduled?(@status, @resource).should == "feh" end end it "should be able to cache data in the Storage module" do data = {} Puppet::Util::Storage.expects(:cache).with(@resource).returns data @harness.cache(@resource, :foo, "something") data[:foo].should == "something" end it "should be able to retrieve data from the cache" do data = {:foo => "other"} Puppet::Util::Storage.expects(:cache).with(@resource).returns data @harness.cached(@resource, :foo).should == "other" end end