Node

class
Superclass
Object
Included Modules
Enumerable
Arborist::HashUtilities
Extended With
Loggability
Pluggability
Arborist::MethodUtilities

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_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

anchor
add_loaded_instance( new_instance )

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

# File lib/arborist/node.rb, line 199
def self::add_loaded_instance( new_instance )
        instances = Thread.current[ LOADED_INSTANCE_KEY ] or return
        # self.log.debug "Adding new instance %p to node tree" % [ new_instance ]
        instances << new_instance
end
anchor
add_subnode_factory_method( subnode_type, &dsl_block )

Add a factory method that can be used to create subnodes of the specified subnode_type on instances of the receiving class.

# File lib/arborist/node.rb, line 241
def self::add_subnode_factory_method( subnode_type, &dsl_block )
        if subnode_type.name
                name = subnode_type.plugin_name
                # self.log.debug "Adding factory constructor for %s nodes to %p" % [ name, self ]
                body = lambda do |*args, &constructor_block|
                        if dsl_block
                                # self.log.debug "Using DSL block to split args: %p" % [ dsl_block ]
                                identifier, attributes = dsl_block.call( *args )
                        else
                                # self.log.debug "Splitting args the default way: %p" % [ args ]
                                identifier, attributes = *args
                        end
                        attributes ||= {}
                        # self.log.debug "Identifier: %p, attributes: %p, self: %p" %
                        #   [ identifier, attributes, self ]

                        return Arborist::Node.create( name, identifier, self, attributes, &constructor_block )
                end

                define_method( name, &body )
        else
                self.log.info "Skipping factory constructor for anonymous subnode class."
        end
end
anchor
curried_create( type )

Return a curried Proc for the ::create method for the specified type.

# File lib/arborist/node.rb, line 172
def self::curried_create( type )
        if type.subnode_type?
                return self.method( :create ).to_proc.curry( 3 )[ type ]
        else
                return self.method( :create ).to_proc.curry( 2 )[ type ]
        end
end
anchor
each_in( loader )

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

# File lib/arborist/node.rb, line 286
def self::each_in( loader )
        return loader.nodes
end
anchor
from_hash( hash )

Create a new node with its state read from the specified hash.

# File lib/arborist/node.rb, line 190
def self::from_hash( hash )
        return self.new( hash[:identifier] ) do
                self.marshal_load( hash )
        end
end
anchor
inherited( subclass )

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

# File lib/arborist/node.rb, line 207
def self::inherited( subclass )
        super

        body = self.curried_create( subclass )
        Arborist.add_dsl_constructor( subclass, &body )
end
anchor
load( file )

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

# File lib/arborist/node.rb, line 268
def self::load( file )
        self.log.info "Loading node file %s..." % [ file ]
        Thread.current[ LOADED_INSTANCE_KEY ] = []

        begin
                Kernel.load( file )
        rescue => err
                self.log.error "%p while loading %s: %s" % [ err.class, file, err.message ]
                raise
        end

        return Thread.current[ LOADED_INSTANCE_KEY ]
ensure
        Thread.current[ LOADED_INSTANCE_KEY ] = nil
end
anchor
new( identifier, *args, &block )

Create a new Node with the specified identifier, which must be unique to the loaded tree.

# File lib/arborist/node.rb, line 293
def initialize( identifier, *args, &block )
        attributes  = args.last.is_a?( Hash ) ? args.pop : {}
        parent_node = args.pop

        raise "Invalid identifier %p" % [identifier] unless
                identifier =~ VALID_IDENTIFIER

        # Attributes of the target
        @identifier      = identifier
        @parent          = parent_node ? parent_node.identifier : '_'
        @description     = nil
        @tags            = Set.new
        @properties      = {}
        @config          = {}
        @source          = nil
        @children        = {}
        @dependencies    = Arborist::Dependency.new( :all )

        # Primary state
        @status          = 'unknown'
        @status_changed  = Time.at( 0 )
        @status_last_changed = Time.at( 0 )

        # Attributes that govern state
        @errors          = {}
        @warnings        = {}
        @ack             = nil
        @previous_ack    = nil
        @last_contacted  = Time.at( 0 )
        @quieted_reasons = {}

        # Event-handling
        @update_delta    = Hash.new do |h,k|
                h[ k ] = Hash.new( &h.default_proc )
        end
        @pending_change_events = []
        @subscriptions  = {}

        self.modify( attributes )
        self.instance_eval( &block ) if block
end
anchor
new( * )

Overridden to track instances of created nodes for the DSL.

# File lib/arborist/node.rb, line 182
def self::new( * )
        new_instance = super
        Arborist::Node.add_loaded_instance( new_instance )
        return new_instance
end
anchor
parent_types( *types, &block )

Get/set the node type instances of the class live under. If no parent_type is set, it is a top-level node type. If a block is given, it can be used to pre-process the arguments into the (identifier, attributes, block) arguments used to create the node instances.

