arborist 0.0.1.pre20161005182540 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA256:
3
- metadata.gz: 1771e739c3bd2da57549f3319a7a745ab1f93633f2b5008e61419b7d47bf036e
4
- data.tar.gz: ee38a2f6e1653aecbe9a685d416a5ed2840361e3f5e964aaa490b7a9d00ea589
2
+ SHA1:
3
+ metadata.gz: cc3ce9e3d25176605f9895778c5654b57e8aa1c0
4
+ data.tar.gz: 4528bd2bcc17d925a9ea2ce63c475117839e98cd
5
5
  SHA512:
6
- metadata.gz: 7d90ade9518414adf18f623412b673f1ef3e285438249fcde328701a2c8f48ae91866c5bccca176f3b26f91465aed74e078acb228c6a9c4374fdea56b237c86c
7
- data.tar.gz: 247ed95856ccd2ae33d239d861bb99245018abe3bbab336532c0cba1431ca3bc34e5b19b62ec516ea60a50c2949f443a4e2ebc5e53b592764800796587ba38e4
6
+ metadata.gz: fd351f828f9d9dd9738541d93d852f82a7a12bcda91f1e4a7bda5bd38adedbf3da6fe52e7f729eab2f0d0678f71cd6b953ce449532e1b1cd9da14d18076a49c2
7
+ data.tar.gz: 3ac9921a9a8fc177ccba6036ab1fa00682cfc6ee892e7fde5aa3db34c5dc2832b32fd5a6e3f59518d8b4a9956b48a36ca39e10a83ff3b57dae895c845c5bf800
@@ -1,2 +1,3 @@
1
- a����RA��vy��mg�ݒ����GNw�DhE ��������Ӧ��i*u1���2y��y�T�
2
- a���W��:�luRZ�6y
1
+ bZ_�Ov �97�~��2�+쳦�;鄀 ��#��D5ma��5PÀ\,�G(� %A3��&���Z!�g��sx4
2
+ ���ʩ�V�da�oMʡ8��K��:���40�R����t˝&�� 3 ��/c����"Y� *��w�����N�;Q��#�~xZ��jw��J#�<�_���C\�X��� ���ܘ5)a�$:��gLd)T��&�z)k ��T)}�V��Dv��^�&��S�T$(� ��vE ���Vn�M,]ST�x��i�OR�L���]�9qn������
3
+ �=�x=�8@fv~���c�gim))���f�@����ހh!�5^���¼Hp� �o 3ϸ�*�c���ꎛ]�c"�X��
data.tar.gz.sig CHANGED
Binary file
data/ChangeLog CHANGED
@@ -1,9 +1,75 @@
1
+ 2016-12-28 Michael Granger <ged@FaerieMUD.org>
2
+
3
+ * .gems, Rakefile:
4
+ Update to latest *ability
5
+ [09438b7a8aff] [tip]
6
+
7
+ 2016-10-19 Michael Granger <ged@FaerieMUD.org>
8
+
9
+ * lib/arborist/client.rb:
10
+ Use ** instead of * for client methods that take keyword args
11
+ [73b6f150a8c1] [github/master]
12
+
13
+ * lib/arborist/mixins.rb, lib/arborist/node.rb,
14
+ spec/arborist/client_spec.rb:
15
+ Add specs for Client#ack and #unack
16
+ [819556c743c6]
17
+
18
+ 2016-10-17 Michael Granger <ged@FaerieMUD.org>
19
+
20
+ * TODO.md:
21
+ Add a note to the TODO list
22
+ [3de683ee0405]
23
+
24
+ 2016-10-12 Michael Granger <ged@FaerieMUD.org>
25
+
26
+ * spec/arborist/node_spec.rb:
27
+ Remove accidentally-commited focus
28
+ [d2cac6a84aaa]
29
+
30
+ 2016-10-12 Mahlon E. Smith <mahlon@martini.nu>
31
+
32
+ * lib/arborist/node.rb, spec/arborist/node_spec.rb:
33
+ Add matching for 'parent' attribute data.
34
+ [c50907900b60]
35
+
36
+ 2016-10-06 Mahlon E. Smith <mahlon@martini.nu>
37
+
38
+ * lib/arborist/manager.rb, lib/arborist/monitor.rb,
39
+ lib/arborist/node.rb, spec/arborist/monitor_spec.rb,
40
+ spec/arborist/node_spec.rb:
41
+ Make monitors automatically set #include_down if matching on an
42
+ unreachable status type.
43
+
44
+ - Add reachable? and unreachable? predacates to node
45
+ - Whitespace fixes
46
+ [3dcff4c89426]
47
+
48
+ 2016-10-05 Mahlon E. Smith <mahlon@martini.nu>
49
+
50
+ * lib/arborist/node.rb:
51
+ Reenable event publishing from handle_event().
52
+ [1918cc1afa1a]
53
+
54
+ 2016-10-05 Michael Granger <ged@FaerieMUD.org>
55
+
56
+ * lib/arborist/node.rb, spec/arborist/node_spec.rb:
57
+ Fix event broadcasting for deep hierarchies.
58
+
59
+ - Refactored event broadcasting so #update and #handle_event share
60
+ broadcasting code.
61
+ [9ab8636bb2c5]
62
+
63
+ * Rakefile, arborist.gemspec:
64
+ Update gemspec with Ruby requirements
65
+ [8ea4314d4dc9]
66
+
1
67
  2016-10-04 Mahlon E. Smith <mahlon@martini.nu>
