lib/arborist/node.rb in arborist-0.4.0 vs lib/arborist/node.rb in arborist-0.5.0

- old
+ new

@@ -37,10 +37,11 @@ VALID_IDENTIFIER = /^\w[\w\-]*$/ # The attributes of a node which are used in the operation of the system OPERATIONAL_ATTRIBUTES = %i[ type + family status tags parent description dependencies @@ -442,10 +443,17 @@ def source=( source ) @source = URI( source ) end + ### Return the node family, so observers can know ancestry after + ### serialization for custom node types that inherit from this class. + def family + return :node + end + + ### 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+. def modify( attributes ) @@ -620,15 +628,53 @@ 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 ) + self.merge_new_properties( new_properties ) end + ### Merge the specified Hash of +new_properties+ with the node's current property + ### Hash. + 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 + + + ### 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. + 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 + + ### Update the errors hash for the specified +monitor_key+ to +value+. def update_errors( monitor_key, value=nil ) if value self.errors[ monitor_key ] = value else @@ -681,39 +727,10 @@ ensure self.clear_transition_temp_vars end - ### Merge the specified +new_properties+ into the node's properties, recording - ### each change in the node's #update_delta. - 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 - - ### Clear out the state used during a transition to track changes. def clear_transition_temp_vars self.previous_ack = nil self.update_delta.clear self.pending_change_events.clear @@ -763,10 +780,12 @@ 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' @@ -834,11 +853,11 @@ ### Send an event to this node's immediate children. 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 ] + self.log.debug "Broadcasting events to %p: %p" % [ identifier, events ] events.flat_map do |event| child.handle_event( event ) end end @@ -847,32 +866,32 @@ ### Handle the specified +event+, delivered either via broadcast or secondary ### dependency subscription. def handle_event( event ) - self.log.debug "Handling %p" % [ 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? + 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 ) + self.publish_events( *results ) unless results.empty? return results ensure self.clear_transition_temp_vars end @@ -1104,11 +1123,10 @@ @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 - @status_history_size = old_node.status_history_size @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 @@ -1126,21 +1144,21 @@ 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, - status_history_size: self.status_history_size, flapping: self.flapping?, errors: self.errors, warnings: self.warnings, dependencies: self.dependencies.to_h, quieted_reasons: self.quieted_reasons, @@ -1180,11 +1198,10 @@ @status = hash[:status] @status_changed = Time.parse( hash[:status_changed] ) @status_last_changed = Time.parse( hash[:status_last_changed] ) @status_history = hash[:status_history] - @status_history_size = hash[:status_history_size] @flapping = hash[:flapping] @ack = Arborist::Node::Ack.from_hash( hash[:ack] ) if hash[:ack] @errors = hash[:errors] @warnings = hash[:warnings] @@ -1381,18 +1398,18 @@ ### 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[ 'errors' ] = [ nil, self.errors_description ] + self.update_delta[ 'errors' ] = [ nil, self.errors ] end ### Callback for when a node goes from up to warn def on_node_warn( transition ) self.log.error "%s is %s" % [ self.identifier, self.status_description ] - self.update_delta[ 'warnings' ] = [ nil, self.warnings_description ] + self.update_delta[ 'warnings' ] = [ nil, self.warnings ] end ### Callback for when a node goes from up to disabled def on_node_disabled( transition ) @@ -1435,10 +1452,10 @@ end ### Retain the status in the node's history. ### - def record_status_history + def record_status_history( transition ) retain = self.status_history_size return if retain.zero? pre_state = self.flapping? self.status_history << self.status