# File lib/arborist/node.rb, line 219
def self::parent_types( *types, &block )
        @parent_types ||= []

        types.each do |new_type|
                subclass = Arborist::Node.get_subclass( new_type )
                @parent_types << subclass
                subclass.add_subnode_factory_method( self, &block )
        end

        return @parent_types
end
anchor
subnode_type?()

Returns true if the receiver must be created under a specific node type.

# File lib/arborist/node.rb, line 234
def self::subnode_type?
        return ! self.parent_types.empty?
end

Public Instance Methods

anchor
acked?()

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

# File lib/arborist/node.rb, line 87
        
anchor
disabled?()

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

# File lib/arborist/node.rb, line 91
        
anchor
down?()

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

# File lib/arborist/node.rb, line 83
        
anchor
human_status_name()

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

# File lib/arborist/node.rb, line 95
        
anchor
modify( attributes )

Set one or more node attributes. This should be overridden by subclasses which wish to allow their operational attributes to be set/updated via the Tree API (modify and graft). Supported attributes are: parent, description, tags, and config.

# File lib/arborist/node.rb, line 420
def modify( attributes )
        attributes = stringify_keys( attributes )

        self.parent( attributes['parent'] )
        self.description( attributes['description'] )
        self.config( attributes['config'] )

        if attributes['tags']
                @tags.clear
                self.tags( attributes['tags'] )
        end
end
anchor
source=( source )

Set the source of the node to source, which should be a valid URI.

# File lib/arborist/node.rb, line 411
def source=( source )
        @source = URI( source )
end
anchor
status()

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

# File lib/arborist/node.rb, line 100
        
anchor
status=( new_status )

Set the status of the node to new_status.

# File lib/arborist/node.rb, line 107
        
anchor
status?( status_name )

Returns true if the node's status is status_name.

# File lib/arborist/node.rb, line 114
state_machine( :status, initial: :unknown ) do

        state :unknown,
                :up,
                :down,
                :warn,
                :acked,
                :disabled,
                :quieted

        event :update do
                transition [:down, :warn, :unknown, :acked] => :up, unless: :has_errors_or_warnings?
                transition [:up, :warn, :unknown] => :down, if: :has_errors?
                transition [:up, :down, :unknown] => :warn, if: :has_only_warnings?
        end

        event :acknowledge do
                transition any - [:down] => :disabled
                transition :down => :acked
        end

        event :unacknowledge do
                transition [:acked, :disabled] => :warn, if: :has_warnings?
                transition [:acked, :disabled] => :down, if: :has_errors?
                transition [:acked, :disabled] => :unknown
        end

        event :handle_event do
                transition any - [:disabled, :quieted, :acked] => :quieted, if: :has_quieted_reason?
                transition :quieted => :unknown, unless: :has_quieted_reason?
        end

        event :reparent do
                transition any - [:disabled, :quieted, :acked] => :unknown
                transition :quieted => :unknown, unless: :has_quieted_reason?
        end

        before_transition [:acked, :disabled] => any, do: :save_previous_ack

        after_transition any => :acked, do: :on_ack
        after_transition :acked => :up, do: :on_ack_cleared
        after_transition :down => :up, do: :on_node_up
        after_transition :up => :warn, do: :on_node_warn
        after_transition [:unknown, :warn, :up] => :down, do: :on_node_down
        after_transition [:acked, :unknown, :warn, :up] => :disabled, do: :on_node_disabled
        after_transition any => :quieted, do: :on_node_quieted
        after_transition :disabled => :unknown, do: :on_node_enabled
        after_transition :quieted => :unknown, do: :on_node_unquieted

        after_transition any => any, do: :log_transition
        after_transition any => any, do: :make_transition_event
        after_transition any => any, do: :update_status_changed

        after_transition do: :add_status_to_update_delta
end
anchor
unknown?()

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

# File lib/arborist/node.rb, line 75
        
anchor
up?()

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

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

DSLish declaration methods

↑ top

Public Instance Methods

anchor
all_of( *identifiers, on: nil )

Group identifiers together in an 'all of' dependency.

# File lib/arborist/node.rb, line 475
def all_of( *identifiers, on: nil )
        return Arborist::Dependency.on( :all, *identifiers, prefixes: on )
end
anchor
any_of( *identifiers, on: nil )

Group identifiers together in an 'any of' dependency.

# File lib/arborist/node.rb, line 469
def any_of( *identifiers, on: nil )
        return Arborist::Dependency.on( :any, *identifiers, prefixes: on )
end
anchor
config( new_config=nil )

Get or set the node's configuration hash. This can be used to pass per-node information to systems using the tree (e.g., monitors, subscribers).

# File lib/arborist/node.rb, line 497
def config( new_config=nil )
        @config.merge!( stringify_keys( new_config ) ) if new_config
        return @config
end
anchor
depends_on( *dependencies, on: nil )

Add secondary dependencies to the receiving node.

# File lib/arborist/node.rb, line 481
def depends_on( *dependencies, on: nil )
        dependencies = self.all_of( *dependencies, on: on )

        self.log.debug "Setting secondary dependencies to: %p" % [ dependencies ]
        self.dependencies = check_dependencies( dependencies )
