arborist 0.3.0 → 0.4.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 +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?
|