Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/facter 2/fact 238 extract resolution mixins #611

Merged
merged 2 commits into from Jan 23, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
96 changes: 96 additions & 0 deletions lib/facter/core/resolvable.rb
@@ -0,0 +1,96 @@
require 'timeout'

# The resolvable mixin defines behavior for evaluating and returning fact
# resolutions.
#
# Classes including this mixin should implement at #name method describing
# the value being resolved and a #resolve_value that actually executes the code
# to resolve the value.
module Facter::Core::Resolvable

# The timeout, in seconds, for evaluating this resolution.
# @return [Integer]
# @api public
attr_accessor :timeout

# Return the timeout period for resolving a value.
# (see #timeout)
# @return [Numeric]
# @comment requiring 'timeout' stdlib class causes Object#timeout to be
# defined which delegates to Timeout.timeout. This method may potentially
# overwrite the #timeout attr_reader on this class, so we define #limit to
# avoid conflicts.
def limit
@timeout || 0
end

##
# on_flush accepts a block and executes the block when the resolution's value
# is flushed. This makes it possible to model a single, expensive system
# call inside of a Ruby object and then define multiple dynamic facts which
# resolve by sending messages to the model instance. If one of the dynamic
# facts is flushed then it can, in turn, flush the data stored in the model
# instance to keep all of the dynamic facts in sync without making multiple,
# expensive, system calls.
#
# Please see the Solaris zones fact for an example of how this feature may be
# used.
#
# @see Facter::Util::Fact#flush
# @see Facter::Util::Resolution#flush
#
# @api public
def on_flush(&block)
@on_flush_block = block
end

##
# flush executes the block, if any, stored by the {on_flush} method
#
# @see Facter::Util::Fact#flush
# @see Facter::Util::Resolution#on_flush
#
# @api private
def flush
@on_flush_block.call if @on_flush_block
end

def value
result = nil

with_timing do
Timeout.timeout(limit) do
result = resolve_value
end
end

Facter::Util::Normalization.normalize(result)

rescue Timeout::Error => detail
Facter.warn "Timed out seeking value for #{self.name}"

# This call avoids zombies -- basically, create a thread that will
# dezombify all of the child processes that we're ignoring because
# of the timeout.
Thread.new { Process.waitall }
return nil
rescue Facter::Util::Normalization::NormalizationError => e
Facter.warn "Fact resolution #{self.name} resolved to an invalid value: #{e.message}"
return nil
rescue => details
Facter.warn "Could not retrieve #{self.name}: #{details.message}"
return nil
end

private

def with_timing
starttime = Time.now.to_f

yield

finishtime = Time.now.to_f
ms = (finishtime - starttime) * 1000
Facter.show_time "#{self.name}: #{"%.2f" % ms}ms"
end
end
80 changes: 80 additions & 0 deletions lib/facter/core/suitable.rb
@@ -0,0 +1,80 @@
require 'facter'

# The Suitable mixin provides mechanisms for confining objects to run on
# certain platforms and determining the run precedence of these objects.
#
# Classes that include the Suitable mixin should define a `#confines` method
# that returns an Array of zero or more Facter::Util::Confine objects.
module Facter::Core::Suitable

attr_writer :weight

# Sets the weight of this resolution. If multiple suitable resolutions
# are found, the one with the highest weight will be used. If weight
# is not given, the number of confines set on a resolution will be
# used as its weight (so that the most specific resolution is used).
#
# @param weight [Integer] the weight of this resolution
#
# @return [void]
#
# @api public
def has_weight(weight)
@weight = weight
end

# Sets the conditions for this resolution to be used. This takes a
# hash of fact names and values. Every fact must match the values
# given for that fact, otherwise this resolution will not be
# considered suitable. The values given for a fact can be an array, in
# which case the value of the fact must be in the array for it to
# match.
#
# @param confines [Hash{String => Object}] a hash of facts and the
# values they should have in order for this resolution to be
# used
#
# @example Confining to Linux
# Facter.add(:powerstates) do
# # This resolution only makes sense on linux systems
# confine :kernel => "Linux"
# setcode do
# Facter::Util::Resolution.exec('cat /sys/power/states')
# end
# end
#
# @return [void]
#
# @api public
def confine(confines)
confines.each do |fact, values|
@confines.push Facter::Util::Confine.new(fact, *values)
end
end

# Returns the importance of this resolution. If the weight was not
# given, the number of confines is used instead (so that a more
# specific resolution wins over a less specific one).
#
# @return [Integer] the weight of this resolution
#
# @api private
def weight
if @weight
@weight
else
@confines.length
end
end

# Is this resolution mechanism suitable on the system in question?
#
# @api private
def suitable?
unless defined? @suitable
@suitable = ! @confines.detect { |confine| ! confine.true? }
end

return @suitable
end
end