end
anchor
description( new_description=nil )

Get/set the node's description.

# File lib/arborist/node.rb, line 454
def description( new_description=nil )
        return @description unless new_description
        @description = new_description.to_s
end
anchor
has_dependencies?()

Returns true if the node has one or more secondary dependencies.

# File lib/arborist/node.rb, line 490
def has_dependencies?
        return !self.dependencies.empty?
end
anchor
parent( new_parent=nil )

Get/set the node's parent node, which should either be an identifier or an object that responds to identifier with one.

# File lib/arborist/node.rb, line 442
def parent( new_parent=nil )
        return @parent if new_parent.nil?

        @parent = if new_parent.respond_to?( :identifier )
                        new_parent.identifier.to_s
                else
                        @parent = new_parent.to_s
                end
end
anchor
tags( *tags )

Declare one or more tags for this node.

# File lib/arborist/node.rb, line 461
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

anchor
<<( node )

Append operator – add the specified node as a child and return self.

# File lib/arborist/node.rb, line 980
def <<( node )
        self.add_child( node )
        return self
end
anchor
add_child( node )

Register the specified node as a child of this node, replacing any existing node with the same identifier.

# File lib/arborist/node.rb, line 971
def add_child( node )
        self.log.debug "Adding node %p as a child. Parent = %p" % [ node, node.parent ]
        raise "%p is not a child of %p" % [ node, self ] if
                node.parent && node.parent != self.identifier
        self.children[ node.identifier ] = node
end
anchor
each( &block )

Enumerable API – iterate over the children of this node.

# File lib/arborist/node.rb, line 937
def each( &block )
        return self.children.values.each( &block )
end
anchor
has_children?()

Returns true if the node has one or more child nodes.

# File lib/arborist/node.rb, line 943
def has_children?
        return !self.children.empty?
end
anchor
operational?()

Returns true if the node is considered operational.

# File lib/arborist/node.rb, line 949
def operational?
        return self.identifier.start_with?( '_' )
end
anchor
reachable?()

Returns true if the node's status indicates it is included by default when traversing nodes.

# File lib/arborist/node.rb, line 964
def reachable?
        return !self.unreachable?
end
anchor
remove_child( node )

Unregister the specified node as a child of this node.

# File lib/arborist/node.rb, line 987
def remove_child( node )
        self.log.debug "Removing node %p from children" % [ node ]
        return self.children.delete( node.identifier )
end
anchor
unreachable?()

Returns true if the node's status indicates it shouldn't be included by default when traversing nodes.

# File lib/arborist/node.rb, line 956
def unreachable?
        self.log.debug "Testing for reachability; status is: %p" % [ self.status ]
        return UNREACHABLE_STATES.include?( self.status )
end

Manager API

↑ top

Public Instance Methods

anchor
acknowledge( **args )

Acknowledge any current or future abnormal status for this node.

# File lib/arborist/node.rb, line 604
def acknowledge( **args )
        super()

        self.ack = args

        events = self.pending_change_events.clone
        events << self.make_delta_event unless self.update_delta.empty?
        results = self.broadcast_events( *events )
        self.log.debug ">>> Results from broadcast: %p" % [ results ]
        events.concat( results )

        return events
ensure
        self.clear_transition_temp_vars
end
anchor
add_subscription( subscription )

Add the specified subscription (an Arborist::Subscription) to the node.

# File lib/arborist/node.rb, line 517
def add_subscription( subscription )
        self.subscriptions[ subscription.id ] = subscription
end
anchor
broadcast_events( *events )

Send an event to this node's immediate children.

# File lib/arborist/node.rb, line 789
def broadcast_events( *events )
        events.flatten!
        results = self.children.flat_map do |identifier, child|
                self.log.debug "Broadcasting %d events to %p" % [ events.length, identifier ]
                events.flat_map do |event|
                        child.handle_event( event )
                end
        end

        return results
end
anchor
clear_transition_temp_vars()

Clear out the state used during a transition to track changes.

# File lib/arborist/node.rb, line 669
def clear_transition_temp_vars
        self.previous_ack = nil
        self.update_delta.clear
        self.pending_change_events.clear
end
anchor
dependencies_down?()

Returns true if this node's dependencies are not met.

# File lib/arborist/node.rb, line 846
def dependencies_down?
        return self.dependencies.down?
end
Also aliased as: has_downed_dependencies?
anchor
dependencies_up?()

Returns true if this node's dependencies are met.

# File lib/arborist/node.rb, line 853
def dependencies_up?
        return !self.dependencies_down?
end
anchor
fetch_values( value_spec=nil )

Return a Hash of node state values that match the specified value_spec.

# File lib/arborist/node.rb, line 736
def fetch_values( value_spec=nil )
        state = self.properties.merge( self.operational_values )
        state = stringify_keys( state )
        state = make_serializable( state )

        if value_spec
                self.log.debug "Eliminating all values except: %p (from keys: %p)" %
                        [ value_spec, state.keys ]
                state.delete_if {|key, _| !value_spec.include?(key) }
        end

        return state