2
68
 
3
69
  * lib/arborist/node.rb:
4
70
  State transition cleanup: Only nodes in a 'down' state can
5
71
  transition to 'acked'.
6
- [eed40468bb5e] [tip]
72
+ [eed40468bb5e]
7
73
 
8
74
  2016-10-03 Mahlon E. Smith <mahlon@laika.com>
9
75
 
@@ -15,7 +81,7 @@
15
81
 
16
82
  * TODO.md, lib/arborist/node.rb, spec/arborist/node_spec.rb:
17
83
  Fix the unknown => disabled transition when updating with an ACK
18
- [30d2548e1308] [github/master]
84
+ [30d2548e1308]
19
85
 
20
86
  * lib/arborist/manager.rb, spec/arborist/manager_spec.rb:
21
87
  Flatten the config into one namespace
data/History.md CHANGED
@@ -1,4 +1,4 @@
1
- # v0.0.1 [YYYY-MM-DD] Michael Granger <ged@FaerieMUD.org>
1
+ # v0.1.0 [2017-01-01] Michael Granger <ged@FaerieMUD.org>
2
2
 
3
3
  Initial release.
4
4
 
@@ -7,7 +7,7 @@ of an Arborist::MonitorRunner object. The `Arborist::Monitor.each_in` method, gi
7
7
 
8
8
  ## Declaration DSL
9
9
 
10
- To facilitate describing monitors to run, Arborist::Monitor also provides a DSL-like syntax for constructing them.
10
+ To facilitate describing monitors to run, Arborist::Monitor also provides a DSL-like syntax for constructing them.
11
11
 
12
12
  For example, this would declare two monitors, one which pings every 'host' node except those tagged as laptops in the network every 20 seconds, and the other which pings 'host' nodes tagged as laptops every 5 minutes.
13
13
 
@@ -15,24 +15,38 @@ For example, this would declare two monitors, one which pings every 'host' node
15
15
  require 'arborist/monitor'
16
16
 
17
17
  Arborist::Monitor 'ping check' do
18
- every 20.seconds
19
- match type: 'host'
20
- exclude tag: :laptop
21
- use :address
22
- exec 'fping'
18
+ key :pingcheck
19
+ every 20.seconds
20
+ match type: 'host'
21
+ exclude tag: :laptop
22
+ use :address
23
+ exec 'fping'
23
24
  end
