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

ack[RW]

The acknowledgement currently in effect. Should be an instance of Arborist::Node::ACK

children[R]

The Hash of nodes which are children of this node, keyed by identifier

dependencies[RW]

The node's secondary dependencies, expressed as an Arborist::Node::Sexp

errors[RW]

The Hash of last errors encountered by a monitor attempting to update this node, keyed by the monitor's `key`.

identifier[R]

The node's identifier

last_contacted[RW]

The Time the node was last contacted

pending_change_events[R]

The Array of events generated by the current update event

previous_ack[RW]

The acknowledgement previously in effect (if any).

properties[R]

Arbitrary attributes attached to this node via the manager API

quieted_reasons[R]

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).

source[R]

The URI of the source the object was read from

status_changed[RW]

The Time the node's status last changed.

status_history[R]

An array of statuses, retained after an update.

status_last_changed[RW]

The previous Time the node's status changed, for duration calculations between states.

subscriptions[R]

The Hash of Subscription objects observing this node and its children, keyed by subscription ID.

update_delta[R]

The Hash of changes tracked during an update.

warnings[RW]

The Hash of last warnings encountered by a monitor attempting to update this node, keyed by the monitor's `key`.

Public Class Methods

add_loaded_instance( new_instance ) click to toggle source

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_subnode_factory_method( subnode_type, &dsl_block ) click to toggle source

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
curried_create( type ) click to toggle source

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
each_in( loader ) click to toggle source

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
from_hash( hash ) click to toggle source

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
inherited( subclass ) click to toggle source

Inheritance hook – add a DSL declarative function for the given subclass.

Calls superclass method
# 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( file ) click to toggle source

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
new( * ) click to toggle source

Overridden to track instances of created nodes for the DSL.

Calls superclass method
# File lib/arborist/node.rb, line 201
def self::new( * )
        new_instance = super
        Arborist::Node.add_loaded_instance( new_instance )
        return new_instance
end
new( identifier, *args, &block ) click to toggle source

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
parent_types( *types, &block ) click to toggle source

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
subnode_type?() click to toggle source

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

acked?() click to toggle source

Returns true if the node is in an 'acked' state.

# File lib/arborist/node.rb, line 100
        
disabled?() click to toggle source

Returns true if the node is in an 'disabled' state.

# File lib/arborist/node.rb, line 104
        
down?() click to toggle source

Returns true if the node is in an 'down' state.

# File lib/arborist/node.rb, line 96
        
family() click to toggle source

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
flapping() click to toggle source

The current flapping state of this node.

# File lib/arborist/node.rb, line 438
attr_predicate_accessor :flapping
human_status_name() click to toggle source

Return the node's status as a human-readable String.

# File lib/arborist/node.rb, line 108
        
modify( attributes ) click to toggle source

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
source=( source ) click to toggle source

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
status() click to toggle source

Return the status of the node. This will be one of: unknown, up, down, acked, or disabled.

# File lib/arborist/node.rb, line 112
        
status=( new_status ) click to toggle source

Set the status of the node to new_status.

# File lib/arborist/node.rb, line 117
        
status?( status_name ) click to toggle source

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
unknown?() click to toggle source

Returns true if the node is in an 'unknown' state.

# File lib/arborist/node.rb, line 88
        
up?() click to toggle source

Returns true if the node is in an 'up' state.

# File lib/arborist/node.rb, line 92
        

DSLish declaration methods

↑ top

Public Instance Methods

all_of( *identifiers, on: nil ) click to toggle source

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
any_of( *identifiers, on: nil ) click to toggle source

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
config( new_config=nil ) click to toggle source

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
depends_on( *dependencies, on: nil ) click to toggle source

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
description( new_description=nil ) click to toggle source

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
flap_threshold( new_count=nil ) click to toggle source

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
has_dependencies?() click to toggle source

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
parent( new_parent=nil ) click to toggle source

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
status_history_size( new_size=nil ) click to toggle source

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
tags( *tags ) click to toggle source

Declare one or more tags for this node.

# File lib/arborist/node.rb, line 499
def tags( *tags )
        tags.flatten!
        @tags.merge( tags.map(&:to_s) ) unless tags.empty?
        return @tags.to_a
end

Hierarchy API

↑ top

Public Instance Methods

<<( node ) click to toggle source

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
add_child( node ) click to toggle source

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
each( &block ) click to toggle source

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
has_children?() click to toggle source

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
operational?() click to toggle source

