The basic node class for an Arborist tree
The key for the thread local that is used to track instances as they're loaded.
The attributes of a node which are used in the operation of the system
Node states that are unreachable by default.
Regex to match a valid identifier
The acknowledgement currently in effect. Should be an instance of Arborist::Node::ACK
The Hash of nodes which are children of this node, keyed by identifier
The node's secondary dependencies, expressed as an Arborist::Node::Sexp
The Hash of last errors encountered by a monitor attempting to update this node, keyed by the monitor's `key`.
The node's identifier
The Time the node was last contacted
The Array of events generated by the current update event
The acknowledgement previously in effect (if any).
Arbitrary attributes attached to this node via the manager API
The reasons this node was quieted. This is a Hash of text descriptions keyed by the type of dependency it came from (either :primary or :secondary).
The URI of the source the object was read from
The Time the node's status last changed.
The previous Time the node's status changed, for duration calculations between states.
The Hash of Subscription objects observing this node and its children, keyed by subscription ID.
The Hash of changes tracked during an update.
The Hash of last warnings encountered by a monitor attempting to update this node, keyed by the monitor's `key`.
Record a new loaded instance if the Thread-local variable is set up to track them.
# File lib/arborist/node.rb, line 199
def self::add_loaded_instance( new_instance )
instances = Thread.current[ LOADED_INSTANCE_KEY ] or return
# self.log.debug "Adding new instance %p to node tree" % [ new_instance ]
instances << new_instance
end
Add a factory method that can be used to create subnodes of the specified
subnode_type
on instances of the receiving class.
# File lib/arborist/node.rb, line 241
def self::add_subnode_factory_method( subnode_type, &dsl_block )
if subnode_type.name
name = subnode_type.plugin_name
# self.log.debug "Adding factory constructor for %s nodes to %p" % [ name, self ]
body = lambda do |*args, &constructor_block|
if dsl_block
# self.log.debug "Using DSL block to split args: %p" % [ dsl_block ]
identifier, attributes = dsl_block.call( *args )
else
# self.log.debug "Splitting args the default way: %p" % [ args ]
identifier, attributes = *args
end
attributes ||= {}
# self.log.debug "Identifier: %p, attributes: %p, self: %p" %
# [ identifier, attributes, self ]
return Arborist::Node.create( name, identifier, self, attributes, &constructor_block )
end
define_method( name, &body )
else
self.log.info "Skipping factory constructor for anonymous subnode class."
end
end
Return a curried Proc for the ::create method for the specified
type
.
# File lib/arborist/node.rb, line 172
def self::curried_create( type )
if type.subnode_type?
return self.method( :create ).to_proc.curry( 3 )[ type ]
else
return self.method( :create ).to_proc.curry( 2 )[ type ]
end
end
Return an iterator for all the nodes supplied by the specified
loader
.
# File lib/arborist/node.rb, line 286
def self::each_in( loader )
return loader.nodes
end
Create a new node with its state read from the specified hash
.
# File lib/arborist/node.rb, line 190
def self::from_hash( hash )
return self.new( hash[:identifier] ) do
self.marshal_load( hash )
end
end
Inheritance hook – add a DSL declarative function for the given
subclass
.
# File lib/arborist/node.rb, line 207
def self::inherited( subclass )
super
body = self.curried_create( subclass )
Arborist.add_dsl_constructor( subclass, &body )
end
Load the specified file
and return any new Nodes created as a
result.
# File lib/arborist/node.rb, line 268
def self::load( file )
self.log.info "Loading node file %s..." % [ file ]
Thread.current[ LOADED_INSTANCE_KEY ] = []
begin
Kernel.load( file )
rescue => err
self.log.error "%p while loading %s: %s" % [ err.class, file, err.message ]
raise
end
return Thread.current[ LOADED_INSTANCE_KEY ]
ensure
Thread.current[ LOADED_INSTANCE_KEY ] = nil
end
Create a new Node with the specified
identifier
, which must be unique to the loaded tree.
# File lib/arborist/node.rb, line 293
def initialize( identifier, *args, &block )
attributes = args.last.is_a?( Hash ) ? args.pop : {}
parent_node = args.pop
raise "Invalid identifier %p" % [identifier] unless
identifier =~ VALID_IDENTIFIER
# Attributes of the target
@identifier = identifier
@parent = parent_node ? parent_node.identifier : '_'
@description = nil
@tags = Set.new
@properties = {}
@config = {}
@source = nil
@children = {}
@dependencies = Arborist::Dependency.new( :all )
# Primary state
@status = 'unknown'
@status_changed = Time.at( 0 )
@status_last_changed = Time.at( 0 )
# Attributes that govern state
@errors = {}
@warnings = {}
@ack = nil
@previous_ack = nil
@last_contacted = Time.at( 0 )
@quieted_reasons = {}
# Event-handling
@update_delta = Hash.new do |h,k|
h[ k ] = Hash.new( &h.default_proc )
end
@pending_change_events = []
@subscriptions = {}
self.modify( attributes )
self.instance_eval( &block ) if block
end
Overridden to track instances of created nodes for the DSL.
# File lib/arborist/node.rb, line 182
def self::new( * )
new_instance = super
Arborist::Node.add_loaded_instance( new_instance )
return new_instance
end
Get/set the node type instances of the class live under. If no parent_type
is set, it is a top-level node type. If a block
is given, it
can be used to pre-process the arguments into the (identifier, attributes,
block) arguments used to create the node instances.
# File lib/arborist/node.rb, line 219
def self::parent_types( *types, &block )
@parent_types ||= []
types.each do |new_type|
subclass = Arborist::Node.get_subclass( new_type )
@parent_types << subclass
subclass.add_subnode_factory_method( self, &block )
end
return @parent_types
end
Returns true
if the receiver must be created under a specific
node type.
# File lib/arborist/node.rb, line 234
def self::subnode_type?
return ! self.parent_types.empty?
end
Returns true
if the node is in an 'acked' state.
# File lib/arborist/node.rb, line 87
Returns true
if the node is in an 'disabled' state.
# File lib/arborist/node.rb, line 91
Returns true
if the node is in an 'down' state.
# File lib/arborist/node.rb, line 83
Return the node's status as a human-readable String.
# File lib/arborist/node.rb, line 95
Set one or more node attributes
. This should be overridden by
subclasses which wish to allow their operational attributes to be
set/updated via the Tree API (modify
and graft
).
Supported attributes are: parent
, description
,
tags
, and config
.
# File lib/arborist/node.rb, line 420
def modify( attributes )
attributes = stringify_keys( attributes )
self.parent( attributes['parent'] )
self.description( attributes['description'] )
self.config( attributes['config'] )
if attributes['tags']
@tags.clear
self.tags( attributes['tags'] )
end
end
Set the source of the node to source
, which should be a valid
URI.
# File lib/arborist/node.rb, line 411
def source=( source )
@source = URI( source )
end
Return the status
of the node. This will be one of:
unknown
, up
, down
,
acked
, or disabled
.
# File lib/arborist/node.rb, line 100
Set the status of the node to new_status
.
# File lib/arborist/node.rb, line 107
Returns true
if the node's status is
status_name
.
# File lib/arborist/node.rb, line 114
state_machine( :status, initial: :unknown ) do
state :unknown,
:up,
:down,
:warn,
:acked,
:disabled,
:quieted
event :update do
transition [:down, :warn, :unknown, :acked] => :up, unless: :has_errors_or_warnings?
transition [:up, :warn, :unknown] => :down, if: :has_errors?
transition [:up, :down, :unknown] => :warn, if: :has_only_warnings?
end
event :acknowledge do
transition any - [:down] => :disabled
transition :down => :acked
end
event :unacknowledge do
transition [:acked, :disabled] => :warn, if: :has_warnings?
transition [:acked, :disabled] => :down, if: :has_errors?
transition [:acked, :disabled] => :unknown
end
event :handle_event do
transition any - [:disabled, :quieted, :acked] => :quieted, if: :has_quieted_reason?
transition :quieted => :unknown, unless: :has_quieted_reason?
end
event :reparent do
transition any - [:disabled, :quieted, :acked] => :unknown
transition :quieted => :unknown, unless: :has_quieted_reason?
end
before_transition [:acked, :disabled] => any, do: :save_previous_ack
after_transition any => :acked, do: :on_ack
after_transition :acked => :up, do: :on_ack_cleared
after_transition :down => :up, do: :on_node_up
after_transition :up => :warn, do: :on_node_warn
after_transition [:unknown, :warn, :up] => :down, do: :on_node_down
after_transition [:acked, :unknown, :warn, :up] => :disabled, do: :on_node_disabled
after_transition any => :quieted, do: :on_node_quieted
after_transition :disabled => :unknown, do: :on_node_enabled
after_transition :quieted => :unknown, do: :on_node_unquieted
after_transition any => any, do: :log_transition
after_transition any => any, do: :make_transition_event
after_transition any => any, do: :update_status_changed
after_transition do: :add_status_to_update_delta
end
Returns true
if the node is in an 'unknown' state.
# File lib/arborist/node.rb, line 75
Returns true
if the node is in an 'up' state.
# File lib/arborist/node.rb, line 79
Group identifiers
together in an 'all of' dependency.
# File lib/arborist/node.rb, line 475
def all_of( *identifiers, on: nil )
return Arborist::Dependency.on( :all, *identifiers, prefixes: on )
end
Group identifiers
together in an 'any of' dependency.
# File lib/arborist/node.rb, line 469
def any_of( *identifiers, on: nil )
return Arborist::Dependency.on( :any, *identifiers, prefixes: on )
end
Get or set the node's configuration hash. This can be used to pass per-node information to systems using the tree (e.g., monitors, subscribers).
# File lib/arborist/node.rb, line 497
def config( new_config=nil )
@config.merge!( stringify_keys( new_config ) ) if new_config
return @config
end
Add secondary dependencies to the receiving node.
# File lib/arborist/node.rb, line 481
def depends_on( *dependencies, on: nil )
dependencies = self.all_of( *dependencies, on: on )
self.log.debug "Setting secondary dependencies to: %p" % [ dependencies ]
self.dependencies = check_dependencies( dependencies )
end
Get/set the node's description.
# File lib/arborist/node.rb, line 454
def description( new_description=nil )
return @description unless new_description
@description = new_description.to_s
end
Returns true
if the node has one or more secondary
dependencies.
# File lib/arborist/node.rb, line 490
def has_dependencies?
return !self.dependencies.empty?
end
Get/set the node's parent node, which should either be an identifier or an object that responds to identifier with one.
# File lib/arborist/node.rb, line 442
def parent( new_parent=nil )
return @parent if new_parent.nil?
@parent = if new_parent.respond_to?( :identifier )
new_parent.identifier.to_s
else
@parent = new_parent.to_s
end
end
Append operator – add the specified node
as a child and return
self
.
# File lib/arborist/node.rb, line 980
def <<( node )
self.add_child( node )
return self
end
Register the specified node
as a child of this node, replacing
any existing node with the same identifier.
# File lib/arborist/node.rb, line 971
def add_child( node )
self.log.debug "Adding node %p as a child. Parent = %p" % [ node, node.parent ]
raise "%p is not a child of %p" % [ node, self ] if
node.parent && node.parent != self.identifier
self.children[ node.identifier ] = node
end
Enumerable API – iterate over the children of this node.
# File lib/arborist/node.rb, line 937
def each( &block )
return self.children.values.each( &block )
end
Returns true
if the node has one or more child nodes.
# File lib/arborist/node.rb, line 943
def has_children?
return !self.children.empty?
end
Returns true
if the node is considered operational.
# File lib/arborist/node.rb, line 949
def operational?
return self.identifier.start_with?( '_' )
end
Returns true
if the node's status indicates it is included
by default when traversing nodes.
# File lib/arborist/node.rb, line 964
def reachable?
return !self.unreachable?
end
Unregister the specified node
as a child of this node.
# File lib/arborist/node.rb, line 987
def remove_child( node )
self.log.debug "Removing node %p from children" % [ node ]
return self.children.delete( node.identifier )
end
Returns true
if the node's status indicates it
shouldn't be included by default when traversing nodes.
# File lib/arborist/node.rb, line 956
def unreachable?
self.log.debug "Testing for reachability; status is: %p" % [ self.status ]
return UNREACHABLE_STATES.include?( self.status )
end
Acknowledge any current or future abnormal status for this node.
# File lib/arborist/node.rb, line 604
def acknowledge( **args )
super()
self.ack = args
events = self.pending_change_events.clone
events << self.make_delta_event unless self.update_delta.empty?
results = self.broadcast_events( *events )
self.log.debug ">>> Results from broadcast: %p" % [ results ]
events.concat( results )
return events
ensure
self.clear_transition_temp_vars
end
Add the specified subscription
(an Arborist::Subscription) to the node.
# File lib/arborist/node.rb, line 517
def add_subscription( subscription )
self.subscriptions[ subscription.id ] = subscription
end
Send an event to this node's immediate children.
# File lib/arborist/node.rb, line 789
def broadcast_events( *events )
events.flatten!
results = self.children.flat_map do |identifier, child|
self.log.debug "Broadcasting %d events to %p" % [ events.length, identifier ]
events.flat_map do |event|
child.handle_event( event )
end
end
return results
end
Clear out the state used during a transition to track changes.
# File lib/arborist/node.rb, line 669
def clear_transition_temp_vars
self.previous_ack = nil
self.update_delta.clear
self.pending_change_events.clear
end
Returns true
if this node's dependencies are not met.
# File lib/arborist/node.rb, line 846
def dependencies_down?
return self.dependencies.down?
end
Returns true
if this node's dependencies are met.
# File lib/arborist/node.rb, line 853
def dependencies_up?
return !self.dependencies_down?
end
Return a Hash of node state values that match the specified
value_spec
.
# File lib/arborist/node.rb, line 736
def fetch_values( value_spec=nil )
state = self.properties.merge( self.operational_values )
state = stringify_keys( state )
state = make_serializable( state )
if value_spec
self.log.debug "Eliminating all values except: %p (from keys: %p)" %
[ value_spec, state.keys ]
state.delete_if {|key, _| !value_spec.include?(key) }
end
return state
end
Return subscriptions matching the specified event
on the
receiving node.
# File lib/arborist/node.rb, line 529
def find_matching_subscriptions( event )
return self.subscriptions.values.find_all {|sub| event =~ sub }
end
Handle the specified event
, delivered either via broadcast or
secondary dependency subscription.
# File lib/arborist/node.rb, line 804
def handle_event( event )
self.log.debug "Handling %p" % [ event ]
handler_name = "handle_%s_event" % [ event.type.gsub('.', '_') ]
if self.respond_to?( handler_name )
self.log.debug "Handling a %s event." % [ event.type ]
self.method( handler_name ).call( event )
else
self.log.debug "No handler for a %s event!" % [ event.type ]
end
self.log.debug ">>> Pending change events before: %p" % [ self.pending_change_events ]
super # to state-machine
results = self.pending_change_events.clone
self.log.debug ">>> Pending change events after: %p" % [ results ]
results << self.make_delta_event unless self.update_delta.empty?
child_results = self.broadcast_events( *results )
results.concat( child_results )
self.publish_events( *results )
return results
ensure
self.clear_transition_temp_vars
end
Handle a 'node.disabled' event received via broadcast.
# File lib/arborist/node.rb, line 882
def handle_node_disabled_event( event )
self.log.debug "Got a node.disabled event: %p" % [ event ]
self.dependencies.mark_down( event.node.identifier )
if self.dependencies_down?
self.quieted_reasons[ :secondary ] = "Secondary dependencies not met: %s" %
[ self.dependencies.down_reason ]
end
if event.node.identifier == self.parent
self.quieted_reasons[ :primary ] = "Parent disabled: %s" % [ self.parent ]
end
end
Handle a 'node.down' event received via broadcast.
# File lib/arborist/node.rb, line 866
def handle_node_down_event( event )
self.log.debug "Got a node.down event: %p" % [ event ]
self.dependencies.mark_down( event.node.identifier )
if self.dependencies_down?
self.quieted_reasons[ :secondary ] = "Secondary dependencies not met: %s" %
[ self.dependencies.down_reason ]
end
if event.node.identifier == self.parent
self.quieted_reasons[ :primary ] = "Parent down: %s" % [ self.parent ] # :TODO: backtrace?
end
end
Handle a 'node.quieted' event received via broadcast.
# File lib/arborist/node.rb, line 898
def handle_node_quieted_event( event )
self.log.debug "Got a node.quieted event: %p" % [ event ]
self.dependencies.mark_down( event.node.identifier )
if self.dependencies_down?
self.quieted_reasons[ :secondary ] = "Secondary dependencies not met: %s" %
[ self.dependencies.down_reason ]
end
if event.node.identifier == self.parent
self.quieted_reasons[ :primary ] = "Parent quieted: %s" % [ self.parent ] # :TODO: backtrace?
end
end
Handle a 'node.up' event received via broadcast.
# File lib/arborist/node.rb, line 914
def handle_node_up_event( event )
self.log.debug "Got a node.%s event: %p" % [ event.type, event ]
self.dependencies.mark_up( event.node.identifier )
self.quieted_reasons.delete( :secondary ) if self.dependencies_up?
if event.node.identifier == self.parent
self.log.info "Parent of %s (%s) came back up." % [
self.identifier,
self.parent
]
self.quieted_reasons.delete( :primary )
end
end
Returns true
if any reasons have been set as to why the node
has been quieted. Guard condition for transition to and from `quieted`
state.
# File lib/arborist/node.rb, line 860
def has_quieted_reason?
return !self.quieted_reasons.empty?
end
Return an Event generated from the node's state changes.
# File lib/arborist/node.rb, line 683
def make_delta_event
self.log.debug "Making node.delta event: %p" % [ self.update_delta ]
return Arborist::Event.create( 'node_delta', self, self.update_delta )
end
Return the node's state in an Arborist::Event of type 'node.update'.
# File lib/arborist/node.rb, line 677
def make_update_event
return Arborist::Event.create( 'node_update', self )
end
Returns true
if the node matches the specified
key
and val
criteria.
# File lib/arborist/node.rb, line 714
def match_criteria?( key, val )
array_val = Array( val )
return case key
when 'status'
array_val.include?( self.status )
when 'type'
array_val.include?( self.type )
when 'parent'
array_val.include?( self.parent )
when 'tag' then @tags.include?( val.to_s )
when 'tags' then array_val.all? {|tag| @tags.include?(tag) }
when 'identifier'
array_val.include?( self.identifier )
when 'config'
val.all? {|ikey, ival| hash_matches(@config, ikey, ival) }
else
hash_matches( @properties, key, val )
end
end
Returns true
if the specified search criteria
all
match this node.
# File lib/arborist/node.rb, line 697
def matches?( criteria, if_empty: true )
# Omit 'delta' criteria from matches; delta matching is done separately.
criteria = criteria.dup
criteria.delete( 'delta' )
self.log.debug "Node matching %p (%p if empty)" % [ criteria, if_empty ]
return if_empty if criteria.empty?
self.log.debug "Matching %p against criteria: %p" % [ self, criteria ]
return criteria.all? do |key, val|
self.match_criteria?( key, val )
end.tap {|match| self.log.debug " node %s match." % [ match ? "DID" : "did not"] }
end
Merge the specified new_properties
into the node's
properties, recording each change in the node's update_delta.
# File lib/arborist/node.rb, line 641
def merge_and_record_delta( key, oldval, newval, prefixes=[] )
self.log.debug "Merging property %s: %p -> %p" % [
(prefixes + [key]).join('.'),
oldval,
newval
]
# Merge them (recursively) if they're both merge-able
if oldval.respond_to?( :merge! ) && newval.respond_to?( :merge! )
return oldval.merge( newval ) do |ikey, ioldval, inewval|
self.merge_and_record_delta( ikey, ioldval, inewval, prefixes + [key] )
end
# Otherwise just directly compare them and record any changes
else
unless oldval == newval
prefixed_delta = prefixes.inject( self.update_delta ) do |hash, key|
hash[ key ]
end
prefixed_delta[ key ] = [ oldval, newval ]
end
return newval
end
end
Return the Set of identifier of nodes that are secondary dependencies of this node.
# File lib/arborist/node.rb, line 535
def node_subscribers
self.log.debug "Finding node subscribers among %d subscriptions" % [ self.subscriptions.length ]
return self.subscriptions.each_with_object( Set.new ) do |(identifier, sub), set|
if sub.respond_to?( :node_identifier )
set.add( sub.node_identifier )
else
self.log.debug "Skipping %p: not a node subscription" % [ sub ]
end
end
end
Return a Hash of the operational values that are included with the node's monitor state.
# File lib/arborist/node.rb, line 753
def operational_values
values = OPERATIONAL_ATTRIBUTES.each_with_object( {} ) do |key, hash|
hash[ key ] = self.send( key )
end
return values
end
Publish the specified events
to any subscriptions the node has
which match them.
# File lib/arborist/node.rb, line 780
def publish_events( *events )
self.log.debug "Got events to publish: %p" % [ events ]
self.subscriptions.each_value do |sub|
sub.on_events( *events )
end
end
Register subscriptions for secondary dependencies on the receiving node
with the given manager
.
# File lib/arborist/node.rb, line 764
def register_secondary_dependencies( manager )
self.dependencies.all_identifiers.each do |identifier|
# Check to be sure the identifier isn't a descendant or ancestor
if manager.ancestors_for( self ).any? {|node| node.identifier == identifier}
raise Arborist::ConfigError, "Can't depend on ancestor node %p." % [ identifier ]
elsif manager.descendants_for( self ).any? {|node| node.identifier == identifier }
raise Arborist::ConfigError, "Can't depend on descendant node %p." % [ identifier ]
end
sub = Arborist::NodeSubscription.new( self )
manager.subscribe( identifier, sub )
end
end
Remove the specified subscription
(an Arborist::Subscription) from the node.
# File lib/arborist/node.rb, line 523
def remove_subscription( subscription_id )
return self.subscriptions.delete( subscription_id )
end
Move a node from old_parent
to new_parent
.
# File lib/arborist/node.rb, line 835
def reparent( old_parent, new_parent )
old_parent.remove_child( self )
self.parent( new_parent.identifier )
new_parent.add_child( self )
self.quieted_reasons.delete( :primary )
super
end
Returns true
if the node's state has changed since the
last time snapshot_state was called.
# File lib/arborist/node.rb, line 691
def state_has_changed?
return ! self.update_delta.empty?
end
Return the simple type of this node (e.g., Arborist::Node::Host => 'host')
# File lib/arborist/node.rb, line 510
def type
return 'anonymous' unless self.class.name
return self.class.name.sub( /.*::/, '' ).downcase
end
Clear any current acknowledgement.
# File lib/arborist/node.rb, line 622
def unacknowledge
super()
self.ack = nil
events = self.pending_change_events.clone
events << self.make_delta_event unless self.update_delta.empty?
results = self.broadcast_events( *events )
self.log.debug ">>> Results from broadcast: %p" % [ results ]
events.concat( results )
return events
ensure
self.clear_transition_temp_vars
end
Update specified properties
for the node.
# File lib/arborist/node.rb, line 548
def update( new_properties, monitor_key='_' )
self.last_contacted = Time.now
self.update_properties( new_properties, monitor_key )
# Super to the state machine event method
super
events = self.pending_change_events.clone
events << self.make_update_event
events << self.make_delta_event unless self.update_delta.empty?
results = self.broadcast_events( *events )
self.log.debug ">>> Results from broadcast: %p" % [ results ]
events.concat( results )
return events
ensure
self.clear_transition_temp_vars
end
Update the errors hash for the specified monitor_key
to
value
.
# File lib/arborist/node.rb, line 584
def update_errors( monitor_key, value=nil )
if value
self.errors[ monitor_key ] = value
else
self.errors.delete( monitor_key )
end
end
Update the node's properties with those in new_properties
(a String-keyed Hash)
# File lib/arborist/node.rb, line 570
def update_properties( new_properties, monitor_key )
monitor_key ||= '_'
new_properties = stringify_keys( new_properties )
self.log.debug "Updated via a %s monitor: %p" % [ monitor_key, new_properties ]
self.update_errors( monitor_key, new_properties.delete('error') )
self.update_warnings( monitor_key, new_properties.delete('warning') )
self.properties.merge!( new_properties, &self.method(:merge_and_record_delta) )
compact_hash( self.properties )
end
Update the warnings hash for the specified monitor_key
to
value
.
# File lib/arborist/node.rb, line 594
def update_warnings( monitor_key, value=nil )
if value
self.warnings[ monitor_key ] = value
else
self.warnings.delete( monitor_key )
end
end
Equality operator – returns true
if other_node
has the same identifier, parent, and state as the receiving one.
# File lib/arborist/node.rb, line 1153
def ==( other_node )
return other_node.identifier == self.identifier &&
other_node.parent == self.parent &&
other_node.description == self.description &&
other_node.tags == self.tags
end
Marshal API – return the node as an object suitable for marshalling.
# File lib/arborist/node.rb, line 1110
def marshal_dump
return self.to_h.merge( dependencies: self.dependencies )
end
Marshal API – set up the object's state using the hash
from a previously-marshalled node.
# File lib/arborist/node.rb, line 1117
def marshal_load( hash )
self.log.debug "Restoring from serialized hash: %p" % [ hash ]
@identifier = hash[:identifier]
@properties = hash[:properties]
@parent = hash[:parent]
@description = hash[:description]
@tags = Set.new( hash[:tags] )
@config = hash[:config]
@children = {}
@status = hash[:status]
@status_changed = Time.parse( hash[:status_changed] )
@status_last_changed = Time.parse( hash[:status_last_changed] )
@ack = Arborist::Node::Ack.from_hash( hash[:ack] ) if hash[:ack]
@errors = hash[:errors]
@warnings = hash[:warnings]
@properties = hash[:properties] || {}
@last_contacted = Time.parse( hash[:last_contacted] )
@quieted_reasons = hash[:quieted_reasons] || {}
self.log.debug "Deps are: %p" % [ hash[:dependencies] ]
@dependencies = hash[:dependencies]
@update_delta = Hash.new do |h,k|
h[ k ] = Hash.new( &h.default_proc )
end
@pending_change_events = []
@subscriptions = {}
end
Restore any saved state from the old_node
loaded from the
state file. This is used to overlay selective bits of the saved node tree
to the equivalent nodes loaded from node definitions.
# File lib/arborist/node.rb, line 1055
def restore( old_node )
@status = old_node.status
@properties = old_node.properties.dup
@ack = old_node.ack.dup if old_node.ack
@last_contacted = old_node.last_contacted
@status_changed = old_node.status_changed
@errors = old_node.errors
@warnings = old_node.warnings
@quieted_reasons = old_node.quieted_reasons
@status_last_changed = old_node.status_last_changed
# Only merge in downed dependencies.
old_node.dependencies.each_downed do |identifier, time|
@dependencies.mark_down( identifier, time )
end
end
Return a Hash of the node's state. If depth
is greater
than 0, that many levels of child nodes are included in the node's
`:children` value. Setting depth
to a negative number will
return all of the node's children.
# File lib/arborist/node.rb, line 1076
def to_h( depth: 0 )
hash = {
identifier: self.identifier,
type: self.class.name.to_s.sub( /.+::/, '' ).downcase,
parent: self.parent,
description: self.description,
tags: self.tags,
config: self.config,
status: self.status,
properties: self.properties.dup,
ack: self.ack ? self.ack.to_h : nil,
last_contacted: self.last_contacted ? self.last_contacted.iso8601 : nil,
status_changed: self.status_changed ? self.status_changed.iso8601 : nil,
status_last_changed: self.status_last_changed ? self.status_last_changed.iso8601 : nil,
errors: self.errors,
warnings: self.warnings,
dependencies: self.dependencies.to_h,
quieted_reasons: self.quieted_reasons,
}
if depth.nonzero?
# self.log.debug "including children for depth %p" % [ depth ]
hash[ :children ] = self.children.each_with_object( {} ) do |(ident, node), h|
h[ ident ] = node.to_h( depth: depth - 1 )
end
else
hash[ :children ] = {}
end
return hash
end
Ack the node with the specified
ack_data
, which should contain
# File lib/arborist/node.rb, line 1167
def ack=( ack_data )
if ack_data
self.log.info "Node %s ACKed with data: %p" % [ self.identifier, ack_data ]
@ack = Arborist::Node::Ack.from_hash( ack_data )
else
self.log.info "Node %s ACK cleared explicitly" % [ self.identifier ]
@ack = nil
end
self.add_previous_ack_to_update_delta
end
State machine guard predicate – Returns true
if the node has
an ACK status set.
# File lib/arborist/node.rb, line 1199
def ack_set?
self.log.debug "Checking to see if this node has been ACKed (it %s)" %
[ @ack ? "has" : "has not" ]
return @ack ? true : false
end
Add the previous and current acknowledgement to the delta if either of them are set.
# File lib/arborist/node.rb, line 1190
def add_previous_ack_to_update_delta
unless self.ack == self.previous_ack
self.log.debug "Adding previous ack to the update delta: %p" % [ self.previous_ack ]
self.update_delta[ 'ack' ] = [ self.previous_ack&.to_h, self.ack&.to_h ]
end
end
Return a string describing the errors that are set on the node.
# File lib/arborist/node.rb, line 1247
def errors_description
return "No errors" if self.errors.empty?
return self.errors.map do |key, msg|
"%s: %s" % [ key, msg ]
end.join( '; ' )
end
State machine guard predicate – returns true
if the node has
errors.
# File lib/arborist/node.rb, line 1207
def has_errors?
has_errors = ! self.errors.empty?
self.log.debug "Checking to see if last contact cleared remaining errors (it %s)" %
[ has_errors ? "did not" : "did" ]
self.log.debug "Errors are: %p" % [ self.errors ]
return has_errors
end
State machine guard predicate – returns true
if the node has
warnings or errors.
# File lib/arborist/node.rb, line 1234
def has_errors_or_warnings?
return self.has_errors? || self.has_warnings?
end
State machine guard predicate – returns true
if the node has
warnings but no errors.
# File lib/arborist/node.rb, line 1241
def has_only_warnings?
return self.has_warnings? && ! self.has_errors?
end
State machine guard predicate – Returns true
if the node has
errors and does not have an ACK status set.
# File lib/arborist/node.rb, line 1218
def has_unacked_errors?
return self.has_errors? && !self.ack_set?
end
State machine guard predicate – returns true
if the node has
warnings.
# File lib/arborist/node.rb, line 1224
def has_warnings?
has_warnings = ! self.warnings.empty?
self.log.debug "Checking to see if last contact cleared remaining warnings (it %s)" %
[ has_warnings ? "did not" : "did" ]
self.log.debug "Warnings are: %p" % [ self.warnings ]
return has_warnings
end
Save off the current acknowledgement so it can be used after transitions which unset it.
# File lib/arborist/node.rb, line 1182
def save_previous_ack
self.log.debug "Saving previous ack: %p" % [ self.ack ]
self.previous_ack = self.ack
end
Return a string describing the warnings that are set on the node.
# File lib/arborist/node.rb, line 1256
def warnings_description
return "No warnings" if self.warnings.empty?
return self.warnings.map do |key, msg|
"%s: %s" % [ key, msg ]
end.join( '; ' )
end
Add the transition from one state to another to the data used to build deltas for the update event.
# File lib/arborist/node.rb, line 1353
def add_status_to_update_delta( transition )
self.update_delta[ 'status' ] = [ transition.from, transition.to ]
end
Log every status transition
# File lib/arborist/node.rb, line 1269
def log_transition( transition )
self.log.debug "Transitioned %s from %s to %s" %
[ self.identifier, transition.from, transition.to ]
end
Queue up a transition event whenever one happens
# File lib/arborist/node.rb, line 1283
def make_transition_event( transition )
node_type = "node_%s" % [ transition.to ]
self.log.debug "Making a %s event for %p" % [ node_type, transition ]
self.pending_change_events << Arborist::Event.create( node_type, self )
end
Callback for when an acknowledgement is set.
# File lib/arborist/node.rb, line 1291
def on_ack( transition )
self.log.warn "ACKed: %s" % [ self.status_description ]
end
Callback for when an acknowledgement is cleared.
# File lib/arborist/node.rb, line 1297
def on_ack_cleared( transition )
self.ack = nil
self.log.warn "ACK cleared for %s" % [ self.identifier ]
end
Callback for when a node goes from up to disabled
# File lib/arborist/node.rb, line 1325
def on_node_disabled( transition )
self.errors.clear
self.warnings.clear
self.log.warn "%s is %s" % [ self.identifier, self.status_description ]
end
Callback for when a node goes from up to down
# File lib/arborist/node.rb, line 1311
def on_node_down( transition )
self.log.error "%s is %s" % [ self.identifier, self.status_description ]
self.update_delta[ 'errors' ] = [ nil, self.errors_description ]
end
Callback for when a node goes from disabled to unknown
# File lib/arborist/node.rb, line 1345
def on_node_enabled( transition )
self.log.warn "%s is %s" % [ self.identifier, self.status_description ]
self.ack = nil
end
Callback for when a node goes from any state to quieted
# File lib/arborist/node.rb, line 1333
def on_node_quieted( transition )
self.log.warn "%s is %s" % [ self.identifier, self.status_description ]
end
Callback for when a node transitions from quieted to unknown
# File lib/arborist/node.rb, line 1339
def on_node_unquieted( transition )
self.log.warn "%s is %s" % [ self.identifier, self.status_description ]
end
Callback for when a node goes from down to up
# File lib/arborist/node.rb, line 1304
def on_node_up( transition )
self.errors.clear
self.log.warn "%s is %s" % [ self.identifier, self.status_description ]
end
Callback for when a node goes from up to warn
# File lib/arborist/node.rb, line 1318
def on_node_warn( transition )
self.log.error "%s is %s" % [ self.identifier, self.status_description ]
self.update_delta[ 'warnings' ] = [ nil, self.warnings_description ]
end
Update the last status change time.
# File lib/arborist/node.rb, line 1276
def update_status_changed( transition )
self.status_last_changed = self.status_changed
self.status_changed = Time.now
end
Return a description of the ack if it's set, or a generic string otherwise.
# File lib/arborist/node.rb, line 999
def acked_description
return self.ack.description if self.ack
return "(unset)"
end
Return a String representation of the object suitable for debugging.
# File lib/arborist/node.rb, line 1033
def inspect
return "#<%p:%#x [%s] -> %s %p %s %s, %d children, %s>" % [
self.class,
self.object_id * 2,
self.identifier,
self.parent || 'root',
self.description || "(no description)",
self.node_description.to_s,
self.source,
self.children.length,
self.status_description,
]
end
Return a string describing node details; returns nil
for the
base class. Subclasses may override this to add to the output of inspect.
# File lib/arborist/node.rb, line 1027
def node_description
return nil
end
Return a string describing the node's status.
# File lib/arborist/node.rb, line 1006
def status_description
case self.status
when 'up', 'down', 'warn'
return "%s as of %s" % [ self.status.upcase, self.last_contacted ]
when 'acked'
return "ACKed %s" % [ self.acked_description ]
when 'disabled'
return "disabled %s" % [ self.acked_description ]
when 'quieted'
reasons = self.quieted_reasons.values.join( ',' )
return "quieted: %s" % [ reasons ]
when 'unknown'
return "in an 'unknown' state"
else
return "in an unhandled state"
end
end