24
25
 
25
26
  Arborist::Monitor 'transient host pings' do
26
- every 5.minutes
27
- match type: 'host', tag: 'laptop'
27
+ key :pingcheck
28
+ every 5.minutes
29
+ match type: 'host', tag: 'laptop'
28
30
  use :address
29
- exec 'fping'
31
+ exec 'fping'
30
32
  end
31
33
 
32
34
  Each monitor is given a human-readable description for use in user interfaces, and one or more attributes that describe which nodes should be monitored, how they should be monitored, and how often the monitor should be run.
33
35
 
34
36
  ### Monitor Attributes
35
37
 
38
+ #### key
39
+
40
+ Declare a namespace for the monitor. The error status for a node is keyed by this value, so that monitors with different keys don't clear each other's errors.
41
+
42
+ This attribute is mandatory.
43
+
44
+ #### description
45
+
46
+ Set a human-readable description for the monitor, for use in interfaces or logs.
47
+
48
+ This attribute is mandatory.
49
+
36
50
  #### every( seconds )
37
51
 
38
52
  Declare the interval between runs of the monitor. The monitor will be skewed by a small amount from this value (unless you specify `splay 0`) to prevent many monitors from starting up simultaneously.
@@ -45,7 +59,7 @@ Manually set the amount of splay (random offset from the interval) the monitor s
45
59
  #### exec {|node_attributes| ... }
46
60
  #### exec( module )
47
61
 
48
- Specify what should be run to do the actual monitoring. The first form simply `spawn`s the specified command with its STDIN opened to a stream of serialized node data.
62
+ Specify what should be run to do the actual monitoring. The first form simply `spawn`s the specified command with its STDIN opened to a stream of serialized node data.
49
63
 
50
64
  By default, the format of the serialized nodes is one node per line, and each line looks like this:
51
65
 
data/Rakefile CHANGED
@@ -16,7 +16,7 @@ Hoe.plugin :signing
16
16
  Hoe.plugin :deveiate
17
17
 
18
18
  Hoe.plugins.delete :rubyforge
19
- Hoe.plugins.delete :gemcutter
19
+
20
20
 
21
21
  hoespec = Hoe.spec 'arborist' do |spec|
22
22
  spec.readme_file = 'README.md'
@@ -34,8 +34,8 @@ hoespec = Hoe.spec 'arborist' do |spec|
34
34
  spec.developer 'Mahlon E. Smith', 'mahlon@martini.nu'
35
35
 
36
36
  spec.dependency 'schedulability', '~> 0.1'
37
- spec.dependency 'loggability', '~> 0.11'
38
- spec.dependency 'configurability', '~> 2.2'
37
+ spec.dependency 'loggability', '~> 0.12'
38
+ spec.dependency 'configurability', '~> 3.0'
39
39
  spec.dependency 'pluggability', '~> 0.4'
40
40
  spec.dependency 'state_machines', '~> 0.2'
41
41
  spec.dependency 'msgpack', '~> 0.6'
data/TODO.md CHANGED
@@ -14,8 +14,6 @@
14
14
  - pop
15
15
  - smtp
16
16
 
17
- * Write a gem for `fping` monitor
18
-
19
17
  * Redo the select loop of the UDP socket monitor to wait for them in parallel instead of in series.
20
18
 
21
19
 
@@ -14,10 +14,10 @@ module Arborist
14
14
  Configurability
15
15
 
16
16
  # Package version
17
- VERSION = '0.0.1'
17
+ VERSION = '0.1.0'
18
18
 
19
19
  # Version control revision
20
- REVISION = %q$Revision: 4cc9924e6b0c $
20
+ REVISION = %q$Revision: 02a11882f53b $
21
21
 
22
22
 
23
23
  # The name of the environment variable which can be used to set the config path
