arborist 0.2.0.pre20170519125456 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/ChangeLog +670 -1
- data/History.md +67 -0
- data/Manifest.txt +9 -6
- data/README.md +1 -3
- data/Rakefile +39 -4
- data/TODO.md +22 -31
- data/lib/arborist.rb +9 -2
- data/lib/arborist/cli.rb +67 -85
- data/lib/arborist/client.rb +125 -59
- data/lib/arborist/command/ack.rb +86 -0
- data/lib/arborist/command/reset.rb +48 -0
- data/lib/arborist/command/start.rb +11 -1
- data/lib/arborist/command/summary.rb +173 -0
- data/lib/arborist/command/tree.rb +215 -0
- data/lib/arborist/command/watch.rb +22 -22
- data/lib/arborist/dependency.rb +24 -4
- data/lib/arborist/event.rb +18 -2
- data/lib/arborist/event/node.rb +6 -2
- data/lib/arborist/event/node_warn.rb +16 -0
- data/lib/arborist/manager.rb +179 -48
- data/lib/arborist/mixins.rb +11 -0
- data/lib/arborist/monitor.rb +29 -17
- data/lib/arborist/monitor/connection_batching.rb +293 -0
- data/lib/arborist/monitor/socket.rb +101 -167
- data/lib/arborist/monitor_runner.rb +101 -24
- data/lib/arborist/node.rb +297 -68
- data/lib/arborist/node/ack.rb +1 -1
- data/lib/arborist/node/host.rb +26 -5
- data/lib/arborist/node/resource.rb +14 -5
- data/lib/arborist/node/root.rb +12 -3
- data/lib/arborist/node/service.rb +29 -26
- data/lib/arborist/node_subscription.rb +65 -0
- data/lib/arborist/observer.rb +8 -0
- data/lib/arborist/observer/action.rb +6 -0
- data/lib/arborist/subscription.rb +22 -16
- data/lib/arborist/tree_api.rb +7 -2
- data/spec/arborist/client_spec.rb +157 -51
- data/spec/arborist/dependency_spec.rb +21 -0
- data/spec/arborist/event/node_spec.rb +5 -0
- data/spec/arborist/event_spec.rb +3 -3
- data/spec/arborist/manager_spec.rb +626 -347
- data/spec/arborist/mixins_spec.rb +19 -0
- data/spec/arborist/monitor/socket_spec.rb +1 -2
- data/spec/arborist/monitor_runner_spec.rb +81 -29
- data/spec/arborist/monitor_spec.rb +89 -14
- data/spec/arborist/node/host_spec.rb +68 -0
- data/spec/arborist/node/resource_spec.rb +2 -0
- data/spec/arborist/node/root_spec.rb +13 -0
- data/spec/arborist/node/service_spec.rb +9 -0
- data/spec/arborist/node_spec.rb +673 -111
- data/spec/arborist/node_subscription_spec.rb +54 -0
- data/spec/arborist/observer/action_spec.rb +6 -0
- data/spec/arborist/observer_runner_spec.rb +8 -1
- data/spec/arborist/tree_api_spec.rb +111 -8
- data/spec/data/monitors/pings.rb +0 -11
- data/spec/data/monitors/port_checks.rb +0 -9
- data/spec/data/nodes/sidonie.rb +1 -0
- data/spec/data/nodes/vhosts.rb +23 -0
- data/spec/data/nodes/yevaud.rb +4 -2
- data/spec/spec_helper.rb +71 -1
- metadata +91 -28
- metadata.gz.sig +0 -0
- data/Events.md +0 -35
- data/Monitors.md +0 -155
- data/Nodes.md +0 -70
- data/Observers.md +0 -72
- data/Protocol.md +0 -276
- data/Tutorial.md +0 -8
@@ -1,6 +1,8 @@
|
|
1
1
|
# -*- ruby -*-
|
2
2
|
#encoding: utf-8
|
3
3
|
|
4
|
+
require 'set'
|
5
|
+
|
4
6
|
require 'cztop'
|
5
7
|
require 'cztop/reactor'
|
6
8
|
require 'cztop/reactor/signal_handling'
|
@@ -21,17 +23,21 @@ class Arborist::MonitorRunner
|
|
21
23
|
# :TODO: :QUIT, :WINCH, :USR2, :TTIN, :TTOU
|
22
24
|
] & Signal.list.keys.map( &:to_sym )
|
23
25
|
|
26
|
+
# Number of seconds between thread cleanup
|
27
|
+
THREAD_CLEANUP_INTERVAL = 5 # seconds
|
28
|
+
|
24
29
|
|
25
30
|
log_to :arborist
|
26
31
|
|
27
32
|
|
28
33
|
### Create a new Arborist::MonitorRunner
|
29
34
|
def initialize
|
30
|
-
@monitors
|
31
|
-
@handler
|
32
|
-
@reactor
|
33
|
-
@client
|
34
|
-
@
|
35
|
+
@monitors = []
|
36
|
+
@handler = nil
|
37
|
+
@reactor = CZTop::Reactor.new
|
38
|
+
@client = Arborist::Client.new
|
39
|
+
@runner_threads = {}
|
40
|
+
@request_queue = {}
|
35
41
|
end
|
36
42
|
|
37
43
|
|
@@ -60,6 +66,10 @@ class Arborist::MonitorRunner
|
|
60
66
|
# The Arborist::Client that will provide the message packing and unpacking
|
61
67
|
attr_reader :client
|
62
68
|
|
69
|
+
##
|
70
|
+
# A hash of monitor object -> thread used to contain and track running monitor threads.
|
71
|
+
attr_reader :runner_threads
|
72
|
+
|
63
73
|
|
64
74
|
### Load monitors from the specified +enumerator+.
|
65
75
|
def load_monitors( enumerator )
|
@@ -69,6 +79,12 @@ class Arborist::MonitorRunner
|
|
69
79
|
|
70
80
|
### Run the specified +monitors+
|
71
81
|
def run
|
82
|
+
self.monitors.each do |mon|
|
83
|
+
self.add_timer_for( mon )
|
84
|
+
end
|
85
|
+
|
86
|
+
self.add_thread_cleanup_timer
|
87
|
+
|
72
88
|
self.with_signal_handler( self.reactor, *QUEUE_SIGS ) do
|
73
89
|
self.reactor.register( self.client.tree_api, :write, &self.method(:handle_io_event) )
|
74
90
|
self.reactor.start_polling
|
@@ -108,45 +124,77 @@ class Arborist::MonitorRunner
|
|
108
124
|
end
|
109
125
|
|
110
126
|
|
111
|
-
###
|
127
|
+
### Update nodes with the results of a monitor's run.
|
112
128
|
def run_monitor( monitor )
|
113
129
|
positive = monitor.positive_criteria
|
114
130
|
negative = monitor.negative_criteria
|
115
|
-
|
131
|
+
exclude_down = monitor.exclude_down?
|
116
132
|
props = monitor.node_properties
|
117
133
|
|
118
|
-
self.
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
134
|
+
self.search( positive, exclude_down, props, negative ) do |nodes|
|
135
|
+
self.log.info "Running %p monitor for %d node(s)" % [
|
136
|
+
monitor.description,
|
137
|
+
nodes.length
|
138
|
+
]
|
139
|
+
|
140
|
+
unless nodes.empty?
|
141
|
+
self.runner_threads[ monitor ] = Thread.new do
|
142
|
+
Thread.current[:monitor_desc] = monitor.description
|
143
|
+
results = self.run_monitor_safely( monitor, nodes )
|
144
|
+
|
145
|
+
self.log.debug " updating with results: %p" % [ results ]
|
146
|
+
self.update( results, monitor.key ) do
|
147
|
+
self.log.debug "Updated %d via the '%s' monitor" %
|
148
|
+
[ results.length, monitor.description ]
|
149
|
+
end
|
150
|
+
end
|
151
|
+
self.log.debug "THREAD: Started %p for %p" % [ self.runner_threads[monitor], monitor ]
|
152
|
+
self.log.debug "THREAD: Runner threads have: %p" % [ self.runner_threads.to_a ]
|
124
153
|
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
125
157
|
|
126
|
-
|
127
|
-
|
128
|
-
|
158
|
+
### Exec +monitor+ against the provided +nodes+ hash, treating
|
159
|
+
### runtime exceptions as an error condition. Returns an update
|
160
|
+
### hash, keyed by node identifier.
|
161
|
+
###
|
162
|
+
def run_monitor_safely( monitor, nodes )
|
163
|
+
results = begin
|
164
|
+
monitor.run( nodes )
|
165
|
+
rescue => err
|
166
|
+
errmsg = "Exception while running %p monitor: %s: %s" % [
|
167
|
+
monitor.description,
|
168
|
+
err.class.name,
|
169
|
+
err.message
|
170
|
+
]
|
171
|
+
self.log.error "%s\n%s" % [ errmsg, err.backtrace.join("\n ") ]
|
172
|
+
nodes.keys.each_with_object({}) do |id, results|
|
173
|
+
results[id] = { error: errmsg }
|
129
174
|
end
|
130
175
|
end
|
176
|
+
|
177
|
+
return results
|
131
178
|
end
|
132
179
|
|
133
180
|
|
134
|
-
### Create a
|
181
|
+
### Create a search request using the runner's client, then queue the request up
|
135
182
|
### with the specified +block+ as the callback.
|
136
|
-
def
|
137
|
-
|
138
|
-
|
183
|
+
def search( criteria, exclude_down, properties, negative={}, &block )
|
184
|
+
search = self.client.make_search_request( criteria,
|
185
|
+
exclude_down: exclude_down,
|
139
186
|
properties: properties,
|
140
187
|
exclude: negative
|
141
188
|
)
|
142
|
-
self.queue_request(
|
189
|
+
self.queue_request( search, &block )
|
143
190
|
end
|
144
191
|
|
145
192
|
|
146
193
|
### Create an update request using the runner's client, then queue the request up
|
147
194
|
### with the specified +block+ as the callback.
|
148
|
-
def update( nodemap, &block )
|
149
|
-
|
195
|
+
def update( nodemap, monitor_key, &block )
|
196
|
+
return if nodemap.empty?
|
197
|
+
update = self.client.make_update_request( nodemap, monitor_key: monitor_key )
|
150
198
|
self.queue_request( update, &block )
|
151
199
|
end
|
152
200
|
|
@@ -198,7 +246,9 @@ class Arborist::MonitorRunner
|
|
198
246
|
self.log.info "Creating timer for %p" % [ monitor ]
|
199
247
|
|
200
248
|
return self.reactor.add_periodic_timer( interval ) do
|
201
|
-
self.
|
249
|
+
unless self.runner_threads.key?( monitor )
|
250
|
+
self.run_monitor( monitor )
|
251
|
+
end
|
202
252
|
end
|
203
253
|
end
|
204
254
|
|
@@ -215,6 +265,33 @@ class Arborist::MonitorRunner
|
|
215
265
|
end
|
216
266
|
|
217
267
|
|
268
|
+
### Set up a timer to clean up monitor threads.
|
269
|
+
def add_thread_cleanup_timer
|
270
|
+
self.log.debug "Starting thread cleanup timer for %p." % [ self.runner_threads ]
|
271
|
+
self.reactor.add_periodic_timer( THREAD_CLEANUP_INTERVAL ) do
|
272
|
+
self.cleanup_monitor_threads
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
|
277
|
+
### :TODO: Handle the thread-interrupt stuff?
|
278
|
+
|
279
|
+
### Clean up any monitor runner threads that are dead.
|
280
|
+
def cleanup_monitor_threads
|
281
|
+
self.runner_threads.values.reject( &:alive? ).each do |thr|
|
282
|
+
monitor = self.runner_threads.key( thr )
|
283
|
+
self.runner_threads.delete( monitor )
|
284
|
+
|
285
|
+
begin
|
286
|
+
thr.join
|
287
|
+
rescue => err
|
288
|
+
self.log.error "%p while running %s: %s" %
|
289
|
+
[ err.class, thr[:monitor_desc], err.message ]
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
|
218
295
|
#
|
219
296
|
# :section: Signal Handling
|
220
297
|
# These methods set up some behavior for starting, restarting, and stopping
|
data/lib/arborist/node.rb
CHANGED
@@ -42,6 +42,7 @@ class Arborist::Node
|
|
42
42
|
description
|
43
43
|
dependencies
|
44
44
|
status_changed
|
45
|
+
status_last_changed
|
45
46
|
last_contacted
|
46
47
|
ack
|
47
48
|
errors
|
@@ -116,16 +117,26 @@ class Arborist::Node
|
|
116
117
|
state :unknown,
|
117
118
|
:up,
|
118
119
|
:down,
|
120
|
+
:warn,
|
119
121
|
:acked,
|
120
122
|
:disabled,
|
121
123
|
:quieted
|
122
124
|
|
123
125
|
event :update do
|
124
|
-
transition [:
|
125
|
-
transition [:
|
126
|
-
transition [:up, :
|
127
|
-
|
128
|
-
|
126
|
+
transition [:down, :warn, :unknown, :acked] => :up, unless: :has_errors_or_warnings?
|
127
|
+
transition [:up, :warn, :unknown] => :down, if: :has_errors?
|
128
|
+
transition [:up, :down, :unknown] => :warn, if: :has_only_warnings?
|
129
|
+
end
|
130
|
+
|
131
|
+
event :acknowledge do
|
132
|
+
transition any - [:down, :acked] => :disabled
|
133
|
+
transition [:down, :acked] => :acked
|
134
|
+
end
|
135
|
+
|
136
|
+
event :unacknowledge do
|
137
|
+
transition [:acked, :disabled] => :warn, if: :has_warnings?
|
138
|
+
transition [:acked, :disabled] => :down, if: :has_errors?
|
139
|
+
transition [:acked, :disabled] => :unknown
|
129
140
|
end
|
130
141
|
|
131
142
|
event :handle_event do
|
@@ -133,11 +144,19 @@ class Arborist::Node
|
|
133
144
|
transition :quieted => :unknown, unless: :has_quieted_reason?
|
134
145
|
end
|
135
146
|
|
147
|
+
event :reparent do
|
148
|
+
transition any - [:disabled, :quieted, :acked] => :unknown
|
149
|
+
transition :quieted => :unknown, unless: :has_quieted_reason?
|
150
|
+
end
|
151
|
+
|
152
|
+
before_transition [:acked, :disabled] => any, do: :save_previous_ack
|
153
|
+
|
136
154
|
after_transition any => :acked, do: :on_ack
|
137
155
|
after_transition :acked => :up, do: :on_ack_cleared
|
138
156
|
after_transition :down => :up, do: :on_node_up
|
139
|
-
after_transition
|
140
|
-
after_transition [:unknown, :up] => :
|
157
|
+
after_transition :up => :warn, do: :on_node_warn
|
158
|
+
after_transition [:unknown, :warn, :up] => :down, do: :on_node_down
|
159
|
+
after_transition [:unknown, :warn, :up] => :disabled, do: :on_node_disabled
|
141
160
|
after_transition any => :quieted, do: :on_node_quieted
|
142
161
|
after_transition :disabled => :unknown, do: :on_node_enabled
|
143
162
|
after_transition :quieted => :unknown, do: :on_node_unquieted
|
@@ -180,7 +199,7 @@ class Arborist::Node
|
|
180
199
|
### them.
|
181
200
|
def self::add_loaded_instance( new_instance )
|
182
201
|
instances = Thread.current[ LOADED_INSTANCE_KEY ] or return
|
183
|
-
self.log.debug "Adding new instance %p to node tree" % [ new_instance ]
|
202
|
+
# self.log.debug "Adding new instance %p to node tree" % [ new_instance ]
|
184
203
|
instances << new_instance
|
185
204
|
end
|
186
205
|
|
@@ -195,14 +214,16 @@ class Arborist::Node
|
|
195
214
|
|
196
215
|
|
197
216
|
### Get/set the node type instances of the class live under. If no parent_type is set, it
|
198
|
-
### is a top-level node type.
|
199
|
-
|
217
|
+
### is a top-level node type. If a +block+ is given, it can be used to pre-process the
|
218
|
+
### arguments into the (identifier, attributes, block) arguments used to create
|
219
|
+
### the node instances.
|
220
|
+
def self::parent_types( *types, &block )
|
200
221
|
@parent_types ||= []
|
201
222
|
|
202
223
|
types.each do |new_type|
|
203
224
|
subclass = Arborist::Node.get_subclass( new_type )
|
204
225
|
@parent_types << subclass
|
205
|
-
subclass.add_subnode_factory_method( self )
|
226
|
+
subclass.add_subnode_factory_method( self, &block )
|
206
227
|
end
|
207
228
|
|
208
229
|
return @parent_types
|
@@ -218,11 +239,23 @@ class Arborist::Node
|
|
218
239
|
|
219
240
|
### Add a factory method that can be used to create subnodes of the specified +subnode_type+
|
220
241
|
### on instances of the receiving class.
|
221
|
-
def self::add_subnode_factory_method( subnode_type )
|
242
|
+
def self::add_subnode_factory_method( subnode_type, &dsl_block )
|
222
243
|
if subnode_type.name
|
223
244
|
name = subnode_type.plugin_name
|
224
|
-
|
225
|
-
|
245
|
+
# self.log.debug "Adding factory constructor for %s nodes to %p" % [ name, self ]
|
246
|
+
body = lambda do |*args, &constructor_block|
|
247
|
+
if dsl_block
|
248
|
+
# self.log.debug "Using DSL block to split args: %p" % [ dsl_block ]
|
249
|
+
identifier, attributes = dsl_block.call( *args )
|
250
|
+
else
|
251
|
+
# self.log.debug "Splitting args the default way: %p" % [ args ]
|
252
|
+
identifier, attributes = *args
|
253
|
+
end
|
254
|
+
attributes ||= {}
|
255
|
+
# self.log.debug "Identifier: %p, attributes: %p, self: %p" %
|
256
|
+
# [ identifier, attributes, self ]
|
257
|
+
|
258
|
+
return Arborist::Node.create( name, identifier, self, attributes, &constructor_block )
|
226
259
|
end
|
227
260
|
|
228
261
|
define_method( name, &body )
|
@@ -279,10 +312,13 @@ class Arborist::Node
|
|
279
312
|
# Primary state
|
280
313
|
@status = 'unknown'
|
281
314
|
@status_changed = Time.at( 0 )
|
315
|
+
@status_last_changed = Time.at( 0 )
|
282
316
|
|
283
317
|
# Attributes that govern state
|
284
318
|
@errors = {}
|
319
|
+
@warnings = {}
|
285
320
|
@ack = nil
|
321
|
+
@previous_ack = nil
|
286
322
|
@last_contacted = Time.at( 0 )
|
287
323
|
@quieted_reasons = {}
|
288
324
|
|
@@ -290,10 +326,9 @@ class Arborist::Node
|
|
290
326
|
@update_delta = Hash.new do |h,k|
|
291
327
|
h[ k ] = Hash.new( &h.default_proc )
|
292
328
|
end
|
293
|
-
@
|
329
|
+
@pending_change_events = []
|
294
330
|
@subscriptions = {}
|
295
331
|
|
296
|
-
self.log.debug "Setting node attributes to: %p" % [ attributes ]
|
297
332
|
self.modify( attributes )
|
298
333
|
self.instance_eval( &block ) if block
|
299
334
|
end
|
@@ -327,22 +362,36 @@ class Arborist::Node
|
|
327
362
|
# The Time the node's status last changed.
|
328
363
|
attr_accessor :status_changed
|
329
364
|
|
365
|
+
##
|
366
|
+
# The previous Time the node's status changed, for duration
|
367
|
+
# calculations between states.
|
368
|
+
attr_accessor :status_last_changed
|
369
|
+
|
330
370
|
##
|
331
371
|
# The Hash of last errors encountered by a monitor attempting to update this
|
332
372
|
# node, keyed by the monitor's `key`.
|
333
373
|
attr_accessor :errors
|
334
374
|
|
375
|
+
##
|
376
|
+
# The Hash of last warnings encountered by a monitor attempting to update this
|
377
|
+
# node, keyed by the monitor's `key`.
|
378
|
+
attr_accessor :warnings
|
379
|
+
|
335
380
|
##
|
336
381
|
# The acknowledgement currently in effect. Should be an instance of Arborist::Node::ACK
|
337
382
|
attr_accessor :ack
|
338
383
|
|
384
|
+
##
|
385
|
+
# The acknowledgement previously in effect (if any).
|
386
|
+
attr_accessor :previous_ack
|
387
|
+
|
339
388
|
##
|
340
389
|
# The Hash of changes tracked during an #update.
|
341
390
|
attr_reader :update_delta
|
342
391
|
|
343
392
|
##
|
344
393
|
# The Array of events generated by the current update event
|
345
|
-
attr_reader :
|
394
|
+
attr_reader :pending_change_events
|
346
395
|
|
347
396
|
##
|
348
397
|
# The Hash of Subscription objects observing this node and its children, keyed by
|
@@ -353,7 +402,6 @@ class Arborist::Node
|
|
353
402
|
# The node's secondary dependencies, expressed as an Arborist::Node::Sexp
|
354
403
|
attr_accessor :dependencies
|
355
404
|
|
356
|
-
|
357
405
|
##
|
358
406
|
# The reasons this node was quieted. This is a Hash of text descriptions keyed by the
|
359
407
|
# type of dependency it came from (either :primary or :secondary).
|
@@ -368,13 +416,14 @@ class Arborist::Node
|
|
368
416
|
|
369
417
|
### Set one or more node +attributes+. This should be overridden by subclasses which
|
370
418
|
### wish to allow their operational attributes to be set/updated via the Tree API
|
371
|
-
### (+modify+ and +graft+). Supported attributes are: +parent+, +description+,
|
372
|
-
### +tags+.
|
419
|
+
### (+modify+ and +graft+). Supported attributes are: +parent+, +description+,
|
420
|
+
### +tags+, and +config+.
|
373
421
|
def modify( attributes )
|
374
422
|
attributes = stringify_keys( attributes )
|
375
423
|
|
376
424
|
self.parent( attributes['parent'] )
|
377
425
|
self.description( attributes['description'] )
|
426
|
+
self.config( attributes['config'] )
|
378
427
|
|
379
428
|
if attributes['tags']
|
380
429
|
@tags.clear
|
@@ -447,7 +496,7 @@ class Arborist::Node
|
|
447
496
|
### Get or set the node's configuration hash. This can be used to pass per-node
|
448
497
|
### information to systems using the tree (e.g., monitors, subscribers).
|
449
498
|
def config( new_config=nil )
|
450
|
-
@config
|
499
|
+
@config.merge!( stringify_keys( new_config ) ) if new_config
|
451
500
|
return @config
|
452
501
|
end
|
453
502
|
|
@@ -483,45 +532,108 @@ class Arborist::Node
|
|
483
532
|
end
|
484
533
|
|
485
534
|
|
535
|
+
### Return the Set of identifier of nodes that are secondary dependencies of this node.
|
536
|
+
def node_subscribers
|
537
|
+
self.log.debug "Finding node subscribers among %d subscriptions" % [ self.subscriptions.length ]
|
538
|
+
return self.subscriptions.each_with_object( Set.new ) do |(identifier, sub), set|
|
539
|
+
if sub.respond_to?( :node_identifier )
|
540
|
+
set.add( sub.node_identifier )
|
541
|
+
else
|
542
|
+
self.log.debug "Skipping %p: not a node subscription" % [ sub ]
|
543
|
+
end
|
544
|
+
end
|
545
|
+
end
|
546
|
+
|
547
|
+
|
486
548
|
### Update specified +properties+ for the node.
|
487
|
-
def update( new_properties )
|
549
|
+
def update( new_properties, monitor_key='_' )
|
550
|
+
self.last_contacted = Time.now
|
551
|
+
self.update_properties( new_properties, monitor_key )
|
552
|
+
|
553
|
+
# Super to the state machine event method
|
554
|
+
super
|
555
|
+
|
556
|
+
events = self.pending_change_events.clone
|
557
|
+
events << self.make_update_event
|
558
|
+
events << self.make_delta_event unless self.update_delta.empty?
|
559
|
+
|
560
|
+
results = self.broadcast_events( *events )
|
561
|
+
self.log.debug ">>> Results from broadcast: %p" % [ results ]
|
562
|
+
events.concat( results )
|
563
|
+
|
564
|
+
return events
|
565
|
+
ensure
|
566
|
+
self.clear_transition_temp_vars
|
567
|
+
end
|
568
|
+
|
569
|
+
|
570
|
+
### Update the node's properties with those in +new_properties+ (a String-keyed Hash)
|
571
|
+
def update_properties( new_properties, monitor_key )
|
572
|
+
monitor_key ||= '_'
|
488
573
|
new_properties = stringify_keys( new_properties )
|
489
|
-
monitor_key = new_properties[ '_monitor_key' ] || '_'
|
490
574
|
|
491
575
|
self.log.debug "Updated via a %s monitor: %p" % [ monitor_key, new_properties ]
|
492
|
-
self.
|
576
|
+
self.update_errors( monitor_key, new_properties.delete('error') )
|
577
|
+
self.update_warnings( monitor_key, new_properties.delete('warning') )
|
578
|
+
|
579
|
+
self.properties.merge!( new_properties, &self.method(:merge_and_record_delta) )
|
580
|
+
compact_hash( self.properties )
|
581
|
+
end
|
582
|
+
|
493
583
|
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
self.errors[ monitor_key ] =
|
584
|
+
### Update the errors hash for the specified +monitor_key+ to +value+.
|
585
|
+
def update_errors( monitor_key, value=nil )
|
586
|
+
if value
|
587
|
+
self.errors[ monitor_key ] = value
|
498
588
|
else
|
499
589
|
self.errors.delete( monitor_key )
|
500
590
|
end
|
591
|
+
end
|
501
592
|
|
502
|
-
self.properties.merge!( new_properties, &self.method(:merge_and_record_delta) )
|
503
|
-
compact_hash( self.properties )
|
504
593
|
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
594
|
+
### Update the warnings hash for the specified +monitor_key+ to +value+.
|
595
|
+
def update_warnings( monitor_key, value=nil )
|
596
|
+
if value
|
597
|
+
self.warnings[ monitor_key ] = value
|
598
|
+
else
|
599
|
+
self.warnings.delete( monitor_key )
|
600
|
+
end
|
509
601
|
end
|
510
602
|
|
511
603
|
|
512
|
-
###
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
604
|
+
### Acknowledge any current or future abnormal status for this node.
|
605
|
+
def acknowledge( **args )
|
606
|
+
super()
|
607
|
+
|
608
|
+
self.ack = args
|
609
|
+
|
610
|
+
events = self.pending_change_events.clone
|
517
611
|
events << self.make_delta_event unless self.update_delta.empty?
|
612
|
+
results = self.broadcast_events( *events )
|
613
|
+
self.log.debug ">>> Results from broadcast: %p" % [ results ]
|
614
|
+
events.concat( results )
|
518
615
|
|
519
|
-
|
616
|
+
return events
|
617
|
+
ensure
|
618
|
+
self.clear_transition_temp_vars
|
619
|
+
end
|
620
|
+
|
621
|
+
|
622
|
+
### Clear any current acknowledgement.
|
623
|
+
def unacknowledge
|
624
|
+
super()
|
625
|
+
|
626
|
+
self.ack = nil
|
627
|
+
|
628
|
+
events = self.pending_change_events.clone
|
629
|
+
events << self.make_delta_event unless self.update_delta.empty?
|
630
|
+
results = self.broadcast_events( *events )
|
631
|
+
self.log.debug ">>> Results from broadcast: %p" % [ results ]
|
632
|
+
events.concat( results )
|
520
633
|
|
521
634
|
return events
|
522
635
|
ensure
|
523
|
-
self.
|
524
|
-
self.pending_update_events.clear
|
636
|
+
self.clear_transition_temp_vars
|
525
637
|
end
|
526
638
|
|
527
639
|
|
@@ -554,6 +666,14 @@ class Arborist::Node
|
|
554
666
|
end
|
555
667
|
|
556
668
|
|
669
|
+
### Clear out the state used during a transition to track changes.
|
670
|
+
def clear_transition_temp_vars
|
671
|
+
self.previous_ack = nil
|
672
|
+
self.update_delta.clear
|
673
|
+
self.pending_change_events.clear
|
674
|
+
end
|
675
|
+
|
676
|
+
|
557
677
|
### Return the node's state in an Arborist::Event of type 'node.update'.
|
558
678
|
def make_update_event
|
559
679
|
return Arborist::Event.create( 'node_update', self )
|
@@ -593,17 +713,18 @@ class Arborist::Node
|
|
593
713
|
|
594
714
|
### Returns +true+ if the node matches the specified +key+ and +val+ criteria.
|
595
715
|
def match_criteria?( key, val )
|
716
|
+
array_val = Array( val )
|
596
717
|
return case key
|
597
718
|
when 'status'
|
598
|
-
self.status
|
719
|
+
array_val.include?( self.status )
|
599
720
|
when 'type'
|
600
|
-
|
601
|
-
self.type == val
|
721
|
+
array_val.include?( self.type )
|
602
722
|
when 'parent'
|
603
|
-
self.parent
|
723
|
+
array_val.include?( self.parent )
|
604
724
|
when 'tag' then @tags.include?( val.to_s )
|
605
|
-
when 'tags' then
|
606
|
-
when 'identifier'
|
725
|
+
when 'tags' then array_val.all? {|tag| @tags.include?(tag) }
|
726
|
+
when 'identifier'
|
727
|
+
array_val.include?( self.identifier )
|
607
728
|
when 'config'
|
608
729
|
val.all? {|ikey, ival| hash_matches(@config, ikey, ival) }
|
609
730
|
else
|
@@ -650,9 +771,7 @@ class Arborist::Node
|
|
650
771
|
raise Arborist::ConfigError, "Can't depend on descendant node %p." % [ identifier ]
|
651
772
|
end
|
652
773
|
|
653
|
-
sub = Arborist::
|
654
|
-
self.handle_event( event )
|
655
|
-
end
|
774
|
+
sub = Arborist::NodeSubscription.new( self )
|
656
775
|
manager.subscribe( identifier, sub )
|
657
776
|
end
|
658
777
|
end
|
@@ -670,12 +789,14 @@ class Arborist::Node
|
|
670
789
|
### Send an event to this node's immediate children.
|
671
790
|
def broadcast_events( *events )
|
672
791
|
events.flatten!
|
673
|
-
self.children.
|
792
|
+
results = self.children.flat_map do |identifier, child|
|
674
793
|
self.log.debug "Broadcasting %d events to %p" % [ events.length, identifier ]
|
675
|
-
events.
|
794
|
+
events.flat_map do |event|
|
676
795
|
child.handle_event( event )
|
677
796
|
end
|
678
797
|
end
|
798
|
+
|
799
|
+
return results
|
679
800
|
end
|
680
801
|
|
681
802
|
|
@@ -692,10 +813,33 @@ class Arborist::Node
|
|
692
813
|
self.log.debug "No handler for a %s event!" % [ event.type ]
|
693
814
|
end
|
694
815
|
|
816
|
+
self.log.debug ">>> Pending change events before: %p" % [ self.pending_change_events ]
|
817
|
+
|
695
818
|
super # to state-machine
|
696
819
|
|
697
|
-
|
698
|
-
self.
|
820
|
+
results = self.pending_change_events.clone
|
821
|
+
self.log.debug ">>> Pending change events after: %p" % [ results ]
|
822
|
+
results << self.make_delta_event unless self.update_delta.empty?
|
823
|
+
|
824
|
+
child_results = self.broadcast_events( *results )
|
825
|
+
results.concat( child_results )
|
826
|
+
|
827
|
+
self.publish_events( *results )
|
828
|
+
|
829
|
+
return results
|
830
|
+
ensure
|
831
|
+
self.clear_transition_temp_vars
|
832
|
+
end
|
833
|
+
|
834
|
+
|
835
|
+
### Move a node from +old_parent+ to +new_parent+.
|
836
|
+
def reparent( old_parent, new_parent )
|
837
|
+
old_parent.remove_child( self )
|
838
|
+
self.parent( new_parent.identifier )
|
839
|
+
new_parent.add_child( self )
|
840
|
+
|
841
|
+
self.quieted_reasons.delete( :primary )
|
842
|
+
super
|
699
843
|
end
|
700
844
|
|
701
845
|
|
@@ -769,7 +913,7 @@ class Arborist::Node
|
|
769
913
|
|
770
914
|
### Handle a 'node.up' event received via broadcast.
|
771
915
|
def handle_node_up_event( event )
|
772
|
-
self.log.debug "Got a node
|
916
|
+
self.log.debug "Got a node.%s event: %p" % [ event.type, event ]
|
773
917
|
|
774
918
|
self.dependencies.mark_up( event.node.identifier )
|
775
919
|
self.quieted_reasons.delete( :secondary ) if self.dependencies_up?
|
@@ -782,6 +926,7 @@ class Arborist::Node
|
|
782
926
|
self.quieted_reasons.delete( :primary )
|
783
927
|
end
|
784
928
|
end
|
929
|
+
alias_method :handle_node_warn_event, :handle_node_up_event
|
785
930
|
|
786
931
|
|
787
932
|
|
@@ -810,6 +955,7 @@ class Arborist::Node
|
|
810
955
|
### Returns +true+ if the node's status indicates it shouldn't be
|
811
956
|
### included by default when traversing nodes.
|
812
957
|
def unreachable?
|
958
|
+
self.log.debug "Testing for reachability; status is: %p" % [ self.status ]
|
813
959
|
return UNREACHABLE_STATES.include?( self.status )
|
814
960
|
end
|
815
961
|
|
@@ -860,7 +1006,7 @@ class Arborist::Node
|
|
860
1006
|
### Return a string describing the node's status.
|
861
1007
|
def status_description
|
862
1008
|
case self.status
|
863
|
-
when 'up', 'down'
|
1009
|
+
when 'up', 'down', 'warn'
|
864
1010
|
return "%s as of %s" % [ self.status.upcase, self.last_contacted ]
|
865
1011
|
when 'acked'
|
866
1012
|
return "ACKed %s" % [ self.acked_description ]
|
@@ -869,8 +1015,10 @@ class Arborist::Node
|
|
869
1015
|
when 'quieted'
|
870
1016
|
reasons = self.quieted_reasons.values.join( ',' )
|
871
1017
|
return "quieted: %s" % [ reasons ]
|
1018
|
+
when 'unknown'
|
1019
|
+
return "in an 'unknown' state"
|
872
1020
|
else
|
873
|
-
return "in an
|
1021
|
+
return "in an unhandled state"
|
874
1022
|
end
|
875
1023
|
end
|
876
1024
|
|
@@ -902,7 +1050,9 @@ class Arborist::Node
|
|
902
1050
|
# :section: Serialization API
|
903
1051
|
#
|
904
1052
|
|
905
|
-
### Restore any saved state from the +old_node+ loaded from the state file.
|
1053
|
+
### Restore any saved state from the +old_node+ loaded from the state file. This is
|
1054
|
+
### used to overlay selective bits of the saved node tree to the equivalent nodes loaded
|
1055
|
+
### from node definitions.
|
906
1056
|
def restore( old_node )
|
907
1057
|
@status = old_node.status
|
908
1058
|
@properties = old_node.properties.dup
|
@@ -910,7 +1060,9 @@ class Arborist::Node
|
|
910
1060
|
@last_contacted = old_node.last_contacted
|
911
1061
|
@status_changed = old_node.status_changed
|
912
1062
|
@errors = old_node.errors
|
1063
|
+
@warnings = old_node.warnings
|
913
1064
|
@quieted_reasons = old_node.quieted_reasons
|
1065
|
+
@status_last_changed = old_node.status_last_changed
|
914
1066
|
|
915
1067
|
# Only merge in downed dependencies.
|
916
1068
|
old_node.dependencies.each_downed do |identifier, time|
|
@@ -919,8 +1071,10 @@ class Arborist::Node
|
|
919
1071
|
end
|
920
1072
|
|
921
1073
|
|
922
|
-
### Return a Hash of the node's state.
|
923
|
-
|
1074
|
+
### Return a Hash of the node's state. If +depth+ is greater than 0, that many
|
1075
|
+
### levels of child nodes are included in the node's `:children` value. Setting
|
1076
|
+
### +depth+ to a negative number will return all of the node's children.
|
1077
|
+
def to_h( depth: 0 )
|
924
1078
|
hash = {
|
925
1079
|
identifier: self.identifier,
|
926
1080
|
type: self.class.name.to_s.sub( /.+::/, '' ).downcase,
|
@@ -933,15 +1087,20 @@ class Arborist::Node
|
|
933
1087
|
ack: self.ack ? self.ack.to_h : nil,
|
934
1088
|
last_contacted: self.last_contacted ? self.last_contacted.iso8601 : nil,
|
935
1089
|
status_changed: self.status_changed ? self.status_changed.iso8601 : nil,
|
1090
|
+
status_last_changed: self.status_last_changed ? self.status_last_changed.iso8601 : nil,
|
936
1091
|
errors: self.errors,
|
1092
|
+
warnings: self.warnings,
|
937
1093
|
dependencies: self.dependencies.to_h,
|
938
1094
|
quieted_reasons: self.quieted_reasons,
|
939
1095
|
}
|
940
1096
|
|
941
|
-
if
|
1097
|
+
if depth.nonzero?
|
1098
|
+
# self.log.debug "including children for depth %p" % [ depth ]
|
942
1099
|
hash[ :children ] = self.children.each_with_object( {} ) do |(ident, node), h|
|
943
|
-
h[ ident ] = node.to_h(
|
1100
|
+
h[ ident ] = node.to_h( depth: depth - 1 )
|
944
1101
|
end
|
1102
|
+
else
|
1103
|
+
hash[ :children ] = {}
|
945
1104
|
end
|
946
1105
|
|
947
1106
|
return hash
|
@@ -969,9 +1128,11 @@ class Arborist::Node
|
|
969
1128
|
|
970
1129
|
@status = hash[:status]
|
971
1130
|
@status_changed = Time.parse( hash[:status_changed] )
|
1131
|
+
@status_last_changed = Time.parse( hash[:status_last_changed] )
|
972
1132
|
@ack = Arborist::Node::Ack.from_hash( hash[:ack] ) if hash[:ack]
|
973
1133
|
|
974
1134
|
@errors = hash[:errors]
|
1135
|
+
@warnings = hash[:warnings]
|
975
1136
|
@properties = hash[:properties] || {}
|
976
1137
|
@last_contacted = Time.parse( hash[:last_contacted] )
|
977
1138
|
@quieted_reasons = hash[:quieted_reasons] || {}
|
@@ -982,7 +1143,7 @@ class Arborist::Node
|
|
982
1143
|
h[ k ] = Hash.new( &h.default_proc )
|
983
1144
|
end
|
984
1145
|
|
985
|
-
@
|
1146
|
+
@pending_change_events = []
|
986
1147
|
@subscriptions = {}
|
987
1148
|
|
988
1149
|
end
|
@@ -1012,6 +1173,26 @@ class Arborist::Node
|
|
1012
1173
|
self.log.info "Node %s ACK cleared explicitly" % [ self.identifier ]
|
1013
1174
|
@ack = nil
|
1014
1175
|
end
|
1176
|
+
|
1177
|
+
self.add_previous_ack_to_update_delta
|
1178
|
+
end
|
1179
|
+
|
1180
|
+
|
1181
|
+
### Save off the current acknowledgement so it can be used after transitions
|
1182
|
+
### which unset it.
|
1183
|
+
def save_previous_ack
|
1184
|
+
self.log.debug "Saving previous ack: %p" % [ self.ack ]
|
1185
|
+
self.previous_ack = self.ack
|
1186
|
+
end
|
1187
|
+
|
1188
|
+
|
1189
|
+
### Add the previous and current acknowledgement to the delta if either of them
|
1190
|
+
### are set.
|
1191
|
+
def add_previous_ack_to_update_delta
|
1192
|
+
unless self.ack == self.previous_ack
|
1193
|
+
self.log.debug "Adding previous ack to the update delta: %p" % [ self.previous_ack ]
|
1194
|
+
self.update_delta[ 'ack' ] = [ self.previous_ack&.to_h, self.ack&.to_h ]
|
1195
|
+
end
|
1015
1196
|
end
|
1016
1197
|
|
1017
1198
|
|
@@ -1023,8 +1204,7 @@ class Arborist::Node
|
|
1023
1204
|
end
|
1024
1205
|
|
1025
1206
|
|
1026
|
-
### State machine guard predicate --
|
1027
|
-
### was monitored resulted in an update.
|
1207
|
+
### State machine guard predicate -- returns +true+ if the node has errors.
|
1028
1208
|
def has_errors?
|
1029
1209
|
has_errors = ! self.errors.empty?
|
1030
1210
|
self.log.debug "Checking to see if last contact cleared remaining errors (it %s)" %
|
@@ -1034,6 +1214,36 @@ class Arborist::Node
|
|
1034
1214
|
end
|
1035
1215
|
|
1036
1216
|
|
1217
|
+
### State machine guard predicate -- Returns +true+ if the node has errors
|
1218
|
+
### and does not have an ACK status set.
|
1219
|
+
def has_unacked_errors?
|
1220
|
+
return self.has_errors? && !self.ack_set?
|
1221
|
+
end
|
1222
|
+
|
1223
|
+
|
1224
|
+
### State machine guard predicate -- returns +true+ if the node has warnings.
|
1225
|
+
def has_warnings?
|
1226
|
+
has_warnings = ! self.warnings.empty?
|
1227
|
+
self.log.debug "Checking to see if last contact cleared remaining warnings (it %s)" %
|
1228
|
+
[ has_warnings ? "did not" : "did" ]
|
1229
|
+
self.log.debug "Warnings are: %p" % [ self.warnings ]
|
1230
|
+
return has_warnings
|
1231
|
+
end
|
1232
|
+
|
1233
|
+
|
1234
|
+
### State machine guard predicate -- returns +true+ if the node has warnings or errors.
|
1235
|
+
def has_errors_or_warnings?
|
1236
|
+
return self.has_errors? || self.has_warnings?
|
1237
|
+
end
|
1238
|
+
|
1239
|
+
|
1240
|
+
### State machine guard predicate -- returns +true+ if the node has warnings but
|
1241
|
+
### no errors.
|
1242
|
+
def has_only_warnings?
|
1243
|
+
return self.has_warnings? && ! self.has_errors?
|
1244
|
+
end
|
1245
|
+
|
1246
|
+
|
1037
1247
|
### Return a string describing the errors that are set on the node.
|
1038
1248
|
def errors_description
|
1039
1249
|
return "No errors" if self.errors.empty?
|
@@ -1042,6 +1252,16 @@ class Arborist::Node
|
|
1042
1252
|
end.join( '; ' )
|
1043
1253
|
end
|
1044
1254
|
|
1255
|
+
|
1256
|
+
### Return a string describing the warnings that are set on the node.
|
1257
|
+
def warnings_description
|
1258
|
+
return "No warnings" if self.warnings.empty?
|
1259
|
+
return self.warnings.map do |key, msg|
|
1260
|
+
"%s: %s" % [ key, msg ]
|
1261
|
+
end.join( '; ' )
|
1262
|
+
end
|
1263
|
+
|
1264
|
+
|
1045
1265
|
#
|
1046
1266
|
# :section: State Callbacks
|
1047
1267
|
#
|
@@ -1055,6 +1275,7 @@ class Arborist::Node
|
|
1055
1275
|
|
1056
1276
|
### Update the last status change time.
|
1057
1277
|
def update_status_changed( transition )
|
1278
|
+
self.status_last_changed = self.status_changed
|
1058
1279
|
self.status_changed = Time.now
|
1059
1280
|
end
|
1060
1281
|
|
@@ -1063,7 +1284,7 @@ class Arborist::Node
|
|
1063
1284
|
def make_transition_event( transition )
|
1064
1285
|
node_type = "node_%s" % [ transition.to ]
|
1065
1286
|
self.log.debug "Making a %s event for %p" % [ node_type, transition ]
|
1066
|
-
self.
|
1287
|
+
self.pending_change_events << Arborist::Event.create( node_type, self )
|
1067
1288
|
end
|
1068
1289
|
|
1069
1290
|
|
@@ -1075,8 +1296,8 @@ class Arborist::Node
|
|
1075
1296
|
|
1076
1297
|
### Callback for when an acknowledgement is cleared.
|
1077
1298
|
def on_ack_cleared( transition )
|
1078
|
-
self.log.warn "ACK cleared for %s" % [ self.identifier ]
|
1079
1299
|
self.ack = nil
|
1300
|
+
self.log.warn "ACK cleared for %s" % [ self.identifier ]
|
1080
1301
|
end
|
1081
1302
|
|
1082
1303
|
|
@@ -1094,6 +1315,13 @@ class Arborist::Node
|
|
1094
1315
|
end
|
1095
1316
|
|
1096
1317
|
|
1318
|
+
### Callback for when a node goes from up to warn
|
1319
|
+
def on_node_warn( transition )
|
1320
|
+
self.log.error "%s is %s" % [ self.identifier, self.status_description ]
|
1321
|
+
self.update_delta[ 'warnings' ] = [ nil, self.warnings_description ]
|
1322
|
+
end
|
1323
|
+
|
1324
|
+
|
1097
1325
|
### Callback for when a node goes from up to disabled
|
1098
1326
|
def on_node_disabled( transition )
|
1099
1327
|
self.log.warn "%s is %s" % [ self.identifier, self.status_description ]
|
@@ -1115,6 +1343,7 @@ class Arborist::Node
|
|
1115
1343
|
### Callback for when a node goes from disabled to unknown
|
1116
1344
|
def on_node_enabled( transition )
|
1117
1345
|
self.log.warn "%s is %s" % [ self.identifier, self.status_description ]
|
1346
|
+
self.ack = nil
|
1118
1347
|
end
|
1119
1348
|
|
1120
1349
|
|