Monitor

class
Superclass
Object
Extended With
Configurability
Loggability
Arborist::MethodUtilities

A declaration of an action to run against Manager nodes to update their state.

Constants

DEFAULT_INTERVAL

The default monitoring interval, in seconds

LOADED_INSTANCE_KEY

The key for the thread local that is used to track instances as they're loaded.

MONITOR_FILE_PATTERN

The glob pattern to use for searching for monitors

Attributes

description[W]

The monitor's (human) description.

exec_block[RW]

The callback to invoke when the monitor is run.

exec_callbacks_mod[RW]

The monitor's execution callbacks contained in a Module

exec_command[RW]

The shell command to exec when running the monitor (if any). This can be any valid arguments to the `Kernel.spawn` method.

interval[W]

The interval between runs in seconds, as set by `every`.

key[W]

The monitor's key. This key should be shared between monitors that check the same resources.

negative_criteria[R]

A Hash of criteria to pass to the Manager to filter out nodes to monitor.

node_properties[R]

The list of node properties to include when running the monitor.

positive_criteria[R]

A Hash of criteria to pass to the Manager when searching for nodes to monitor.

source[RW]

The path to the source this Monitor was loaded from, if applicable

splay[W]

The number of seconds of splay to use when running the monitor.

Public Class Methods

anchor
add_loaded_instance( new_instance )

Record a new loaded instance if the Thread-local variable is set up to track them.

# File lib/arborist/monitor.rb, line 128
def self::add_loaded_instance( new_instance )
        instances = Thread.current[ LOADED_INSTANCE_KEY ] or return
        instances << new_instance
end
anchor
each_in( loader )

Return an iterator for all the monitors supplied by the specified loader.

# File lib/arborist/monitor.rb, line 146
def self::each_in( loader )
        return loader.monitors
end
anchor
load( file )

Load the specified file and return any new Nodes created as a result.

# File lib/arborist/monitor.rb, line 135
def self::load( file )
        self.log.info "Loading monitor file %s..." % [ file ]
        Thread.current[ LOADED_INSTANCE_KEY ] = []
        Kernel.load( file )
        return Thread.current[ LOADED_INSTANCE_KEY ]
ensure
        Thread.current[ LOADED_INSTANCE_KEY ] = nil
end
anchor
new( description=nil, key=nil, &block )

Create a new Monitor with the specified description. If the block is given, it will be evaluated in the context of the new Monitor before it's returned.

# File lib/arborist/monitor.rb, line 154
def initialize( description=nil, key=nil, &block )
        @key = key
        @description = description || self.class.name
        @interval = DEFAULT_INTERVAL
        @splay = Arborist::Monitor.splay

        @positive_criteria = {}
        @negative_criteria = {}
        @exclude_down = false
        @node_properties = []

        @exec_command = nil
        @exec_block = nil
        @exec_callbacks_mod = Module.new

        @source = nil

        self.instance_exec( &block ) if block

        self.check_config
end
anchor
new( * )

Overridden to track instances of created nodes for the DSL.

# File lib/arborist/monitor.rb, line 119
def self::new( * )
        new_instance = super
        Arborist::Monitor.add_loaded_instance( new_instance )
        return new_instance
end

Public Instance Methods

anchor
check_config()

Check the monitor for sanity, raising an Arborist::ConfigError if it isn't.

# File lib/arborist/monitor.rb, line 246
def check_config
        raise Arborist::ConfigError, "No description set" unless self.description
        raise Arborist::ConfigError, "No key set" unless self.key
end
anchor
description( new_value=nil )

Get/set the description of the monitor.

# File lib/arborist/monitor.rb, line 253
def description( new_value=nil )
        self.description = new_value if new_value
        return @description
end
anchor
every( seconds=nil )

Specify that the monitor should be run every seconds seconds.

# File lib/arborist/monitor.rb, line 322
def every( seconds=nil )
        @interval = seconds if seconds
        return @interval
end
Also aliased as: interval
anchor
exclude( criteria )

Specify that the monitor should exclude nodes which match the specified criteria when searching for nodes it will run against.

# File lib/arborist/monitor.rb, line 348
def exclude( criteria )
        self.negative_criteria.merge!( criteria )
end
anchor
exclude_down()

Flag for whether the monitor will include downed hosts in its search. Defaults to false.

# File lib/arborist/monitor.rb, line 209
attr_predicate :exclude_down
anchor
exec( *command, &block )

Specify what should be run to do the actual monitoring. Accepts an Array of strings (which are passed to `spawn`), a block, or an object that responds to the run method.

# File lib/arborist/monitor.rb, line 369
def exec( *command, &block )
        unless command.empty?
                self.log.warn "Ignored block with exec %s (%p)" % [ command.first, block ] if block

                if command.first.respond_to?( :run )
                        runner = command.first
                        @exec_block = runner.method( :run )
                        @node_properties |= runner.node_properties if runner.respond_to?( :node_properties )
                else
                        @exec_command = command.map( &:to_s )
                end

                return
        end
        @exec_block = block