end
anchor
find_matching_subscriptions( event )

Return subscriptions matching the specified event on the receiving node.

# File lib/arborist/node.rb, line 529
def find_matching_subscriptions( event )
        return self.subscriptions.values.find_all {|sub| event =~ sub }
end
anchor
handle_event( event )

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

# File lib/arborist/node.rb, line 804
def handle_event( event )
        self.log.debug "Handling %p" % [ event ]
        handler_name = "handle_%s_event" % [ event.type.gsub('.', '_') ]

        if self.respond_to?( handler_name )
                self.log.debug "Handling a %s event." % [ event.type ]
                self.method( handler_name ).call( event )
        else
                self.log.debug "No handler for a %s event!" % [ event.type ]
        end

        self.log.debug ">>> Pending change events before: %p" % [ self.pending_change_events ]

        super # to state-machine

        results = self.pending_change_events.clone
        self.log.debug ">>> Pending change events after: %p" % [ results ]
        results << self.make_delta_event unless self.update_delta.empty?

        child_results = self.broadcast_events( *results )
        results.concat( child_results )

        self.publish_events( *results )

        return results
ensure
        self.clear_transition_temp_vars
end
anchor
handle_node_disabled_event( event )

Handle a 'node.disabled' event received via broadcast.

# File lib/arborist/node.rb, line 882
def handle_node_disabled_event( event )
        self.log.debug "Got a node.disabled event: %p" % [ event ]
        self.dependencies.mark_down( event.node.identifier )

        if self.dependencies_down?
                self.quieted_reasons[ :secondary ] = "Secondary dependencies not met: %s" %
                        [ self.dependencies.down_reason ]
        end

        if event.node.identifier == self.parent
                self.quieted_reasons[ :primary ] = "Parent disabled: %s" % [ self.parent ]
        end
end
anchor
handle_node_down_event( event )

Handle a 'node.down' event received via broadcast.

# File lib/arborist/node.rb, line 866
def handle_node_down_event( event )
        self.log.debug "Got a node.down event: %p" % [ event ]
        self.dependencies.mark_down( event.node.identifier )

        if self.dependencies_down?
                self.quieted_reasons[ :secondary ] = "Secondary dependencies not met: %s" %
                        [ self.dependencies.down_reason ]
        end

        if event.node.identifier == self.parent
                self.quieted_reasons[ :primary ] = "Parent down: %s" % [ self.parent ] # :TODO: backtrace?
        end
end
anchor
handle_node_quieted_event( event )

Handle a 'node.quieted' event received via broadcast.

# File lib/arborist/node.rb, line 898
def handle_node_quieted_event( event )
        self.log.debug "Got a node.quieted event: %p" % [ event ]
        self.dependencies.mark_down( event.node.identifier )

        if self.dependencies_down?
                self.quieted_reasons[ :secondary ] = "Secondary dependencies not met: %s" %
                        [ self.dependencies.down_reason ]
        end

        if event.node.identifier == self.parent
                self.quieted_reasons[ :primary ] = "Parent quieted: %s" % [ self.parent ] # :TODO: backtrace?
        end
end
anchor
handle_node_up_event( event )

Handle a 'node.up' event received via broadcast.

# File lib/arborist/node.rb, line 914
def handle_node_up_event( event )
        self.log.debug "Got a node.%s event: %p" % [ event.type, event ]

        self.dependencies.mark_up( event.node.identifier )
        self.quieted_reasons.delete( :secondary ) if self.dependencies_up?

        if event.node.identifier == self.parent
                self.log.info "Parent of %s (%s) came back up." % [
                        self.identifier,
                        self.parent
                ]
                self.quieted_reasons.delete( :primary )
        end
end
Also aliased as: handle_node_warn_event
anchor
handle_node_warn_event( event )
anchor
has_downed_dependencies?()
Alias for: dependencies_down?
anchor
has_quieted_reason?()

Returns true if any reasons have been set as to why the node has been quieted. Guard condition for transition to and from `quieted` state.

# File lib/arborist/node.rb, line 860
def has_quieted_reason?
        return !self.quieted_reasons.empty?
end
anchor
make_delta_event()

Return an Event generated from the node's state changes.

# File lib/arborist/node.rb, line 683
def make_delta_event
        self.log.debug "Making node.delta event: %p" % [ self.update_delta ]
        return Arborist::Event.create( 'node_delta', self, self.update_delta )
end
anchor
make_update_event()

Return the node's state in an Arborist::Event of type 'node.update'.

# File lib/arborist/node.rb, line 677
def make_update_event
        return Arborist::Event.create( 'node_update', self )
end
anchor
match_criteria?( key, val )

Returns true if the node matches the specified key and val criteria.

# File lib/arborist/node.rb, line 714
def match_criteria?( key, val )
        array_val = Array( val )
        return case key
                when 'status'
                        array_val.include?( self.status )
                when 'type'
                        array_val.include?( self.type )
                when 'parent'
                        array_val.include?( self.parent )
                when 'tag' then @tags.include?( val.to_s )
                when 'tags' then array_val.all? {|tag| @tags.include?(tag) }
                when 'identifier'
                        array_val.include?( self.identifier )
                when 'config'
                        val.all? {|ikey, ival| hash_matches(@config, ikey, ival) }
                else
                        hash_matches( @properties, key, val )
                end
