diff --git a/lib/puppet/application/inspect.rb b/lib/puppet/application/inspect.rb
index caa32a7c2..2f068a271 100644
--- a/lib/puppet/application/inspect.rb
+++ b/lib/puppet/application/inspect.rb
@@ -1,82 +1,82 @@
require 'puppet/application'
class Puppet::Application::Inspect < Puppet::Application
should_parse_config
run_mode :agent
option("--debug","-d")
option("--verbose","-v")
option("--logdest LOGDEST", "-l") do |arg|
begin
Puppet::Util::Log.newdestination(arg)
options[:logset] = true
rescue => detail
$stderr.puts detail.to_s
end
end
def setup
exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs?
raise "Inspect requires reporting to be enabled. Set report=true in puppet.conf to enable reporting." unless Puppet[:report]
@report = Puppet::Transaction::Report.new("inspect")
Puppet::Util::Log.newdestination(@report)
Puppet::Util::Log.newdestination(:console) unless options[:logset]
trap(:INT) do
$stderr.puts "Exiting"
exit(1)
end
if options[:debug]
Puppet::Util::Log.level = :debug
elsif options[:verbose]
Puppet::Util::Log.level = :info
end
Puppet::Transaction::Report.terminus_class = :rest
Puppet::Resource::Catalog.terminus_class = :yaml
end
def run_command
retrieval_starttime = Time.now
unless catalog = Puppet::Resource::Catalog.find(Puppet[:certname])
raise "Could not find catalog for #{Puppet[:certname]}"
end
@report.configuration_version = catalog.version
- retrieval_time = Time.now - retrieval_starttime
- @report.add_times("config_retrieval", retrieval_time)
-
- starttime = Time.now
+ inspect_starttime = Time.now
+ @report.add_times("config_retrieval", inspect_starttime - retrieval_starttime)
catalog.to_ral.resources.each do |ral_resource|
audited_attributes = ral_resource[:audit]
next unless audited_attributes
audited_resource = ral_resource.to_resource
status = Puppet::Resource::Status.new(ral_resource)
audited_attributes.each do |name|
event = ral_resource.event(:previous_value => audited_resource[name], :property => name, :status => "audit", :message => "inspected value is #{audited_resource[name].inspect}")
status.add_event(event)
end
@report.add_resource_status(status)
end
- @report.add_metric(:time, {"config_retrieval" => retrieval_time, "inspect" => Time.now - starttime})
+ finishtime = Time.now
+ @report.add_times("inspect", finishtime - inspect_starttime)
+ @report.finalize_report
begin
@report.save
rescue => detail
puts detail.backtrace if Puppet[:trace]
Puppet.err "Could not send report: #{detail}"
end
end
end
diff --git a/lib/puppet/configurer.rb b/lib/puppet/configurer.rb
index 070176554..d3c902576 100644
--- a/lib/puppet/configurer.rb
+++ b/lib/puppet/configurer.rb
@@ -1,237 +1,237 @@
# The client for interacting with the puppetmaster config server.
require 'sync'
require 'timeout'
require 'puppet/network/http_pool'
require 'puppet/util'
class Puppet::Configurer
class CommandHookError < RuntimeError; end
require 'puppet/configurer/fact_handler'
require 'puppet/configurer/plugin_handler'
include Puppet::Configurer::FactHandler
include Puppet::Configurer::PluginHandler
# For benchmarking
include Puppet::Util
attr_reader :compile_time
# Provide more helpful strings to the logging that the Agent does
def self.to_s
"Puppet configuration client"
end
class << self
# Puppetd should only have one instance running, and we need a way
# to retrieve it.
attr_accessor :instance
include Puppet::Util
end
# How to lock instances of this class.
def self.lockfile_path
Puppet[:puppetdlockfile]
end
def clear
@catalog.clear(true) if @catalog
@catalog = nil
end
def execute_postrun_command
execute_from_setting(:postrun_command)
end
def execute_prerun_command
execute_from_setting(:prerun_command)
end
# Initialize and load storage
def dostorage
Puppet::Util::Storage.load
@compile_time ||= Puppet::Util::Storage.cache(:configuration)[:compile_time]
rescue => detail
puts detail.backtrace if Puppet[:trace]
Puppet.err "Corrupt state file #{Puppet[:statefile]}: #{detail}"
begin
::File.unlink(Puppet[:statefile])
retry
rescue => detail
raise Puppet::Error.new("Cannot remove #{Puppet[:statefile]}: #{detail}")
end
end
# Just so we can specify that we are "the" instance.
def initialize
Puppet.settings.use(:main, :ssl, :agent)
self.class.instance = self
@running = false
@splayed = false
end
# Prepare for catalog retrieval. Downloads everything necessary, etc.
def prepare(options)
dostorage
download_plugins unless options[:skip_plugin_download]
download_fact_plugins unless options[:skip_plugin_download]
execute_prerun_command
end
# Get the remote catalog, yo. Returns nil if no catalog can be found.
def retrieve_catalog
if Puppet::Resource::Catalog.indirection.terminus_class == :rest
# This is a bit complicated. We need the serialized and escaped facts,
# and we need to know which format they're encoded in. Thus, we
# get a hash with both of these pieces of information.
fact_options = facts_for_uploading
else
fact_options = {}
end
# First try it with no cache, then with the cache.
unless (Puppet[:use_cached_catalog] and result = retrieve_catalog_from_cache(fact_options)) or result = retrieve_new_catalog(fact_options)
if ! Puppet[:usecacheonfailure]
Puppet.warning "Not using cache on failed catalog"
return nil
end
result = retrieve_catalog_from_cache(fact_options)
end
return nil unless result
convert_catalog(result, @duration)
end
# Convert a plain resource catalog into our full host catalog.
def convert_catalog(result, duration)
catalog = result.to_ral
catalog.finalize
catalog.retrieval_duration = duration
catalog.write_class_file
catalog
end
# The code that actually runs the catalog.
# This just passes any options on to the catalog,
# which accepts :tags and :ignoreschedules.
def run(options = {})
begin
prepare(options)
rescue SystemExit,NoMemoryError
raise
rescue Exception => detail
puts detail.backtrace if Puppet[:trace]
Puppet.err "Failed to prepare catalog: #{detail}"
end
options[:report] ||= Puppet::Transaction::Report.new("apply")
report = options[:report]
Puppet::Util::Log.newdestination(report)
if catalog = options[:catalog]
options.delete(:catalog)
elsif ! catalog = retrieve_catalog
Puppet.err "Could not retrieve catalog; skipping run"
return
end
report.configuration_version = catalog.version
transaction = nil
begin
benchmark(:notice, "Finished catalog run") do
transaction = catalog.apply(options)
end
report
rescue => detail
puts detail.backtrace if Puppet[:trace]
Puppet.err "Failed to apply catalog: #{detail}"
return
end
ensure
# Make sure we forget the retained module_directories of any autoload
# we might have used.
Thread.current[:env_module_directories] = nil
# Now close all of our existing http connections, since there's no
# reason to leave them lying open.
Puppet::Network::HttpPool.clear_http_instances
execute_postrun_command
Puppet::Util::Log.close(report)
send_report(report, transaction)
end
- def send_report(report, trans = nil)
- trans.generate_report if trans
+ def send_report(report, trans)
+ report.finalize_report if trans
puts report.summary if Puppet[:summarize]
report.save if Puppet[:report]
rescue => detail
puts detail.backtrace if Puppet[:trace]
Puppet.err "Could not send report: #{detail}"
end
private
def self.timeout
timeout = Puppet[:configtimeout]
case timeout
when String
if timeout =~ /^\d+$/
timeout = Integer(timeout)
else
raise ArgumentError, "Configuration timeout must be an integer"
end
when Integer # nothing
else
raise ArgumentError, "Configuration timeout must be an integer"
end
timeout
end
def execute_from_setting(setting)
return if (command = Puppet[setting]) == ""
begin
Puppet::Util.execute([command])
rescue => detail
raise CommandHookError, "Could not run command from #{setting}: #{detail}"
end
end
def retrieve_catalog_from_cache(fact_options)
result = nil
@duration = thinmark do
result = Puppet::Resource::Catalog.find(Puppet[:certname], fact_options.merge(:ignore_terminus => true))
end
Puppet.notice "Using cached catalog"
result
rescue => detail
puts detail.backtrace if Puppet[:trace]
Puppet.err "Could not retrieve catalog from cache: #{detail}"
return nil
end
def retrieve_new_catalog(fact_options)
result = nil
@duration = thinmark do
result = Puppet::Resource::Catalog.find(Puppet[:certname], fact_options.merge(:ignore_cache => true))
end
result
rescue SystemExit,NoMemoryError
raise
rescue Exception => detail
puts detail.backtrace if Puppet[:trace]
Puppet.err "Could not retrieve catalog from remote server: #{detail}"
return nil
end
end
diff --git a/lib/puppet/resource/status.rb b/lib/puppet/resource/status.rb
index f34edc469..ee83004bb 100644
--- a/lib/puppet/resource/status.rb
+++ b/lib/puppet/resource/status.rb
@@ -1,67 +1,77 @@
module Puppet
class Resource
class Status
include Puppet::Util::Tagging
include Puppet::Util::Logging
- attr_accessor :resource, :node, :version, :file, :line, :current_values, :skipped_reason, :status, :evaluation_time
+ 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
+ attr_reader :change_count, :out_of_sync_count, :resource_type, :title
+
+ YAML_ATTRIBUTES = %w{@resource @file @line @evaluation_time @change_count @out_of_sync_count @tags @time @events @out_of_sync @changed @resource_type @title}
# 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, :version].each do |attr|
+ [:file, :line].each do |attr|
send(attr.to_s + "=", resource.send(attr))
end
tag(*resource.tags)
@time = Time.now
@events = []
+ @resource_type = resource.type.to_s.capitalize
+ @title = resource.title
+ end
+
+ def to_yaml_properties
+ (YAML_ATTRIBUTES & instance_variables).sort
end
private
def log_source
source_description
end
end
end
end
diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb
index 4db971477..aa650eea1 100644
--- a/lib/puppet/transaction.rb
+++ b/lib/puppet/transaction.rb
@@ -1,360 +1,334 @@
# the class that actually walks our resource/property tree, collects the changes,
# and performs them
require 'puppet'
require 'puppet/util/tagging'
require 'puppet/application'
class Puppet::Transaction
require 'puppet/transaction/event'
require 'puppet/transaction/event_manager'
require 'puppet/transaction/resource_harness'
require 'puppet/resource/status'
attr_accessor :component, :catalog, :ignoreschedules
attr_accessor :sorted_resources, :configurator
# The report, once generated.
attr_accessor :report
# Routes and stores any events and subscriptions.
attr_reader :event_manager
# Handles most of the actual interacting with resources
attr_reader :resource_harness
include Puppet::Util
include Puppet::Util::Tagging
# Wraps application run state check to flag need to interrupt processing
def stop_processing?
Puppet::Application.stop_requested?
end
# Add some additional times for reporting
def add_times(hash)
hash.each do |name, num|
report.add_times(name, num)
end
end
# Are there any failed resources in this transaction?
def any_failed?
report.resource_statuses.values.detect { |status| status.failed? }
end
# Apply all changes for a resource
def apply(resource, ancestor = nil)
status = resource_harness.evaluate(resource)
add_resource_status(status)
event_manager.queue_events(ancestor || resource, status.events)
rescue => detail
resource.err "Could not evaluate: #{detail}"
end
# Find all of the changed resources.
def changed?
report.resource_statuses.values.find_all { |status| status.changed }.collect { |status| catalog.resource(status.resource) }
end
# Copy an important relationships from the parent to the newly-generated
# child resource.
def make_parent_child_relationship(resource, children)
depthfirst = resource.depthfirst?
children.each do |gen_child|
if depthfirst
edge = [gen_child, resource]
else
edge = [resource, gen_child]
end
relationship_graph.add_vertex(gen_child)
unless relationship_graph.edge?(edge[1], edge[0])
relationship_graph.add_edge(*edge)
else
resource.debug "Skipping automatic relationship to #{gen_child}"
end
end
end
# See if the resource generates new resources at evaluation time.
def eval_generate(resource)
generate_additional_resources(resource, :eval_generate)
end
# Evaluate a single resource.
def eval_resource(resource, ancestor = nil)
if skip?(resource)
resource_status(resource).skipped = true
else
eval_children_and_apply_resource(resource, ancestor)
end
# Check to see if there are any events queued for this resource
event_manager.process_events(resource)
end
def eval_children_and_apply_resource(resource, ancestor = nil)
resource_status(resource).scheduled = true
# We need to generate first regardless, because the recursive
# actions sometimes change how the top resource is applied.
children = eval_generate(resource)
if ! children.empty? and resource.depthfirst?
children.each do |child|
# The child will never be skipped when the parent isn't
eval_resource(child, ancestor || resource)
end
end
# Perform the actual changes
apply(resource, ancestor)
if ! children.empty? and ! resource.depthfirst?
children.each do |child|
eval_resource(child, ancestor || resource)
end
end
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.
def evaluate
# Start logging.
Puppet::Util::Log.newdestination(@report)
prepare
Puppet.info "Applying configuration version '#{catalog.version}'" if catalog.version
begin
@sorted_resources.each do |resource|
next if stop_processing?
if resource.is_a?(Puppet::Type::Component)
Puppet.warning "Somehow left a component in the relationship graph"
next
end
ret = nil
seconds = thinmark do
ret = eval_resource(resource)
end
resource.info "Evaluated in %0.2f seconds" % seconds if Puppet[:evaltrace] and @catalog.host_config?
ret
end
ensure
# And then close the transaction log.
Puppet::Util::Log.close(@report)
end
Puppet.debug "Finishing transaction #{object_id}"
end
def events
event_manager.events
end
def failed?(resource)
s = resource_status(resource) and s.failed?
end
# Does this resource have any failed dependencies?
def failed_dependencies?(resource)
# First make sure there are no failed dependencies. To do this,
# we check for failures in any of the vertexes above us. It's not
# enough to check the immediate dependencies, which is why we use
# a tree from the reversed graph.
found_failed = false
relationship_graph.dependencies(resource).each do |dep|
next unless failed?(dep)
resource.notice "Dependency #{dep} has failures: #{resource_status(dep).failed}"
found_failed = true
end
found_failed
end
# A general method for recursively generating new resources from a
# resource.
def generate_additional_resources(resource, method)
return [] unless resource.respond_to?(method)
begin
made = resource.send(method)
rescue => detail
puts detail.backtrace if Puppet[:trace]
resource.err "Failed to generate additional resources using '#{method}': #{detail}"
end
return [] unless made
made = [made] unless made.is_a?(Array)
made.uniq.find_all do |res|
begin
res.tag(*resource.tags)
@catalog.add_resource(res) do |r|
r.finish
make_parent_child_relationship(resource, [r])
# Call 'generate' recursively
generate_additional_resources(r, method)
end
true
rescue Puppet::Resource::Catalog::DuplicateResourceError
res.info "Duplicate generated resource; skipping"
false
end
end
end
# Collect any dynamically generated resources. This method is called
# before the transaction starts.
def generate
list = @catalog.vertices
newlist = []
while ! list.empty?
list.each do |resource|
newlist += generate_additional_resources(resource, :generate)
end
list = newlist
newlist = []
end
end
- # Generate a transaction report.
- def generate_report
- @report.calculate_metrics
- @report
- end
-
# Should we ignore tags?
def ignore_tags?
! (@catalog.host_config? or Puppet[:name] == "puppet")
end
# this should only be called by a Puppet::Type::Component resource now
# and it should only receive an array
def initialize(catalog)
@catalog = catalog
@report = Report.new("apply", catalog.version)
@event_manager = Puppet::Transaction::EventManager.new(self)
@resource_harness = Puppet::Transaction::ResourceHarness.new(self)
end
# Prefetch any providers that support it. We don't support prefetching
# types, just providers.
def prefetch
prefetchers = {}
@catalog.vertices.each do |resource|
if provider = resource.provider and provider.class.respond_to?(:prefetch)
prefetchers[provider.class] ||= {}
prefetchers[provider.class][resource.name] = resource
end
end
# Now call prefetch, passing in the resources so that the provider instances can be replaced.
prefetchers.each do |provider, resources|
Puppet.debug "Prefetching #{provider.name} resources for #{provider.resource_type.name}"
begin
provider.prefetch(resources)
rescue => detail
puts detail.backtrace if Puppet[:trace]
Puppet.err "Could not prefetch #{provider.resource_type.name} provider '#{provider.name}': #{detail}"
end
end
end
# Prepare to evaluate the resources in a transaction.
def prepare
# Now add any dynamically generated resources
generate
# Then prefetch. It's important that we generate and then prefetch,
# so that any generated resources also get prefetched.
prefetch
# This will throw an error if there are cycles in the graph.
@sorted_resources = relationship_graph.topsort
end
def relationship_graph
catalog.relationship_graph
end
- # Send off the transaction report.
- def send_report
- begin
- report = generate_report
- rescue => detail
- Puppet.err "Could not generate report: #{detail}"
- return
- end
-
- puts report.summary if Puppet[:summarize]
-
- if Puppet[:report]
- begin
- report.save
- rescue => detail
- Puppet.err "Reporting failed: #{detail}"
- end
- end
- end
-
def add_resource_status(status)
report.add_resource_status status
end
def resource_status(resource)
report.resource_statuses[resource.to_s] || add_resource_status(Puppet::Resource::Status.new(resource))
end
# Is the resource currently scheduled?
def scheduled?(resource)
self.ignoreschedules or resource_harness.scheduled?(resource_status(resource), resource)
end
# Should this resource be skipped?
def skip?(resource)
if missing_tags?(resource)
resource.debug "Not tagged with #{tags.join(", ")}"
elsif ! scheduled?(resource)
resource.debug "Not scheduled"
elsif failed_dependencies?(resource)
resource.warning "Skipping because of failed dependencies"
elsif resource.virtual?
resource.debug "Skipping because virtual"
else
return false
end
true
end
# The tags we should be checking.
def tags
self.tags = Puppet[:tags] unless defined?(@tags)
super
end
def handle_qualified_tags( qualified )
# The default behavior of Puppet::Util::Tagging is
# to split qualified tags into parts. That would cause
# qualified tags to match too broadly here.
return
end
# Is this resource tagged appropriately?
def missing_tags?(resource)
return false if ignore_tags?
return false if tags.empty?
not resource.tagged?(*tags)
end
end
require 'puppet/transaction/report'
diff --git a/lib/puppet/transaction/event.rb b/lib/puppet/transaction/event.rb
index da5b14727..64980f1d9 100644
--- a/lib/puppet/transaction/event.rb
+++ b/lib/puppet/transaction/event.rb
@@ -1,61 +1,67 @@
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, :node, :version, :file, :line, :source_description, :audited]
+ ATTRIBUTES = [:name, :resource, :property, :previous_value, :desired_value, :historical_value, :status, :message, :file, :line, :source_description, :audited]
+ YAML_ATTRIBUTES = %w{@audited @property @previous_value @desired_value @historical_value @message @name @status @time}
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
+ def to_yaml_properties
+ (YAML_ATTRIBUTES & instance_variables).sort
+ 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/lib/puppet/transaction/report.rb b/lib/puppet/transaction/report.rb
index 8a928454f..8e04759ad 100644
--- a/lib/puppet/transaction/report.rb
+++ b/lib/puppet/transaction/report.rb
@@ -1,154 +1,169 @@
require 'puppet'
require 'puppet/indirector'
# A class for reporting what happens on each client. Reports consist of
# two types of data: Logs and Metrics. Logs are the output that each
# change produces, and Metrics are all of the numerical data involved
# in the transaction.
class Puppet::Transaction::Report
extend Puppet::Indirector
indirects :report, :terminus_class => :processor
attr_accessor :configuration_version
- attr_reader :resource_statuses, :logs, :metrics, :host, :time, :kind
+ attr_reader :resource_statuses, :logs, :metrics, :host, :time, :kind, :status
# This is necessary since Marshall doesn't know how to
# dump hash with default proc (see below @records)
def self.default_format
:yaml
end
def <<(msg)
@logs << msg
self
end
def add_times(name, value)
@external_times[name] = value
end
def add_metric(name, hash)
metric = Puppet::Util::Metric.new(name)
hash.each do |name, value|
metric.newvalue(name, value)
end
@metrics[metric.name] = metric
metric
end
def add_resource_status(status)
@resource_statuses[status.resource] = status
end
- def calculate_metrics
- calculate_resource_metrics
- calculate_time_metrics
- calculate_change_metrics
- calculate_event_metrics
+ def compute_status(resource_metrics, change_metric)
+ if (resource_metrics["failed"] || 0) > 0
+ 'failed'
+ elsif change_metric > 0
+ 'changed'
+ else
+ 'unchanged'
+ end
+ end
+
+ def finalize_report
+ resource_metrics = add_metric(:resources, calculate_resource_metrics)
+ add_metric(:time, calculate_time_metrics)
+ change_metric = calculate_change_metric
+ add_metric(:changes, {"total" => change_metric})
+ add_metric(:events, calculate_event_metrics)
+ @status = compute_status(resource_metrics, change_metric)
end
def initialize(kind, configuration_version=nil)
@metrics = {}
@logs = []
@resource_statuses = {}
@external_times ||= {}
@host = Puppet[:certname]
@time = Time.now
@kind = kind
@report_format = 2
@puppet_version = Puppet.version
@configuration_version = configuration_version
+ @status = 'failed' # assume failed until the report is finalized
end
def name
host
end
# Provide a summary of this report.
def summary
ret = ""
@metrics.sort { |a,b| a[1].label <=> b[1].label }.each do |name, metric|
ret += "#{metric.label}:\n"
metric.values.sort { |a,b|
# sort by label
if a[0] == :total
1
elsif b[0] == :total
-1
else
a[1] <=> b[1]
end
}.each do |name, label, value|
next if value == 0
value = "%0.2f" % value if value.is_a?(Float)
ret += " %15s %s\n" % [label + ":", value]
end
end
ret
end
# Based on the contents of this report's metrics, compute a single number
# that represents the report. The resulting number is a bitmask where
# individual bits represent the presence of different metrics.
def exit_status
status = 0
- status |= 2 if @metrics["changes"][:total] > 0
- status |= 4 if @metrics["resources"][:failed] > 0
+ status |= 2 if @metrics["changes"]["total"] > 0
+ status |= 4 if @metrics["resources"]["failed"] > 0
status
end
- private
+ def to_yaml_properties
+ (instance_variables - ["@external_times"]).sort
+ end
- def calculate_change_metrics
- metrics = Hash.new(0)
- resource_statuses.each do |name, status|
- metrics[:total] += status.change_count if status.change_count
- end
+ private
- add_metric(:changes, metrics)
+ def calculate_change_metric
+ resource_statuses.map { |name, status| status.change_count || 0 }.inject(0) { |a,b| a+b }
end
def calculate_event_metrics
metrics = Hash.new(0)
+ metrics["total"] = 0
resource_statuses.each do |name, status|
- metrics[:total] += status.events.length
+ metrics["total"] += status.events.length
status.events.each do |event|
metrics[event.status] += 1
end
end
- add_metric(:events, metrics)
+ metrics
end
def calculate_resource_metrics
metrics = Hash.new(0)
- metrics[:total] = resource_statuses.length
+ metrics["total"] = resource_statuses.length
resource_statuses.each do |name, status|
Puppet::Resource::Status::STATES.each do |state|
- metrics[state] += 1 if status.send(state)
+ metrics[state.to_s] += 1 if status.send(state)
end
end
- add_metric(:resources, metrics)
+ metrics
end
def calculate_time_metrics
metrics = Hash.new(0)
resource_statuses.each do |name, status|
type = Puppet::Resource.new(name).type
metrics[type.to_s.downcase] += status.evaluation_time if status.evaluation_time
end
@external_times.each do |name, value|
metrics[name.to_s.downcase] = value
end
- add_metric(:time, metrics)
+ metrics["total"] = metrics.values.inject(0) { |a,b| a+b }
+
+ metrics
end
end
diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb
index 1b6e7dcd7..ea3944b4e 100644
--- a/lib/puppet/type.rb
+++ b/lib/puppet/type.rb
@@ -1,1897 +1,1897 @@
require 'puppet'
require 'puppet/util/log'
require 'puppet/util/metric'
require 'puppet/property'
require 'puppet/parameter'
require 'puppet/util'
require 'puppet/util/autoload'
require 'puppet/metatype/manager'
require 'puppet/util/errors'
require 'puppet/util/log_paths'
require 'puppet/util/logging'
require 'puppet/util/cacher'
require 'puppet/file_collection/lookup'
require 'puppet/util/tagging'
# see the bottom of the file for the rest of the inclusions
module Puppet
class Type
include Puppet::Util
include Puppet::Util::Errors
include Puppet::Util::LogPaths
include Puppet::Util::Logging
include Puppet::Util::Cacher
include Puppet::FileCollection::Lookup
include Puppet::Util::Tagging
###############################
# Code related to resource type attributes.
class << self
include Puppet::Util::ClassGen
include Puppet::Util::Warnings
attr_reader :properties
end
def self.states
warnonce "The states method is deprecated; use properties"
properties
end
# All parameters, in the appropriate order. The key_attributes come first, then
# the provider, then the properties, and finally the params and metaparams
# in the order they were specified in the files.
def self.allattrs
key_attributes | (parameters & [:provider]) | properties.collect { |property| property.name } | parameters | metaparams
end
# Retrieve an attribute alias, if there is one.
def self.attr_alias(param)
@attr_aliases[symbolize(param)]
end
# Create an alias to an existing attribute. This will cause the aliased
# attribute to be valid when setting and retrieving values on the instance.
def self.set_attr_alias(hash)
hash.each do |new, old|
@attr_aliases[symbolize(new)] = symbolize(old)
end
end
# Find the class associated with any given attribute.
def self.attrclass(name)
@attrclasses ||= {}
# We cache the value, since this method gets called such a huge number
# of times (as in, hundreds of thousands in a given run).
unless @attrclasses.include?(name)
@attrclasses[name] = case self.attrtype(name)
when :property; @validproperties[name]
when :meta; @@metaparamhash[name]
when :param; @paramhash[name]
end
end
@attrclasses[name]
end
# What type of parameter are we dealing with? Cache the results, because
# this method gets called so many times.
def self.attrtype(attr)
@attrtypes ||= {}
unless @attrtypes.include?(attr)
@attrtypes[attr] = case
when @validproperties.include?(attr); :property
when @paramhash.include?(attr); :param
when @@metaparamhash.include?(attr); :meta
end
end
@attrtypes[attr]
end
def self.eachmetaparam
@@metaparams.each { |p| yield p.name }
end
# Create the 'ensure' class. This is a separate method so other types
# can easily call it and create their own 'ensure' values.
def self.ensurable(&block)
if block_given?
self.newproperty(:ensure, :parent => Puppet::Property::Ensure, &block)
else
self.newproperty(:ensure, :parent => Puppet::Property::Ensure) do
self.defaultvalues
end
end
end
# Should we add the 'ensure' property to this class?
def self.ensurable?
# If the class has all three of these methods defined, then it's
# ensurable.
ens = [:exists?, :create, :destroy].inject { |set, method|
set &&= self.public_method_defined?(method)
}
ens
end
# Deal with any options passed into parameters.
def self.handle_param_options(name, options)
# If it's a boolean parameter, create a method to test the value easily
if options[:boolean]
define_method(name.to_s + "?") do
val = self[name]
if val == :true or val == true
return true
end
end
end
end
# Is the parameter in question a meta-parameter?
def self.metaparam?(param)
@@metaparamhash.include?(symbolize(param))
end
# Find the metaparameter class associated with a given metaparameter name.
def self.metaparamclass(name)
@@metaparamhash[symbolize(name)]
end
def self.metaparams
@@metaparams.collect { |param| param.name }
end
def self.metaparamdoc(metaparam)
@@metaparamhash[metaparam].doc
end
# Create a new metaparam. Requires a block and a name, stores it in the
# @parameters array, and does some basic checking on it.
def self.newmetaparam(name, options = {}, &block)
@@metaparams ||= []
@@metaparamhash ||= {}
name = symbolize(name)
param = genclass(
name,
:parent => options[:parent] || Puppet::Parameter,
:prefix => "MetaParam",
:hash => @@metaparamhash,
:array => @@metaparams,
:attributes => options[:attributes],
&block
)
# Grr.
param.required_features = options[:required_features] if options[:required_features]
handle_param_options(name, options)
param.metaparam = true
param
end
def self.key_attribute_parameters
@key_attribute_parameters ||= (
params = @parameters.find_all { |param|
param.isnamevar? or param.name == :name
}
)
end
def self.key_attributes
key_attribute_parameters.collect { |p| p.name }
end
def self.title_patterns
case key_attributes.length
when 0; []
when 1;
identity = lambda {|x| x}
[ [ /(.*)/m, [ [key_attributes.first, identity ] ] ] ]
else
raise Puppet::DevError,"you must specify title patterns when there are two or more key attributes"
end
end
def uniqueness_key
to_resource.uniqueness_key
end
# Create a new parameter. Requires a block and a name, stores it in the
# @parameters array, and does some basic checking on it.
def self.newparam(name, options = {}, &block)
options[:attributes] ||= {}
param = genclass(
name,
:parent => options[:parent] || Puppet::Parameter,
:attributes => options[:attributes],
:block => block,
:prefix => "Parameter",
:array => @parameters,
:hash => @paramhash
)
handle_param_options(name, options)
# Grr.
param.required_features = options[:required_features] if options[:required_features]
param.isnamevar if options[:namevar]
param
end
def self.newstate(name, options = {}, &block)
Puppet.warning "newstate() has been deprecrated; use newproperty(#{name})"
newproperty(name, options, &block)
end
# Create a new property. The first parameter must be the name of the property;
# this is how users will refer to the property when creating new instances.
# The second parameter is a hash of options; the options are:
# * :parent: The parent class for the property. Defaults to Puppet::Property.
# * :retrieve: The method to call on the provider or @parent object (if
# the provider is not set) to retrieve the current value.
def self.newproperty(name, options = {}, &block)
name = symbolize(name)
# This is here for types that might still have the old method of defining
# a parent class.
unless options.is_a? Hash
raise Puppet::DevError,
"Options must be a hash, not #{options.inspect}"
end
raise Puppet::DevError, "Class #{self.name} already has a property named #{name}" if @validproperties.include?(name)
if parent = options[:parent]
options.delete(:parent)
else
parent = Puppet::Property
end
# We have to create our own, new block here because we want to define
# an initial :retrieve method, if told to, and then eval the passed
# block if available.
prop = genclass(name, :parent => parent, :hash => @validproperties, :attributes => options) do
# If they've passed a retrieve method, then override the retrieve
# method on the class.
if options[:retrieve]
define_method(:retrieve) do
provider.send(options[:retrieve])
end
end
class_eval(&block) if block
end
# If it's the 'ensure' property, always put it first.
if name == :ensure
@properties.unshift prop
else
@properties << prop
end
prop
end
def self.paramdoc(param)
@paramhash[param].doc
end
# Return the parameter names
def self.parameters
return [] unless defined?(@parameters)
@parameters.collect { |klass| klass.name }
end
# Find the parameter class associated with a given parameter name.
def self.paramclass(name)
@paramhash[name]
end
# Return the property class associated with a name
def self.propertybyname(name)
@validproperties[name]
end
def self.validattr?(name)
name = symbolize(name)
return true if name == :name
@validattrs ||= {}
unless @validattrs.include?(name)
@validattrs[name] = !!(self.validproperty?(name) or self.validparameter?(name) or self.metaparam?(name))
end
@validattrs[name]
end
# does the name reflect a valid property?
def self.validproperty?(name)
name = symbolize(name)
@validproperties.include?(name) && @validproperties[name]
end
# Return the list of validproperties
def self.validproperties
return {} unless defined?(@parameters)
@validproperties.keys
end
# does the name reflect a valid parameter?
def self.validparameter?(name)
raise Puppet::DevError, "Class #{self} has not defined parameters" unless defined?(@parameters)
!!(@paramhash.include?(name) or @@metaparamhash.include?(name))
end
# This is a forward-compatibility method - it's the validity interface we'll use in Puppet::Resource.
def self.valid_parameter?(name)
validattr?(name)
end
# Return either the attribute alias or the attribute.
def attr_alias(name)
name = symbolize(name)
if synonym = self.class.attr_alias(name)
return synonym
else
return name
end
end
# Are we deleting this resource?
def deleting?
obj = @parameters[:ensure] and obj.should == :absent
end
# Create a new property if it is valid but doesn't exist
# Returns: true if a new parameter was added, false otherwise
def add_property_parameter(prop_name)
if self.class.validproperty?(prop_name) && !@parameters[prop_name]
self.newattr(prop_name)
return true
end
false
end
#
# The name_var is the key_attribute in the case that there is only one.
#
def name_var
key_attributes = self.class.key_attributes
(key_attributes.length == 1) && key_attributes.first
end
# abstract accessing parameters and properties, and normalize
# access to always be symbols, not strings
# This returns a value, not an object. It returns the 'is'
# value, but you can also specifically return 'is' and 'should'
# values using 'object.is(:property)' or 'object.should(:property)'.
def [](name)
name = attr_alias(name)
fail("Invalid parameter #{name}(#{name.inspect})") unless self.class.validattr?(name)
if name == :name
name = name_var
end
if obj = @parameters[name]
# Note that if this is a property, then the value is the "should" value,
# not the current value.
obj.value
else
return nil
end
end
# Abstract setting parameters and properties, and normalize
# access to always be symbols, not strings. This sets the 'should'
# value on properties, and otherwise just sets the appropriate parameter.
def []=(name,value)
name = attr_alias(name)
fail("Invalid parameter #{name}") unless self.class.validattr?(name)
if name == :name
name = name_var
end
raise Puppet::Error.new("Got nil value for #{name}") if value.nil?
property = self.newattr(name)
if property
begin
# make sure the parameter doesn't have any errors
property.value = value
rescue => detail
error = Puppet::Error.new("Parameter #{name} failed: #{detail}")
error.set_backtrace(detail.backtrace)
raise error
end
end
nil
end
# remove a property from the object; useful in testing or in cleanup
# when an error has been encountered
def delete(attr)
attr = symbolize(attr)
if @parameters.has_key?(attr)
@parameters.delete(attr)
else
raise Puppet::DevError.new("Undefined attribute '#{attr}' in #{self}")
end
end
# iterate across the existing properties
def eachproperty
# properties is a private method
properties.each { |property|
yield property
}
end
# Create a transaction event. Called by Transaction or by
# a property.
def event(options = {})
- Puppet::Transaction::Event.new({:resource => self, :file => file, :line => line, :tags => tags, :version => version}.merge(options))
+ Puppet::Transaction::Event.new({:resource => self, :file => file, :line => line, :tags => tags}.merge(options))
end
# Let the catalog determine whether a given cached value is
# still valid or has expired.
def expirer
catalog
end
# retrieve the 'should' value for a specified property
def should(name)
name = attr_alias(name)
(prop = @parameters[name] and prop.is_a?(Puppet::Property)) ? prop.should : nil
end
# Create the actual attribute instance. Requires either the attribute
# name or class as the first argument, then an optional hash of
# attributes to set during initialization.
def newattr(name)
if name.is_a?(Class)
klass = name
name = klass.name
end
unless klass = self.class.attrclass(name)
raise Puppet::Error, "Resource type #{self.class.name} does not support parameter #{name}"
end
if provider and ! provider.class.supports_parameter?(klass)
missing = klass.required_features.find_all { |f| ! provider.class.feature?(f) }
info "Provider %s does not support features %s; not managing attribute %s" % [provider.class.name, missing.join(", "), name]
return nil
end
return @parameters[name] if @parameters.include?(name)
@parameters[name] = klass.new(:resource => self)
end
# return the value of a parameter
def parameter(name)
@parameters[name.to_sym]
end
def parameters
@parameters.dup
end
# Is the named property defined?
def propertydefined?(name)
name = name.intern unless name.is_a? Symbol
@parameters.include?(name)
end
# Return an actual property instance by name; to return the value, use 'resource[param]'
# LAK:NOTE(20081028) Since the 'parameter' method is now a superset of this method,
# this one should probably go away at some point.
def property(name)
(obj = @parameters[symbolize(name)] and obj.is_a?(Puppet::Property)) ? obj : nil
end
# For any parameters or properties that have defaults and have not yet been
# set, set them now. This method can be handed a list of attributes,
# and if so it will only set defaults for those attributes.
def set_default(attr)
return unless klass = self.class.attrclass(attr)
return unless klass.method_defined?(:default)
return if @parameters.include?(klass.name)
return unless parameter = newattr(klass.name)
if value = parameter.default and ! value.nil?
parameter.value = value
else
@parameters.delete(parameter.name)
end
end
# Convert our object to a hash. This just includes properties.
def to_hash
rethash = {}
@parameters.each do |name, obj|
rethash[name] = obj.value
end
rethash
end
def type
self.class.name
end
# Return a specific value for an attribute.
def value(name)
name = attr_alias(name)
(obj = @parameters[name] and obj.respond_to?(:value)) ? obj.value : nil
end
def version
return 0 unless catalog
catalog.version
end
# Return all of the property objects, in the order specified in the
# class.
def properties
self.class.properties.collect { |prop| @parameters[prop.name] }.compact
end
# Is this type's name isomorphic with the object? That is, if the
# name conflicts, does it necessarily mean that the objects conflict?
# Defaults to true.
def self.isomorphic?
if defined?(@isomorphic)
return @isomorphic
else
return true
end
end
def isomorphic?
self.class.isomorphic?
end
# is the instance a managed instance? A 'yes' here means that
# the instance was created from the language, vs. being created
# in order resolve other questions, such as finding a package
# in a list
def managed?
# Once an object is managed, it always stays managed; but an object
# that is listed as unmanaged might become managed later in the process,
# so we have to check that every time
if @managed
return @managed
else
@managed = false
properties.each { |property|
s = property.should
if s and ! property.class.unmanaged
@managed = true
break
end
}
return @managed
end
end
###############################
# Code related to the container behaviour.
# this is a retarded hack method to get around the difference between
# component children and file children
def self.depthfirst?
@depthfirst
end
def depthfirst?
self.class.depthfirst?
end
# Remove an object. The argument determines whether the object's
# subscriptions get eliminated, too.
def remove(rmdeps = true)
# This is hackish (mmm, cut and paste), but it works for now, and it's
# better than warnings.
@parameters.each do |name, obj|
obj.remove
end
@parameters.clear
@parent = nil
# Remove the reference to the provider.
if self.provider
@provider.clear
@provider = nil
end
end
###############################
# Code related to evaluating the resources.
# Flush the provider, if it supports it. This is called by the
# transaction.
def flush
self.provider.flush if self.provider and self.provider.respond_to?(:flush)
end
# if all contained objects are in sync, then we're in sync
# FIXME I don't think this is used on the type instances any more,
# it's really only used for testing
def insync?(is)
insync = true
if property = @parameters[:ensure]
unless is.include? property
raise Puppet::DevError,
"The is value is not in the is array for '#{property.name}'"
end
ensureis = is[property]
if property.insync?(ensureis) and property.should == :absent
return true
end
end
properties.each { |property|
unless is.include? property
raise Puppet::DevError,
"The is value is not in the is array for '#{property.name}'"
end
propis = is[property]
unless property.insync?(propis)
property.debug("Not in sync: #{propis.inspect} vs #{property.should.inspect}")
insync = false
#else
# property.debug("In sync")
end
}
#self.debug("#{self} sync status is #{insync}")
insync
end
# retrieve the current value of all contained properties
def retrieve
fail "Provider #{provider.class.name} is not functional on this host" if self.provider.is_a?(Puppet::Provider) and ! provider.class.suitable?
result = Puppet::Resource.new(type, title)
# Provide the name, so we know we'll always refer to a real thing
result[:name] = self[:name] unless self[:name] == title
if ensure_prop = property(:ensure) or (self.class.validattr?(:ensure) and ensure_prop = newattr(:ensure))
result[:ensure] = ensure_state = ensure_prop.retrieve
else
ensure_state = nil
end
properties.each do |property|
next if property.name == :ensure
if ensure_state == :absent
result[property] = :absent
else
result[property] = property.retrieve
end
end
result
end
def retrieve_resource
resource = retrieve
resource = Resource.new(type, title, :parameters => resource) if resource.is_a? Hash
resource
end
# Get a hash of the current properties. Returns a hash with
# the actual property instance as the key and the current value
# as the, um, value.
def currentpropvalues
# It's important to use the 'properties' method here, as it follows the order
# in which they're defined in the class. It also guarantees that 'ensure'
# is the first property, which is important for skipping 'retrieve' on
# all the properties if the resource is absent.
ensure_state = false
return properties.inject({}) do | prophash, property|
if property.name == :ensure
ensure_state = property.retrieve
prophash[property] = ensure_state
else
if ensure_state == :absent
prophash[property] = :absent
else
prophash[property] = property.retrieve
end
end
prophash
end
end
# Are we running in noop mode?
def noop?
# If we're not a host_config, we're almost certainly part of
# Settings, and we want to ignore 'noop'
return false if catalog and ! catalog.host_config?
if defined?(@noop)
@noop
else
Puppet[:noop]
end
end
def noop
noop?
end
###############################
# Code related to managing resource instances.
require 'puppet/transportable'
# retrieve a named instance of the current type
def self.[](name)
raise "Global resource access is deprecated"
@objects[name] || @aliases[name]
end
# add an instance by name to the class list of instances
def self.[]=(name,object)
raise "Global resource storage is deprecated"
newobj = nil
if object.is_a?(Puppet::Type)
newobj = object
else
raise Puppet::DevError, "must pass a Puppet::Type object"
end
if exobj = @objects[name] and self.isomorphic?
msg = "Object '#{newobj.class.name}[#{name}]' already exists"
msg += ("in file #{object.file} at line #{object.line}") if exobj.file and exobj.line
msg += ("and cannot be redefined in file #{object.file} at line #{object.line}") if object.file and object.line
error = Puppet::Error.new(msg)
raise error
else
#Puppet.info("adding %s of type %s to class list" %
# [name,object.class])
@objects[name] = newobj
end
end
# Create an alias. We keep these in a separate hash so that we don't encounter
# the objects multiple times when iterating over them.
def self.alias(name, obj)
raise "Global resource aliasing is deprecated"
if @objects.include?(name)
unless @objects[name] == obj
raise Puppet::Error.new(
"Cannot create alias #{name}: object already exists"
)
end
end
if @aliases.include?(name)
unless @aliases[name] == obj
raise Puppet::Error.new(
"Object #{@aliases[name].name} already has alias #{name}"
)
end
end
@aliases[name] = obj
end
# remove all of the instances of a single type
def self.clear
raise "Global resource removal is deprecated"
if defined?(@objects)
@objects.each do |name, obj|
obj.remove(true)
end
@objects.clear
end
@aliases.clear if defined?(@aliases)
end
# Force users to call this, so that we can merge objects if
# necessary.
def self.create(args)
# LAK:DEP Deprecation notice added 12/17/2008
Puppet.warning "Puppet::Type.create is deprecated; use Puppet::Type.new"
new(args)
end
# remove a specified object
def self.delete(resource)
raise "Global resource removal is deprecated"
return unless defined?(@objects)
@objects.delete(resource.title) if @objects.include?(resource.title)
@aliases.delete(resource.title) if @aliases.include?(resource.title)
if @aliases.has_value?(resource)
names = []
@aliases.each do |name, otherres|
if otherres == resource
names << name
end
end
names.each { |name| @aliases.delete(name) }
end
end
# iterate across each of the type's instances
def self.each
raise "Global resource iteration is deprecated"
return unless defined?(@objects)
@objects.each { |name,instance|
yield instance
}
end
# does the type have an object with the given name?
def self.has_key?(name)
raise "Global resource access is deprecated"
@objects.has_key?(name)
end
# Retrieve all known instances. Either requires providers or must be overridden.
def self.instances
raise Puppet::DevError, "#{self.name} has no providers and has not overridden 'instances'" if provider_hash.empty?
# Put the default provider first, then the rest of the suitable providers.
provider_instances = {}
providers_by_source.collect do |provider|
provider.instances.collect do |instance|
# We always want to use the "first" provider instance we find, unless the resource
# is already managed and has a different provider set
if other = provider_instances[instance.name]
Puppet.warning "%s %s found in both %s and %s; skipping the %s version" %
[self.name.to_s.capitalize, instance.name, other.class.name, instance.class.name, instance.class.name]
next
end
provider_instances[instance.name] = instance
new(:name => instance.name, :provider => instance, :audit => :all)
end
end.flatten.compact
end
# Return a list of one suitable provider per source, with the default provider first.
def self.providers_by_source
# Put the default provider first, then the rest of the suitable providers.
sources = []
[defaultprovider, suitableprovider].flatten.uniq.collect do |provider|
next if sources.include?(provider.source)
sources << provider.source
provider
end.compact
end
# Convert a simple hash into a Resource instance.
def self.hash2resource(hash)
hash = hash.inject({}) { |result, ary| result[ary[0].to_sym] = ary[1]; result }
title = hash.delete(:title)
title ||= hash[:name]
title ||= hash[key_attributes.first] if key_attributes.length == 1
raise Puppet::Error, "Title or name must be provided" unless title
# Now create our resource.
resource = Puppet::Resource.new(self.name, title)
[:catalog].each do |attribute|
if value = hash[attribute]
hash.delete(attribute)
resource.send(attribute.to_s + "=", value)
end
end
hash.each do |param, value|
resource[param] = value
end
resource
end
# Create the path for logging and such.
def pathbuilder
if p = parent
[p.pathbuilder, self.ref].flatten
else
[self.ref]
end
end
###############################
# Add all of the meta parameters.
newmetaparam(:noop) do
desc "Boolean flag indicating whether work should actually
be done."
newvalues(:true, :false)
munge do |value|
case value
when true, :true, "true"; @resource.noop = true
when false, :false, "false"; @resource.noop = false
end
end
end
newmetaparam(:schedule) do
desc "On what schedule the object should be managed. You must create a
schedule object, and then reference the name of that object to use
that for your schedule:
schedule { daily:
period => daily,
range => \"2-4\"
}
exec { \"/usr/bin/apt-get update\":
schedule => daily
}
The creation of the schedule object does not need to appear in the
configuration before objects that use it."
end
newmetaparam(:audit) do
desc "Audit specified attributes of resources over time, and report if any have changed.
This attribute can be used to track changes to any resource over time, and can
provide an audit trail of every change that happens on any given machine.
Note that you cannot both audit and manage an attribute - managing it guarantees
the value, and any changes already get logged."
validate do |list|
list = Array(list).collect {|p| p.to_sym}
unless list == [:all]
list.each do |param|
next if @resource.class.validattr?(param)
fail "Cannot audit #{param}: not a valid attribute for #{resource}"
end
end
end
munge do |args|
properties_to_audit(args).each do |param|
next unless resource.class.validproperty?(param)
resource.newattr(param)
end
end
def all_properties
resource.class.properties.find_all do |property|
resource.provider.nil? or resource.provider.class.supports_parameter?(property)
end.collect do |property|
property.name
end
end
def properties_to_audit(list)
if !list.kind_of?(Array) && list.to_sym == :all
list = all_properties
else
list = Array(list).collect { |p| p.to_sym }
end
end
end
newmetaparam(:check) do
desc "Audit specified attributes of resources over time, and report if any have changed.
This parameter has been deprecated in favor of 'audit'."
munge do |args|
resource.warning "'check' attribute is deprecated; use 'audit' instead"
resource[:audit] = args
end
end
newmetaparam(:loglevel) do
desc "Sets the level that information will be logged.
The log levels have the biggest impact when logs are sent to
syslog (which is currently the default)."
defaultto :notice
newvalues(*Puppet::Util::Log.levels)
newvalues(:verbose)
munge do |loglevel|
val = super(loglevel)
if val == :verbose
val = :info
end
val
end
end
newmetaparam(:alias) do
desc "Creates an alias for the object. Puppet uses this internally when you
provide a symbolic name:
file { sshdconfig:
path => $operatingsystem ? {
solaris => \"/usr/local/etc/ssh/sshd_config\",
default => \"/etc/ssh/sshd_config\"
},
source => \"...\"
}
service { sshd:
subscribe => File[sshdconfig]
}
When you use this feature, the parser sets `sshdconfig` as the name,
and the library sets that as an alias for the file so the dependency
lookup for `sshd` works. You can use this parameter yourself,
but note that only the library can use these aliases; for instance,
the following code will not work:
file { \"/etc/ssh/sshd_config\":
owner => root,
group => root,
alias => sshdconfig
}
file { sshdconfig:
mode => 644
}
There's no way here for the Puppet parser to know that these two stanzas
should be affecting the same file.
See the [Language Tutorial](http://docs.puppetlabs.com/guides/language_tutorial.html) for more information.
"
munge do |aliases|
aliases = [aliases] unless aliases.is_a?(Array)
raise(ArgumentError, "Cannot add aliases without a catalog") unless @resource.catalog
aliases.each do |other|
if obj = @resource.catalog.resource(@resource.class.name, other)
unless obj.object_id == @resource.object_id
self.fail("#{@resource.title} can not create alias #{other}: object already exists")
end
next
end
# Newschool, add it to the catalog.
@resource.catalog.alias(@resource, other)
end
end
end
newmetaparam(:tag) do
desc "Add the specified tags to the associated resource. While all resources
are automatically tagged with as much information as possible
(e.g., each class and definition containing the resource), it can
be useful to add your own tags to a given resource.
Tags are currently useful for things like applying a subset of a
host's configuration:
puppet agent --test --tags mytag
This way, when you're testing a configuration you can run just the
portion you're testing."
munge do |tags|
tags = [tags] unless tags.is_a? Array
tags.each do |tag|
@resource.tag(tag)
end
end
end
class RelationshipMetaparam < Puppet::Parameter
class << self
attr_accessor :direction, :events, :callback, :subclasses
end
@subclasses = []
def self.inherited(sub)
@subclasses << sub
end
def munge(references)
references = [references] unless references.is_a?(Array)
references.collect do |ref|
if ref.is_a?(Puppet::Resource)
ref
else
Puppet::Resource.new(ref)
end
end
end
def validate_relationship
@value.each do |ref|
unless @resource.catalog.resource(ref.to_s)
description = self.class.direction == :in ? "dependency" : "dependent"
fail "Could not find #{description} #{ref} for #{resource.ref}"
end
end
end
# Create edges from each of our relationships. :in
# relationships are specified by the event-receivers, and :out
# relationships are specified by the event generator. This
# way 'source' and 'target' are consistent terms in both edges
# and events -- that is, an event targets edges whose source matches
# the event's source. The direction of the relationship determines
# which resource is applied first and which resource is considered
# to be the event generator.
def to_edges
@value.collect do |reference|
reference.catalog = resource.catalog
# Either of the two retrieval attempts could have returned
# nil.
unless related_resource = reference.resolve
self.fail "Could not retrieve dependency '#{reference}' of #{@resource.ref}"
end
# Are we requiring them, or vice versa? See the method docs
# for futher info on this.
if self.class.direction == :in
source = related_resource
target = @resource
else
source = @resource
target = related_resource
end
if method = self.class.callback
subargs = {
:event => self.class.events,
:callback => method
}
self.debug("subscribes to #{related_resource.ref}")
else
# If there's no callback, there's no point in even adding
# a label.
subargs = nil
self.debug("requires #{related_resource.ref}")
end
rel = Puppet::Relationship.new(source, target, subargs)
end
end
end
def self.relationship_params
RelationshipMetaparam.subclasses
end
# Note that the order in which the relationships params is defined
# matters. The labelled params (notify and subcribe) must be later,
# so that if both params are used, those ones win. It's a hackish
# solution, but it works.
newmetaparam(:require, :parent => RelationshipMetaparam, :attributes => {:direction => :in, :events => :NONE}) do
desc "One or more objects that this object depends on.
This is used purely for guaranteeing that changes to required objects
happen before the dependent object. For instance:
# Create the destination directory before you copy things down
file { \"/usr/local/scripts\":
ensure => directory
}
file { \"/usr/local/scripts/myscript\":
source => \"puppet://server/module/myscript\",
mode => 755,
require => File[\"/usr/local/scripts\"]
}
Multiple dependencies can be specified by providing a comma-seperated list
of resources, enclosed in square brackets:
require => [ File[\"/usr/local\"], File[\"/usr/local/scripts\"] ]
Note that Puppet will autorequire everything that it can, and
there are hooks in place so that it's easy for resources to add new
ways to autorequire objects, so if you think Puppet could be
smarter here, let us know.
In fact, the above code was redundant -- Puppet will autorequire
any parent directories that are being managed; it will
automatically realize that the parent directory should be created
before the script is pulled down.
Currently, exec resources will autorequire their CWD (if it is
specified) plus any fully qualified paths that appear in the
command. For instance, if you had an `exec` command that ran
the `myscript` mentioned above, the above code that pulls the
file down would be automatically listed as a requirement to the
`exec` code, so that you would always be running againts the
most recent version.
"
end
newmetaparam(:subscribe, :parent => RelationshipMetaparam, :attributes => {:direction => :in, :events => :ALL_EVENTS, :callback => :refresh}) do
desc "One or more objects that this object depends on. Changes in the
subscribed to objects result in the dependent objects being
refreshed (e.g., a service will get restarted). For instance:
class nagios {
file { \"/etc/nagios/nagios.conf\":
source => \"puppet://server/module/nagios.conf\",
alias => nagconf # just to make things easier for me
}
service { nagios:
ensure => running,
subscribe => File[nagconf]
}
}
Currently the `exec`, `mount` and `service` type support
refreshing.
"
end
newmetaparam(:before, :parent => RelationshipMetaparam, :attributes => {:direction => :out, :events => :NONE}) do
desc %{This parameter is the opposite of **require** -- it guarantees
that the specified object is applied later than the specifying
object:
file { "/var/nagios/configuration":
source => "...",
recurse => true,
before => Exec["nagios-rebuid"]
}
exec { "nagios-rebuild":
command => "/usr/bin/make",
cwd => "/var/nagios/configuration"
}
This will make sure all of the files are up to date before the
make command is run.}
end
newmetaparam(:notify, :parent => RelationshipMetaparam, :attributes => {:direction => :out, :events => :ALL_EVENTS, :callback => :refresh}) do
desc %{This parameter is the opposite of **subscribe** -- it sends events
to the specified object:
file { "/etc/sshd_config":
source => "....",
notify => Service[sshd]
}
service { sshd:
ensure => running
}
This will restart the sshd service if the sshd config file changes.}
end
newmetaparam(:stage) do
desc %{Which run stage a given resource should reside in. This just creates
a dependency on or from the named milestone. For instance, saying that
this is in the 'bootstrap' stage creates a dependency on the 'bootstrap'
milestone.
By default, all classes get directly added to the
'main' stage. You can create new stages as resources:
stage { [pre, post]: }
To order stages, use standard relationships:
stage { pre: before => Stage[main] }
Or use the new relationship syntax:
Stage[pre] -> Stage[main] -> Stage[post]
Then use the new class parameters to specify a stage:
class { foo: stage => pre }
Stages can only be set on classes, not individual resources. This will
fail:
file { '/foo': stage => pre, ensure => file }
}
end
###############################
# All of the provider plumbing for the resource types.
require 'puppet/provider'
require 'puppet/util/provider_features'
# Add the feature handling module.
extend Puppet::Util::ProviderFeatures
attr_reader :provider
# the Type class attribute accessors
class << self
attr_accessor :providerloader
attr_writer :defaultprovider
end
# Find the default provider.
def self.defaultprovider
unless @defaultprovider
suitable = suitableprovider
# Find which providers are a default for this system.
defaults = suitable.find_all { |provider| provider.default? }
# If we don't have any default we use suitable providers
defaults = suitable if defaults.empty?
max = defaults.collect { |provider| provider.specificity }.max
defaults = defaults.find_all { |provider| provider.specificity == max }
retval = nil
if defaults.length > 1
Puppet.warning(
"Found multiple default providers for #{self.name}: #{defaults.collect { |i| i.name.to_s }.join(", ")}; using #{defaults[0].name}"
)
retval = defaults.shift
elsif defaults.length == 1
retval = defaults.shift
else
raise Puppet::DevError, "Could not find a default provider for #{self.name}"
end
@defaultprovider = retval
end
@defaultprovider
end
def self.provider_hash_by_type(type)
@provider_hashes ||= {}
@provider_hashes[type] ||= {}
end
def self.provider_hash
Puppet::Type.provider_hash_by_type(self.name)
end
# Retrieve a provider by name.
def self.provider(name)
name = Puppet::Util.symbolize(name)
# If we don't have it yet, try loading it.
@providerloader.load(name) unless provider_hash.has_key?(name)
provider_hash[name]
end
# Just list all of the providers.
def self.providers
provider_hash.keys
end
def self.validprovider?(name)
name = Puppet::Util.symbolize(name)
(provider_hash.has_key?(name) && provider_hash[name].suitable?)
end
# Create a new provider of a type. This method must be called
# directly on the type that it's implementing.
def self.provide(name, options = {}, &block)
name = Puppet::Util.symbolize(name)
if obj = provider_hash[name]
Puppet.debug "Reloading #{name} #{self.name} provider"
unprovide(name)
end
parent = if pname = options[:parent]
options.delete(:parent)
if pname.is_a? Class
pname
else
if provider = self.provider(pname)
provider
else
raise Puppet::DevError,
"Could not find parent provider #{pname} of #{name}"
end
end
else
Puppet::Provider
end
options[:resource_type] ||= self
self.providify
provider = genclass(
name,
:parent => parent,
:hash => provider_hash,
:prefix => "Provider",
:block => block,
:include => feature_module,
:extend => feature_module,
:attributes => options
)
provider
end
# Make sure we have a :provider parameter defined. Only gets called if there
# are providers.
def self.providify
return if @paramhash.has_key? :provider
newparam(:provider) do
desc "The specific backend for #{self.name.to_s} to use. You will
seldom need to specify this -- Puppet will usually discover the
appropriate provider for your platform."
# This is so we can refer back to the type to get a list of
# providers for documentation.
class << self
attr_accessor :parenttype
end
# We need to add documentation for each provider.
def self.doc
@doc + " Available providers are:\n\n" + parenttype.providers.sort { |a,b|
a.to_s <=> b.to_s
}.collect { |i|
"* **#{i}**: #{parenttype().provider(i).doc}"
}.join("\n")
end
defaultto {
@resource.class.defaultprovider.name
}
validate do |provider_class|
provider_class = provider_class[0] if provider_class.is_a? Array
provider_class = provider_class.class.name if provider_class.is_a?(Puppet::Provider)
unless provider = @resource.class.provider(provider_class)
raise ArgumentError, "Invalid #{@resource.class.name} provider '#{provider_class}'"
end
end
munge do |provider|
provider = provider[0] if provider.is_a? Array
provider = provider.intern if provider.is_a? String
@resource.provider = provider
if provider.is_a?(Puppet::Provider)
provider.class.name
else
provider
end
end
end.parenttype = self
end
def self.unprovide(name)
if provider_hash.has_key? name
rmclass(
name,
:hash => provider_hash,
:prefix => "Provider"
)
if @defaultprovider and @defaultprovider.name == name
@defaultprovider = nil
end
end
end
# Return an array of all of the suitable providers.
def self.suitableprovider
providerloader.loadall if provider_hash.empty?
provider_hash.find_all { |name, provider|
provider.suitable?
}.collect { |name, provider|
provider
}.reject { |p| p.name == :fake } # For testing
end
def provider=(name)
if name.is_a?(Puppet::Provider)
@provider = name
@provider.resource = self
elsif klass = self.class.provider(name)
@provider = klass.new(self)
else
raise ArgumentError, "Could not find #{name} provider of #{self.class.name}"
end
end
###############################
# All of the relationship code.
# Specify a block for generating a list of objects to autorequire. This
# makes it so that you don't have to manually specify things that you clearly
# require.
def self.autorequire(name, &block)
@autorequires ||= {}
@autorequires[name] = block
end
# Yield each of those autorequires in turn, yo.
def self.eachautorequire
@autorequires ||= {}
@autorequires.each { |type, block|
yield(type, block)
}
end
# Figure out of there are any objects we can automatically add as
# dependencies.
def autorequire(rel_catalog = nil)
rel_catalog ||= catalog
raise(Puppet::DevError, "You cannot add relationships without a catalog") unless rel_catalog
reqs = []
self.class.eachautorequire { |type, block|
# Ignore any types we can't find, although that would be a bit odd.
next unless typeobj = Puppet::Type.type(type)
# Retrieve the list of names from the block.
next unless list = self.instance_eval(&block)
list = [list] unless list.is_a?(Array)
# Collect the current prereqs
list.each { |dep|
obj = nil
# Support them passing objects directly, to save some effort.
unless dep.is_a? Puppet::Type
# Skip autorequires that we aren't managing
unless dep = rel_catalog.resource(type, dep)
next
end
end
reqs << Puppet::Relationship.new(dep, self)
}
}
reqs
end
# Build the dependencies associated with an individual object.
def builddepends
# Handle the requires
self.class.relationship_params.collect do |klass|
if param = @parameters[klass.name]
param.to_edges
end
end.flatten.reject { |r| r.nil? }
end
# Define the initial list of tags.
def tags=(list)
tag(self.class.name)
tag(*list)
end
# Types (which map to resources in the languages) are entirely composed of
# attribute value pairs. Generally, Puppet calls any of these things an
# 'attribute', but these attributes always take one of three specific
# forms: parameters, metaparams, or properties.
# In naming methods, I have tried to consistently name the method so
# that it is clear whether it operates on all attributes (thus has 'attr' in
# the method name, or whether it operates on a specific type of attributes.
attr_writer :title
attr_writer :noop
include Enumerable
# class methods dealing with Type management
public
# the Type class attribute accessors
class << self
attr_reader :name
attr_accessor :self_refresh
include Enumerable, Puppet::Util::ClassGen
include Puppet::MetaType::Manager
include Puppet::Util
include Puppet::Util::Logging
end
# all of the variables that must be initialized for each subclass
def self.initvars
# all of the instances of this class
@objects = Hash.new
@aliases = Hash.new
@defaults = {}
@parameters ||= []
@validproperties = {}
@properties = []
@parameters = []
@paramhash = {}
@attr_aliases = {}
@paramdoc = Hash.new { |hash,key|
key = key.intern if key.is_a?(String)
if hash.include?(key)
hash[key]
else
"Param Documentation for #{key} not found"
end
}
@doc ||= ""
end
def self.to_s
if defined?(@name)
"Puppet::Type::#{@name.to_s.capitalize}"
else
super
end
end
# Create a block to validate that our object is set up entirely. This will
# be run before the object is operated on.
def self.validate(&block)
define_method(:validate, &block)
#@validate = block
end
# The catalog that this resource is stored in.
attr_accessor :catalog
# is the resource exported
attr_accessor :exported
# is the resource virtual (it should not :-))
attr_accessor :virtual
# create a log at specified level
def log(msg)
Puppet::Util::Log.create(
:level => @parameters[:loglevel].value,
:message => msg,
:source => self
)
end
# instance methods related to instance intrinsics
# e.g., initialize and name
public
attr_reader :original_parameters
# initialize the type instance
def initialize(resource)
raise Puppet::DevError, "Got TransObject instead of Resource or hash" if resource.is_a?(Puppet::TransObject)
resource = self.class.hash2resource(resource) unless resource.is_a?(Puppet::Resource)
# The list of parameter/property instances.
@parameters = {}
# Set the title first, so any failures print correctly.
if resource.type.to_s.downcase.to_sym == self.class.name
self.title = resource.title
else
# This should only ever happen for components
self.title = resource.ref
end
[:file, :line, :catalog, :exported, :virtual].each do |getter|
setter = getter.to_s + "="
if val = resource.send(getter)
self.send(setter, val)
end
end
@tags = resource.tags
@original_parameters = resource.to_hash
set_name(@original_parameters)
set_default(:provider)
set_parameters(@original_parameters)
self.validate if self.respond_to?(:validate)
end
private
# Set our resource's name.
def set_name(hash)
self[name_var] = hash.delete(name_var) if name_var
end
# Set all of the parameters from a hash, in the appropriate order.
def set_parameters(hash)
# Use the order provided by allattrs, but add in any
# extra attributes from the resource so we get failures
# on invalid attributes.
no_values = []
(self.class.allattrs + hash.keys).uniq.each do |attr|
begin
# Set any defaults immediately. This is mostly done so
# that the default provider is available for any other
# property validation.
if hash.has_key?(attr)
self[attr] = hash[attr]
else
no_values << attr
end
rescue ArgumentError, Puppet::Error, TypeError
raise
rescue => detail
error = Puppet::DevError.new( "Could not set #{attr} on #{self.class.name}: #{detail}")
error.set_backtrace(detail.backtrace)
raise error
end
end
no_values.each do |attr|
set_default(attr)
end
end
public
# Set up all of our autorequires.
def finish
# Make sure all of our relationships are valid. Again, must be done
# when the entire catalog is instantiated.
self.class.relationship_params.collect do |klass|
if param = @parameters[klass.name]
param.validate_relationship
end
end.flatten.reject { |r| r.nil? }
end
# For now, leave the 'name' method functioning like it used to. Once 'title'
# works everywhere, I'll switch it.
def name
self[:name]
end
# Look up our parent in the catalog, if we have one.
def parent
return nil unless catalog
unless defined?(@parent)
if parents = catalog.adjacent(self, :direction => :in)
# We should never have more than one parent, so let's just ignore
# it if we happen to.
@parent = parents.shift
else
@parent = nil
end
end
@parent
end
# Return the "type[name]" style reference.
def ref
"#{self.class.name.to_s.capitalize}[#{self.title}]"
end
def self_refresh?
self.class.self_refresh
end
# Mark that we're purging.
def purging
@purging = true
end
# Is this resource being purged? Used by transactions to forbid
# deletion when there are dependencies.
def purging?
if defined?(@purging)
@purging
else
false
end
end
# Retrieve the title of an object. If no title was set separately,
# then use the object's name.
def title
unless @title
if self.class.validparameter?(name_var)
@title = self[:name]
elsif self.class.validproperty?(name_var)
@title = self.should(name_var)
else
self.devfail "Could not find namevar #{name_var} for #{self.class.name}"
end
end
@title
end
# convert to a string
def to_s
self.ref
end
# Convert to a transportable object
def to_trans(ret = true)
trans = TransObject.new(self.title, self.class.name)
values = retrieve_resource
values.each do |name, value|
name = name.name if name.respond_to? :name
trans[name] = value
end
@parameters.each do |name, param|
# Avoid adding each instance name twice
next if param.class.isnamevar? and param.value == self.title
# We've already got property values
next if param.is_a?(Puppet::Property)
trans[name] = param.value
end
trans.tags = self.tags
# FIXME I'm currently ignoring 'parent' and 'path'
trans
end
def to_resource
# this 'type instance' versus 'resource' distinction seems artificial
# I'd like to see it collapsed someday ~JW
self.to_trans.to_resource
end
def virtual?; !!@virtual; end
def exported?; !!@exported; end
end
end
require 'puppet/provider'
# Always load these types.
require 'puppet/type/component'
diff --git a/lib/puppet/util/log.rb b/lib/puppet/util/log.rb
index 7764dc1d1..3fdac3f69 100644
--- a/lib/puppet/util/log.rb
+++ b/lib/puppet/util/log.rb
@@ -1,258 +1,258 @@
require 'puppet/util/tagging'
require 'puppet/util/classgen'
# Pass feedback to the user. Log levels are modeled after syslog's, and it is
# expected that that will be the most common log destination. Supports
# multiple destinations, one of which is a remote server.
class Puppet::Util::Log
include Puppet::Util
extend Puppet::Util::ClassGen
include Puppet::Util::Tagging
@levels = [:debug,:info,:notice,:warning,:err,:alert,:emerg,:crit]
@loglevel = 2
@desttypes = {}
# Create a new destination type.
def self.newdesttype(name, options = {}, &block)
dest = genclass(
name,
:parent => Puppet::Util::Log::Destination,
:prefix => "Dest",
:block => block,
:hash => @desttypes,
:attributes => options
)
dest.match(dest.name)
dest
end
require 'puppet/util/log/destination'
require 'puppet/util/log/destinations'
@destinations = {}
@queued = []
class << self
include Puppet::Util
include Puppet::Util::ClassGen
attr_reader :desttypes
end
# Reset log to basics. Basically just flushes and closes files and
# undefs other objects.
def Log.close(destination)
if @destinations.include?(destination)
@destinations[destination].flush if @destinations[destination].respond_to?(:flush)
@destinations[destination].close if @destinations[destination].respond_to?(:close)
@destinations.delete(destination)
end
end
def self.close_all
destinations.keys.each { |dest|
close(dest)
}
end
# Flush any log destinations that support such operations.
def Log.flush
@destinations.each { |type, dest|
dest.flush if dest.respond_to?(:flush)
}
end
# Create a new log message. The primary role of this method is to
# avoid creating log messages below the loglevel.
def Log.create(hash)
raise Puppet::DevError, "Logs require a level" unless hash.include?(:level)
raise Puppet::DevError, "Invalid log level #{hash[:level]}" unless @levels.index(hash[:level])
@levels.index(hash[:level]) >= @loglevel ? Puppet::Util::Log.new(hash) : nil
end
def Log.destinations
@destinations
end
# Yield each valid level in turn
def Log.eachlevel
@levels.each { |level| yield level }
end
# Return the current log level.
def Log.level
@levels[@loglevel]
end
# Set the current log level.
def Log.level=(level)
level = level.intern unless level.is_a?(Symbol)
raise Puppet::DevError, "Invalid loglevel #{level}" unless @levels.include?(level)
@loglevel = @levels.index(level)
end
def Log.levels
@levels.dup
end
# Create a new log destination.
def Log.newdestination(dest)
# Each destination can only occur once.
if @destinations.find { |name, obj| obj.name == dest }
return
end
name, type = @desttypes.find do |name, klass|
klass.match?(dest)
end
raise Puppet::DevError, "Unknown destination type #{dest}" unless type
begin
if type.instance_method(:initialize).arity == 1
@destinations[dest] = type.new(dest)
else
@destinations[dest] = type.new
end
flushqueue
@destinations[dest]
rescue => detail
puts detail.backtrace if Puppet[:debug]
# If this was our only destination, then add the console back in.
newdestination(:console) if @destinations.empty? and (dest != :console and dest != "console")
end
end
# Route the actual message. FIXME There are lots of things this method
# should do, like caching and a bit more. It's worth noting that there's
# a potential for a loop here, if the machine somehow gets the destination set as
# itself.
def Log.newmessage(msg)
return if @levels.index(msg.level) < @loglevel
queuemessage(msg) if @destinations.length == 0
@destinations.each do |name, dest|
threadlock(dest) do
dest.handle(msg)
end
end
end
def Log.queuemessage(msg)
@queued.push(msg)
end
def Log.flushqueue
return unless @destinations.size >= 1
@queued.each do |msg|
Log.newmessage(msg)
end
@queued.clear
end
def Log.sendlevel?(level)
@levels.index(level) >= @loglevel
end
# Reopen all of our logs.
def Log.reopen
Puppet.notice "Reopening log files"
types = @destinations.keys
@destinations.each { |type, dest|
dest.close if dest.respond_to?(:close)
}
@destinations.clear
# We need to make sure we always end up with some kind of destination
begin
types.each { |type|
Log.newdestination(type)
}
rescue => detail
if @destinations.empty?
Log.newdestination(:syslog)
Puppet.err detail.to_s
end
end
end
# Is the passed level a valid log level?
def self.validlevel?(level)
@levels.include?(level)
end
- attr_accessor :time, :remote, :file, :line, :version, :source
+ attr_accessor :time, :remote, :file, :line, :source
attr_reader :level, :message
def initialize(args)
self.level = args[:level]
self.message = args[:message]
self.source = args[:source] || "Puppet"
@time = Time.now
if tags = args[:tags]
tags.each { |t| self.tag(t) }
end
- [:file, :line, :version].each do |attr|
+ [:file, :line].each do |attr|
next unless value = args[attr]
send(attr.to_s + "=", value)
end
Log.newmessage(self)
end
def message=(msg)
raise ArgumentError, "Puppet::Util::Log requires a message" unless msg
@message = msg.to_s
end
def level=(level)
raise ArgumentError, "Puppet::Util::Log requires a log level" unless level
@level = level.to_sym
raise ArgumentError, "Invalid log level #{@level}" unless self.class.validlevel?(@level)
# Tag myself with my log level
tag(level)
end
# If they pass a source in to us, we make sure it is a string, and
# we retrieve any tags we can.
def source=(source)
if source.respond_to?(:source_descriptors)
descriptors = source.source_descriptors
@source = descriptors[:path]
descriptors[:tags].each { |t| tag(t) }
- [:file, :line, :version].each do |param|
+ [:file, :line].each do |param|
next unless descriptors[param]
send(param.to_s + "=", descriptors[param])
end
else
@source = source.to_s
end
end
def to_report
"#{time} #{source} (#{level}): #{to_s}"
end
def to_s
message
end
end
# This is for backward compatibility from when we changed the constant to Puppet::Util::Log
# because the reports include the constant name. Apparently the alias was created in
# March 2007, should could probably be removed soon.
Puppet::Log = Puppet::Util::Log
diff --git a/lib/puppet/util/log_paths.rb b/lib/puppet/util/log_paths.rb
index f59197ed1..2fefd4505 100644
--- a/lib/puppet/util/log_paths.rb
+++ b/lib/puppet/util/log_paths.rb
@@ -1,27 +1,27 @@
# Created by Luke Kanies on 2007-07-04.
# Copyright (c) 2007. All rights reserved.
module Puppet::Util::LogPaths
# return the full path to us, for logging and rollback
# some classes (e.g., FileTypeRecords) will have to override this
def path
@path ||= pathbuilder
"/" + @path.join("/")
end
def source_descriptors
descriptors = {}
descriptors[:tags] = tags
- [:path, :file, :line, :version].each do |param|
+ [:path, :file, :line].each do |param|
next unless value = send(param)
descriptors[param] = value
end
descriptors
end
end
diff --git a/lib/puppet/util/logging.rb b/lib/puppet/util/logging.rb
index f20444a3b..bc52b17f0 100644
--- a/lib/puppet/util/logging.rb
+++ b/lib/puppet/util/logging.rb
@@ -1,40 +1,40 @@
# A module to make logging a bit easier.
require 'puppet/util/log'
module Puppet::Util::Logging
def send_log(level, message)
Puppet::Util::Log.create({:level => level, :source => log_source, :message => message}.merge(log_metadata))
end
# Create a method for each log level.
Puppet::Util::Log.eachlevel do |level|
define_method(level) do |args|
args = args.join(" ") if args.is_a?(Array)
send_log(level, args)
end
end
private
def is_resource?
defined?(Puppet::Type) && is_a?(Puppet::Type)
end
def is_resource_parameter?
defined?(Puppet::Parameter) && is_a?(Puppet::Parameter)
end
def log_metadata
- [:file, :line, :version, :tags].inject({}) do |result, attr|
+ [:file, :line, :tags].inject({}) do |result, attr|
result[attr] = send(attr) if respond_to?(attr)
result
end
end
def log_source
# We need to guard the existence of the constants, since this module is used by the base Puppet module.
(is_resource? or is_resource_parameter?) and respond_to?(:path) and return path.to_s
to_s
end
end
diff --git a/lib/puppet/util/metric.rb b/lib/puppet/util/metric.rb
index 8f55e7b44..09bbb6137 100644
--- a/lib/puppet/util/metric.rb
+++ b/lib/puppet/util/metric.rb
@@ -1,187 +1,188 @@
# included so we can test object types
require 'puppet'
# A class for handling metrics. This is currently ridiculously hackish.
class Puppet::Util::Metric
attr_accessor :type, :name, :value, :label
attr_writer :values
attr_writer :basedir
# Return a specific value
def [](name)
if value = @values.find { |v| v[0] == name }
return value[2]
else
return 0
end
end
def basedir
if defined?(@basedir)
@basedir
else
Puppet[:rrddir]
end
end
def create(start = nil)
Puppet.settings.use(:main, :metrics)
start ||= Time.now.to_i - 5
args = []
if Puppet.features.rrd_legacy? && ! Puppet.features.rrd?
@rrd = RRDtool.new(self.path)
end
values.each { |value|
# the 7200 is the heartbeat -- this means that any data that isn't
# more frequently than every two hours gets thrown away
args.push "DS:#{value[0]}:GAUGE:7200:U:U"
}
args.push "RRA:AVERAGE:0.5:1:300"
begin
if Puppet.features.rrd_legacy? && ! Puppet.features.rrd?
@rrd.create( Puppet[:rrdinterval].to_i, start, args)
else
RRD.create( self.path, '-s', Puppet[:rrdinterval].to_i.to_s, '-b', start.to_i.to_s, *args)
end
rescue => detail
raise "Could not create RRD file #{path}: #{detail}"
end
end
def dump
if Puppet.features.rrd_legacy? && ! Puppet.features.rrd?
puts @rrd.info
else
puts RRD.info(self.path)
end
end
def graph(range = nil)
unless Puppet.features.rrd? || Puppet.features.rrd_legacy?
Puppet.warning "RRD library is missing; cannot graph metrics"
return
end
unit = 60 * 60 * 24
colorstack = %w{#00ff00 #ff0000 #0000ff #ffff00 #ff99ff #ff9966 #66ffff #990000 #099000 #000990 #f00990 #0f0f0f #555555 #333333 #ffffff}
{:daily => unit, :weekly => unit * 7, :monthly => unit * 30, :yearly => unit * 365}.each do |name, time|
file = self.path.sub(/\.rrd$/, "-#{name}.png")
args = [file]
args.push("--title",self.label)
args.push("--imgformat","PNG")
args.push("--interlace")
i = 0
defs = []
lines = []
#p @values.collect { |s,l| s }
values.zip(colorstack).each { |value,color|
next if value.nil?
# this actually uses the data label
defs.push("DEF:#{value[0]}=#{self.path}:#{value[0]}:AVERAGE")
lines.push("LINE2:#{value[0]}#{color}:#{value[1]}")
}
args << defs
args << lines
args.flatten!
if range
if Puppet.features.rrd_legacy? && ! Puppet.features.rrd?
args.push("--start",range[0],"--end",range[1])
else
args.push("--start",range[0].to_i.to_s,"--end",range[1].to_i.to_s)
end
else
if Puppet.features.rrd_legacy? && ! Puppet.features.rrd?
args.push("--start", Time.now.to_i - time, "--end", Time.now.to_i)
else
args.push("--start", (Time.now.to_i - time).to_s, "--end", Time.now.to_i.to_s)
end
end
begin
#Puppet.warning "args = #{args}"
if Puppet.features.rrd_legacy? && ! Puppet.features.rrd?
RRDtool.graph( args )
else
RRD.graph( *args )
end
rescue => detail
Puppet.err "Failed to graph #{self.name}: #{detail}"
end
end
end
def initialize(name,label = nil)
@name = name.to_s
@label = label || labelize(name)
@values = []
end
def path
File.join(self.basedir, @name + ".rrd")
end
def newvalue(name,value,label = nil)
+ raise ArgumentError.new("metric name #{name.inspect} is not a string") unless name.is_a? String
label ||= labelize(name)
@values.push [name,label,value]
end
def store(time)
unless Puppet.features.rrd? || Puppet.features.rrd_legacy?
Puppet.warning "RRD library is missing; cannot store metrics"
return
end
self.create(time - 5) unless FileTest.exists?(self.path)
if Puppet.features.rrd_legacy? && ! Puppet.features.rrd?
@rrd ||= RRDtool.new(self.path)
end
# XXX this is not terribly error-resistant
args = [time]
temps = []
values.each { |value|
#Puppet.warning "value[0]: #{value[0]}; value[1]: #{value[1]}; value[2]: #{value[2]}; "
args.push value[2]
temps.push value[0]
}
arg = args.join(":")
template = temps.join(":")
begin
if Puppet.features.rrd_legacy? && ! Puppet.features.rrd?
@rrd.update( template, [ arg ] )
else
RRD.update( self.path, '-t', template, arg )
end
#system("rrdtool updatev #{self.path} '#{arg}'")
rescue => detail
raise Puppet::Error, "Failed to update #{self.name}: #{detail}"
end
end
def values
@values.sort { |a, b| a[1] <=> b[1] }
end
private
# Convert a name into a label.
def labelize(name)
name.to_s.capitalize.gsub("_", " ")
end
end
# This is necessary because we changed the class path in early 2007,
# and reports directly yaml-dump these metrics, so both client and server
# have to agree on the class name.
Puppet::Metric = Puppet::Util::Metric
diff --git a/spec/integration/indirector/report/rest_spec.rb b/spec/integration/indirector/report/rest_spec.rb
index 7fa026b73..5b5b2ddd8 100644
--- a/spec/integration/indirector/report/rest_spec.rb
+++ b/spec/integration/indirector/report/rest_spec.rb
@@ -1,97 +1,93 @@
#!/usr/bin/env ruby
Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") }
require 'puppet/transaction/report'
require 'puppet/network/server'
require 'puppet/network/http/webrick/rest'
describe "Report REST Terminus" do
before do
Puppet[:masterport] = 34343
Puppet[:server] = "localhost"
# Get a safe temporary file
@tmpfile = Tempfile.new("webrick_integration_testing")
@dir = @tmpfile.path + "_dir"
Puppet.settings[:confdir] = @dir
Puppet.settings[:vardir] = @dir
Puppet.settings[:group] = Process.gid
Puppet.settings[:server] = "127.0.0.1"
Puppet.settings[:masterport] = "34343"
Puppet::Util::Cacher.expire
Puppet[:servertype] = 'webrick'
Puppet[:server] = '127.0.0.1'
Puppet[:certname] = '127.0.0.1'
# Generate the certificate with a local CA
Puppet::SSL::Host.ca_location = :local
ca = Puppet::SSL::CertificateAuthority.new
ca.generate(Puppet[:certname]) unless Puppet::SSL::Certificate.find(Puppet[:certname])
ca.generate("foo.madstop.com") unless Puppet::SSL::Certificate.find(Puppet[:certname])
@host = Puppet::SSL::Host.new(Puppet[:certname])
@params = { :port => 34343, :handlers => [ :report ] }
@server = Puppet::Network::Server.new(@params)
@server.listen
# Let's use REST for our reports :-)
@old_terminus = Puppet::Transaction::Report.indirection.terminus_class
Puppet::Transaction::Report.terminus_class = :rest
# LAK:NOTE We need to have a fake model here so that our indirected methods get
# passed through REST; otherwise we'd be stubbing 'save', which would cause an immediate
# return.
@report = stub_everything 'report'
@mock_model = stub_everything 'faked model', :name => "report", :convert_from => @report
Puppet::Indirector::Request.any_instance.stubs(:model).returns(@mock_model)
Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:check_authorization)
end
after do
Puppet::Network::HttpPool.expire
Puppet::SSL::Host.ca_location = :none
Puppet.settings.clear
@server.unlisten
Puppet::Transaction::Report.terminus_class = @old_terminus
end
it "should be able to send a report to the server" do
@report.expects(:save)
report = Puppet::Transaction::Report.new("apply")
resourcemetrics = {
- :total => 12,
- :out_of_sync => 20,
- :applied => 45,
- :skipped => 1,
- :restarted => 23,
- :failed_restarts => 1,
- :scheduled => 10
+ "total" => 12,
+ "out_of_sync" => 20,
+ "applied" => 45,
+ "skipped" => 1,
+ "restarted" => 23,
+ "failed_restarts" => 1,
+ "scheduled" => 10
}
report.add_metric(:resources, resourcemetrics)
timemetrics = {
- :resource1 => 10,
- :resource2 => 50,
- :resource3 => 40,
- :resource4 => 20,
+ "resource1" => 10,
+ "resource2" => 50,
+ "resource3" => 40,
+ "resource4" => 20,
}
report.add_metric(:times, timemetrics)
- report.add_metric(
- :changes,
-
- :total => 20
- )
+ report.add_metric(:changes, "total" => 20)
report.save
end
end
diff --git a/spec/unit/configurer_spec.rb b/spec/unit/configurer_spec.rb
index 3b2a44f0f..cf73a3706 100755
--- a/spec/unit/configurer_spec.rb
+++ b/spec/unit/configurer_spec.rb
@@ -1,486 +1,478 @@
#!/usr/bin/env ruby
#
# Created by Luke Kanies on 2007-11-12.
# Copyright (c) 2007. All rights reserved.
require File.dirname(__FILE__) + '/../spec_helper'
require 'puppet/configurer'
describe Puppet::Configurer do
before do
Puppet.settings.stubs(:use).returns(true)
@agent = Puppet::Configurer.new
end
it "should include the Plugin Handler module" do
Puppet::Configurer.ancestors.should be_include(Puppet::Configurer::PluginHandler)
end
it "should include the Fact Handler module" do
Puppet::Configurer.ancestors.should be_include(Puppet::Configurer::FactHandler)
end
it "should use the puppetdlockfile as its lockfile path" do
Puppet.settings.expects(:value).with(:puppetdlockfile).returns("/my/lock")
Puppet::Configurer.lockfile_path.should == "/my/lock"
end
describe "when executing a pre-run hook" do
it "should do nothing if the hook is set to an empty string" do
Puppet.settings[:prerun_command] = ""
Puppet::Util.expects(:exec).never
@agent.execute_prerun_command
end
it "should execute any pre-run command provided via the 'prerun_command' setting" do
Puppet.settings[:prerun_command] = "/my/command"
Puppet::Util.expects(:execute).with { |args| args[0] == "/my/command" }
@agent.execute_prerun_command
end
it "should fail if the command fails" do
Puppet.settings[:prerun_command] = "/my/command"
Puppet::Util.expects(:execute).raises Puppet::ExecutionFailure
lambda { @agent.execute_prerun_command }.should raise_error(Puppet::Configurer::CommandHookError)
end
end
describe "when executing a post-run hook" do
it "should do nothing if the hook is set to an empty string" do
Puppet.settings[:postrun_command] = ""
Puppet::Util.expects(:exec).never
@agent.execute_postrun_command
end
it "should execute any post-run command provided via the 'postrun_command' setting" do
Puppet.settings[:postrun_command] = "/my/command"
Puppet::Util.expects(:execute).with { |args| args[0] == "/my/command" }
@agent.execute_postrun_command
end
it "should fail if the command fails" do
Puppet.settings[:postrun_command] = "/my/command"
Puppet::Util.expects(:execute).raises Puppet::ExecutionFailure
lambda { @agent.execute_postrun_command }.should raise_error(Puppet::Configurer::CommandHookError)
end
end
end
describe Puppet::Configurer, "when executing a catalog run" do
before do
Puppet.settings.stubs(:use).returns(true)
@agent = Puppet::Configurer.new
@agent.stubs(:prepare)
@agent.stubs(:facts_for_uploading).returns({})
@catalog = Puppet::Resource::Catalog.new
@catalog.stubs(:apply)
@agent.stubs(:retrieve_catalog).returns @catalog
Puppet::Util::Log.stubs(:newdestination)
Puppet::Util::Log.stubs(:close)
end
it "should prepare for the run" do
@agent.expects(:prepare)
@agent.run
end
it "should initialize a transaction report if one is not provided" do
report = Puppet::Transaction::Report.new("apply")
Puppet::Transaction::Report.expects(:new).returns report
@agent.run
end
it "should pass the new report to the catalog" do
report = Puppet::Transaction::Report.new("apply")
Puppet::Transaction::Report.stubs(:new).returns report
@catalog.expects(:apply).with{|options| options[:report] == report}
@agent.run
end
it "should use the provided report if it was passed one" do
report = Puppet::Transaction::Report.new("apply")
Puppet::Transaction::Report.expects(:new).never
@catalog.expects(:apply).with{|options| options[:report] == report}
@agent.run(:report => report)
end
it "should set the report as a log destination" do
report = Puppet::Transaction::Report.new("apply")
Puppet::Transaction::Report.expects(:new).returns report
Puppet::Util::Log.expects(:newdestination).with(report)
@agent.run
end
it "should retrieve the catalog" do
@agent.expects(:retrieve_catalog)
@agent.run
end
it "should log a failure and do nothing if no catalog can be retrieved" do
@agent.expects(:retrieve_catalog).returns nil
Puppet.expects(:err).with "Could not retrieve catalog; skipping run"
@agent.run
end
it "should apply the catalog with all options to :run" do
@agent.expects(:retrieve_catalog).returns @catalog
@catalog.expects(:apply).with { |args| args[:one] == true }
@agent.run :one => true
end
it "should accept a catalog and use it instead of retrieving a different one" do
@agent.expects(:retrieve_catalog).never
@catalog.expects(:apply)
@agent.run :one => true, :catalog => @catalog
end
it "should benchmark how long it takes to apply the catalog" do
@agent.expects(:benchmark).with(:notice, "Finished catalog run")
@agent.expects(:retrieve_catalog).returns @catalog
@catalog.expects(:apply).never # because we're not yielding
@agent.run
end
it "should execute post-run hooks after the run" do
@agent.expects(:execute_postrun_command)
@agent.run
end
it "should send the report" do
report = Puppet::Transaction::Report.new("apply")
Puppet::Transaction::Report.expects(:new).returns(report)
@agent.expects(:send_report).with { |r, trans| r == report }
@agent.run
end
it "should send the transaction report with a reference to the transaction if a run was actually made" do
report = Puppet::Transaction::Report.new("apply")
Puppet::Transaction::Report.expects(:new).returns(report)
trans = stub 'transaction'
@catalog.expects(:apply).returns trans
@agent.expects(:send_report).with { |r, t| t == trans }
@agent.run :catalog => @catalog
end
it "should send the transaction report even if the catalog could not be retrieved" do
@agent.expects(:retrieve_catalog).returns nil
report = Puppet::Transaction::Report.new("apply")
Puppet::Transaction::Report.expects(:new).returns(report)
@agent.expects(:send_report)
@agent.run
end
it "should send the transaction report even if there is a failure" do
@agent.expects(:retrieve_catalog).raises "whatever"
report = Puppet::Transaction::Report.new("apply")
Puppet::Transaction::Report.expects(:new).returns(report)
@agent.expects(:send_report)
lambda { @agent.run }.should raise_error
end
it "should remove the report as a log destination when the run is finished" do
report = Puppet::Transaction::Report.new("apply")
Puppet::Transaction::Report.expects(:new).returns(report)
Puppet::Util::Log.expects(:close).with(report)
@agent.run
end
it "should return the report as the result of the run" do
report = Puppet::Transaction::Report.new("apply")
Puppet::Transaction::Report.expects(:new).returns(report)
@agent.run.should equal(report)
end
end
describe Puppet::Configurer, "when sending a report" do
before do
Puppet.settings.stubs(:use).returns(true)
@configurer = Puppet::Configurer.new
@report = Puppet::Transaction::Report.new("apply")
@trans = stub 'transaction'
end
- it "should require a report" do
- lambda { @configurer.send_report }.should raise_error(ArgumentError)
- end
-
- it "should allow specification of a transaction" do
- lambda { @configurer.send_report(@report, @trans) }.should_not raise_error(ArgumentError)
- end
-
- it "should use any provided transaction to add metrics to the report" do
- @trans.expects(:generate_report)
+ it "should finalize the report" do
+ @report.expects(:finalize_report)
@configurer.send_report(@report, @trans)
end
it "should print a report summary if configured to do so" do
Puppet.settings[:summarize] = true
@report.expects(:summary).returns "stuff"
@configurer.expects(:puts).with("stuff")
- @configurer.send_report(@report)
+ @configurer.send_report(@report, nil)
end
it "should not print a report summary if not configured to do so" do
Puppet.settings[:summarize] = false
@configurer.expects(:puts).never
- @configurer.send_report(@report)
+ @configurer.send_report(@report, nil)
end
it "should save the report if reporting is enabled" do
Puppet.settings[:report] = true
@report.expects(:save)
- @configurer.send_report(@report)
+ @configurer.send_report(@report, nil)
end
it "should not save the report if reporting is disabled" do
Puppet.settings[:report] = false
@report.expects(:save).never
- @configurer.send_report(@report)
+ @configurer.send_report(@report, nil)
end
it "should log but not fail if saving the report fails" do
Puppet.settings[:report] = true
@report.expects(:save).raises "whatever"
Puppet.expects(:err)
- lambda { @configurer.send_report(@report) }.should_not raise_error
+ lambda { @configurer.send_report(@report, nil) }.should_not raise_error
end
end
describe Puppet::Configurer, "when retrieving a catalog" do
before do
Puppet.settings.stubs(:use).returns(true)
@agent = Puppet::Configurer.new
@agent.stubs(:facts_for_uploading).returns({})
@catalog = Puppet::Resource::Catalog.new
# this is the default when using a Configurer instance
Puppet::Resource::Catalog.indirection.stubs(:terminus_class).returns :rest
@agent.stubs(:convert_catalog).returns @catalog
end
describe "and configured to only retrieve a catalog from the cache" do
before do
Puppet.settings[:use_cached_catalog] = true
end
it "should first look in the cache for a catalog" do
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_terminus] == true }.returns @catalog
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_cache] == true }.never
@agent.retrieve_catalog.should == @catalog
end
it "should compile a new catalog if none is found in the cache" do
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_terminus] == true }.returns nil
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns @catalog
@agent.retrieve_catalog.should == @catalog
end
end
describe "when not using a REST terminus for catalogs" do
it "should not pass any facts when retrieving the catalog" do
@agent.expects(:facts_for_uploading).never
Puppet::Resource::Catalog.expects(:find).with { |name, options|
options[:facts].nil?
}.returns @catalog
@agent.retrieve_catalog
end
end
describe "when using a REST terminus for catalogs" do
it "should pass the prepared facts and the facts format as arguments when retrieving the catalog" do
@agent.expects(:facts_for_uploading).returns(:facts => "myfacts", :facts_format => :foo)
Puppet::Resource::Catalog.expects(:find).with { |name, options|
options[:facts] == "myfacts" and options[:facts_format] == :foo
}.returns @catalog
@agent.retrieve_catalog
end
end
it "should use the Catalog class to get its catalog" do
Puppet::Resource::Catalog.expects(:find).returns @catalog
@agent.retrieve_catalog
end
it "should use its certname to retrieve the catalog" do
Facter.stubs(:value).returns "eh"
Puppet.settings[:certname] = "myhost.domain.com"
Puppet::Resource::Catalog.expects(:find).with { |name, options| name == "myhost.domain.com" }.returns @catalog
@agent.retrieve_catalog
end
it "should default to returning a catalog retrieved directly from the server, skipping the cache" do
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns @catalog
@agent.retrieve_catalog.should == @catalog
end
it "should log and return the cached catalog when no catalog can be retrieved from the server" do
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns nil
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_terminus] == true }.returns @catalog
Puppet.expects(:notice)
@agent.retrieve_catalog.should == @catalog
end
it "should not look in the cache for a catalog if one is returned from the server" do
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns @catalog
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_terminus] == true }.never
@agent.retrieve_catalog.should == @catalog
end
it "should return the cached catalog when retrieving the remote catalog throws an exception" do
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_cache] == true }.raises "eh"
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_terminus] == true }.returns @catalog
@agent.retrieve_catalog.should == @catalog
end
it "should log and return nil if no catalog can be retrieved from the server and :usecacheonfailure is disabled" do
Puppet.stubs(:[])
Puppet.expects(:[]).with(:usecacheonfailure).returns false
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns nil
Puppet.expects(:warning)
@agent.retrieve_catalog.should be_nil
end
it "should return nil if no cached catalog is available and no catalog can be retrieved from the server" do
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns nil
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_terminus] == true }.returns nil
@agent.retrieve_catalog.should be_nil
end
it "should convert the catalog before returning" do
Puppet::Resource::Catalog.stubs(:find).returns @catalog
@agent.expects(:convert_catalog).with { |cat, dur| cat == @catalog }.returns "converted catalog"
@agent.retrieve_catalog.should == "converted catalog"
end
it "should return nil if there is an error while retrieving the catalog" do
Puppet::Resource::Catalog.expects(:find).at_least_once.raises "eh"
@agent.retrieve_catalog.should be_nil
end
end
describe Puppet::Configurer, "when converting the catalog" do
before do
Puppet.settings.stubs(:use).returns(true)
@agent = Puppet::Configurer.new
@catalog = Puppet::Resource::Catalog.new
@oldcatalog = stub 'old_catalog', :to_ral => @catalog
end
it "should convert the catalog to a RAL-formed catalog" do
@oldcatalog.expects(:to_ral).returns @catalog
@agent.convert_catalog(@oldcatalog, 10).should equal(@catalog)
end
it "should finalize the catalog" do
@catalog.expects(:finalize)
@agent.convert_catalog(@oldcatalog, 10)
end
it "should record the passed retrieval time with the RAL catalog" do
@catalog.expects(:retrieval_duration=).with 10
@agent.convert_catalog(@oldcatalog, 10)
end
it "should write the RAL catalog's class file" do
@catalog.expects(:write_class_file)
@agent.convert_catalog(@oldcatalog, 10)
end
end
describe Puppet::Configurer, "when preparing for a run" do
before do
Puppet.settings.stubs(:use).returns(true)
@agent = Puppet::Configurer.new
@agent.stubs(:dostorage)
@agent.stubs(:download_fact_plugins)
@agent.stubs(:download_plugins)
@agent.stubs(:execute_prerun_command)
@facts = {"one" => "two", "three" => "four"}
end
it "should initialize the metadata store" do
@agent.class.stubs(:facts).returns(@facts)
@agent.expects(:dostorage)
@agent.prepare({})
end
it "should download fact plugins" do
@agent.expects(:download_fact_plugins)
@agent.prepare({})
end
it "should download plugins" do
@agent.expects(:download_plugins)
@agent.prepare({})
end
it "should perform the pre-run commands" do
@agent.expects(:execute_prerun_command)
@agent.prepare({})
end
end
diff --git a/spec/unit/parameter_spec.rb b/spec/unit/parameter_spec.rb
index 966bbfb81..f8ab05d62 100755
--- a/spec/unit/parameter_spec.rb
+++ b/spec/unit/parameter_spec.rb
@@ -1,172 +1,171 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../spec_helper'
require 'puppet/parameter'
describe Puppet::Parameter do
before do
@class = Class.new(Puppet::Parameter) do
@name = :foo
end
@class.initvars
@resource = mock 'resource'
@resource.stub_everything
@parameter = @class.new :resource => @resource
end
it "should create a value collection" do
@class = Class.new(Puppet::Parameter)
@class.value_collection.should be_nil
@class.initvars
@class.value_collection.should be_instance_of(Puppet::Parameter::ValueCollection)
end
it "should return its name as a string when converted to a string" do
@parameter.to_s.should == @parameter.name.to_s
end
it "should be able to use cached attributes" do
Puppet::Parameter.ancestors.should be_include(Puppet::Util::Cacher)
end
it "should use the resource catalog for expiration" do
catalog = mock 'catalog'
@resource.stubs(:catalog).returns catalog
@parameter.expirer.should equal(catalog)
end
[:line, :file, :version].each do |data|
it "should return its resource's #{data} as its #{data}" do
@resource.expects(data).returns "foo"
@parameter.send(data).should == "foo"
end
end
it "should return the resource's tags plus its name as its tags" do
@resource.expects(:tags).returns %w{one two}
@parameter.tags.should == %w{one two foo}
end
it "should provide source_descriptors" do
@resource.expects(:line).returns 10
@resource.expects(:file).returns "file"
@resource.expects(:tags).returns %w{one two}
- @resource.expects(:version).returns 50
- @parameter.source_descriptors.should == {:tags=>["one", "two", "foo"], :path=>"//foo", :version=>50, :file => "file", :line => 10}
+ @parameter.source_descriptors.should == {:tags=>["one", "two", "foo"], :path=>"//foo", :file => "file", :line => 10}
end
describe "when returning the value" do
it "should return nil if no value is set" do
@parameter.value.should be_nil
end
it "should validate the value" do
@parameter.expects(:validate).with("foo")
@parameter.value = "foo"
end
it "should munge the value and use any result as the actual value" do
@parameter.expects(:munge).with("foo").returns "bar"
@parameter.value = "foo"
@parameter.value.should == "bar"
end
it "should unmunge the value when accessing the actual value" do
@parameter.class.unmunge do |value| value.to_sym end
@parameter.value = "foo"
@parameter.value.should == :foo
end
it "should return the actual value by default when unmunging" do
@parameter.unmunge("bar").should == "bar"
end
it "should return any set value" do
@parameter.value = "foo"
@parameter.value.should == "foo"
end
end
describe "when validating values" do
it "should do nothing if no values or regexes have been defined" do
@parameter.validate("foo")
end
it "should catch abnormal failures thrown during validation" do
@class.validate { |v| raise "This is broken" }
lambda { @parameter.validate("eh") }.should raise_error(Puppet::DevError)
end
it "should fail if the value is not a defined value or alias and does not match a regex" do
@class.newvalues :foo
lambda { @parameter.validate("bar") }.should raise_error(Puppet::Error)
end
it "should succeed if the value is one of the defined values" do
@class.newvalues :foo
lambda { @parameter.validate(:foo) }.should_not raise_error(ArgumentError)
end
it "should succeed if the value is one of the defined values even if the definition uses a symbol and the validation uses a string" do
@class.newvalues :foo
lambda { @parameter.validate("foo") }.should_not raise_error(ArgumentError)
end
it "should succeed if the value is one of the defined values even if the definition uses a string and the validation uses a symbol" do
@class.newvalues "foo"
lambda { @parameter.validate(:foo) }.should_not raise_error(ArgumentError)
end
it "should succeed if the value is one of the defined aliases" do
@class.newvalues :foo
@class.aliasvalue :bar, :foo
lambda { @parameter.validate("bar") }.should_not raise_error(ArgumentError)
end
it "should succeed if the value matches one of the regexes" do
@class.newvalues %r{\d}
lambda { @parameter.validate("10") }.should_not raise_error(ArgumentError)
end
end
describe "when munging values" do
it "should do nothing if no values or regexes have been defined" do
@parameter.munge("foo").should == "foo"
end
it "should catch abnormal failures thrown during munging" do
@class.munge { |v| raise "This is broken" }
lambda { @parameter.munge("eh") }.should raise_error(Puppet::DevError)
end
it "should return return any matching defined values" do
@class.newvalues :foo, :bar
@parameter.munge("foo").should == :foo
end
it "should return any matching aliases" do
@class.newvalues :foo
@class.aliasvalue :bar, :foo
@parameter.munge("bar").should == :foo
end
it "should return the value if it matches a regex" do
@class.newvalues %r{\w}
@parameter.munge("bar").should == "bar"
end
it "should return the value if no other option is matched" do
@class.newvalues :foo
@parameter.munge("bar").should == "bar"
end
end
describe "when logging" do
it "should use its resource's log level and the provided message" do
@resource.expects(:[]).with(:loglevel).returns :notice
@parameter.expects(:send_log).with(:notice, "mymessage")
@parameter.log "mymessage"
end
end
end
diff --git a/spec/unit/resource/status_spec.rb b/spec/unit/resource/status_spec.rb
index bda8aea00..4e76fa463 100755
--- a/spec/unit/resource/status_spec.rb
+++ b/spec/unit/resource/status_spec.rb
@@ -1,138 +1,153 @@
#!/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, :version, :file, :line, :current_values, :skipped_reason, :status, :evaluation_time].each do |attr|
+ it "should compute type and title correctly" do
+ @status.resource_type.should == "File"
+ @status.title.should == "/my/file"
+ 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, :version].each do |attr|
+ [: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, :version].each do |attr|
+ [: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
+
+ describe "When converting to YAML" do
+ it "should include only documented attributes" do
+ @status.file = "/foo.rb"
+ @status.line = 27
+ @status.evaluation_time = 2.7
+ @status.tags = %w{one two}
+ @status.to_yaml_properties.should == Puppet::Resource::Status::YAML_ATTRIBUTES.sort
+ end
+ end
end
diff --git a/spec/unit/transaction/event_spec.rb b/spec/unit/transaction/event_spec.rb
index 9cf6bc165..6ed14722b 100755
--- a/spec/unit/transaction/event_spec.rb
+++ b/spec/unit/transaction/event_spec.rb
@@ -1,108 +1,127 @@
#!/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, :node, :version, :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, :version].each do |attr|
+ [: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
+
+ describe "When converting to YAML" do
+ it "should include only documented attributes" do
+ resource = Puppet::Type.type(:file).new(:title => "/tmp/foo")
+ event = Puppet::Transaction::Event.new(:source_description => "/my/param", :resource => resource,
+ :file => "/foo.rb", :line => 27, :tags => %w{one two},
+ :desired_value => 7, :historical_value => 'Brazil',
+ :message => "Help I'm trapped in a spec test",
+ :name => :mode_changed, :previous_value => 6, :property => :mode,
+ :status => 'success')
+ event.to_yaml_properties.should == Puppet::Transaction::Event::YAML_ATTRIBUTES.sort
+ end
+ end
end
diff --git a/spec/unit/transaction/report_spec.rb b/spec/unit/transaction/report_spec.rb
index 34c6ecd96..766d4f14d 100755
--- a/spec/unit/transaction/report_spec.rb
+++ b/spec/unit/transaction/report_spec.rb
@@ -1,248 +1,290 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet/transaction/report'
describe Puppet::Transaction::Report do
before do
Puppet::Util::Storage.stubs(:store)
end
it "should set its host name to the certname" do
Puppet.settings.expects(:value).with(:certname).returns "myhost"
Puppet::Transaction::Report.new("apply").host.should == "myhost"
end
it "should return its host name as its name" do
r = Puppet::Transaction::Report.new("apply")
r.name.should == r.host
end
it "should create an initialization timestamp" do
Time.expects(:now).returns "mytime"
Puppet::Transaction::Report.new("apply").time.should == "mytime"
end
it "should take a 'kind' as an argument" do
Puppet::Transaction::Report.new("inspect").kind.should == "inspect"
end
it "should take a 'configuration_version' as an argument" do
Puppet::Transaction::Report.new("inspect", "some configuration version").configuration_version.should == "some configuration version"
end
it "should be able to set configuration_version" do
report = Puppet::Transaction::Report.new("inspect")
report.configuration_version = "some version"
report.configuration_version.should == "some version"
end
describe "when accepting logs" do
before do
@report = Puppet::Transaction::Report.new("apply")
end
it "should add new logs to the log list" do
@report << "log"
@report.logs[-1].should == "log"
end
it "should return self" do
r = @report << "log"
r.should equal(@report)
end
end
describe "when accepting resource statuses" do
before do
@report = Puppet::Transaction::Report.new("apply")
end
it "should add each status to its status list" do
status = stub 'status', :resource => "foo"
@report.add_resource_status status
@report.resource_statuses["foo"].should equal(status)
end
end
describe "when using the indirector" do
it "should redirect :find to the indirection" do
@indirection = stub 'indirection', :name => :report
Puppet::Transaction::Report.stubs(:indirection).returns(@indirection)
@indirection.expects(:find)
Puppet::Transaction::Report.find(:report)
end
it "should redirect :save to the indirection" do
Facter.stubs(:value).returns("eh")
@indirection = stub 'indirection', :name => :report
Puppet::Transaction::Report.stubs(:indirection).returns(@indirection)
report = Puppet::Transaction::Report.new("apply")
@indirection.expects(:save)
report.save
end
it "should default to the 'processor' terminus" do
Puppet::Transaction::Report.indirection.terminus_class.should == :processor
end
it "should delegate its name attribute to its host method" do
report = Puppet::Transaction::Report.new("apply")
report.expects(:host).returns "me"
report.name.should == "me"
end
after do
Puppet::Util::Cacher.expire
end
end
describe "when computing exit status" do
it "should produce 2 if changes are present" do
report = Puppet::Transaction::Report.new("apply")
- report.add_metric("changes", {:total => 1})
- report.add_metric("resources", {:failed => 0})
+ report.add_metric("changes", {"total" => 1})
+ report.add_metric("resources", {"failed" => 0})
report.exit_status.should == 2
end
it "should produce 4 if failures are present" do
report = Puppet::Transaction::Report.new("apply")
- report.add_metric("changes", {:total => 0})
- report.add_metric("resources", {:failed => 1})
+ report.add_metric("changes", {"total" => 0})
+ report.add_metric("resources", {"failed" => 1})
report.exit_status.should == 4
end
it "should produce 6 if both changes and failures are present" do
report = Puppet::Transaction::Report.new("apply")
- report.add_metric("changes", {:total => 1})
- report.add_metric("resources", {:failed => 1})
+ report.add_metric("changes", {"total" => 1})
+ report.add_metric("resources", {"failed" => 1})
report.exit_status.should == 6
end
end
- describe "when calculating metrics" do
+ describe "before finalizing the report" do
+ it "should have a status of 'failed'" do
+ report = Puppet::Transaction::Report.new("apply")
+ report.status.should == 'failed'
+ end
+ end
+
+ describe "when finalizing the report" do
before do
@report = Puppet::Transaction::Report.new("apply")
end
def metric(name, value)
if metric = @report.metrics[name.to_s]
metric[value]
else
nil
end
end
def add_statuses(count, type = :file)
- 3.times do |i|
+ count.times do |i|
status = Puppet::Resource::Status.new(Puppet::Type.type(type).new(:title => "/my/path#{i}"))
yield status if block_given?
@report.add_resource_status status
end
end
[:time, :resources, :changes, :events].each do |type|
it "should add #{type} metrics" do
- @report.calculate_metrics
+ @report.finalize_report
@report.metrics[type.to_s].should be_instance_of(Puppet::Transaction::Metric)
end
end
describe "for resources" do
it "should provide the total number of resources" do
add_statuses(3)
- @report.calculate_metrics
- metric(:resources, :total).should == 3
+ @report.finalize_report
+ metric(:resources, "total").should == 3
end
Puppet::Resource::Status::STATES.each do |state|
it "should provide the number of #{state} resources as determined by the status objects" do
add_statuses(3) { |status| status.send(state.to_s + "=", true) }
- @report.calculate_metrics
- metric(:resources, state).should == 3
+ @report.finalize_report
+ metric(:resources, state.to_s).should == 3
end
end
+
+ it "should mark the report as 'failed' if there are failing resources" do
+ add_statuses(1) { |status| status.failed = true }
+ @report.finalize_report
+ @report.status.should == 'failed'
+ end
end
describe "for changes" do
- it "should provide the number of changes from the resource statuses" do
+ it "should provide the number of changes from the resource statuses and mark the report as 'changed'" do
add_statuses(3) { |status| 3.times { status << Puppet::Transaction::Event.new(:status => 'success') } }
- @report.calculate_metrics
- metric(:changes, :total).should == 9
+ @report.finalize_report
+ metric(:changes, "total").should == 9
+ @report.status.should == 'changed'
+ end
+
+ it "should provide a total even if there are no changes, and mark the report as 'unchanged'" do
+ @report.finalize_report
+ metric(:changes, "total").should == 0
+ @report.status.should == 'unchanged'
end
end
describe "for times" do
it "should provide the total amount of time for each resource type" do
add_statuses(3, :file) do |status|
status.evaluation_time = 1
end
add_statuses(3, :exec) do |status|
status.evaluation_time = 2
end
add_statuses(3, :mount) do |status|
status.evaluation_time = 3
end
- @report.calculate_metrics
+ @report.finalize_report
metric(:time, "file").should == 3
metric(:time, "exec").should == 6
metric(:time, "mount").should == 9
end
it "should add any provided times from external sources" do
@report.add_times :foobar, 50
- @report.calculate_metrics
+ @report.finalize_report
metric(:time, "foobar").should == 50
end
+
+ it "should have a total time" do
+ add_statuses(3, :file) do |status|
+ status.evaluation_time = 1.25
+ end
+ @report.add_times :config_retrieval, 0.5
+ @report.finalize_report
+ metric(:time, "total").should == 4.25
+ end
end
describe "for events" do
it "should provide the total number of events" do
add_statuses(3) do |status|
- 3.times { |i| status.add_event(Puppet::Transaction::Event.new) }
+ 3.times { |i| status.add_event(Puppet::Transaction::Event.new :status => 'success') }
end
- @report.calculate_metrics
- metric(:events, :total).should == 9
+ @report.finalize_report
+ metric(:events, "total").should == 9
+ end
+
+ it "should provide the total even if there are no events" do
+ @report.finalize_report
+ metric(:events, "total").should == 0
end
Puppet::Transaction::Event::EVENT_STATUSES.each do |status_name|
it "should provide the number of #{status_name} events" do
add_statuses(3) do |status|
3.times do |i|
event = Puppet::Transaction::Event.new
event.status = status_name
status.add_event(event)
end
end
- @report.calculate_metrics
+ @report.finalize_report
metric(:events, status_name).should == 9
end
end
end
end
describe "when producing a summary" do
before do
resource = Puppet::Type.type(:notify).new(:name => "testing")
catalog = Puppet::Resource::Catalog.new
catalog.add_resource resource
trans = catalog.apply
@report = trans.report
- @report.calculate_metrics
+ @report.finalize_report
end
%w{Changes Total Resources}.each do |main|
it "should include information on #{main} in the summary" do
@report.summary.should be_include(main)
end
end
end
+
+ describe "when outputting yaml" do
+ it "should not include @external_times" do
+ report = Puppet::Transaction::Report.new('apply')
+ report.add_times('config_retrieval', 1.0)
+ report.to_yaml_properties.should_not include('@external_times')
+ 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
diff --git a/spec/unit/transaction_spec.rb b/spec/unit/transaction_spec.rb
index 566c90438..862413a31 100755
--- a/spec/unit/transaction_spec.rb
+++ b/spec/unit/transaction_spec.rb
@@ -1,452 +1,447 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../spec_helper'
require 'puppet/transaction'
def without_warnings
flag = $VERBOSE
$VERBOSE = nil
yield
$VERBOSE = flag
end
describe Puppet::Transaction do
before do
@basepath = Puppet.features.posix? ? "/what/ever" : "C:/tmp"
@transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new)
end
it "should delegate its event list to the event manager" do
@transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new)
@transaction.event_manager.expects(:events).returns %w{my events}
@transaction.events.should == %w{my events}
end
it "should delegate adding times to its report" do
@transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new)
@transaction.report.expects(:add_times).with(:foo, 10)
@transaction.report.expects(:add_times).with(:bar, 20)
@transaction.add_times :foo => 10, :bar => 20
end
it "should be able to accept resource status instances" do
resource = Puppet::Type.type(:notify).new :title => "foobar"
status = Puppet::Resource::Status.new(resource)
@transaction.add_resource_status(status)
@transaction.resource_status(resource).should equal(status)
end
it "should be able to look resource status up by resource reference" do
resource = Puppet::Type.type(:notify).new :title => "foobar"
status = Puppet::Resource::Status.new(resource)
@transaction.add_resource_status(status)
@transaction.resource_status(resource.to_s).should equal(status)
end
# This will basically only ever be used during testing.
it "should automatically create resource statuses if asked for a non-existent status" do
resource = Puppet::Type.type(:notify).new :title => "foobar"
@transaction.resource_status(resource).should be_instance_of(Puppet::Resource::Status)
end
it "should add provided resource statuses to its report" do
resource = Puppet::Type.type(:notify).new :title => "foobar"
status = Puppet::Resource::Status.new(resource)
@transaction.add_resource_status(status)
@transaction.report.resource_statuses[resource.to_s].should equal(status)
end
- it "should calculate metrics on and report the report when asked to generate a report" do
- @transaction.report.expects(:calculate_metrics)
- @transaction.generate_report.should equal(@transaction.report)
- end
-
it "should consider a resource to be failed if a status instance exists for that resource and indicates it is failed" do
resource = Puppet::Type.type(:notify).new :name => "yayness"
status = Puppet::Resource::Status.new(resource)
status.failed = "some message"
@transaction.add_resource_status(status)
@transaction.should be_failed(resource)
end
it "should not consider a resource to be failed if a status instance exists for that resource but indicates it is not failed" do
resource = Puppet::Type.type(:notify).new :name => "yayness"
status = Puppet::Resource::Status.new(resource)
@transaction.add_resource_status(status)
@transaction.should_not be_failed(resource)
end
it "should consider there to be failed resources if any statuses are marked failed" do
resource = Puppet::Type.type(:notify).new :name => "yayness"
status = Puppet::Resource::Status.new(resource)
status.failed = "some message"
@transaction.add_resource_status(status)
@transaction.should be_any_failed
end
it "should not consider there to be failed resources if no statuses are marked failed" do
resource = Puppet::Type.type(:notify).new :name => "yayness"
status = Puppet::Resource::Status.new(resource)
@transaction.add_resource_status(status)
@transaction.should_not be_any_failed
end
it "should be possible to replace the report object" do
report = Puppet::Transaction::Report.new("apply")
@transaction.report = report
@transaction.report.should == report
end
it "should consider a resource to have failed dependencies if any of its dependencies are failed"
describe "when initializing" do
it "should create an event manager" do
@transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new)
@transaction.event_manager.should be_instance_of(Puppet::Transaction::EventManager)
@transaction.event_manager.transaction.should equal(@transaction)
end
it "should create a resource harness" do
@transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new)
@transaction.resource_harness.should be_instance_of(Puppet::Transaction::ResourceHarness)
@transaction.resource_harness.transaction.should equal(@transaction)
end
end
describe "when evaluating a resource" do
before do
@transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new)
@transaction.stubs(:eval_children_and_apply_resource)
@transaction.stubs(:skip?).returns false
@resource = Puppet::Type.type(:file).new :path => @basepath
end
it "should check whether the resource should be skipped" do
@transaction.expects(:skip?).with(@resource).returns false
@transaction.eval_resource(@resource)
end
it "should eval and apply children" do
@transaction.expects(:eval_children_and_apply_resource).with(@resource, nil)
@transaction.eval_resource(@resource)
end
it "should process events" do
@transaction.event_manager.expects(:process_events).with(@resource)
@transaction.eval_resource(@resource)
end
describe "and the resource should be skipped" do
before do
@transaction.expects(:skip?).with(@resource).returns true
end
it "should mark the resource's status as skipped" do
@transaction.eval_resource(@resource)
@transaction.resource_status(@resource).should be_skipped
end
end
end
describe "when applying a resource" do
before do
@resource = Puppet::Type.type(:file).new :path => @basepath
@status = Puppet::Resource::Status.new(@resource)
@transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new)
@transaction.event_manager.stubs(:queue_events)
@transaction.resource_harness.stubs(:evaluate).returns(@status)
end
it "should use its resource harness to apply the resource" do
@transaction.resource_harness.expects(:evaluate).with(@resource)
@transaction.apply(@resource)
end
it "should add the resulting resource status to its status list" do
@transaction.apply(@resource)
@transaction.resource_status(@resource).should be_instance_of(Puppet::Resource::Status)
end
it "should queue any events added to the resource status" do
@status.expects(:events).returns %w{a b}
@transaction.event_manager.expects(:queue_events).with(@resource, ["a", "b"])
@transaction.apply(@resource)
end
it "should log and skip any resources that cannot be applied" do
@transaction.resource_harness.expects(:evaluate).raises ArgumentError
@resource.expects(:err)
@transaction.apply(@resource)
@transaction.report.resource_statuses[@resource.to_s].should be_nil
end
end
describe "when generating resources" do
it "should call 'generate' on all created resources" do
first = Puppet::Type.type(:notify).new(:name => "first")
second = Puppet::Type.type(:notify).new(:name => "second")
third = Puppet::Type.type(:notify).new(:name => "third")
@catalog = Puppet::Resource::Catalog.new
@transaction = Puppet::Transaction.new(@catalog)
first.expects(:generate).returns [second]
second.expects(:generate).returns [third]
third.expects(:generate)
@transaction.generate_additional_resources(first, :generate)
end
it "should finish all resources" do
generator = stub 'generator', :depthfirst? => true, :tags => []
resource = stub 'resource', :tag => nil
@catalog = Puppet::Resource::Catalog.new
@transaction = Puppet::Transaction.new(@catalog)
generator.expects(:generate).returns [resource]
@catalog.expects(:add_resource).yields(resource)
resource.expects(:finish)
@transaction.generate_additional_resources(generator, :generate)
end
it "should skip generated resources that conflict with existing resources" do
generator = mock 'generator', :tags => []
resource = stub 'resource', :tag => nil
@catalog = Puppet::Resource::Catalog.new
@transaction = Puppet::Transaction.new(@catalog)
generator.expects(:generate).returns [resource]
@catalog.expects(:add_resource).raises(Puppet::Resource::Catalog::DuplicateResourceError.new("foo"))
resource.expects(:finish).never
resource.expects(:info) # log that it's skipped
@transaction.generate_additional_resources(generator, :generate).should be_empty
end
it "should copy all tags to the newly generated resources" do
child = stub 'child'
generator = stub 'resource', :tags => ["one", "two"]
@catalog = Puppet::Resource::Catalog.new
@transaction = Puppet::Transaction.new(@catalog)
generator.stubs(:generate).returns [child]
@catalog.stubs(:add_resource)
child.expects(:tag).with("one", "two")
@transaction.generate_additional_resources(generator, :generate)
end
end
describe "when skipping a resource" do
before :each do
@resource = Puppet::Type.type(:notify).new :name => "foo"
@catalog = Puppet::Resource::Catalog.new
@resource.catalog = @catalog
@transaction = Puppet::Transaction.new(@catalog)
end
it "should skip resource with missing tags" do
@transaction.stubs(:missing_tags?).returns(true)
@transaction.should be_skip(@resource)
end
it "should skip unscheduled resources" do
@transaction.stubs(:scheduled?).returns(false)
@transaction.should be_skip(@resource)
end
it "should skip resources with failed dependencies" do
@transaction.stubs(:failed_dependencies?).returns(true)
@transaction.should be_skip(@resource)
end
it "should skip virtual resource" do
@resource.stubs(:virtual?).returns true
@transaction.should be_skip(@resource)
end
end
describe "when determining if tags are missing" do
before :each do
@resource = Puppet::Type.type(:notify).new :name => "foo"
@catalog = Puppet::Resource::Catalog.new
@resource.catalog = @catalog
@transaction = Puppet::Transaction.new(@catalog)
@transaction.stubs(:ignore_tags?).returns false
end
it "should not be missing tags if tags are being ignored" do
@transaction.expects(:ignore_tags?).returns true
@resource.expects(:tagged?).never
@transaction.should_not be_missing_tags(@resource)
end
it "should not be missing tags if the transaction tags are empty" do
@transaction.tags = []
@resource.expects(:tagged?).never
@transaction.should_not be_missing_tags(@resource)
end
it "should otherwise let the resource determine if it is missing tags" do
tags = ['one', 'two']
@transaction.tags = tags
@resource.expects(:tagged?).with(*tags).returns(false)
@transaction.should be_missing_tags(@resource)
end
end
describe "when determining if a resource should be scheduled" do
before :each do
@resource = Puppet::Type.type(:notify).new :name => "foo"
@catalog = Puppet::Resource::Catalog.new
@resource.catalog = @catalog
@transaction = Puppet::Transaction.new(@catalog)
end
it "should always schedule resources if 'ignoreschedules' is set" do
@transaction.ignoreschedules = true
@transaction.resource_harness.expects(:scheduled?).never
@transaction.should be_scheduled(@resource)
end
it "should let the resource harness determine whether the resource should be scheduled" do
@transaction.resource_harness.expects(:scheduled?).with(@transaction.resource_status(@resource), @resource).returns "feh"
@transaction.scheduled?(@resource).should == "feh"
end
end
describe "when prefetching" do
it "should match resources by name, not title" do
@catalog = Puppet::Resource::Catalog.new
@transaction = Puppet::Transaction.new(@catalog)
# Have both a title and name
resource = Puppet::Type.type(:sshkey).create :title => "foo", :name => "bar", :type => :dsa, :key => "eh"
@catalog.add_resource resource
resource.provider.class.expects(:prefetch).with("bar" => resource)
@transaction.prefetch
end
end
it "should return all resources for which the resource status indicates the resource has changed when determinig changed resources" do
@catalog = Puppet::Resource::Catalog.new
@transaction = Puppet::Transaction.new(@catalog)
names = []
2.times do |i|
name = File.join(@basepath, "file#{i}")
resource = Puppet::Type.type(:file).new :path => name
names << resource.to_s
@catalog.add_resource resource
@transaction.add_resource_status Puppet::Resource::Status.new(resource)
end
@transaction.resource_status(names[0]).changed = true
@transaction.changed?.should == [@catalog.resource(names[0])]
end
describe 'when checking application run state' do
before do
without_warnings { Puppet::Application = Class.new(Puppet::Application) }
@catalog = Puppet::Resource::Catalog.new
@transaction = Puppet::Transaction.new(@catalog)
end
after do
without_warnings { Puppet::Application = Puppet::Application.superclass }
end
it 'should return true for :stop_processing? if Puppet::Application.stop_requested? is true' do
Puppet::Application.stubs(:stop_requested?).returns(true)
@transaction.stop_processing?.should be_true
end
it 'should return false for :stop_processing? if Puppet::Application.stop_requested? is false' do
Puppet::Application.stubs(:stop_requested?).returns(false)
@transaction.stop_processing?.should be_false
end
describe 'within an evaluate call' do
before do
@resource = stub 'resource', :ref => 'some_ref'
@catalog.add_resource @resource
@transaction.stubs(:prepare)
@transaction.sorted_resources = [@resource]
end
it 'should stop processing if :stop_processing? is true' do
@transaction.expects(:stop_processing?).returns(true)
@transaction.expects(:eval_resource).never
@transaction.evaluate
end
it 'should continue processing if :stop_processing? is false' do
@transaction.expects(:stop_processing?).returns(false)
@transaction.expects(:eval_resource).returns(nil)
@transaction.evaluate
end
end
end
end
describe Puppet::Transaction, " when determining tags" do
before do
@config = Puppet::Resource::Catalog.new
@transaction = Puppet::Transaction.new(@config)
end
it "should default to the tags specified in the :tags setting" do
Puppet.expects(:[]).with(:tags).returns("one")
@transaction.tags.should == %w{one}
end
it "should split tags based on ','" do
Puppet.expects(:[]).with(:tags).returns("one,two")
@transaction.tags.should == %w{one two}
end
it "should use any tags set after creation" do
Puppet.expects(:[]).with(:tags).never
@transaction.tags = %w{one two}
@transaction.tags.should == %w{one two}
end
it "should always convert assigned tags to an array" do
@transaction.tags = "one::two"
@transaction.tags.should == %w{one::two}
end
it "should accept a comma-delimited string" do
@transaction.tags = "one, two"
@transaction.tags.should == %w{one two}
end
it "should accept an empty string" do
@transaction.tags = ""
@transaction.tags.should == []
end
end
diff --git a/spec/unit/type_spec.rb b/spec/unit/type_spec.rb
index 48b00ec4a..b7a08977e 100755
--- a/spec/unit/type_spec.rb
+++ b/spec/unit/type_spec.rb
@@ -1,573 +1,573 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../spec_helper'
describe Puppet::Type do
it "should include the Cacher module" do
Puppet::Type.ancestors.should be_include(Puppet::Util::Cacher)
end
it "should consider a parameter to be valid if it is a valid parameter" do
Puppet::Type.type(:mount).should be_valid_parameter(:path)
end
it "should consider a parameter to be valid if it is a valid property" do
Puppet::Type.type(:mount).should be_valid_parameter(:fstype)
end
it "should consider a parameter to be valid if it is a valid metaparam" do
Puppet::Type.type(:mount).should be_valid_parameter(:noop)
end
it "should use its catalog as its expirer" do
catalog = Puppet::Resource::Catalog.new
resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present)
resource.catalog = catalog
resource.expirer.should equal(catalog)
end
it "should do nothing when asked to expire when it has no catalog" do
resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present)
lambda { resource.expire }.should_not raise_error
end
it "should be able to retrieve a property by name" do
resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present)
resource.property(:fstype).must be_instance_of(Puppet::Type.type(:mount).attrclass(:fstype))
end
it "should be able to retrieve a parameter by name" do
resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present)
resource.parameter(:name).must be_instance_of(Puppet::Type.type(:mount).attrclass(:name))
end
it "should be able to retrieve a property by name using the :parameter method" do
resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present)
resource.parameter(:fstype).must be_instance_of(Puppet::Type.type(:mount).attrclass(:fstype))
end
it "should be able to retrieve all set properties" do
resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present)
props = resource.properties
props.should_not be_include(nil)
[:fstype, :ensure, :pass].each do |name|
props.should be_include(resource.parameter(name))
end
end
it "should have a method for setting default values for resources" do
Puppet::Type.type(:mount).new(:name => "foo").should respond_to(:set_default)
end
it "should do nothing for attributes that have no defaults and no specified value" do
Puppet::Type.type(:mount).new(:name => "foo").parameter(:noop).should be_nil
end
it "should have a method for adding tags" do
Puppet::Type.type(:mount).new(:name => "foo").should respond_to(:tags)
end
it "should use the tagging module" do
Puppet::Type.type(:mount).ancestors.should be_include(Puppet::Util::Tagging)
end
it "should delegate to the tagging module when tags are added" do
resource = Puppet::Type.type(:mount).new(:name => "foo")
resource.stubs(:tag).with(:mount)
resource.expects(:tag).with(:tag1, :tag2)
resource.tags = [:tag1,:tag2]
end
it "should add the current type as tag" do
resource = Puppet::Type.type(:mount).new(:name => "foo")
resource.stubs(:tag)
resource.expects(:tag).with(:mount)
resource.tags = [:tag1,:tag2]
end
it "should have a method to know if the resource is exported" do
Puppet::Type.type(:mount).new(:name => "foo").should respond_to(:exported?)
end
it "should have a method to know if the resource is virtual" do
Puppet::Type.type(:mount).new(:name => "foo").should respond_to(:virtual?)
end
it "should consider its version to be its catalog version" do
resource = Puppet::Type.type(:mount).new(:name => "foo")
catalog = Puppet::Resource::Catalog.new
catalog.version = 50
catalog.add_resource resource
resource.version.should == 50
end
it "should consider its version to be zero if it has no catalog" do
Puppet::Type.type(:mount).new(:name => "foo").version.should == 0
end
it "should provide source_descriptors" do
resource = Puppet::Type.type(:mount).new(:name => "foo")
catalog = Puppet::Resource::Catalog.new
catalog.version = 50
catalog.add_resource resource
- resource.source_descriptors.should == {:version=>50, :tags=>["mount", "foo"], :path=>"/Mount[foo]"}
+ resource.source_descriptors.should == {:tags=>["mount", "foo"], :path=>"/Mount[foo]"}
end
it "should consider its type to be the name of its class" do
Puppet::Type.type(:mount).new(:name => "foo").type.should == :mount
end
it "should use any provided noop value" do
Puppet::Type.type(:mount).new(:name => "foo", :noop => true).must be_noop
end
it "should use the global noop value if none is provided" do
Puppet[:noop] = true
Puppet::Type.type(:mount).new(:name => "foo").must be_noop
end
it "should not be noop if in a non-host_config catalog" do
resource = Puppet::Type.type(:mount).new(:name => "foo")
catalog = Puppet::Resource::Catalog.new
catalog.add_resource resource
resource.should_not be_noop
end
describe "when creating an event" do
before do
@resource = Puppet::Type.type(:mount).new :name => "foo"
end
it "should have the resource's reference as the resource" do
@resource.event.resource.should == "Mount[foo]"
end
it "should have the resource's log level as the default log level" do
@resource[:loglevel] = :warning
@resource.event.default_log_level.should == :warning
end
- {:file => "/my/file", :line => 50, :tags => %{foo bar}, :version => 50}.each do |attr, value|
+ {:file => "/my/file", :line => 50, :tags => %{foo bar}}.each do |attr, value|
it "should set the #{attr}" do
@resource.stubs(attr).returns value
@resource.event.send(attr).should == value
end
end
it "should allow specification of event attributes" do
@resource.event(:status => "noop").status.should == "noop"
end
end
describe "when choosing a default provider" do
it "should choose the provider with the highest specificity" do
# Make a fake type
type = Puppet::Type.newtype(:defaultprovidertest) do
newparam(:name) do end
end
basic = type.provide(:basic) {}
greater = type.provide(:greater) {}
basic.stubs(:specificity).returns 1
greater.stubs(:specificity).returns 2
type.defaultprovider.should equal(greater)
end
end
describe "when initializing" do
describe "and passed a TransObject" do
it "should fail" do
trans = Puppet::TransObject.new("/foo", :mount)
lambda { Puppet::Type.type(:mount).new(trans) }.should raise_error(Puppet::DevError)
end
end
describe "and passed a Puppet::Resource instance" do
it "should set its title to the title of the resource if the resource type is equal to the current type" do
resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:name => "/other"})
Puppet::Type.type(:mount).new(resource).title.should == "/foo"
end
it "should set its title to the resource reference if the resource type is not equal to the current type" do
resource = Puppet::Resource.new(:user, "foo")
Puppet::Type.type(:mount).new(resource).title.should == "User[foo]"
end
[:line, :file, :catalog, :exported, :virtual].each do |param|
it "should copy '#{param}' from the resource if present" do
resource = Puppet::Resource.new(:mount, "/foo")
resource.send(param.to_s + "=", "foo")
resource.send(param.to_s + "=", "foo")
Puppet::Type.type(:mount).new(resource).send(param).should == "foo"
end
end
it "should copy any tags from the resource" do
resource = Puppet::Resource.new(:mount, "/foo")
resource.tag "one", "two"
tags = Puppet::Type.type(:mount).new(resource).tags
tags.should be_include("one")
tags.should be_include("two")
end
it "should copy the resource's parameters as its own" do
resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:atboot => true, :fstype => "boo"})
params = Puppet::Type.type(:mount).new(resource).to_hash
params[:fstype].should == "boo"
params[:atboot].should == true
end
end
describe "and passed a Hash" do
it "should extract the title from the hash" do
Puppet::Type.type(:mount).new(:title => "/yay").title.should == "/yay"
end
it "should work when hash keys are provided as strings" do
Puppet::Type.type(:mount).new("title" => "/yay").title.should == "/yay"
end
it "should work when hash keys are provided as symbols" do
Puppet::Type.type(:mount).new(:title => "/yay").title.should == "/yay"
end
it "should use the name from the hash as the title if no explicit title is provided" do
Puppet::Type.type(:mount).new(:name => "/yay").title.should == "/yay"
end
it "should use the Resource Type's namevar to determine how to find the name in the hash" do
Puppet::Type.type(:file).new(:path => "/yay").title.should == "/yay"
end
[:catalog].each do |param|
it "should extract '#{param}' from the hash if present" do
Puppet::Type.type(:mount).new(:name => "/yay", param => "foo").send(param).should == "foo"
end
end
it "should use any remaining hash keys as its parameters" do
resource = Puppet::Type.type(:mount).new(:title => "/foo", :catalog => "foo", :atboot => true, :fstype => "boo")
resource[:fstype].must == "boo"
resource[:atboot].must == true
end
end
it "should fail if any invalid attributes have been provided" do
lambda { Puppet::Type.type(:mount).new(:title => "/foo", :nosuchattr => "whatever") }.should raise_error(Puppet::Error)
end
it "should set its name to the resource's title if the resource does not have a :name or namevar parameter set" do
resource = Puppet::Resource.new(:mount, "/foo")
Puppet::Type.type(:mount).new(resource).name.should == "/foo"
end
it "should fail if no title, name, or namevar are provided" do
lambda { Puppet::Type.type(:file).new(:atboot => true) }.should raise_error(Puppet::Error)
end
it "should set the attributes in the order returned by the class's :allattrs method" do
Puppet::Type.type(:mount).stubs(:allattrs).returns([:name, :atboot, :noop])
resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:name => "myname", :atboot => "myboot", :noop => "whatever"})
set = []
Puppet::Type.type(:mount).any_instance.stubs(:newattr).with do |param, hash|
set << param
true
end.returns(stub_everything("a property"))
Puppet::Type.type(:mount).new(resource)
set[-1].should == :noop
set[-2].should == :atboot
end
it "should always set the name and then default provider before anything else" do
Puppet::Type.type(:mount).stubs(:allattrs).returns([:provider, :name, :atboot])
resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:name => "myname", :atboot => "myboot"})
set = []
Puppet::Type.type(:mount).any_instance.stubs(:newattr).with do |param, hash|
set << param
true
end.returns(stub_everything("a property"))
Puppet::Type.type(:mount).new(resource)
set[0].should == :name
set[1].should == :provider
end
# This one is really hard to test :/
it "should each default immediately if no value is provided" do
defaults = []
Puppet::Type.type(:package).any_instance.stubs(:set_default).with { |value| defaults << value; true }
Puppet::Type.type(:package).new :name => "whatever"
defaults[0].should == :provider
end
it "should retain a copy of the originally provided parameters" do
Puppet::Type.type(:mount).new(:name => "foo", :atboot => true, :noop => false).original_parameters.should == {:atboot => true, :noop => false}
end
it "should delete the name via the namevar from the originally provided parameters" do
Puppet::Type.type(:file).new(:name => "/foo").original_parameters[:path].should be_nil
end
end
it "should have a class method for converting a hash into a Puppet::Resource instance" do
Puppet::Type.type(:mount).must respond_to(:hash2resource)
end
describe "when converting a hash to a Puppet::Resource instance" do
before do
@type = Puppet::Type.type(:mount)
end
it "should treat a :title key as the title of the resource" do
@type.hash2resource(:name => "/foo", :title => "foo").title.should == "foo"
end
it "should use the name from the hash as the title if no explicit title is provided" do
@type.hash2resource(:name => "foo").title.should == "foo"
end
it "should use the Resource Type's namevar to determine how to find the name in the hash" do
@type.stubs(:key_attributes).returns([ :myname ])
@type.hash2resource(:myname => "foo").title.should == "foo"
end
[:catalog].each do |attr|
it "should use any provided #{attr}" do
@type.hash2resource(:name => "foo", attr => "eh").send(attr).should == "eh"
end
end
it "should set all provided parameters on the resource" do
@type.hash2resource(:name => "foo", :fstype => "boo", :boot => "fee").to_hash.should == {:name => "foo", :fstype => "boo", :boot => "fee"}
end
it "should not set the title as a parameter on the resource" do
@type.hash2resource(:name => "foo", :title => "eh")[:title].should be_nil
end
it "should not set the catalog as a parameter on the resource" do
@type.hash2resource(:name => "foo", :catalog => "eh")[:catalog].should be_nil
end
it "should treat hash keys equivalently whether provided as strings or symbols" do
resource = @type.hash2resource("name" => "foo", "title" => "eh", "fstype" => "boo")
resource.title.should == "eh"
resource[:name].should == "foo"
resource[:fstype].should == "boo"
end
end
describe "when retrieving current property values" do
before do
@resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present)
@resource.property(:ensure).stubs(:retrieve).returns :absent
end
it "should fail if its provider is unsuitable" do
@resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present)
@resource.provider.class.expects(:suitable?).returns false
lambda { @resource.retrieve_resource }.should raise_error(Puppet::Error)
end
it "should return a Puppet::Resource instance with its type and title set appropriately" do
result = @resource.retrieve_resource
result.should be_instance_of(Puppet::Resource)
result.type.should == "Mount"
result.title.should == "foo"
end
it "should set the name of the returned resource if its own name and title differ" do
@resource[:name] = "my name"
@resource.title = "other name"
@resource.retrieve_resource[:name].should == "my name"
end
it "should provide a value for all set properties" do
values = @resource.retrieve_resource
[:ensure, :fstype, :pass].each { |property| values[property].should_not be_nil }
end
it "should provide a value for 'ensure' even if no desired value is provided" do
@resource = Puppet::Type.type(:file).new(:path => "/my/file/that/can't/exist")
end
it "should not call retrieve on non-ensure properties if the resource is absent and should consider the property absent" do
@resource.property(:ensure).expects(:retrieve).returns :absent
@resource.property(:fstype).expects(:retrieve).never
@resource.retrieve_resource[:fstype].should == :absent
end
it "should include the result of retrieving each property's current value if the resource is present" do
@resource.property(:ensure).expects(:retrieve).returns :present
@resource.property(:fstype).expects(:retrieve).returns 15
@resource.retrieve_resource[:fstype] == 15
end
end
describe ".title_patterns" do
describe "when there's one namevar" do
before do
@type_class = Puppet::Type.type(:notify)
@type_class.stubs(:key_attributes).returns([:one])
end
it "should have a default pattern for when there's one namevar" do
patterns = @type_class.title_patterns
patterns.length.should == 1
patterns[0].length.should == 2
end
it "should have a regexp that captures the entire string" do
patterns = @type_class.title_patterns
string = "abc\n\tdef"
patterns[0][0] =~ string
$1.should == "abc\n\tdef"
end
end
end
describe "when in a catalog" do
before do
@catalog = Puppet::Resource::Catalog.new
@container = Puppet::Type.type(:component).new(:name => "container")
@one = Puppet::Type.type(:file).new(:path => "/file/one")
@two = Puppet::Type.type(:file).new(:path => "/file/two")
@catalog.add_resource @container
@catalog.add_resource @one
@catalog.add_resource @two
@catalog.add_edge @container, @one
@catalog.add_edge @container, @two
end
it "should have no parent if there is no in edge" do
@container.parent.should be_nil
end
it "should set its parent to its in edge" do
@one.parent.ref.should == @container.ref
end
after do
@catalog.clear(true)
end
end
it "should have a 'stage' metaparam" do
Puppet::Type.metaparamclass(:stage).should be_instance_of(Class)
end
end
describe Puppet::Type::RelationshipMetaparam do
it "should be a subclass of Puppet::Parameter" do
Puppet::Type::RelationshipMetaparam.superclass.should equal(Puppet::Parameter)
end
it "should be able to produce a list of subclasses" do
Puppet::Type::RelationshipMetaparam.should respond_to(:subclasses)
end
describe "when munging relationships" do
before do
@resource = Puppet::Type.type(:mount).new :name => "/foo"
@metaparam = Puppet::Type.metaparamclass(:require).new :resource => @resource
end
it "should accept Puppet::Resource instances" do
ref = Puppet::Resource.new(:file, "/foo")
@metaparam.munge(ref)[0].should equal(ref)
end
it "should turn any string into a Puppet::Resource" do
@metaparam.munge("File[/ref]")[0].should be_instance_of(Puppet::Resource)
end
end
it "should be able to validate relationships" do
Puppet::Type.metaparamclass(:require).new(:resource => mock("resource")).should respond_to(:validate_relationship)
end
it "should fail if any specified resource is not found in the catalog" do
catalog = mock 'catalog'
resource = stub 'resource', :catalog => catalog, :ref => "resource"
param = Puppet::Type.metaparamclass(:require).new(:resource => resource, :value => %w{Foo[bar] Class[test]})
catalog.expects(:resource).with("Foo[bar]").returns "something"
catalog.expects(:resource).with("Class[Test]").returns nil
param.expects(:fail).with { |string| string.include?("Class[Test]") }
param.validate_relationship
end
end
describe Puppet::Type.metaparamclass(:check) do
it "should warn and create an instance of ':audit'" do
file = Puppet::Type.type(:file).new :path => "/foo"
file.expects(:warning)
file[:check] = :mode
file[:audit].should == [:mode]
end
end
describe Puppet::Type.metaparamclass(:audit) do
before do
@resource = Puppet::Type.type(:file).new :path => "/foo"
end
it "should default to being nil" do
@resource[:audit].should be_nil
end
it "should specify all possible properties when asked to audit all properties" do
@resource[:audit] = :all
list = @resource.class.properties.collect { |p| p.name }
@resource[:audit].should == list
end
it "should accept the string 'all' to specify auditing all possible properties" do
@resource[:audit] = 'all'
list = @resource.class.properties.collect { |p| p.name }
@resource[:audit].should == list
end
it "should fail if asked to audit an invalid property" do
lambda { @resource[:audit] = :foobar }.should raise_error(Puppet::Error)
end
it "should create an attribute instance for each auditable property" do
@resource[:audit] = :mode
@resource.parameter(:mode).should_not be_nil
end
it "should accept properties specified as a string" do
@resource[:audit] = "mode"
@resource.parameter(:mode).should_not be_nil
end
it "should not create attribute instances for parameters, only properties" do
@resource[:audit] = :noop
@resource.parameter(:noop).should be_nil
end
end
diff --git a/spec/unit/util/log_spec.rb b/spec/unit/util/log_spec.rb
index 7d96fe190..f3fd1b051 100755
--- a/spec/unit/util/log_spec.rb
+++ b/spec/unit/util/log_spec.rb
@@ -1,215 +1,222 @@
#!/usr/bin/env ruby
Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") }
require 'puppet/util/log'
describe Puppet::Util::Log do
it "should write a given message to the specified destination" do
arraydest = []
Puppet::Util::Log.newdestination(arraydest)
Puppet::Util::Log.new(:level => :notice, :message => "foo")
message = arraydest.last.message
message.should == "foo"
end
describe Puppet::Util::Log::DestConsole do
before do
@console = Puppet::Util::Log::DestConsole.new
end
it "should colorize if Puppet[:color] is :ansi" do
Puppet[:color] = :ansi
@console.colorize(:alert, "abc").should == "\e[0;31mabc\e[0m"
end
it "should colorize if Puppet[:color] is 'yes'" do
Puppet[:color] = "yes"
@console.colorize(:alert, "abc").should == "\e[0;31mabc\e[0m"
end
it "should htmlize if Puppet[:color] is :html" do
Puppet[:color] = :html
@console.colorize(:alert, "abc").should == "abc"
end
it "should do nothing if Puppet[:color] is false" do
Puppet[:color] = false
@console.colorize(:alert, "abc").should == "abc"
end
it "should do nothing if Puppet[:color] is invalid" do
Puppet[:color] = "invalid option"
@console.colorize(:alert, "abc").should == "abc"
end
end
describe "instances" do
before do
Puppet::Util::Log.stubs(:newmessage)
end
[:level, :message, :time, :remote].each do |attr|
it "should have a #{attr} attribute" do
log = Puppet::Util::Log.new :level => :notice, :message => "A test message"
log.should respond_to(attr)
log.should respond_to(attr.to_s + "=")
end
end
it "should fail if created without a level" do
lambda { Puppet::Util::Log.new(:message => "A test message") }.should raise_error(ArgumentError)
end
it "should fail if created without a message" do
lambda { Puppet::Util::Log.new(:level => :notice) }.should raise_error(ArgumentError)
end
it "should make available the level passed in at initialization" do
Puppet::Util::Log.new(:level => :notice, :message => "A test message").level.should == :notice
end
it "should make available the message passed in at initialization" do
Puppet::Util::Log.new(:level => :notice, :message => "A test message").message.should == "A test message"
end
# LAK:NOTE I don't know why this behavior is here, I'm just testing what's in the code,
# at least at first.
it "should always convert messages to strings" do
Puppet::Util::Log.new(:level => :notice, :message => :foo).message.should == "foo"
end
it "should flush the log queue when the first destination is specified" do
Puppet::Util::Log.close_all
Puppet::Util::Log.expects(:flushqueue)
Puppet::Util::Log.newdestination([])
end
it "should convert the level to a symbol if it's passed in as a string" do
Puppet::Util::Log.new(:level => "notice", :message => :foo).level.should == :notice
end
it "should fail if the level is not a symbol or string" do
lambda { Puppet::Util::Log.new(:level => 50, :message => :foo) }.should raise_error(ArgumentError)
end
it "should fail if the provided level is not valid" do
Puppet::Util::Log.expects(:validlevel?).with(:notice).returns false
lambda { Puppet::Util::Log.new(:level => :notice, :message => :foo) }.should raise_error(ArgumentError)
end
it "should set its time to the initialization time" do
time = mock 'time'
Time.expects(:now).returns time
Puppet::Util::Log.new(:level => "notice", :message => :foo).time.should equal(time)
end
it "should make available any passed-in tags" do
log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :tags => %w{foo bar})
log.tags.should be_include("foo")
log.tags.should be_include("bar")
end
it "should use an passed-in source" do
Puppet::Util::Log.any_instance.expects(:source=).with "foo"
Puppet::Util::Log.new(:level => "notice", :message => :foo, :source => "foo")
end
- [:file, :line, :version].each do |attr|
+ [:file, :line].each do |attr|
it "should use #{attr} if provided" do
Puppet::Util::Log.any_instance.expects(attr.to_s + "=").with "foo"
Puppet::Util::Log.new(:level => "notice", :message => :foo, attr => "foo")
end
end
it "should default to 'Puppet' as its source" do
Puppet::Util::Log.new(:level => "notice", :message => :foo).source.should == "Puppet"
end
it "should register itself with Log" do
Puppet::Util::Log.expects(:newmessage)
Puppet::Util::Log.new(:level => "notice", :message => :foo)
end
it "should have a method for determining if a tag is present" do
Puppet::Util::Log.new(:level => "notice", :message => :foo).should respond_to(:tagged?)
end
it "should match a tag if any of the tags are equivalent to the passed tag as a string" do
Puppet::Util::Log.new(:level => "notice", :message => :foo, :tags => %w{one two}).should be_tagged(:one)
end
it "should tag itself with its log level" do
Puppet::Util::Log.new(:level => "notice", :message => :foo).should be_tagged(:notice)
end
it "should return its message when converted to a string" do
Puppet::Util::Log.new(:level => "notice", :message => :foo).to_s.should == "foo"
end
it "should include its time, source, level, and message when prepared for reporting" do
log = Puppet::Util::Log.new(:level => "notice", :message => :foo)
report = log.to_report
report.should be_include("notice")
report.should be_include("foo")
report.should be_include(log.source)
report.should be_include(log.time.to_s)
end
describe "when setting the source as a RAL object" do
it "should tag itself with any tags the source has" do
source = Puppet::Type.type(:file).new :path => "/foo/bar"
log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :source => source)
source.tags.each do |tag|
log.tags.should be_include(tag)
end
end
it "should use the source_descriptors" do
source = stub "source"
source.stubs(:source_descriptors).returns(:tags => ["tag","tag2"], :path => "path", :version => 100)
log = Puppet::Util::Log.new(:level => "notice", :message => :foo)
log.expects(:tag).with("tag")
log.expects(:tag).with("tag2")
- log.expects(:version=).with(100)
log.source = source
log.source.should == "path"
end
- it "should copy over any version information" do
- catalog = Puppet::Resource::Catalog.new
- catalog.version = 25
- source = Puppet::Type.type(:file).new :path => "/foo/bar"
- catalog.add_resource source
-
- log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :source => source)
- log.version.should == 25
- end
-
it "should copy over any file and line information" do
source = Puppet::Type.type(:file).new :path => "/foo/bar"
source.file = "/my/file"
source.line = 50
log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :source => source)
log.file.should == "/my/file"
log.line.should == 50
end
end
describe "when setting the source as a non-RAL object" do
it "should not try to copy over file, version, line, or tag information" do
source = Puppet::Module.new("foo")
source.expects(:file).never
log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :source => source)
end
end
end
+
+ describe "to_yaml" do
+ it "should not include the @version attribute" do
+ log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :version => 100)
+ log.to_yaml_properties.should_not include('@version')
+ end
+
+ it "should include attributes @level, @message, @source, @tags, and @time" do
+ log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :version => 100)
+ log.to_yaml_properties.should == %w{@level @message @source @tags @time}
+ end
+
+ it "should include attributes @file and @line if specified" do
+ log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :file => "foo", :line => 35)
+ log.to_yaml_properties.should include('@file')
+ log.to_yaml_properties.should include('@line')
+ end
+ end
end
diff --git a/spec/unit/util/logging_spec.rb b/spec/unit/util/logging_spec.rb
index 46ae5386f..411cd17a9 100755
--- a/spec/unit/util/logging_spec.rb
+++ b/spec/unit/util/logging_spec.rb
@@ -1,95 +1,95 @@
#!/usr/bin/env ruby
Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") }
require 'puppet/util/logging'
class LoggingTester
include Puppet::Util::Logging
end
describe Puppet::Util::Logging do
before do
@logger = LoggingTester.new
end
Puppet::Util::Log.eachlevel do |level|
it "should have a method for sending '#{level}' logs" do
@logger.should respond_to(level)
end
end
it "should have a method for sending a log with a specified log level" do
@logger.expects(:to_s).returns "I'm a string!"
Puppet::Util::Log.expects(:create).with { |args| args[:source] == "I'm a string!" and args[:level] == "loglevel" and args[:message] == "mymessage" }
@logger.send_log "loglevel", "mymessage"
end
describe "when sending a log" do
it "should use the Log's 'create' entrance method" do
Puppet::Util::Log.expects(:create)
@logger.notice "foo"
end
it "should send itself converted to a string as the log source" do
@logger.expects(:to_s).returns "I'm a string!"
Puppet::Util::Log.expects(:create).with { |args| args[:source] == "I'm a string!" }
@logger.notice "foo"
end
it "should queue logs sent without a specified destination" do
Puppet::Util::Log.close_all
Puppet::Util::Log.expects(:queuemessage)
@logger.notice "foo"
end
it "should use the path of any provided resource type" do
resource = Puppet::Type.type(:mount).new :name => "foo"
resource.expects(:path).returns "/path/to/mount".to_sym
Puppet::Util::Log.expects(:create).with { |args| args[:source] == "/path/to/mount" }
resource.notice "foo"
end
it "should use the path of any provided resource parameter" do
resource = Puppet::Type.type(:mount).new :name => "foo"
param = resource.parameter(:name)
param.expects(:path).returns "/path/to/param".to_sym
Puppet::Util::Log.expects(:create).with { |args| args[:source] == "/path/to/param" }
param.notice "foo"
end
it "should send the provided argument as the log message" do
Puppet::Util::Log.expects(:create).with { |args| args[:message] == "foo" }
@logger.notice "foo"
end
it "should join any provided arguments into a single string for the message" do
Puppet::Util::Log.expects(:create).with { |args| args[:message] == "foo bar baz" }
@logger.notice ["foo", "bar", "baz"]
end
- [:file, :line, :version, :tags].each do |attr|
+ [:file, :line, :tags].each do |attr|
it "should include #{attr} if available" do
@logger.singleton_class.send(:attr_accessor, attr)
@logger.send(attr.to_s + "=", "myval")
Puppet::Util::Log.expects(:create).with { |args| args[attr] == "myval" }
@logger.notice "foo"
end
end
end
end
diff --git a/spec/unit/util/metric_spec.rb b/spec/unit/util/metric_spec.rb
index 72571ee4a..600b88f85 100755
--- a/spec/unit/util/metric_spec.rb
+++ b/spec/unit/util/metric_spec.rb
@@ -1,95 +1,95 @@
#!/usr/bin/env ruby
Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") }
require 'puppet/util/metric'
describe Puppet::Util::Metric do
before do
@metric = Puppet::Util::Metric.new("foo")
end
it "should be aliased to Puppet::Metric" do
Puppet::Util::Metric.should equal(Puppet::Metric)
end
[:type, :name, :value, :label, :basedir].each do |name|
it "should have a #{name} attribute" do
@metric.should respond_to(name)
@metric.should respond_to(name.to_s + "=")
end
end
it "should default to the :rrdir as the basedir "do
Puppet.settings.expects(:value).with(:rrddir).returns "myrrd"
@metric.basedir.should == "myrrd"
end
it "should use any provided basedir" do
@metric.basedir = "foo"
@metric.basedir.should == "foo"
end
it "should require a name at initialization" do
lambda { Puppet::Util::Metric.new }.should raise_error(ArgumentError)
end
it "should always convert its name to a string" do
Puppet::Util::Metric.new(:foo).name.should == "foo"
end
it "should support a label" do
Puppet::Util::Metric.new("foo", "mylabel").label.should == "mylabel"
end
it "should autogenerate a label if none is provided" do
Puppet::Util::Metric.new("foo_bar").label.should == "Foo bar"
end
it "should have a method for adding values" do
@metric.should respond_to(:newvalue)
end
it "should have a method for returning values" do
@metric.should respond_to(:values)
end
it "should require a name and value for its values" do
lambda { @metric.newvalue }.should raise_error(ArgumentError)
end
it "should support a label for values" do
- @metric.newvalue(:foo, 10, "label")
+ @metric.newvalue("foo", 10, "label")
@metric.values[0][1].should == "label"
end
it "should autogenerate value labels if none is provided" do
@metric.newvalue("foo_bar", 10)
@metric.values[0][1].should == "Foo bar"
end
it "should return its values sorted by label" do
- @metric.newvalue(:foo, 10, "b")
- @metric.newvalue(:bar, 10, "a")
+ @metric.newvalue("foo", 10, "b")
+ @metric.newvalue("bar", 10, "a")
- @metric.values.should == [[:bar, "a", 10], [:foo, "b", 10]]
+ @metric.values.should == [["bar", "a", 10], ["foo", "b", 10]]
end
it "should use an array indexer method to retrieve individual values" do
- @metric.newvalue(:foo, 10)
- @metric[:foo].should == 10
+ @metric.newvalue("foo", 10)
+ @metric["foo"].should == 10
end
it "should return nil if the named value cannot be found" do
- @metric[:foo].should == 0
+ @metric["foo"].should == 0
end
# LAK: I'm not taking the time to develop these tests right now.
# I expect they should actually be extracted into a separate class
# anyway.
it "should be able to graph metrics using RRDTool"
it "should be able to create a new RRDTool database"
it "should be able to store metrics into an RRDTool database"
end