Class: Arborist::Monitor

Inherits:
Object
  • Object
show all
Extended by:
MethodUtilities, Configurability, Loggability
Defined in:
lib/arborist/monitor.rb

Overview

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

Defined Under Namespace

Modules: ConnectionBatching, DefaultCallbacks, Socket Classes: RunContext

Constant Summary collapse

LOADED_INSTANCE_KEY =

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

:loaded_monitor_instances
MONITOR_FILE_PATTERN =

The glob pattern to use for searching for monitors

'**/*.rb'
DEFAULT_INTERVAL =

The default monitoring interval, in seconds

5.minutes

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from MethodUtilities

attr_predicate, attr_predicate_accessor, dsl_accessor, singleton_attr_accessor, singleton_attr_reader, singleton_attr_writer, singleton_method_alias, singleton_predicate_accessor, singleton_predicate_reader

Constructor Details

#initialize(description = nil, key = nil, &block) ⇒ Monitor

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.



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/arborist/monitor.rb', line 155

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

Instance Attribute Details

#description(new_value = nil) ⇒ Object

Get/set the description of the monitor.



254
255
256
257
# File 'lib/arborist/monitor.rb', line 254

def description( new_value=nil )
  self.description = new_value if new_value
  return @description
end

#exec_blockObject

The callback to invoke when the monitor is run.



223
224
225
# File 'lib/arborist/monitor.rb', line 223

def exec_block
  @exec_block
end

#exec_callbacks_modObject

The monitor’s execution callbacks contained in a Module



227
228
229
# File 'lib/arborist/monitor.rb', line 227

def exec_callbacks_mod
  @exec_callbacks_mod
end

#exec_commandObject

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



219
220
221
# File 'lib/arborist/monitor.rb', line 219

def exec_command
  @exec_command
end

#interval=(value) ⇒ Object (writeonly)

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



193
194
195
# File 'lib/arborist/monitor.rb', line 193

def interval=(value)
  @interval = value
end

#key(new_value = nil) ⇒ Object

Get/set the key used by the monitor.



261
262
263
264
# File 'lib/arborist/monitor.rb', line 261

def key( new_value=nil )
  self.key = new_value if new_value
  return @key
end

#negative_criteriaObject (readonly)

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



205
206
207
# File 'lib/arborist/monitor.rb', line 205

def negative_criteria
  @negative_criteria
end

#node_propertiesObject (readonly)

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



214
215
216
# File 'lib/arborist/monitor.rb', line 214

def node_properties
  @node_properties
end

#positive_criteriaObject (readonly)

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



201
202
203
# File 'lib/arborist/monitor.rb', line 201

def positive_criteria
  @positive_criteria
end

#sourceObject

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



231
232
233
# File 'lib/arborist/monitor.rb', line 231

def source
  @source
end

#splay(seconds = nil) ⇒ Object

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



332
333
334
335
# File 'lib/arborist/monitor.rb', line 332

def splay( seconds=nil )
  @splay = seconds if seconds
  return @splay
end

Class Method Details

.add_loaded_instance(new_instance) ⇒ Object

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



129
130
131
132
# File 'lib/arborist/monitor.rb', line 129

def self::add_loaded_instance( new_instance )
  instances = Thread.current[ LOADED_INSTANCE_KEY ] or return
  instances << new_instance
end

.each_in(loader) ⇒ Object

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



147
148
149
# File 'lib/arborist/monitor.rb', line 147

def self::each_in( loader )
  return loader.monitors
end

.load(file) ⇒ Object

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



136
137
138
139
140
141
142
143
# File 'lib/arborist/monitor.rb', line 136

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

.newObject

Overridden to track instances of created nodes for the DSL.



120
121
122
123
124
# File 'lib/arborist/monitor.rb', line 120

def self::new( * )
  new_instance = super
  Arborist::Monitor.add_loaded_instance( new_instance )
  return new_instance
end

Instance Method Details

#check_configObject

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



247
248
249
250
# File 'lib/arborist/monitor.rb', line 247

def check_config
  raise Arborist::ConfigError, "No description set" unless self.description
  raise Arborist::ConfigError, "No key set" unless self.key
end

#every(seconds = nil) ⇒ Object Also known as: interval

Specify that the monitor should be run every seconds seconds.



323
324
325
326
# File 'lib/arborist/monitor.rb', line 323

def every( seconds=nil )
  @interval = seconds if seconds
  return @interval
end

#exclude(criteria) ⇒ Object

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



349
350
351
# File 'lib/arborist/monitor.rb', line 349

def exclude( criteria )
  self.negative_criteria.merge!( criteria )
end

#exclude_down(flag = nil) ⇒ Object

Specify that the monitor should (or should not) include nodes which have been marked ‘down’.



210
# File 'lib/arborist/monitor.rb', line 210

attr_predicate :exclude_down

#exec(*command, &block) ⇒ Object

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.



370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
# File 'lib/arborist/monitor.rb', line 370

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

#exec_arguments(&block) ⇒ Object

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.



390
391
392
393
394
# File 'lib/arborist/monitor.rb', line 390

def exec_arguments( &block )
  self.exec_callbacks_mod.instance_exec( block ) do |method_body|
    define_method( :exec_arguments, &method_body )
  end
end

#exec_callbacks(mod) ⇒ Object

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



420
421
422
423
424
# File 'lib/arborist/monitor.rb', line 420

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

#exec_input(&block) ⇒ Object

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.



400
401
402
403
404
# File 'lib/arborist/monitor.rb', line 400

def exec_input( &block )
  self.exec_callbacks_mod.instance_exec( block ) do |method_body|
    define_method( :exec_input, &method_body )
  end
end

#handle_results(&block) ⇒ Object

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.



411
412
413
414
415
# File 'lib/arborist/monitor.rb', line 411

def handle_results( &block )
  self.exec_callbacks_mod.instance_exec( block ) do |method_body|
    define_method( :handle_results, &method_body )
  end
end

#inspectObject

Return a string representation of the object suitable for debugging.



235
236
237
238
239
240
241
242
243
# File 'lib/arborist/monitor.rb', line 235

def inspect
  return "#<%p:%#x %s (every %ds +-%ds)>" % [
    self.class,
    self.object_id * 2,
    self.description || "(no description)",
    @interval,
    @splay,
  ]
end

#match(criteria) ⇒ Object

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



340
341
342
343
344
# File 'lib/arborist/monitor.rb', line 340

def match( criteria )
  self.positive_criteria.merge!( criteria )
  @exclude_down = self.exclude_down &&
    Arborist::Node::UNREACHABLE_STATES.include?( self.positive_criteria[:status] )
end

#run(nodes) ⇒ Object

Run the monitor



267
268
269
270
271
272
273
274
# File 'lib/arborist/monitor.rb', line 267

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

#run_external_command(command, nodes) ⇒ Object

Run the external command against the specified nodes.



278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
# File 'lib/arborist/monitor.rb', line 278

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

#use(*properties) ⇒ Object

Specify properties from each node to provide to the monitor.



363
364
365
# File 'lib/arborist/monitor.rb', line 363

def use( *properties )
  @node_properties = properties
end