end
anchor
matches?( criteria, if_empty: true )

Returns true if the specified search criteria all match this node.

# File lib/arborist/node.rb, line 697
def matches?( criteria, if_empty: true )

        # Omit 'delta' criteria from matches; delta matching is done separately.
        criteria = criteria.dup
        criteria.delete( 'delta' )

        self.log.debug "Node matching %p (%p if empty)" % [ criteria, if_empty ]
        return if_empty if criteria.empty?

        self.log.debug "Matching %p against criteria: %p" % [ self, criteria ]
        return criteria.all? do |key, val|
                self.match_criteria?( key, val )
        end.tap {|match| self.log.debug "  node %s match." % [ match ? "DID" : "did not"] }
end
anchor
merge_and_record_delta( key, oldval, newval, prefixes=[] )

Merge the specified new_properties into the node's properties, recording each change in the node's update_delta.

# File lib/arborist/node.rb, line 641
def merge_and_record_delta( key, oldval, newval, prefixes=[] )
        self.log.debug "Merging property %s: %p -> %p" % [
                (prefixes + [key]).join('.'),
                oldval,
                newval
        ]

        # Merge them (recursively) if they're both merge-able
        if oldval.respond_to?( :merge! ) && newval.respond_to?( :merge! )
                return oldval.merge( newval ) do |ikey, ioldval, inewval|
                        self.merge_and_record_delta( ikey, ioldval, inewval, prefixes + [key] )
                end

        # Otherwise just directly compare them and record any changes
        else
                unless oldval == newval
                        prefixed_delta = prefixes.inject( self.update_delta ) do |hash, key|
                                hash[ key ]
                        end
                        prefixed_delta[ key ] = [ oldval, newval ]
                end

                return newval
        end
end
anchor
node_subscribers()

Return the Set of identifier of nodes that are secondary dependencies of this node.

# File lib/arborist/node.rb, line 535
def node_subscribers
        self.log.debug "Finding node subscribers among %d subscriptions" % [ self.subscriptions.length ]
        return self.subscriptions.each_with_object( Set.new ) do |(identifier, sub), set|
                if sub.respond_to?( :node_identifier )
                        set.add( sub.node_identifier )
                else
                        self.log.debug "Skipping %p: not a node subscription" % [ sub ]
                end
        end
end
anchor
operational_values()

Return a Hash of the operational values that are included with the node's monitor state.

# File lib/arborist/node.rb, line 753
def operational_values
        values = OPERATIONAL_ATTRIBUTES.each_with_object( {} ) do |key, hash|
                hash[ key ] = self.send( key )
        end

        return values
end
anchor
publish_events( *events )

Publish the specified events to any subscriptions the node has which match them.

# File lib/arborist/node.rb, line 780
def publish_events( *events )
        self.log.debug "Got events to publish: %p" % [ events ]
        self.subscriptions.each_value do |sub|
                sub.on_events( *events )
        end
end
anchor
register_secondary_dependencies( manager )

Register subscriptions for secondary dependencies on the receiving node with the given manager.

# File lib/arborist/node.rb, line 764
def register_secondary_dependencies( manager )
        self.dependencies.all_identifiers.each do |identifier|
                # Check to be sure the identifier isn't a descendant or ancestor
                if manager.ancestors_for( self ).any? {|node| node.identifier == identifier}
                        raise Arborist::ConfigError, "Can't depend on ancestor node %p." % [ identifier ]
                elsif manager.descendants_for( self ).any? {|node| node.identifier == identifier }
                        raise Arborist::ConfigError, "Can't depend on descendant node %p." % [ identifier ]
                end

                sub = Arborist::NodeSubscription.new( self )
                manager.subscribe( identifier, sub )
        end
end
anchor
remove_subscription( subscription_id )

Remove the specified subscription (an Arborist::Subscription) from the node.

# File lib/arborist/node.rb, line 523
def remove_subscription( subscription_id )
        return self.subscriptions.delete( subscription_id )
end
anchor
reparent( old_parent, new_parent )

Move a node from old_parent to new_parent.

# File lib/arborist/node.rb, line 835
def reparent( old_parent, new_parent )
        old_parent.remove_child( self )
        self.parent( new_parent.identifier )
        new_parent.add_child( self )

        self.quieted_reasons.delete( :primary )
        super
end
anchor
state_has_changed?()

Returns true if the node's state has changed since the last time snapshot_state was called.

# File lib/arborist/node.rb, line 691
def state_has_changed?
        return ! self.update_delta.empty?
end
anchor
type()

Return the simple type of this node (e.g., Arborist::Node::Host => 'host')

# File lib/arborist/node.rb, line 510
def type
        return 'anonymous' unless self.class.name
        return self.class.name.sub( /.*::/, '' ).downcase
end
anchor
unacknowledge()

Clear any current acknowledgement.