@@ -92,8 +92,8 @@ class Arborist::Client
92
92
 
93
93
 
94
94
  ### Return the manager's current node tree.
95
- def list( *args )
96
- request = self.make_list_request( *args )
95
+ def list( **args )
96
+ request = self.make_list_request( **args )
97
97
  return self.send_tree_api_request( request )
98
98
  end
99
99
 
@@ -110,8 +110,8 @@ class Arborist::Client
110
110
 
111
111
 
112
112
  ### Return the manager's current node tree.
113
- def fetch( criteria={}, *args )
114
- request = self.make_fetch_request( criteria, *args )
113
+ def fetch( criteria={}, **args )
114
+ request = self.make_fetch_request( criteria, **args )
115
115
  return self.send_tree_api_request( request )
116
116
  end
117
117
 
@@ -141,8 +141,8 @@ class Arborist::Client
141
141
 
142
142
 
143
143
  ### Add a subscription
144
- def subscribe( *args )
145
- request = self.make_subscribe_request( *args )
144
+ def subscribe( **args )
145
+ request = self.make_subscribe_request( **args )
146
146
  response = self.send_tree_api_request( request )
147
147
  return response.first
148
148
  end
@@ -96,12 +96,12 @@ module Arborist::CLI::Watch
96
96
 
97
97
  case event_type
98
98
  when 'node.update'
99
- type, status, error = event['data'].values_at( *%w'type status error' )
99
+ type, status, errors = event['data'].values_at( *%w'type status errors' )
100
100
  return "%s updated: %s is %s%s" % [
101
101
  hl( id ).color( :cyan ),
102
102
  type,
103
103
  hl( status ).color( status.to_sym ),
104
- error ? " (#{error})" : ''
104
+ errors ? " (#{errors})" : ''
105
105
  ]
106
106
  when 'node.delta'
107
107
  pairs = diff_pairs( event['data'] )
@@ -639,9 +639,7 @@ class Arborist::Manager
639
639
  ### Yield each node that is not down to the specified +block+, or return
640
640
  ### an Enumerator if no block is given.
641
641
  def reachable_nodes( &block )
642
- iter = self.enumerator_for( self.root ) do |node|
643
- !(node.down? || node.disabled? || node.quieted?)
644
- end
642
+ iter = self.enumerator_for( self.root ) {|node| node.reachable? }
645
643
  return iter.each( &block ) if block
646
644
  return iter
647
645
  end
@@ -326,7 +326,7 @@ module Arborist
326
326
  alias_method :internify_keys, :symbolify_keys
327
327
 
328
328
 
329
- # Recursive hash-merge function
329
+ ### Recursive hash-merge function
330
330
  def merge_recursively( key, oldval, newval )
331
331
  case oldval
332
332
  when Hash
@@ -351,7 +351,7 @@ module Arborist
351
351
  end
352
352
 
353
353
 
354
- # Recursively remove hash pairs in place whose value is nil.
354
+ ### Recursively remove hash pairs in place whose value is nil.
355
355
  def compact_hash( hash )
356
356
  hash.each_key do |k|
357
357
  hash.delete( k ) if hash[ k ].nil?
@@ -35,8 +35,8 @@ class Arborist::Monitor
35
35
  DEFAULT_SPLAY = 0
36
36
 
37
37
 
38
- Arborist.add_dsl_constructor( self ) do |description, &block|
39
- Arborist::Monitor.new( description, &block )
38
+ Arborist.add_dsl_constructor( self ) do |description=nil, key=nil, &block|
39
+ Arborist::Monitor.new( description, key, &block )
40
40
  end
41
41
 
42
42
 
@@ -143,7 +143,8 @@ class Arborist::Monitor
143
143
  ### Create a new Monitor with the specified +description+. If the +block+ is
144
144
  ### given, it will be evaluated in the context of the new Monitor before it's
145
145
  ### returned.