end
anchor
exec_arguments( &block )

Declare an argument-building callback for the command run by 'exec'. The block should accept an Array of nodes and return an Array of arguments for the command.

# File lib/arborist/monitor.rb, line 389
def exec_arguments( &block )
        self.exec_callbacks_mod.instance_exec( block ) do |method_body|
                define_method( :exec_arguments, &method_body )
        end
end
anchor
exec_callbacks( mod )

Set the module to use for the callbacks when interacting with the executed external command.

# File lib/arborist/monitor.rb, line 419
def exec_callbacks( mod )
        self.log.info "Setting exec callbacks handler to: %p" % [ mod.name ]
        @node_properties |= mod.node_properties if mod.respond_to?( :node_properties )
        self.exec_callbacks_mod = mod
end
anchor
exec_input( &block )

Declare an input-building callback for the command run by 'exec'. The block should accept an Array of nodes and a writable IO object, and should write out the necessary input to drive the command to the IO.

# File lib/arborist/monitor.rb, line 399
def exec_input( &block )
        self.exec_callbacks_mod.instance_exec( block ) do |method_body|
                define_method( :exec_input, &method_body )
        end
end
anchor
handle_results( &block )

Declare a results handler block that will be used to parse the results for external commands. The block should accept 2 or 3 arguments: a PID, an IO that will be opened to the command's STDOUT, and optionally an IO that will be opened to the command's STDERR.

# File lib/arborist/monitor.rb, line 410
def handle_results( &block )
        self.exec_callbacks_mod.instance_exec( block ) do |method_body|
                define_method( :handle_results, &method_body )
        end
end
anchor
inspect()

Return a string representation of the object suitable for debugging.

# File lib/arborist/monitor.rb, line 234
def inspect
        return "#<%p:%#x %s (every %ds +-%ds)>" % [
                self.class,
                self.object_id * 2,
                self.description || "(no description)",
                @interval,
                @splay,
        ]
end
anchor
interval( seconds=nil )
Alias for: every
anchor
key( new_value=nil )

Get/set the key used by the monitor.

# File lib/arborist/monitor.rb, line 260
def key( new_value=nil )
        self.key = new_value if new_value
        return @key
end
anchor
match( criteria )

Specify that the monitor should include the specified criteria when searching for nodes it will run against.

# File lib/arborist/monitor.rb, line 339
def match( criteria )
        self.positive_criteria.merge!( criteria )
        @exclude_down = self.exclude_down &&
                Arborist::Node::UNREACHABLE_STATES.include?( self.positive_criteria[:status] )
end
anchor
run( nodes )

Run the monitor

# File lib/arborist/monitor.rb, line 266
def run( nodes )
        if self.exec_block
                return self.exec_block.call( nodes )
        elsif self.exec_command
                command = self.exec_command
                return self.run_external_command( command, nodes )
        end
end
anchor
run_external_command( command, nodes )

Run the external command against the specified nodes.

# File lib/arborist/monitor.rb, line 277
def run_external_command( command, nodes )
        self.log.debug "Running external command %p for %d nodes" % [ command, nodes.size ]
        context = Arborist::Monitor::RunContext.new
        context.extend( self.exec_callbacks_mod ) if self.exec_callbacks_mod

        arguments = Array( context.exec_arguments(nodes) )
        command += arguments.flatten( 1 )
        self.log.debug "  command after adding arguments: %p" % [ command ]

        child_stdin, parent_writer = IO.pipe
        parent_reader, child_stdout = IO.pipe
        parent_err_reader, child_stderr = IO.pipe

        self.log.debug "Spawning command: %s" % [ Shellwords.join(command) ]
        pid = Process.spawn( *command, out: child_stdout, in: child_stdin, err: child_stderr )

        child_stdout.close
        child_stdin.close
        child_stderr.close

        context.exec_input( nodes, parent_writer )
        parent_writer.close

        return context.handle_results( pid, parent_reader, parent_err_reader )
rescue SystemCallError => err
        self.log.error "%p while running external monitor command `%s`: %s" % [
                err.class,
                Shellwords.join( command ),
                err.message
        ]
        self.log.debug "  %s" % [ err.backtrace.join("\n  ") ]
        return {}

ensure
        if pid
                begin
                        Process.kill( 0, pid ) # waitpid if it's still alive
                        Process.waitpid( pid )
                rescue Errno::ESRCH
                end
        end
end
anchor
splay( seconds=nil )

Specify the number of seconds of interval splay that should be used when running the monitor.

# File lib/arborist/monitor.rb, line 331
def splay( seconds=nil )
        @splay = seconds if seconds
        return @splay
end
anchor
use( *properties )

Specify properties from each node to provide to the monitor.

# File lib/arborist/monitor.rb, line 362
def use( *properties )
        @node_properties = properties
end