class Arborist::Node
The basic node class for an Arborist
tree
Constants
- LOADED_INSTANCE_KEY
The key for the thread local that is used to track instances as they're loaded.
- OPERATIONAL_ATTRIBUTES
The attributes of a node which are used in the operation of the system
- UNREACHABLE_STATES
Node
states that are unreachable by default.- VALID_IDENTIFIER
Regex to match a valid identifier
Attributes
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.
An array of statuses, retained after an update.
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`.
Public Class Methods
Record a new loaded instance if the Thread-local variable is set up to track them.
# File lib/arborist/node.rb, line 218 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 260 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 191 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 305 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 209 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 226 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 287 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
Overridden to track instances of created nodes for the DSL.
# File lib/arborist/node.rb, line 201 def self::new( * ) new_instance = super Arborist::Node.add_loaded_instance( new_instance ) return new_instance end
Create a new Node
with the specified identifier
, which must be unique to the loaded tree.
# File lib/arborist/node.rb, line 312 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 ) @flap_threshold = nil @flapping = false @status_history_size = nil # Primary state @status = 'unknown' @status_changed = Time.at( 0 ) @status_last_changed = Time.at( 0 ) @status_history = [] # 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
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 238 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 253 def self::subnode_type? return ! self.parent_types.empty? end
Public Instance Methods
Returns true
if the node is in an 'acked' state.
# File lib/arborist/node.rb, line 100
Returns true
if the node is in an 'disabled' state.
# File lib/arborist/node.rb, line 104
Returns true
if the node is in an 'down' state.
# File lib/arborist/node.rb, line 96
Return the node family, so observers can know ancestry after serialization for custom node types that inherit from this class.
# File lib/arborist/node.rb, line 449 def family return :node end
The current flapping state of this node.
# File lib/arborist/node.rb, line 438 attr_predicate_accessor :flapping
Return the node's status as a human-readable String.
# File lib/arborist/node.rb, line 108
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 458 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 442 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 112
Set the status of the node to new_status
.
# File lib/arborist/node.rb, line 117
Returns true
if the node's status is status_name
.
# File lib/arborist/node.rb, line 130 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 after_transition do: :record_status_history after_failure do: :record_status_history end
Returns true
if the node is in an 'unknown' state.
# File lib/arborist/node.rb, line 88
Returns true
if the node is in an 'up' state.
# File lib/arborist/node.rb, line 92
DSLish declaration methods
↑ topPublic Instance Methods
Group identifiers
together in an 'all of' dependency.
# File lib/arborist/node.rb, line 513 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 507 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 535 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 519 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 492 def description( new_description=nil ) return @description unless new_description @description = new_description.to_s end
Get or set the number of transitions in the status history to determine if a node is considering 'flapping'.
# File lib/arborist/node.rb, line 551 def flap_threshold( new_count=nil ) @flap_threshold = new_count if new_count return @flap_threshold || Arborist::Node.flap_threshold || 0 end
Returns true
if the node has one or more secondary dependencies.
# File lib/arborist/node.rb, line 528 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 480 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
Get or set the number of entries to store for the status history.
# File lib/arborist/node.rb, line 543 def status_history_size( new_size=nil ) @status_history_size = new_size if new_size return @status_history_size || Arborist::Node.status_history_size || 0 end
Hierarchy API
↑ topPublic Instance Methods
Append operator – add the specified node
as a child and return self
.
# File lib/arborist/node.rb, line 1045 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 1036 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 1002 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 1008 def has_children? return !self.children.empty? end
Returns true
if the node is considered operational.
# File lib/arborist/node.rb, line 1014 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 1029 def reachable? return !self.unreachable? end
Unregister the specified node
as a child of this node.
# File lib/arborist/node.rb, line 1052 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 1021 def unreachable? self.log.debug "Testing for reachability; status is: %p" % [ self.status ] return UNREACHABLE_STATES.include?( self.status ) end
Manager API
↑ topPublic Instance Methods
Acknowledge any current or future abnormal status for this node.
# File lib/arborist/node.rb, line 696 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 571 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 854 def broadcast_events( *events ) events.flatten! results = self.children.flat_map do |identifier, child| self.log.debug "Broadcasting events to %p: %p" % [ identifier, events ] 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 732 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 911 def dependencies_down? return self.dependencies.down? end
Returns true
if this node's dependencies are met.
# File lib/arborist/node.rb, line 918 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 801 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 583 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 869 def handle_event( event ) self.log.debug "Handling event %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 # Don't transition on informational events return [] if event.informational? 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 ) unless results.empty? return results ensure self.clear_transition_temp_vars end
Handle a 'node.disabled' event received via broadcast.
# File lib/arborist/node.rb, line 947 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 931 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 963 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 979 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 925 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 746 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 740 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 777 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 'family' array_val.include?( self.family.to_s ) 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 760 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 newval
into the node's properties at the given key
, recording each change in the node's update_delta
if the oldval
is different.
# File lib/arborist/node.rb, line 651 def merge_and_record_delta( properties, new_properties ) delta = {} new_properties.each_key do |key| newval = new_properties[ key ] oldval = properties[ key ] subdelta = nil # Merge them (recursively) if they're both merge-able if oldval.respond_to?( :merge! ) && newval.respond_to?( :merge! ) newval, subdelta = self.merge_and_record_delta( oldval, newval ) # Otherwise just directly compare them and record any changes elsif oldval != newval subdelta = [ oldval, newval ] end properties[ key ] = newval delta[ key ] = subdelta if subdelta end return properties, delta end
Merge the specified Hash of new_properties
with the node's current property Hash.
# File lib/arborist/node.rb, line 638 def merge_new_properties( new_properties ) props = self.properties.dup updated_properties, properties_delta = self.merge_and_record_delta( props, new_properties ) compact_hash( updated_properties ) self.properties.replace( updated_properties ) self.update_delta['properties'] = properties_delta unless properties_delta.empty? end
Return the Set of identifier of nodes that are secondary dependencies of this node.
# File lib/arborist/node.rb, line 589 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 818 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 845 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 829 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 577 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 900 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 754 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 564 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 714 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 602 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 676 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 624 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.merge_new_properties( new_properties ) end
Update the warnings hash for the specified monitor_key
to value
.
# File lib/arborist/node.rb, line 686 def update_warnings( monitor_key, value=nil ) if value self.warnings[ monitor_key ] = value else self.warnings.delete( monitor_key ) end end
Serialization API
↑ topPublic Instance Methods
Equality operator – returns true
if other_node
has the same identifier, parent, and state as the receiving one.
# File lib/arborist/node.rb, line 1226 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 1180 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 1187 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] ) @status_history = hash[:status_history] @flapping = hash[:flapping] @status_history_size = nil @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 1120 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 @status_history = old_node.status_history @flapping = old_node.flapping? @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 1143 def to_h( depth: 0 ) hash = { identifier: self.identifier, type: self.class.name.to_s.sub( /.+::/, '' ).downcase, parent: self.parent, family: self.family, 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, status_history: self.status_history, flapping: self.flapping?, 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
Protected Instance Methods
Ack
the node with the specified ack_data
, which should contain
# File lib/arborist/node.rb, line 1257 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 1289 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 1280 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
Detects if this node is considered 'flapping', returning true
if so.
# File lib/arborist/node.rb, line 1240 def check_flapping return false if self.flap_threshold.zero? runs = self.status_history.each_cons( 2 ).count do |status_1, status_2| status_1 != status_2 end self.log.debug "Observed %d changes in %d samples." % [ runs, self.status_history.size ] return runs >= self.flap_threshold end
Return a string describing the errors that are set on the node.
# File lib/arborist/node.rb, line 1337 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 1297 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 1324 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 1331 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 1308 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 1314 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 1272 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 1346 def warnings_description return "No warnings" if self.warnings.empty? return self.warnings.map do |key, msg| "%s: %s" % [ key, msg ] end.join( '; ' ) end
State Callbacks
↑ topProtected Instance Methods
Add a flap change to the delta event.
# File lib/arborist/node.rb, line 1449 def add_flap_to_update_delta( from, to ) return if from == to self.update_delta[ 'flapping' ] = [ from, to ] 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 1443 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 1359 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 1373 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 1381 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 1387 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 1415 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 1401 def on_node_down( transition ) self.log.error "%s is %s" % [ self.identifier, self.status_description ] self.update_delta[ 'errors' ] = [ nil, self.errors ] end
Callback for when a node goes from disabled to unknown
# File lib/arborist/node.rb, line 1435 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 1423 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 1429 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 1394 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 1408 def on_node_warn( transition ) self.log.error "%s is %s" % [ self.identifier, self.status_description ] self.update_delta[ 'warnings' ] = [ nil, self.warnings ] end
Retain the status in the node's history.
# File lib/arborist/node.rb, line 1457 def record_status_history( transition ) retain = self.status_history_size return if retain.zero? pre_state = self.flapping? self.status_history << self.status self.flapping = self.check_flapping current_size = self.status_history.size self.status_history.slice!( 0, current_size - retain ) if current_size >= retain self.add_flap_to_update_delta( pre_state, self.flapping? ) end
Update the last status change time.
# File lib/arborist/node.rb, line 1366 def update_status_changed( transition ) self.status_last_changed = self.status_changed self.status_changed = Time.now end
Private Instance Methods
Check the specified dependencies
(an Arborist::Dependency
) for illegal dependencies and raise an error if any are found.
# File lib/arborist/node.rb, line 1478 def check_dependencies( dependencies ) identifiers = dependencies.all_identifiers self.log.debug "Checking dependency identifiers: %p" % [ identifiers ] if identifiers.include?( '_' ) raise Arborist::ConfigError, "a node can't depend on the root node" elsif identifiers.include?( self.identifier ) raise Arborist::ConfigError, "a node can't depend on itself" end return dependencies end
Turn any non-msgpack-able objects in the values of a copy of hash
to values that can be serialized and return the copy.
# File lib/arborist/node.rb, line 1494 def make_serializable( hash ) new_hash = hash.dup new_hash.keys.each do |key| val = new_hash[ key ] case val when Hash new_hash[ key ] = make_serializable( val ) when Arborist::Dependency, Arborist::Node::Ack new_hash[ key ] = val.to_h when Time new_hash[ key ] = val.iso8601 end end return new_hash end
Utility methods
↑ topPublic Instance Methods
Return a description of the ack if it's set, or a generic string otherwise.
# File lib/arborist/node.rb, line 1064 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 1098 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 1092 def node_description return nil end
Return a string describing the node's status.
# File lib/arborist/node.rb, line 1071 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