146
- def initialize( description, &block )
146
+ def initialize( description=nil, key=nil, &block )
147
+ @key = key
147
148
  @description = description
148
149
  @interval = DEFAULT_INTERVAL
149
150
  @splay = DEFAULT_SPLAY
@@ -160,6 +161,8 @@ class Arborist::Monitor
160
161
  @source = nil
161
162
 
162
163
  self.instance_exec( &block ) if block
164
+
165
+ self.check_config
163
166
  end
164
167
 
165
168
 
@@ -168,8 +171,13 @@ class Arborist::Monitor
168
171
  ######
169
172
 
170
173
  ##
171
- # The object's description
172
- attr_accessor :description
174
+ # The monitor's key. This key should be shared between monitors that check the
175
+ # same resources.
176
+ attr_writer :key
177
+
178
+ ##
179
+ # The monitor's (human) description.
180
+ attr_writer :description
173
181
 
174
182
  ##
175
183
  # The interval between runs in seconds, as set by `every`.
@@ -226,6 +234,26 @@ class Arborist::Monitor
226
234
  end
227
235
 
228
236
 
237
+ ### Check the monitor for sanity, raising an Arborist::ConfigError if it isn't.
238
+ def check_config
239
+ raise Arborist::ConfigError, "No description set" unless self.description
240
+ raise Arborist::ConfigError, "No key set" unless self.key
241
+ end
242
+
243
+
244
+ ### Get/set the description of the monitor.
245
+ def description( new_value=nil )
246
+ self.description = new_value if new_value
247
+ return @description
248
+ end
249
+
250
+
251
+ ### Get/set the key used by the monitor.
252
+ def key( new_value=nil )
253
+ self.key = new_value if new_value
254
+ return @key
255
+ end
256
+
229
257
  ### Run the monitor
230
258
  def run( nodes )
231
259
  if self.exec_block
@@ -252,10 +280,10 @@ class Arborist::Monitor
252
280
  parent_err_reader, child_stderr = IO.pipe
253
281
 
254
282
  self.log.debug "Spawning command: %s" % [ Shellwords.join(command) ]
255
- pid = Process.spawn( *command, out: child_stdout, in: child_stdin, err: child_stderr )
283
+ pid = Process.spawn( *command, out: child_stdout, in: child_stdin, err: child_stderr )
256
284
 
257
- child_stdout.close
258
- child_stdin.close
285
+ child_stdout.close
286
+ child_stdin.close
259
287
  child_stderr.close
260
288
 
261
289
  context.exec_input( nodes, parent_writer )
@@ -302,6 +330,8 @@ class Arborist::Monitor
302
330
  ### for nodes it will run against.
303
331
  def match( criteria )
304
332
  self.positive_criteria.merge!( criteria )
333
+ @include_down = !self.include_down &&
334
+ Arborist::Node::UNREACHABLE_STATES.include?( self.positive_criteria[:status] )
305
335
  end
306
336
 
307
337
 
@@ -69,6 +69,12 @@ class Arborist::MonitorRunner
69
69
 
70
70
  self.fetch( positive, include_down, props, negative ) do |nodes|
71
71
  results = monitor.run( nodes )
72
+ monitor_key = monitor.key
73
+
74
+ results.each do |ident, properties|
75
+ properties['_monitor_key'] = monitor_key
76
+ end
77
+
72
78
  self.update( results ) do
73
79
  self.log.debug "Updated %d via the '%s' monitor" %
74
80
  [ results.length, monitor.description ]
@@ -44,11 +44,19 @@ class Arborist::Node
44
44
  status_changed
45
45
  last_contacted
46
46
  ack
47
- error
47
+ errors
48
48
  quieted_reasons
49
49
  config
50
50
  ]
51
51
 