# File lib/arborist/node.rb, line 622
def unacknowledge
        super()

        self.ack = nil

        events = self.pending_change_events.clone
        events << self.make_delta_event unless self.update_delta.empty?
        results = self.broadcast_events( *events )
        self.log.debug ">>> Results from broadcast: %p" % [ results ]
        events.concat( results )

        return events
ensure
        self.clear_transition_temp_vars
end
anchor
update( new_properties, monitor_key='_' )

Update specified properties for the node.

# File lib/arborist/node.rb, line 548
def update( new_properties, monitor_key='_' )
        self.last_contacted = Time.now
        self.update_properties( new_properties, monitor_key )

        # Super to the state machine event method
        super

        events = self.pending_change_events.clone
        events << self.make_update_event
        events << self.make_delta_event unless self.update_delta.empty?

        results = self.broadcast_events( *events )
        self.log.debug ">>> Results from broadcast: %p" % [ results ]
        events.concat( results )

        return events
ensure
        self.clear_transition_temp_vars
end
anchor
update_errors( monitor_key, value=nil )

Update the errors hash for the specified monitor_key to value.

# File lib/arborist/node.rb, line 584
def update_errors( monitor_key, value=nil )
        if value
                self.errors[ monitor_key ] = value
        else
                self.errors.delete( monitor_key )
        end
end
anchor
update_properties( new_properties, monitor_key )

Update the node's properties with those in new_properties (a String-keyed Hash)

# File lib/arborist/node.rb, line 570
def update_properties( new_properties, monitor_key )
        monitor_key ||= '_'
        new_properties = stringify_keys( new_properties )

        self.log.debug "Updated via a %s monitor: %p" % [ monitor_key, new_properties ]
        self.update_errors( monitor_key, new_properties.delete('error') )
        self.update_warnings( monitor_key, new_properties.delete('warning') )

        self.properties.merge!( new_properties, &self.method(:merge_and_record_delta) )
        compact_hash( self.properties )
end
anchor
update_warnings( monitor_key, value=nil )

Update the warnings hash for the specified monitor_key to value.

# File lib/arborist/node.rb, line 594
def update_warnings( monitor_key, value=nil )
        if value
                self.warnings[ monitor_key ] = value
        else
                self.warnings.delete( monitor_key )
        end
end

Serialization API

↑ top

Public Instance Methods

anchor
==( other_node )

Equality operator – returns true if other_node has the same identifier, parent, and state as the receiving one.

# File lib/arborist/node.rb, line 1153
def ==( other_node )
        return                          other_node.identifier == self.identifier &&
                other_node.parent == self.parent &&
                other_node.description == self.description &&
                other_node.tags == self.tags
end
anchor
marshal_dump()

Marshal API – return the node as an object suitable for marshalling.

# File lib/arborist/node.rb, line 1110
def marshal_dump
        return self.to_h.merge( dependencies: self.dependencies )
end
anchor
marshal_load( hash )

Marshal API – set up the object's state using the hash from a previously-marshalled node.

# File lib/arborist/node.rb, line 1117
def marshal_load( hash )
        self.log.debug "Restoring from serialized hash: %p" % [ hash ]
        @identifier      = hash[:identifier]
        @properties      = hash[:properties]

        @parent          = hash[:parent]
        @description     = hash[:description]
        @tags            = Set.new( hash[:tags] )
        @config          = hash[:config]
        @children        = {}

        @status          = hash[:status]
        @status_changed  = Time.parse( hash[:status_changed] )
        @status_last_changed = Time.parse( hash[:status_last_changed] )
        @ack             = Arborist::Node::Ack.from_hash( hash[:ack] ) if hash[:ack]

        @errors          = hash[:errors]
        @warnings        = hash[:warnings]
        @properties      = hash[:properties] || {}
        @last_contacted  = Time.parse( hash[:last_contacted] )
        @quieted_reasons = hash[:quieted_reasons] || {}
        self.log.debug "Deps are: %p" % [ hash[:dependencies] ]
        @dependencies    = hash[:dependencies]

        @update_delta    = Hash.new do |h,k|
                h[ k ] = Hash.new( &h.default_proc )
        end

        @pending_change_events = []
        @subscriptions         = {}

end
anchor
restore( old_node )

Restore any saved state from the old_node loaded from the state file. This is used to overlay selective bits of the saved node tree to the equivalent nodes loaded from node definitions.

# File lib/arborist/node.rb, line 1055
def restore( old_node )
        @status          = old_node.status
        @properties      = old_node.properties.dup
        @ack             = old_node.ack.dup if old_node.ack
        @last_contacted  = old_node.last_contacted
        @status_changed  = old_node.status_changed
        @errors          = old_node.errors
        @warnings        = old_node.warnings
        @quieted_reasons = old_node.quieted_reasons
        @status_last_changed = old_node.status_last_changed

        # Only merge in downed dependencies.
        old_node.dependencies.each_downed do |identifier, time|
                @dependencies.mark_down( identifier, time )
        end
end
anchor
to_h( depth: 0 )

