lib/arborist/node.rb in arborist-0.0.1.pre20161005182540 vs lib/arborist/node.rb in arborist-0.1.0

- old
+ new

@@ -42,15 +42,23 @@ description dependencies status_changed last_contacted ack - error + errors quieted_reasons config ] + # Node states that are unreachable by default. + UNREACHABLE_STATES = %w[ + down + disabled + quieted + ] + + autoload :Root, 'arborist/node/root' autoload :Ack, 'arborist/node/ack' # Log via the Arborist logger @@ -112,12 +120,12 @@ :disabled, :quieted event :update do transition [:up, :unknown] => :disabled, if: :ack_set? - transition [:down, :unknown, :acked] => :up, if: :last_contact_successful? - transition [:up, :unknown] => :down, unless: :last_contact_successful? + transition [:down, :unknown, :acked] => :up, unless: :has_errors? + transition [:up, :unknown, :acked] => :down, if: :has_errors? transition :down => :acked, if: :ack_set? transition :disabled => :unknown, unless: :ack_set? end event :handle_event do @@ -272,11 +280,11 @@ # Primary state @status = 'unknown' @status_changed = Time.at( 0 ) # Attributes that govern state - @error = nil + @errors = {} @ack = nil @last_contacted = Time.at( 0 ) @quieted_reasons = {} # Event-handling @@ -319,12 +327,13 @@ ## # The Time the node's status last changed. attr_accessor :status_changed ## - # The last error encountered by a monitor attempting to update this node. - attr_accessor :error + # The Hash of last errors encountered by a monitor attempting to update this + # node, keyed by the monitor's `key`. + attr_accessor :errors ## # The acknowledgement currently in effect. Should be an instance of Arborist::Node::ACK attr_accessor :ack @@ -476,17 +485,21 @@ ### Update specified +properties+ for the node. def update( new_properties ) new_properties = stringify_keys( new_properties ) - self.log.debug "Updated: %p" % [ new_properties ] + monitor_key = new_properties[ '_monitor_key' ] || '_' + self.log.debug "Updated via a %s monitor: %p" % [ monitor_key, new_properties ] self.last_contacted = Time.now + if new_properties.key?( 'ack' ) self.ack = new_properties.delete( 'ack' ) + elsif new_properties['error'] + self.errors[ monitor_key ] = new_properties.delete( 'error' ) else - self.error = new_properties.delete( 'error' ) + self.errors.delete( monitor_key ) end self.properties.merge!( new_properties, &self.method(:merge_and_record_delta) ) compact_hash( self.properties ) @@ -585,10 +598,12 @@ when 'status' self.status == val when 'type' self.log.debug "Checking node type %p against %p" % [ self.type, val ] self.type == val + when 'parent' + self.parent == val when 'tag' then @tags.include?( val.to_s ) when 'tags' then Array(val).all? {|tag| @tags.include?(tag) } when 'identifier' then @identifier == val when 'config' val.all? {|ikey, ival| hash_matches(@config, ikey, ival) } @@ -678,11 +693,12 @@ self.log.debug "No handler for a %s event!" % [ event.type ] end super # to state-machine - return self.collect_events + self.publish_events( event, *self.pending_update_events ) + self.collect_events end ### Returns +true+ if this node's dependencies are not met. def dependencies_down? @@ -790,10 +806,24 @@ def operational? return self.identifier.start_with?( '_' ) end + ### Returns +true+ if the node's status indicates it shouldn't be + ### included by default when traversing nodes. + def unreachable? + return UNREACHABLE_STATES.include?( self.status ) + end + + + ### Returns +true+ if the node's status indicates it is included by + ### default when traversing nodes. + def reachable? + return !self.unreachable? + end + + ### Register the specified +node+ as a child of this node, replacing any existing ### node with the same identifier. 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 @@ -878,11 +908,11 @@ @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 - @error = old_node.error + @errors = old_node.errors @quieted_reasons = old_node.quieted_reasons # Only merge in downed dependencies. old_node.dependencies.each_downed do |identifier, time| @dependencies.mark_down( identifier, time ) @@ -902,11 +932,11 @@ 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, - error: self.error, + errors: self.errors, dependencies: self.dependencies.to_h, quieted_reasons: self.quieted_reasons, } end @@ -932,11 +962,11 @@ @status = hash[:status] @status_changed = Time.parse( hash[:status_changed] ) @ack = Arborist::Node::Ack.from_hash( hash[:ack] ) if hash[:ack] - @error = hash[:error] + @errors = hash[:errors] @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] @@ -986,17 +1016,27 @@ end ### State machine guard predicate -- Returns +true+ if the last time the node ### was monitored resulted in an update. - def last_contact_successful? - self.log.debug "Checking to see if last contact was successful (it %s)" % - [ self.error ? "wasn't" : "was" ] - return !self.error + 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 + ### Return a string describing the errors that are set on the node. + def errors_description + return "No errors" if self.errors.empty? + return self.errors.map do |key, msg| + "%s: %s" % [ key, msg ] + end.join( '; ' ) + end + # # :section: State Callbacks # ### Log every status transition @@ -1033,18 +1073,18 @@ end ### Callback for when a node goes from down to up def on_node_up( transition ) - self.error = nil + self.errors.clear self.log.warn "%s is %s" % [ self.identifier, self.status_description ] end ### Callback for when a node goes from up to down def on_node_down( transition ) self.log.error "%s is %s" % [ self.identifier, self.status_description ] - self.update_delta[ 'error' ] = [ nil, self.error ] + self.update_delta[ 'errors' ] = [ nil, self.errors_description ] end ### Callback for when a node goes from up to disabled def on_node_disabled( transition )