arborist 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/ChangeLog +1646 -1569
- data/History.md +14 -0
- data/Rakefile +1 -1
- data/lib/arborist.rb +2 -2
- data/lib/arborist/client.rb +3 -3
- data/lib/arborist/command/summary.rb +1 -1
- data/lib/arborist/event.rb +8 -0
- data/lib/arborist/event/node.rb +4 -3
- data/lib/arborist/event/node_delta.rb +7 -0
- data/lib/arborist/event/node_update.rb +7 -0
- data/lib/arborist/monitor/socket.rb +1 -1
- data/lib/arborist/monitor_runner.rb +2 -4
- data/lib/arborist/node.rb +124 -28
- data/lib/arborist/node/root.rb +1 -1
- data/lib/arborist/observer/action.rb +15 -2
- data/spec/arborist/event/node_spec.rb +3 -1
- data/spec/arborist/event_spec.rb +10 -0
- data/spec/arborist/manager_spec.rb +20 -22
- data/spec/arborist/mixins_spec.rb +3 -1
- data/spec/arborist/node_spec.rb +76 -16
- data/spec/arborist/observer/action_spec.rb +25 -0
- metadata +27 -32
- metadata.gz.sig +0 -0
data/History.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
## v0.4.0 [2018-11-21] Mahlon E. Smith <mahlon@martini.nu>
|
2
|
+
|
3
|
+
Enhancements:
|
4
|
+
|
5
|
+
- Add an 'informational' predicate for node events.
|
6
|
+
- Introduce basic flap heuristics for node state changes.
|
7
|
+
|
8
|
+
|
9
|
+
Fixes:
|
10
|
+
|
11
|
+
- Clean up excess ruby warnings.
|
12
|
+
|
13
|
+
|
14
|
+
|
1
15
|
## v0.3.0 [2018-08-29] Michael Granger <ged@FaerieMUD.org>
|
2
16
|
|
3
17
|
Enhancements:
|
data/Rakefile
CHANGED
@@ -38,7 +38,7 @@ hoespec = Hoe.spec 'arborist' do |spec|
|
|
38
38
|
spec.developer 'Michael Granger', 'ged@FaerieMUD.org'
|
39
39
|
spec.developer 'Mahlon E. Smith', 'mahlon@martini.nu'
|
40
40
|
|
41
|
-
spec.dependency 'schedulability', '~> 0.
|
41
|
+
spec.dependency 'schedulability', '~> 0.4'
|
42
42
|
spec.dependency 'loggability', '~> 0.12'
|
43
43
|
spec.dependency 'configurability', '~> 3.0'
|
44
44
|
spec.dependency 'pluggability', '~> 0.4'
|
data/lib/arborist.rb
CHANGED
@@ -14,10 +14,10 @@ module Arborist
|
|
14
14
|
Configurability
|
15
15
|
|
16
16
|
# Package version
|
17
|
-
VERSION = '0.
|
17
|
+
VERSION = '0.4.0'
|
18
18
|
|
19
19
|
# Version control revision
|
20
|
-
REVISION = %q$Revision
|
20
|
+
REVISION = %q$Revision$
|
21
21
|
|
22
22
|
|
23
23
|
# The name of the environment variable which can be used to set the config path
|
data/lib/arborist/client.rb
CHANGED
@@ -242,7 +242,7 @@ class Arborist::Client
|
|
242
242
|
### Modify operational attributes of a node.
|
243
243
|
def modify( *args )
|
244
244
|
request = self.make_modify_request( *args )
|
245
|
-
|
245
|
+
self.send_tree_api_request( request )
|
246
246
|
return true
|
247
247
|
end
|
248
248
|
|
@@ -263,7 +263,7 @@ class Arborist::Client
|
|
263
263
|
### +time+ of now.
|
264
264
|
def acknowledge( *args )
|
265
265
|
request = self.make_acknowledge_request( *args )
|
266
|
-
|
266
|
+
self.send_tree_api_request( request )
|
267
267
|
return true
|
268
268
|
end
|
269
269
|
alias_method :ack, :acknowledge
|
@@ -286,7 +286,7 @@ class Arborist::Client
|
|
286
286
|
### Clear the acknowledgement for a node.
|
287
287
|
def clear_acknowledgement( *args )
|
288
288
|
request = self.make_unack_request( *args )
|
289
|
-
|
289
|
+
self.send_tree_api_request( request )
|
290
290
|
return true
|
291
291
|
end
|
292
292
|
alias_method :unack, :clear_acknowledgement
|
data/lib/arborist/event.rb
CHANGED
@@ -45,6 +45,14 @@ class Arborist::Event
|
|
45
45
|
end
|
46
46
|
|
47
47
|
|
48
|
+
### Returns +true+ if the event contains node information other than about a
|
49
|
+
### change in its state.
|
50
|
+
def informational?
|
51
|
+
return false
|
52
|
+
end
|
53
|
+
alias_method :is_informational?, :informational?
|
54
|
+
|
55
|
+
|
48
56
|
### Match operator -- returns +true+ if the other object matches this event.
|
49
57
|
def match( object )
|
50
58
|
rval = object.respond_to?( :event_type ) &&
|
data/lib/arborist/event/node.rb
CHANGED
@@ -41,9 +41,10 @@ class Arborist::Event::Node < Arborist::Event
|
|
41
41
|
### Inject useful node metadata into the generated hash.
|
42
42
|
def to_h
|
43
43
|
return super.merge(
|
44
|
-
identifier:
|
45
|
-
parent:
|
46
|
-
nodetype:
|
44
|
+
identifier: self.node.identifier,
|
45
|
+
parent: self.node.parent,
|
46
|
+
nodetype: self.node.type,
|
47
|
+
flapping: self.node.flapping?
|
47
48
|
)
|
48
49
|
end
|
49
50
|
|
@@ -25,6 +25,13 @@ class Arborist::Event::NodeDelta < Arborist::Event::Node
|
|
25
25
|
end
|
26
26
|
|
27
27
|
|
28
|
+
### Returns +true+ if the event contains node information other than about a
|
29
|
+
### change in its state.
|
30
|
+
def informational?
|
31
|
+
return true
|
32
|
+
end
|
33
|
+
|
34
|
+
|
28
35
|
### Returns +true+ if the specified +object+ matches this event.
|
29
36
|
def match( object )
|
30
37
|
rval = super &&
|
@@ -7,4 +7,11 @@ require 'arborist/event/node'
|
|
7
7
|
# An event sent on every node update, regardless of whether or not the update resulted in
|
8
8
|
# any changes
|
9
9
|
class Arborist::Event::NodeUpdate < Arborist::Event::Node
|
10
|
+
|
11
|
+
### Returns +true+ if the event contains node information other than about a
|
12
|
+
### change in its state.
|
13
|
+
def informational?
|
14
|
+
return true
|
15
|
+
end
|
16
|
+
|
10
17
|
end # class Arborist::Event::NodeUpdate
|
@@ -110,7 +110,7 @@ module Arborist::Monitor::Socket
|
|
110
110
|
sock = conn_hash[:conn]
|
111
111
|
# Why getpeername? Testing socket success without read()ing, I think?
|
112
112
|
# FreeBSD source?
|
113
|
-
|
113
|
+
sock.getpeername
|
114
114
|
return {
|
115
115
|
tcp_socket_connect: { duration: duration }
|
116
116
|
}
|
@@ -169,8 +169,8 @@ class Arborist::MonitorRunner
|
|
169
169
|
err.message
|
170
170
|
]
|
171
171
|
self.log.error "%s\n%s" % [ errmsg, err.backtrace.join("\n ") ]
|
172
|
-
nodes.keys.each_with_object({}) do |id,
|
173
|
-
|
172
|
+
nodes.keys.each_with_object({}) do |id, node_results|
|
173
|
+
node_results[id] = { error: errmsg }
|
174
174
|
end
|
175
175
|
end
|
176
176
|
|
@@ -230,8 +230,6 @@ class Arborist::MonitorRunner
|
|
230
230
|
|
231
231
|
### Register a timer for the specified +monitor+.
|
232
232
|
def add_timer_for( monitor )
|
233
|
-
interval = monitor.interval
|
234
|
-
|
235
233
|
if monitor.splay.nonzero?
|
236
234
|
self.add_splay_timer_for( monitor )
|
237
235
|
else
|
data/lib/arborist/node.rb
CHANGED
@@ -7,8 +7,10 @@ require 'time'
|
|
7
7
|
require 'pathname'
|
8
8
|
require 'state_machines'
|
9
9
|
|
10
|
+
require 'configurability'
|
10
11
|
require 'loggability'
|
11
12
|
require 'pluggability'
|
13
|
+
|
12
14
|
require 'arborist' unless defined?( Arborist )
|
13
15
|
require 'arborist/mixins'
|
14
16
|
require 'arborist/exceptions'
|
@@ -23,6 +25,7 @@ class Arborist::Node
|
|
23
25
|
Arborist::HashUtilities
|
24
26
|
extend Loggability,
|
25
27
|
Pluggability,
|
28
|
+
Configurability,
|
26
29
|
Arborist::MethodUtilities
|
27
30
|
|
28
31
|
|
@@ -46,6 +49,7 @@ class Arborist::Node
|
|
46
49
|
last_contacted
|
47
50
|
ack
|
48
51
|
errors
|
52
|
+
warnings
|
49
53
|
quieted_reasons
|
50
54
|
config
|
51
55
|
]
|
@@ -69,6 +73,17 @@ class Arborist::Node
|
|
69
73
|
plugin_prefixes 'arborist/node'
|
70
74
|
|
71
75
|
|
76
|
+
# Configurability API
|
77
|
+
configurability( 'arborist.node' ) do
|
78
|
+
|
79
|
+
# How many status entries to keep in a ring buffer
|
80
|
+
setting :status_history_size, default: 0
|
81
|
+
|
82
|
+
# How many status transitions in the history constitutes flapping
|
83
|
+
setting :flap_threshold, default: 0
|
84
|
+
end
|
85
|
+
|
86
|
+
|
72
87
|
##
|
73
88
|
# :method: unknown?
|
74
89
|
# Returns +true+ if the node is in an 'unknown' state.
|
@@ -166,6 +181,9 @@ class Arborist::Node
|
|
166
181
|
after_transition any => any, do: :update_status_changed
|
167
182
|
|
168
183
|
after_transition do: :add_status_to_update_delta
|
184
|
+
|
185
|
+
after_transition do: :record_status_history
|
186
|
+
after_failure do: :record_status_history
|
169
187
|
end
|
170
188
|
|
171
189
|
|
@@ -308,11 +326,15 @@ class Arborist::Node
|
|
308
326
|
@source = nil
|
309
327
|
@children = {}
|
310
328
|
@dependencies = Arborist::Dependency.new( :all )
|
329
|
+
@flap_threshold = nil
|
330
|
+
@flapping = false
|
331
|
+
@status_history_size = nil
|
311
332
|
|
312
333
|
# Primary state
|
313
334
|
@status = 'unknown'
|
314
335
|
@status_changed = Time.at( 0 )
|
315
336
|
@status_last_changed = Time.at( 0 )
|
337
|
+
@status_history = []
|
316
338
|
|
317
339
|
# Attributes that govern state
|
318
340
|
@errors = {}
|
@@ -407,6 +429,14 @@ class Arborist::Node
|
|
407
429
|
# type of dependency it came from (either :primary or :secondary).
|
408
430
|
attr_reader :quieted_reasons
|
409
431
|
|
432
|
+
##
|
433
|
+
# An array of statuses, retained after an update.
|
434
|
+
attr_reader :status_history
|
435
|
+
|
436
|
+
##
|
437
|
+
# The current flapping state of this node.
|
438
|
+
attr_predicate_accessor :flapping
|
439
|
+
|
410
440
|
|
411
441
|
### Set the source of the node to +source+, which should be a valid URI.
|
412
442
|
def source=( source )
|
@@ -501,6 +531,22 @@ class Arborist::Node
|
|
501
531
|
end
|
502
532
|
|
503
533
|
|
534
|
+
### Get or set the number of entries to store for the status
|
535
|
+
### history.
|
536
|
+
def status_history_size( new_size=nil )
|
537
|
+
@status_history_size = new_size if new_size
|
538
|
+
return @status_history_size || Arborist::Node.status_history_size || 0
|
539
|
+
end
|
540
|
+
|
541
|
+
|
542
|
+
### Get or set the number of transitions in the status
|
543
|
+
### history to determine if a node is considering 'flapping'.
|
544
|
+
def flap_threshold( new_count=nil )
|
545
|
+
@flap_threshold = new_count if new_count
|
546
|
+
return @flap_threshold || Arborist::Node.flap_threshold || 0
|
547
|
+
end
|
548
|
+
|
549
|
+
|
504
550
|
#
|
505
551
|
# :section: Manager API
|
506
552
|
# Methods used by the manager to manage its nodes.
|
@@ -813,8 +859,8 @@ class Arborist::Node
|
|
813
859
|
self.log.debug "No handler for a %s event!" % [ event.type ]
|
814
860
|
end
|
815
861
|
|
816
|
-
|
817
|
-
|
862
|
+
# Don't transition on informational events
|
863
|
+
return if event.informational?
|
818
864
|
super # to state-machine
|
819
865
|
|
820
866
|
results = self.pending_change_events.clone
|
@@ -1054,14 +1100,17 @@ class Arborist::Node
|
|
1054
1100
|
### used to overlay selective bits of the saved node tree to the equivalent nodes loaded
|
1055
1101
|
### from node definitions.
|
1056
1102
|
def restore( old_node )
|
1057
|
-
@status
|
1058
|
-
@properties
|
1059
|
-
@ack
|
1060
|
-
@last_contacted
|
1061
|
-
@status_changed
|
1062
|
-
@
|
1063
|
-
@
|
1064
|
-
@
|
1103
|
+
@status = old_node.status
|
1104
|
+
@properties = old_node.properties.dup
|
1105
|
+
@ack = old_node.ack.dup if old_node.ack
|
1106
|
+
@last_contacted = old_node.last_contacted
|
1107
|
+
@status_changed = old_node.status_changed
|
1108
|
+
@status_history = old_node.status_history
|
1109
|
+
@status_history_size = old_node.status_history_size
|
1110
|
+
@flapping = old_node.flapping?
|
1111
|
+
@errors = old_node.errors
|
1112
|
+
@warnings = old_node.warnings
|
1113
|
+
@quieted_reasons = old_node.quieted_reasons
|
1065
1114
|
@status_last_changed = old_node.status_last_changed
|
1066
1115
|
|
1067
1116
|
# Only merge in downed dependencies.
|
@@ -1088,6 +1137,9 @@ class Arborist::Node
|
|
1088
1137
|
last_contacted: self.last_contacted ? self.last_contacted.iso8601 : nil,
|
1089
1138
|
status_changed: self.status_changed ? self.status_changed.iso8601 : nil,
|
1090
1139
|
status_last_changed: self.status_last_changed ? self.status_last_changed.iso8601 : nil,
|
1140
|
+
status_history: self.status_history,
|
1141
|
+
status_history_size: self.status_history_size,
|
1142
|
+
flapping: self.flapping?,
|
1091
1143
|
errors: self.errors,
|
1092
1144
|
warnings: self.warnings,
|
1093
1145
|
dependencies: self.dependencies.to_h,
|
@@ -1117,29 +1169,32 @@ class Arborist::Node
|
|
1117
1169
|
### previously-marshalled node.
|
1118
1170
|
def marshal_load( hash )
|
1119
1171
|
self.log.debug "Restoring from serialized hash: %p" % [ hash ]
|
1120
|
-
@identifier
|
1121
|
-
@properties
|
1172
|
+
@identifier = hash[:identifier]
|
1173
|
+
@properties = hash[:properties]
|
1122
1174
|
|
1123
|
-
@parent
|
1124
|
-
@description
|
1125
|
-
@tags
|
1126
|
-
@config
|
1127
|
-
@children
|
1175
|
+
@parent = hash[:parent]
|
1176
|
+
@description = hash[:description]
|
1177
|
+
@tags = Set.new( hash[:tags] )
|
1178
|
+
@config = hash[:config]
|
1179
|
+
@children = {}
|
1128
1180
|
|
1129
|
-
@status
|
1130
|
-
@status_changed
|
1181
|
+
@status = hash[:status]
|
1182
|
+
@status_changed = Time.parse( hash[:status_changed] )
|
1131
1183
|
@status_last_changed = Time.parse( hash[:status_last_changed] )
|
1132
|
-
@
|
1133
|
-
|
1134
|
-
@
|
1135
|
-
@
|
1136
|
-
|
1137
|
-
@
|
1138
|
-
@
|
1184
|
+
@status_history = hash[:status_history]
|
1185
|
+
@status_history_size = hash[:status_history_size]
|
1186
|
+
@flapping = hash[:flapping]
|
1187
|
+
@ack = Arborist::Node::Ack.from_hash( hash[:ack] ) if hash[:ack]
|
1188
|
+
|
1189
|
+
@errors = hash[:errors]
|
1190
|
+
@warnings = hash[:warnings]
|
1191
|
+
@properties = hash[:properties] || {}
|
1192
|
+
@last_contacted = Time.parse( hash[:last_contacted] )
|
1193
|
+
@quieted_reasons = hash[:quieted_reasons] || {}
|
1139
1194
|
self.log.debug "Deps are: %p" % [ hash[:dependencies] ]
|
1140
|
-
@dependencies
|
1195
|
+
@dependencies = hash[:dependencies]
|
1141
1196
|
|
1142
|
-
@update_delta
|
1197
|
+
@update_delta = Hash.new do |h,k|
|
1143
1198
|
h[ k ] = Hash.new( &h.default_proc )
|
1144
1199
|
end
|
1145
1200
|
|
@@ -1164,6 +1219,23 @@ class Arborist::Node
|
|
1164
1219
|
protected
|
1165
1220
|
#########
|
1166
1221
|
|
1222
|
+
### Detects if this node is considered 'flapping', returning +true+ if so.
|
1223
|
+
def check_flapping
|
1224
|
+
return false if self.flap_threshold.zero?
|
1225
|
+
|
1226
|
+
runs = self.status_history.each_cons( 2 ).count do |status_1, status_2|
|
1227
|
+
status_1 != status_2
|
1228
|
+
end
|
1229
|
+
|
1230
|
+
self.log.debug "Observed %d changes in %d samples." % [
|
1231
|
+
runs,
|
1232
|
+
self.status_history.size
|
1233
|
+
]
|
1234
|
+
|
1235
|
+
return runs >= self.flap_threshold
|
1236
|
+
end
|
1237
|
+
|
1238
|
+
|
1167
1239
|
### Ack the node with the specified +ack_data+, which should contain
|
1168
1240
|
def ack=( ack_data )
|
1169
1241
|
if ack_data
|
@@ -1356,6 +1428,30 @@ class Arborist::Node
|
|
1356
1428
|
end
|
1357
1429
|
|
1358
1430
|
|
1431
|
+
### Add a flap change to the delta event.
|
1432
|
+
def add_flap_to_update_delta( from, to )
|
1433
|
+
return if from == to
|
1434
|
+
self.update_delta[ 'flapping' ] = [ from, to ]
|
1435
|
+
end
|
1436
|
+
|
1437
|
+
|
1438
|
+
### Retain the status in the node's history.
|
1439
|
+
###
|
1440
|
+
def record_status_history
|
1441
|
+
retain = self.status_history_size
|
1442
|
+
return if retain.zero?
|
1443
|
+
|
1444
|
+
pre_state = self.flapping?
|
1445
|
+
self.status_history << self.status
|
1446
|
+
self.flapping = self.check_flapping
|
1447
|
+
|
1448
|
+
current_size = self.status_history.size
|
1449
|
+
self.status_history.slice!( 0, current_size - retain ) if current_size >= retain
|
1450
|
+
|
1451
|
+
self.add_flap_to_update_delta( pre_state, self.flapping? )
|
1452
|
+
end
|
1453
|
+
|
1454
|
+
|
1359
1455
|
#######
|
1360
1456
|
private
|
1361
1457
|
#######
|
data/lib/arborist/node/root.rb
CHANGED
@@ -19,12 +19,13 @@ class Arborist::Observer::Action
|
|
19
19
|
### Create a new Action that will call the specified +block+ +during+ the given schedule,
|
20
20
|
### but only +after+ the specified number of events have arrived +within+ the given
|
21
21
|
### time threshold.
|
22
|
-
def initialize( within: 0, after: 1, during: nil, &block )
|
22
|
+
def initialize( within: 0, after: 1, during: nil, ignore_flapping: false, &block )
|
23
23
|
raise ArgumentError, "Action requires a block" unless block
|
24
24
|
|
25
25
|
@block = block
|
26
26
|
@time_threshold = within
|
27
27
|
@schedule = Schedulability::Schedule.parse( during ) if during
|
28
|
+
@ignore_flapping = ignore_flapping
|
28
29
|
|
29
30
|
if within.zero?
|
30
31
|
@count_threshold = after
|
@@ -58,6 +59,11 @@ class Arborist::Observer::Action
|
|
58
59
|
# The schedule that applies to this action.
|
59
60
|
attr_reader :schedule
|
60
61
|
|
62
|
+
##
|
63
|
+
# Take no action if the node the event belongs to is in a flapping
|
64
|
+
# state.
|
65
|
+
attr_reader :ignore_flapping
|
66
|
+
|
61
67
|
##
|
62
68
|
# The Hash of recent events, keyed by their arrival time.
|
63
69
|
attr_reader :event_history
|
@@ -66,7 +72,7 @@ class Arborist::Observer::Action
|
|
66
72
|
### Call the action for the specified +event+.
|
67
73
|
def handle_event( event )
|
68
74
|
self.record_event( event )
|
69
|
-
self.call_block( event ) if self.should_run?
|
75
|
+
self.call_block( event ) if self.should_run? && ! self.flapping?( event )
|
70
76
|
end
|
71
77
|
|
72
78
|
|
@@ -107,6 +113,13 @@ class Arborist::Observer::Action
|
|
107
113
|
end
|
108
114
|
|
109
115
|
|
116
|
+
### Returns +true+ if this observer respects the flapping state of
|
117
|
+
### a node, and the generated event is attached to a flapping node.
|
118
|
+
def flapping?( event )
|
119
|
+
return self.ignore_flapping && event[ 'flapping' ]
|
120
|
+
end
|
121
|
+
|
122
|
+
|
110
123
|
### Returns +true+ if the time between the first and last event in the #event_history is
|
111
124
|
### less than the #time_threshold.
|
112
125
|
def time_threshold_exceeded?
|