52
+ # Node states that are unreachable by default.
53
+ UNREACHABLE_STATES = %w[
54
+ down
55
+ disabled
56
+ quieted
57
+ ]
58
+
59
+
52
60
  autoload :Root, 'arborist/node/root'
53
61
  autoload :Ack, 'arborist/node/ack'
54
62
 
@@ -114,8 +122,8 @@ class Arborist::Node
114
122
 
115
123
  event :update do
116
124
  transition [:up, :unknown] => :disabled, if: :ack_set?
117
- transition [:down, :unknown, :acked] => :up, if: :last_contact_successful?
118
- transition [:up, :unknown] => :down, unless: :last_contact_successful?
125
+ transition [:down, :unknown, :acked] => :up, unless: :has_errors?
126
+ transition [:up, :unknown, :acked] => :down, if: :has_errors?
119
127
  transition :down => :acked, if: :ack_set?
120
128
  transition :disabled => :unknown, unless: :ack_set?
121
129
  end
@@ -274,7 +282,7 @@ class Arborist::Node
274
282
  @status_changed = Time.at( 0 )
275
283
 
276
284
  # Attributes that govern state
277
- @error = nil
285
+ @errors = {}
278
286
  @ack = nil
279
287
  @last_contacted = Time.at( 0 )
280
288
  @quieted_reasons = {}
@@ -321,8 +329,9 @@ class Arborist::Node
321
329
  attr_accessor :status_changed
322
330
 
323
331
  ##
324
- # The last error encountered by a monitor attempting to update this node.
325
- attr_accessor :error
332
+ # The Hash of last errors encountered by a monitor attempting to update this
333
+ # node, keyed by the monitor's `key`.
334
+ attr_accessor :errors
326
335
 
327
336
  ##
328
337
  # The acknowledgement currently in effect. Should be an instance of Arborist::Node::ACK
@@ -478,13 +487,17 @@ class Arborist::Node
478
487
  ### Update specified +properties+ for the node.
479
488
  def update( new_properties )
480
489
  new_properties = stringify_keys( new_properties )
481
- self.log.debug "Updated: %p" % [ new_properties ]
490
+ monitor_key = new_properties[ '_monitor_key' ] || '_'
482
491
 
492
+ self.log.debug "Updated via a %s monitor: %p" % [ monitor_key, new_properties ]
483
493
  self.last_contacted = Time.now
494
+
484
495
  if new_properties.key?( 'ack' )
485
496
  self.ack = new_properties.delete( 'ack' )
497
+ elsif new_properties['error']
498
+ self.errors[ monitor_key ] = new_properties.delete( 'error' )
486
499
  else
487
- self.error = new_properties.delete( 'error' )
500
+ self.errors.delete( monitor_key )
488
501
  end
489
502
 
490
503
  self.properties.merge!( new_properties, &self.method(:merge_and_record_delta) )
@@ -587,6 +600,8 @@ class Arborist::Node
587
600
  when 'type'
588
601
  self.log.debug "Checking node type %p against %p" % [ self.type, val ]
589
602
  self.type == val
603
+ when 'parent'
604
+ self.parent == val
590
605
  when 'tag' then @tags.include?( val.to_s )
591
606
  when 'tags' then Array(val).all? {|tag| @tags.include?(tag) }
592
607
  when 'identifier' then @identifier == val
@@ -680,7 +695,8 @@ class Arborist::Node
680
695
 
681
696
  super # to state-machine
682
697
 
683
- return self.collect_events
698
+ self.publish_events( event, *self.pending_update_events )
699
+ self.collect_events
684
700
  end
685
701
 
686
702
 
@@ -792,6 +808,20 @@ class Arborist::Node
792
808
  end
793
809
 
794
810
 
811
+ ### Returns +true+ if the node's status indicates it shouldn't be
812
+ ### included by default when traversing nodes.
813
+ def unreachable?
814
+ return UNREACHABLE_STATES.include?( self.status )
815
+ end
816
+
817
+
818
+ ### Returns +true+ if the node's status indicates it is included by
819
+ ### default when traversing nodes.
820
+ def reachable?
821
+ return !self.unreachable?
822
+ end
823
+
824
+
795
825
  ### Register the specified +node+ as a child of this node, replacing any existing