Return a Hash of the node's state. If depth is greater than 0, that many levels of child nodes are included in the node's `:children` value. Setting depth to a negative number will return all of the node's children.

# File lib/arborist/node.rb, line 1076
def to_h( depth: 0 )
        hash = {
                identifier: self.identifier,
                type: self.class.name.to_s.sub( /.+::/, '' ).downcase,
                parent: self.parent,
                description: self.description,
                tags: self.tags,
                config: self.config,
                status: self.status,
                properties: self.properties.dup,
                ack: self.ack ? self.ack.to_h : nil,
                last_contacted: self.last_contacted ? self.last_contacted.iso8601 : nil,
                status_changed: self.status_changed ? self.status_changed.iso8601 : nil,
                status_last_changed: self.status_last_changed ? self.status_last_changed.iso8601 : nil,
                errors: self.errors,
                warnings: self.warnings,
                dependencies: self.dependencies.to_h,
                quieted_reasons: self.quieted_reasons,
        }

        if depth.nonzero?
                # self.log.debug "including children for depth %p" % [ depth ]
                hash[ :children ] = self.children.each_with_object( {} ) do |(ident, node), h|
                        h[ ident ] = node.to_h( depth: depth - 1 )
                end
        else
                hash[ :children ] = {}
        end

        return hash
end

Protected Instance Methods

anchor
ack=( ack_data )

Ack the node with the specified ack_data, which should contain

# File lib/arborist/node.rb, line 1167
def ack=( ack_data )
        if ack_data
                self.log.info "Node %s ACKed with data: %p" % [ self.identifier, ack_data ]
                @ack = Arborist::Node::Ack.from_hash( ack_data )
        else
                self.log.info "Node %s ACK cleared explicitly" % [ self.identifier ]
                @ack = nil
        end

        self.add_previous_ack_to_update_delta
end
anchor
ack_set?()

State machine guard predicate – Returns true if the node has an ACK status set.

# File lib/arborist/node.rb, line 1199
def ack_set?
        self.log.debug "Checking to see if this node has been ACKed (it %s)" %
                [ @ack ? "has" : "has not" ]
        return @ack ? true : false
end
anchor
add_previous_ack_to_update_delta()

Add the previous and current acknowledgement to the delta if either of them are set.

# File lib/arborist/node.rb, line 1190
def add_previous_ack_to_update_delta
        unless self.ack == self.previous_ack
                self.log.debug "Adding previous ack to the update delta: %p" % [ self.previous_ack ]
                self.update_delta[ 'ack' ] = [ self.previous_ack&.to_h, self.ack&.to_h ]
        end
end
anchor
errors_description()

Return a string describing the errors that are set on the node.

# File lib/arborist/node.rb, line 1247
def errors_description
        return "No errors" if self.errors.empty?
        return self.errors.map do |key, msg|
                "%s: %s" % [ key, msg ]
        end.join( '; ' )
end
anchor
has_errors?()

State machine guard predicate – returns true if the node has errors.

# File lib/arborist/node.rb, line 1207
def has_errors?
        has_errors = ! self.errors.empty?
        self.log.debug "Checking to see if last contact cleared remaining errors (it %s)" %
                [ has_errors ? "did not" : "did" ]
        self.log.debug "Errors are: %p" % [ self.errors ]
        return has_errors
end
anchor
has_errors_or_warnings?()

State machine guard predicate – returns true if the node has warnings or errors.

# File lib/arborist/node.rb, line 1234
def has_errors_or_warnings?
        return self.has_errors? || self.has_warnings?
end
anchor
has_only_warnings?()

State machine guard predicate – returns true if the node has warnings but no errors.

# File lib/arborist/node.rb, line 1241
def has_only_warnings?
        return self.has_warnings? && ! self.has_errors?
end
anchor
has_unacked_errors?()

State machine guard predicate – Returns true if the node has errors and does not have an ACK status set.

# File lib/arborist/node.rb, line 1218
def has_unacked_errors?
        return self.has_errors? && !self.ack_set?
end
anchor
has_warnings?()

State machine guard predicate – returns true if the node has warnings.

# File lib/arborist/node.rb, line 1224
def has_warnings?
        has_warnings = ! self.warnings.empty?
        self.log.debug "Checking to see if last contact cleared remaining warnings (it %s)" %
                [ has_warnings ? "did not" : "did" ]
        self.log.debug "Warnings are: %p" % [ self.warnings ]
        return has_warnings
end
anchor
save_previous_ack()

Save off the current acknowledgement so it can be used after transitions which unset it.

# File lib/arborist/node.rb, line 1182
def save_previous_ack
        self.log.debug "Saving previous ack: %p" % [ self.ack ]
        self.previous_ack = self.ack
end
anchor
warnings_description()

Return a string describing the warnings that are set on the node.

# File lib/arborist/node.rb, line 1256
def warnings_description
        return "No warnings" if self.warnings.empty?
        return self.warnings.map do |key, msg|
                "%s: %s" % [ key, msg ]
        end.join( '; ' )
end

State Callbacks

↑ top

Protected Instance Methods