Returns true if the node is considered operational.

# File lib/arborist/node.rb, line 1014
def operational?
        return self.identifier.start_with?( '_' )
end
reachable?() click to toggle source

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
remove_child( node ) click to toggle source

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
unreachable?() click to toggle source

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

↑ top

Public Instance Methods

acknowledge( **args ) click to toggle source

Acknowledge any current or future abnormal status for this node.

Calls superclass method
# 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_subscription( subscription ) click to toggle source

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
broadcast_events( *events ) click to toggle source

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_transition_temp_vars() click to toggle source

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
dependencies_down?() click to toggle source

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
Also aliased as: has_downed_dependencies?
dependencies_up?() click to toggle source

Returns true if this node's dependencies are met.

# File lib/arborist/node.rb, line 918
def dependencies_up?
        return !self.dependencies_down?
end
fetch_values( value_spec=nil ) click to toggle source

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
find_matching_subscriptions( event ) click to toggle source

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_event( event ) click to toggle source

Handle the specified event, delivered either via broadcast or secondary dependency subscription.

Calls superclass method
# 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_node_disabled_event( event ) click to toggle source

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_node_down_event( event ) click to toggle source

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_node_quieted_event( event ) click to toggle source

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_node_up_event( event ) click to toggle source

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
Also aliased as: handle_node_warn_event
handle_node_warn_event( event )
has_downed_dependencies?()
Alias for: dependencies_down?
has_quieted_reason?() click to toggle source

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
make_delta_event() click to toggle source

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
make_update_event() click to toggle source

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
match_criteria?( key, val ) click to toggle source

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
matches?( criteria, if_empty: true ) click to toggle source

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_and_record_delta( properties, new_properties ) click to toggle source

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_new_properties( new_properties ) click to toggle source

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
node_subscribers() click to toggle source

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
operational_values() click to toggle source

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_events( *events ) click to toggle source

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_secondary_dependencies( manager ) click to toggle source

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_subscription( subscription_id ) click to toggle source

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
reparent( old_parent, new_parent ) click to toggle source

Move a node from old_parent to new_parent.

Calls superclass method
# 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
state_has_changed?() click to toggle source

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
type() click to toggle source

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
unacknowledge() click to toggle source

Clear any current acknowledgement.

Calls superclass method
# 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( new_properties, monitor_key='_' ) click to toggle source

Update specified properties for the node.

Calls superclass method
# 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_errors( monitor_key, value=nil ) click to toggle source

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_properties( new_properties, monitor_key ) click to toggle source

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_warnings( monitor_key, value=nil ) click to toggle source

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

↑ top

Public Instance Methods

==( other_node ) click to toggle source

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_dump() click to toggle source

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_load( hash ) click to toggle source

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( old_node ) click to toggle source

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
to_h( depth: 0 ) click to toggle source

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=( ack_data ) click to toggle source

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
ack_set?() click to toggle source

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_previous_ack_to_update_delta() click to toggle source

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
check_flapping() click to toggle source

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
errors_description() click to toggle source

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
has_errors?() click to toggle source

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
has_errors_or_warnings?() click to toggle source

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
has_only_warnings?() click to toggle source

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
has_unacked_errors?() click to toggle source

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
has_warnings?() click to toggle source

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_previous_ack() click to toggle source

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
warnings_description() click to toggle source

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

↑ top

Protected Instance Methods

add_flap_to_update_delta( from, to ) click to toggle source

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_status_to_update_delta( transition ) click to toggle source

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_transition( transition ) click to toggle source

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
make_transition_event( transition ) click to toggle source

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
on_ack( transition ) click to toggle source

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
on_ack_cleared( transition ) click to toggle source

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
on_node_disabled( transition ) click to toggle source

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
on_node_down( transition ) click to toggle source

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
on_node_enabled( transition ) click to toggle source

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
on_node_quieted( transition ) click to toggle source

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
on_node_unquieted( transition ) click to toggle source

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
on_node_up( transition ) click to toggle source

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
on_node_warn( transition ) click to toggle source

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
record_status_history( transition ) click to toggle source

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_status_changed( transition ) click to toggle source

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_dependencies( dependencies ) click to toggle source

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
make_serializable( hash ) click to toggle source

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

↑ top

Public Instance Methods

acked_description() click to toggle source

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
inspect() click to toggle source

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
node_description() click to toggle source

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
status_description() click to toggle source

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