796
826
  ### node with the same identifier.
797
827
  def add_child( node )
@@ -880,7 +910,7 @@ class Arborist::Node
880
910
  @ack = old_node.ack.dup if old_node.ack
881
911
  @last_contacted = old_node.last_contacted
882
912
  @status_changed = old_node.status_changed
883
- @error = old_node.error
913
+ @errors = old_node.errors
884
914
  @quieted_reasons = old_node.quieted_reasons
885
915
 
886
916
  # Only merge in downed dependencies.
@@ -904,7 +934,7 @@ class Arborist::Node
904
934
  ack: self.ack ? self.ack.to_h : nil,
905
935
  last_contacted: self.last_contacted ? self.last_contacted.iso8601 : nil,
906
936
  status_changed: self.status_changed ? self.status_changed.iso8601 : nil,
907
- error: self.error,
937
+ errors: self.errors,
908
938
  dependencies: self.dependencies.to_h,
909
939
  quieted_reasons: self.quieted_reasons,
910
940
  }
@@ -934,7 +964,7 @@ class Arborist::Node
934
964
  @status_changed = Time.parse( hash[:status_changed] )
935
965
  @ack = Arborist::Node::Ack.from_hash( hash[:ack] ) if hash[:ack]
936
966
 
937
- @error = hash[:error]
967
+ @errors = hash[:errors]
938
968
  @properties = hash[:properties] || {}
939
969
  @last_contacted = Time.parse( hash[:last_contacted] )
940
970
  @quieted_reasons = hash[:quieted_reasons] || {}
@@ -988,13 +1018,23 @@ class Arborist::Node
988
1018
 
989
1019
  ### State machine guard predicate -- Returns +true+ if the last time the node
990
1020
  ### was monitored resulted in an update.
991
- def last_contact_successful?
992
- self.log.debug "Checking to see if last contact was successful (it %s)" %
993
- [ self.error ? "wasn't" : "was" ]
994
- return !self.error
1021
+ def has_errors?
1022
+ has_errors = ! self.errors.empty?
1023
+ self.log.debug "Checking to see if last contact cleared remaining errors (it %s)" %
1024
+ [ has_errors ? "did not" : "did" ]
1025
+ self.log.debug "Errors are: %p" % [ self.errors ]
1026
+ return has_errors
995
1027
  end
996
1028
 
997
1029
 
1030
+ ### Return a string describing the errors that are set on the node.
1031
+ def errors_description
1032
+ return "No errors" if self.errors.empty?
1033
+ return self.errors.map do |key, msg|
1034
+ "%s: %s" % [ key, msg ]
1035
+ end.join( '; ' )
1036
+ end
1037
+
998
1038
  #
999
1039
  # :section: State Callbacks
1000
1040
  #
@@ -1035,7 +1075,7 @@ class Arborist::Node
1035
1075
 
1036
1076
  ### Callback for when a node goes from down to up
1037
1077
  def on_node_up( transition )
1038
- self.error = nil
1078
+ self.errors.clear
1039
1079
  self.log.warn "%s is %s" % [ self.identifier, self.status_description ]
1040
1080
  end
1041
1081
 
@@ -1043,7 +1083,7 @@ class Arborist::Node
1043
1083
  ### Callback for when a node goes from up to down
1044
1084
  def on_node_down( transition )
1045
1085
  self.log.error "%s is %s" % [ self.identifier, self.status_description ]
1046
- self.update_delta[ 'error' ] = [ nil, self.error ]
1086
+ self.update_delta[ 'errors' ] = [ nil, self.errors_description ]
1047
1087
  end
1048
1088
 
1049
1089