anchor
add_status_to_update_delta( transition )

Add the transition from one state to another to the data used to build deltas for the update event.

# File lib/arborist/node.rb, line 1353
def add_status_to_update_delta( transition )
        self.update_delta[ 'status' ] = [ transition.from, transition.to ]
end
anchor
log_transition( transition )

Log every status transition

# File lib/arborist/node.rb, line 1269
def log_transition( transition )
        self.log.debug "Transitioned %s from %s to %s" %
                [ self.identifier, transition.from, transition.to ]
end
anchor
make_transition_event( transition )

Queue up a transition event whenever one happens

# File lib/arborist/node.rb, line 1283
def make_transition_event( transition )
        node_type = "node_%s" % [ transition.to ]
        self.log.debug "Making a %s event for %p" % [ node_type, transition ]
        self.pending_change_events << Arborist::Event.create( node_type, self )
end
anchor
on_ack( transition )

Callback for when an acknowledgement is set.

# File lib/arborist/node.rb, line 1291
def on_ack( transition )
        self.log.warn "ACKed: %s" % [ self.status_description ]
end
anchor
on_ack_cleared( transition )

Callback for when an acknowledgement is cleared.

# File lib/arborist/node.rb, line 1297
def on_ack_cleared( transition )
        self.ack = nil
        self.log.warn "ACK cleared for %s" % [ self.identifier ]
end
anchor
on_node_disabled( transition )

Callback for when a node goes from up to disabled

# File lib/arborist/node.rb, line 1325
def on_node_disabled( transition )
        self.errors.clear
        self.warnings.clear
        self.log.warn "%s is %s" % [ self.identifier, self.status_description ]
end
anchor
on_node_down( transition )

Callback for when a node goes from up to down

# File lib/arborist/node.rb, line 1311
def on_node_down( transition )
        self.log.error "%s is %s" % [ self.identifier, self.status_description ]
        self.update_delta[ 'errors' ] = [ nil, self.errors_description ]
end
anchor
on_node_enabled( transition )

Callback for when a node goes from disabled to unknown

# File lib/arborist/node.rb, line 1345
def on_node_enabled( transition )
        self.log.warn "%s is %s" % [ self.identifier, self.status_description ]
        self.ack = nil
end
anchor
on_node_quieted( transition )

Callback for when a node goes from any state to quieted

# File lib/arborist/node.rb, line 1333
def on_node_quieted( transition )
        self.log.warn "%s is %s" % [ self.identifier, self.status_description ]
end
anchor
on_node_unquieted( transition )

Callback for when a node transitions from quieted to unknown

# File lib/arborist/node.rb, line 1339
def on_node_unquieted( transition )
        self.log.warn "%s is %s" % [ self.identifier, self.status_description ]
end
anchor
on_node_up( transition )

Callback for when a node goes from down to up

# File lib/arborist/node.rb, line 1304
def on_node_up( transition )
        self.errors.clear
        self.log.warn "%s is %s" % [ self.identifier, self.status_description ]
end
anchor
on_node_warn( transition )

Callback for when a node goes from up to warn

# File lib/arborist/node.rb, line 1318
def on_node_warn( transition )
        self.log.error "%s is %s" % [ self.identifier, self.status_description ]
        self.update_delta[ 'warnings' ] = [ nil, self.warnings_description ]
end
anchor
update_status_changed( transition )

Update the last status change time.

# File lib/arborist/node.rb, line 1276
def update_status_changed( transition )
        self.status_last_changed = self.status_changed
        self.status_changed = Time.now
end

Utility methods

↑ top

Public Instance Methods

anchor
acked_description()

Return a description of the ack if it's set, or a generic string otherwise.

# File lib/arborist/node.rb, line 999
def acked_description
        return self.ack.description if self.ack
        return "(unset)"
end
anchor
inspect()

Return a String representation of the object suitable for debugging.

# File lib/arborist/node.rb, line 1033
def inspect
        return "#<%p:%#x [%s] -> %s %p %s %s, %d children, %s>" % [
                self.class,
                self.object_id * 2,
                self.identifier,
                self.parent || 'root',
                self.description || "(no description)",
                self.node_description.to_s,
                self.source,
                self.children.length,
                self.status_description,
        ]
end
anchor
node_description()

Return a string describing node details; returns nil for the base class. Subclasses may override this to add to the output of inspect.

# File lib/arborist/node.rb, line 1027
def node_description
        return nil
end
anchor
status_description()

Return a string describing the node's status.

# File lib/arborist/node.rb, line 1006
def status_description
        case self.status
        when 'up', 'down', 'warn'
                return "%s as of %s" % [ self.status.upcase, self.last_contacted ]
        when 'acked'
                return "ACKed %s" % [ self.acked_description ]
        when 'disabled'
                return "disabled %s" % [ self.acked_description ]
        when 'quieted'
                reasons = self.quieted_reasons.values.join( ',' )
                return "quieted: %s" % [ reasons ]
        when 'unknown'
                return "in an 'unknown' state"
        else
                return "in an unhandled state"
        end
end