arborist 0.0.1.pre20160106113421 → 0.0.1.pre20160128152542
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Manifest.txt +9 -3
- data/README.md +15 -6
- data/Rakefile +16 -2
- data/TODO.md +10 -1
- data/Tutorial.md +8 -0
- data/bin/arborist +8 -0
- data/lib/arborist.rb +12 -11
- data/lib/arborist/cli.rb +380 -0
- data/lib/arborist/client.rb +16 -0
- data/lib/arborist/command/client.rb +30 -0
- data/lib/arborist/command/config.rb +20 -0
- data/lib/arborist/command/start.rb +60 -0
- data/lib/arborist/command/watch.rb +88 -0
- data/lib/arborist/event/node_matching.rb +5 -0
- data/lib/arborist/loader.rb +43 -0
- data/lib/arborist/loader/file.rb +72 -0
- data/lib/arborist/manager.rb +4 -3
- data/lib/arborist/monitor.rb +8 -22
- data/lib/arborist/node.rb +57 -37
- data/lib/arborist/observer.rb +4 -18
- data/spec/arborist/client_spec.rb +1 -1
- data/spec/arborist/manager/tree_api_spec.rb +1 -0
- data/spec/arborist/manager_spec.rb +15 -4
- data/spec/arborist/monitor_spec.rb +13 -9
- data/spec/arborist/node_spec.rb +36 -6
- data/spec/arborist_spec.rb +36 -5
- data/spec/data/monitors/pings.rb +1 -0
- data/spec/data/monitors/port_checks.rb +0 -3
- data/spec/spec_helper.rb +4 -1
- metadata +18 -11
- data/bin/amanagerd +0 -10
- data/bin/amonitord +0 -12
- data/bin/aobserverd +0 -12
data/lib/arborist/node.rb
CHANGED
@@ -29,10 +29,6 @@ class Arborist::Node
|
|
29
29
|
# loaded.
|
30
30
|
LOADED_INSTANCE_KEY = :loaded_node_instances
|
31
31
|
|
32
|
-
##
|
33
|
-
# The glob pattern to use for searching for node
|
34
|
-
NODE_FILE_PATTERN = '**/*.rb'
|
35
|
-
|
36
32
|
|
37
33
|
##
|
38
34
|
# The struct for the 'ack' operational property
|
@@ -57,18 +53,25 @@ class Arborist::Node
|
|
57
53
|
state :unknown,
|
58
54
|
:up,
|
59
55
|
:down,
|
60
|
-
:acked
|
56
|
+
:acked,
|
57
|
+
:disabled
|
61
58
|
|
62
59
|
event :update do
|
63
|
-
transition
|
64
|
-
transition
|
65
|
-
transition
|
60
|
+
transition [:down, :unknown, :acked] => :up, if: :last_contact_successful?
|
61
|
+
transition [:up, :unknown] => :down, unless: :last_contact_successful?
|
62
|
+
transition :down => :acked, if: :ack_set?
|
63
|
+
transition [:unknown, :up] => :disabled, if: :ack_set?
|
64
|
+
transition :disabled => :unknown, unless: :ack_set?
|
66
65
|
end
|
67
66
|
|
68
67
|
after_transition any => :acked, do: :on_ack
|
69
68
|
after_transition :acked => :up, do: :on_ack_cleared
|
70
69
|
after_transition :down => :up, do: :on_node_up
|
71
70
|
after_transition [:unknown, :up] => :down, do: :on_node_down
|
71
|
+
after_transition [:unknown, :up] => :disabled, do: :on_node_disabled
|
72
|
+
after_transition :disabled => :unknown, do: :on_node_enabled
|
73
|
+
|
74
|
+
after_transition any => any, do: :log_transition
|
72
75
|
|
73
76
|
after_transition do: :add_status_to_update_delta
|
74
77
|
end
|
@@ -100,6 +103,8 @@ class Arborist::Node
|
|
100
103
|
### them.
|
101
104
|
def self::add_loaded_instance( new_instance )
|
102
105
|
instances = Thread.current[ LOADED_INSTANCE_KEY ] or return
|
106
|
+
self.log.debug "Adding new instance %p to loaded instances %p" %
|
107
|
+
[ new_instance, instances ]
|
103
108
|
instances << new_instance
|
104
109
|
end
|
105
110
|
|
@@ -137,24 +142,9 @@ class Arborist::Node
|
|
137
142
|
end
|
138
143
|
|
139
144
|
|
140
|
-
### Return an iterator for all the
|
141
|
-
def self::each_in(
|
142
|
-
|
143
|
-
paths = if path.directory?
|
144
|
-
Pathname.glob( directory + NODE_FILE_PATTERN )
|
145
|
-
else
|
146
|
-
[ path ]
|
147
|
-
end
|
148
|
-
|
149
|
-
return paths.flat_map do |file|
|
150
|
-
file_url = "file://%s" % [ file.expand_path ]
|
151
|
-
nodes = self.load( file )
|
152
|
-
self.log.debug "Loaded nodes %p..." % [ nodes ]
|
153
|
-
nodes.each do |node|
|
154
|
-
node.source = file_url
|
155
|
-
end
|
156
|
-
nodes
|
157
|
-
end
|
145
|
+
### Return an iterator for all the nodes supplied by the specified +loader+.
|
146
|
+
def self::each_in( loader )
|
147
|
+
return loader.nodes
|
158
148
|
end
|
159
149
|
|
160
150
|
|
@@ -323,8 +313,11 @@ class Arborist::Node
|
|
323
313
|
self.log.debug "Updated: %p" % [ new_properties ]
|
324
314
|
|
325
315
|
self.last_contacted = Time.now
|
326
|
-
|
327
|
-
|
316
|
+
if new_properties.key?( 'ack' )
|
317
|
+
self.ack = new_properties.delete( 'ack' )
|
318
|
+
else
|
319
|
+
self.error = new_properties.delete( 'error' )
|
320
|
+
end
|
328
321
|
|
329
322
|
self.properties.merge!( new_properties, &self.method(:merge_and_record_delta) )
|
330
323
|
compact_hash( self.properties )
|
@@ -504,6 +497,8 @@ class Arborist::Node
|
|
504
497
|
return "%s as of %s" % [ self.status.upcase, self.last_contacted ]
|
505
498
|
when 'acked'
|
506
499
|
return "ACKed by %s %s" % [ self.ack.sender, self.ack.time.as_delta ]
|
500
|
+
when 'disabled'
|
501
|
+
return "disabled by %s %s" % [ self.ack.sender, self.ack.time.as_delta ]
|
507
502
|
else
|
508
503
|
return "in an unknown state"
|
509
504
|
end
|
@@ -607,17 +602,21 @@ class Arborist::Node
|
|
607
602
|
|
608
603
|
### Ack the node with the specified +ack_data+, which should contain
|
609
604
|
def ack=( ack_data )
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
605
|
+
if ack_data
|
606
|
+
self.log.info "Node %s ACKed with data: %p" % [ self.identifier, ack_data ]
|
607
|
+
ack_data['time'] ||= Time.now
|
608
|
+
ack_values = ack_data.values_at( *Arborist::Node::ACK.members.map(&:to_s) )
|
609
|
+
new_ack = Arborist::Node::ACK.new( *ack_values )
|
610
|
+
|
611
|
+
if missing = ACK_REQUIRED_PROPERTIES.find {|prop| new_ack[prop].nil? }
|
612
|
+
raise "Missing required ACK attribute %s" % [ missing ]
|
613
|
+
end
|
615
614
|
|
616
|
-
|
617
|
-
|
615
|
+
@ack = new_ack
|
616
|
+
else
|
617
|
+
self.log.info "Node %s ACK cleared explicitly" % [ self.identifier ]
|
618
|
+
@ack = nil
|
618
619
|
end
|
619
|
-
|
620
|
-
@ack = new_ack
|
621
620
|
end
|
622
621
|
|
623
622
|
|
@@ -642,6 +641,13 @@ class Arborist::Node
|
|
642
641
|
# :section: State Callbacks
|
643
642
|
#
|
644
643
|
|
644
|
+
### Log every status transition
|
645
|
+
def log_transition( transition )
|
646
|
+
self.log.debug "Transitioned %s from %s to %s" %
|
647
|
+
[ self.identifier, transition.from, transition.to ]
|
648
|
+
end
|
649
|
+
|
650
|
+
|
645
651
|
### Callback for when an acknowledgement is set.
|
646
652
|
def on_ack( transition )
|
647
653
|
self.log.warn "ACKed: %s" % [ self.status_description ]
|
@@ -652,12 +658,14 @@ class Arborist::Node
|
|
652
658
|
|
653
659
|
### Callback for when an acknowledgement is cleared.
|
654
660
|
def on_ack_cleared( transition )
|
661
|
+
self.error = nil
|
655
662
|
self.log.warn "ACK cleared for %s" % [ self.identifier ]
|
656
663
|
end
|
657
664
|
|
658
665
|
|
659
666
|
### Callback for when a node goes from down to up
|
660
667
|
def on_node_up( transition )
|
668
|
+
self.error = nil
|
661
669
|
self.log.warn "%s is %s" % [ self.identifier, self.status_description ]
|
662
670
|
end
|
663
671
|
|
@@ -669,6 +677,18 @@ class Arborist::Node
|
|
669
677
|
end
|
670
678
|
|
671
679
|
|
680
|
+
### Callback for when a node goes from up to disabled
|
681
|
+
def on_node_disabled( transition )
|
682
|
+
self.log.warn "%s is %s" % [ self.identifier, self.status_description ]
|
683
|
+
end
|
684
|
+
|
685
|
+
|
686
|
+
### Callback for when a node goes from disabled to unknown
|
687
|
+
def on_node_enabled( transition )
|
688
|
+
self.log.warn "%s is %s" % [ self.identifier, self.status_description ]
|
689
|
+
end
|
690
|
+
|
691
|
+
|
672
692
|
### Add the transition from one state to another to the data used to build
|
673
693
|
### deltas for the #update event.
|
674
694
|
def add_status_to_update_delta( transition )
|
data/lib/arborist/observer.rb
CHANGED
@@ -61,27 +61,13 @@ class Arborist::Observer
|
|
61
61
|
end
|
62
62
|
|
63
63
|
|
64
|
-
### Return an iterator for all the
|
65
|
-
def self::each_in(
|
66
|
-
|
67
|
-
paths = if path.directory?
|
68
|
-
Pathname.glob( directory + OBSERVER_FILE_PATTERN )
|
69
|
-
else
|
70
|
-
[ path ]
|
71
|
-
end
|
72
|
-
|
73
|
-
return paths.flat_map do |file|
|
74
|
-
file_url = "file://%s" % [ file.expand_path ]
|
75
|
-
observers = self.load( file )
|
76
|
-
self.log.debug "Loaded observers %p..." % [ observers ]
|
77
|
-
observers.each do |observer|
|
78
|
-
observer.source = file_url
|
79
|
-
end
|
80
|
-
observers
|
81
|
-
end
|
64
|
+
### Return an iterator for all the observers supplied by the specified +loader+.
|
65
|
+
def self::each_in( loader )
|
66
|
+
return loader.observers
|
82
67
|
end
|
83
68
|
|
84
69
|
|
70
|
+
|
85
71
|
### Create a new Observer with the specified +description+.
|
86
72
|
def initialize( description, &block )
|
87
73
|
@description = description
|
@@ -109,7 +109,7 @@ describe Arborist::Client do
|
|
109
109
|
end
|
110
110
|
|
111
111
|
|
112
|
-
it "can update the properties of managed nodes" do
|
112
|
+
it "can update the properties of managed nodes", :no_ci do
|
113
113
|
client.update( duir: { ping: {rtt: 24} } )
|
114
114
|
|
115
115
|
expect( manager.nodes['duir'].properties ).to include( 'ping' )
|
@@ -424,6 +424,7 @@ describe Arborist::Manager::TreeAPI, :testing_manager do
|
|
424
424
|
manager.create_subscription( nil, 'node.delta', {type: 'host'} )
|
425
425
|
end
|
426
426
|
|
427
|
+
|
427
428
|
it "removes the subscription with the specified ID" do
|
428
429
|
msg = pack_message( :unsubscribe, {subscription_id: subscription.id}, nil )
|
429
430
|
|
@@ -239,21 +239,32 @@ describe Arborist::Manager do
|
|
239
239
|
expect( manager.nodes[ 'host_a' ] ).to be_down
|
240
240
|
manager.nodes[ 'host_c' ].update( error: "gamma rays" )
|
241
241
|
expect( manager.nodes[ 'host_c' ] ).to be_down
|
242
|
+
manager.nodes[ 'host_b_nfs' ].
|
243
|
+
update( ack: {sender: 'nancy_kerrigan', message: 'bad case of disk rot'} )
|
244
|
+
expect( manager.nodes[ 'host_b_nfs' ] ).to be_disabled
|
245
|
+
expect( manager.nodes[ 'host_b_nfs' ] ).to_not be_down
|
242
246
|
|
243
247
|
iter = manager.reachable_nodes
|
244
248
|
|
245
249
|
expect( iter ).to be_a( Enumerator )
|
246
250
|
|
247
|
-
nodes = iter.
|
248
|
-
expect( nodes
|
249
|
-
expect( nodes.map(&:identifier) ).to include(
|
251
|
+
nodes = iter.map( &:identifier )
|
252
|
+
expect( nodes ).to include(
|
250
253
|
"_",
|
251
254
|
"router",
|
252
255
|
"host_b",
|
253
256
|
"host_b_www",
|
254
|
-
"host_b_nfs",
|
255
257
|
"host_b_ssh"
|
256
258
|
)
|
259
|
+
expect( nodes ).to_not include(
|
260
|
+
"host_b_nfs",
|
261
|
+
"host_c",
|
262
|
+
"host_c_www",
|
263
|
+
"host_a",
|
264
|
+
'host_a_www',
|
265
|
+
'host_a_smtp',
|
266
|
+
'host_a_imap'
|
267
|
+
)
|
257
268
|
end
|
258
269
|
|
259
270
|
|
@@ -25,7 +25,11 @@ describe Arborist::Monitor do
|
|
25
25
|
end
|
26
26
|
|
27
27
|
|
28
|
-
let( :testing_nodes ) {
|
28
|
+
let( :testing_nodes ) {{
|
29
|
+
'trunk' => trunk_node.to_hash,
|
30
|
+
'branch' => branch_node.to_hash,
|
31
|
+
'leaf' => leaf_node.to_hash
|
32
|
+
}}
|
29
33
|
|
30
34
|
|
31
35
|
it "can be created with just a description" do
|
@@ -112,7 +116,7 @@ describe Arborist::Monitor do
|
|
112
116
|
|
113
117
|
output = mon.run( testing_nodes )
|
114
118
|
expect( output ).to be_a( Hash )
|
115
|
-
expect( output ).to include( *(testing_nodes.
|
119
|
+
expect( output ).to include( *(testing_nodes.keys) )
|
116
120
|
end
|
117
121
|
|
118
122
|
|
@@ -159,7 +163,7 @@ describe Arborist::Monitor do
|
|
159
163
|
exec_input {|*| }
|
160
164
|
exec_arguments do |nodes|
|
161
165
|
Loggability[ Arborist ].debug "In the argument-builder."
|
162
|
-
nodes.
|
166
|
+
nodes.keys
|
163
167
|
end
|
164
168
|
end
|
165
169
|
|
@@ -183,7 +187,7 @@ describe Arborist::Monitor do
|
|
183
187
|
exec 'cat'
|
184
188
|
|
185
189
|
exec_input do |nodes, writer|
|
186
|
-
writer.puts( nodes.
|
190
|
+
writer.puts( nodes.keys )
|
187
191
|
end
|
188
192
|
handle_results do |pid, out, err|
|
189
193
|
return out.readlines.map( &:chomp )
|
@@ -192,7 +196,7 @@ describe Arborist::Monitor do
|
|
192
196
|
|
193
197
|
results = mon.run( testing_nodes )
|
194
198
|
|
195
|
-
expect( results ).to eq( testing_nodes.
|
199
|
+
expect( results ).to eq( testing_nodes.keys )
|
196
200
|
end
|
197
201
|
|
198
202
|
|
@@ -203,7 +207,7 @@ describe Arborist::Monitor do
|
|
203
207
|
|
204
208
|
exec_arguments {|*| }
|
205
209
|
exec_input do |nodes, writer|
|
206
|
-
writer.puts( nodes.
|
210
|
+
writer.puts( nodes.keys )
|
207
211
|
end
|
208
212
|
handle_results do |pid, out, err|
|
209
213
|
out.readlines.map( &:chomp ).map( &:upcase )
|
@@ -212,7 +216,7 @@ describe Arborist::Monitor do
|
|
212
216
|
|
213
217
|
results = mon.run( testing_nodes )
|
214
218
|
|
215
|
-
expect( results ).to eq( testing_nodes.
|
219
|
+
expect( results ).to eq( testing_nodes.keys.map(&:upcase) )
|
216
220
|
end
|
217
221
|
|
218
222
|
|
@@ -220,7 +224,7 @@ describe Arborist::Monitor do
|
|
220
224
|
the_module = Module.new do
|
221
225
|
|
222
226
|
def exec_input( nodes, writer )
|
223
|
-
writer.puts( nodes.
|
227
|
+
writer.puts( nodes.keys )
|
224
228
|
end
|
225
229
|
|
226
230
|
def handle_results( pid, out, err )
|
@@ -241,7 +245,7 @@ describe Arborist::Monitor do
|
|
241
245
|
|
242
246
|
expect( results ).to be_a( Hash )
|
243
247
|
expect( results.size ).to eq( 3 )
|
244
|
-
expect( results ).to include( *testing_nodes.
|
248
|
+
expect( results ).to include( *testing_nodes.keys )
|
245
249
|
expect( results['trunk'] ).to eq({ echoed: 'yep' })
|
246
250
|
expect( results['branch'] ).to eq({ echoed: 'yep' })
|
247
251
|
expect( results['leaf'] ).to eq({ echoed: 'yep' })
|
data/spec/arborist/node_spec.rb
CHANGED
@@ -145,11 +145,39 @@ describe Arborist::Node do
|
|
145
145
|
expect( node ).to be_acked
|
146
146
|
end
|
147
147
|
|
148
|
-
it "transitions to `
|
149
|
-
node.
|
150
|
-
node.update(
|
148
|
+
it "transitions to `disabled` from `up` status if it's updated with an `ack` property" do
|
149
|
+
node.status = 'up'
|
150
|
+
node.update( ack: {message: "Maintenance", sender: 'mahlon'} )
|
151
151
|
|
152
|
-
expect( node ).to
|
152
|
+
expect( node ).to be_disabled
|
153
|
+
end
|
154
|
+
|
155
|
+
it "stays `disabled` if it gets an error" do
|
156
|
+
node.status = 'up'
|
157
|
+
node.update( ack: {message: "Maintenance", sender: 'mahlon'} )
|
158
|
+
node.update( error: "take me to the virus hospital" )
|
159
|
+
|
160
|
+
expect( node ).to be_disabled
|
161
|
+
expect( node.ack ).to_not be_nil
|
162
|
+
end
|
163
|
+
|
164
|
+
it "stays `disabled` if it gets a successful update" do
|
165
|
+
node.status = 'up'
|
166
|
+
node.update( ack: {message: "Maintenance", sender: 'mahlon'} )
|
167
|
+
node.update( ping: {time: 0.02} )
|
168
|
+
|
169
|
+
expect( node ).to be_disabled
|
170
|
+
expect( node.ack ).to_not be_nil
|
171
|
+
end
|
172
|
+
|
173
|
+
it "transitions to `unknown` from `disabled` status if its ack is cleared" do
|
174
|
+
node.status = 'up'
|
175
|
+
node.update( ack: {message: "Maintenance", sender: 'mahlon'} )
|
176
|
+
node.update( ack: nil )
|
177
|
+
|
178
|
+
expect( node ).to_not be_disabled
|
179
|
+
expect( node ).to be_unknown
|
180
|
+
expect( node.ack ).to be_nil
|
153
181
|
end
|
154
182
|
|
155
183
|
end
|
@@ -307,8 +335,9 @@ describe Arborist::Node do
|
|
307
335
|
|
308
336
|
|
309
337
|
it "an ACKed node stays ACKed when reconstituted" do
|
310
|
-
node.update(
|
311
|
-
|
338
|
+
node.update( error: "there's a fire" )
|
339
|
+
node.update( ack: {
|
340
|
+
message: 'We know about the fire. It rages on.',
|
312
341
|
sender: '1986 Labyrinth David Bowie'
|
313
342
|
})
|
314
343
|
cloned_node = concrete_class.from_hash( node.to_hash )
|
@@ -409,6 +438,7 @@ describe Arborist::Node do
|
|
409
438
|
|
410
439
|
|
411
440
|
it "generates a node.acked event when a node is acked" do
|
441
|
+
node.update( error: 'ping failed ')
|
412
442
|
events = node.update(ack: {
|
413
443
|
message: "I have a poisonous friend. She's living in the house.",
|
414
444
|
sender: 'Seabound'
|
data/spec/arborist_spec.rb
CHANGED
@@ -10,14 +10,16 @@ describe Arborist do
|
|
10
10
|
|
11
11
|
before( :all ) do
|
12
12
|
@original_config_env = ENV[Arborist::CONFIG_ENV]
|
13
|
-
@data_dir = Pathname( __FILE__ ).dirname + 'data'
|
14
|
-
@nodes_dir = @data_dir + 'nodes'
|
15
13
|
end
|
16
14
|
|
17
15
|
before( :each ) do
|
18
16
|
ENV.delete(Arborist::CONFIG_ENV)
|
19
17
|
end
|
20
18
|
|
19
|
+
after( :each ) do
|
20
|
+
Arborist::Node::Root.reset
|
21
|
+
end
|
22
|
+
|
21
23
|
after( :all ) do
|
22
24
|
ENV[Arborist::CONFIG_ENV] = @original_config_env
|
23
25
|
end
|
@@ -28,7 +30,7 @@ describe Arborist do
|
|
28
30
|
end
|
29
31
|
|
30
32
|
|
31
|
-
describe "configurability" do
|
33
|
+
describe "configurability", log: :fatal do
|
32
34
|
|
33
35
|
before( :each ) do
|
34
36
|
Configurability.configure_objects( Configurability.default_config )
|
@@ -133,8 +135,37 @@ describe Arborist do
|
|
133
135
|
end
|
134
136
|
|
135
137
|
|
136
|
-
it "can
|
137
|
-
|
138
|
+
it "can construct a Manager for all nodes provided by a Loader" do
|
139
|
+
loader = instance_double( Arborist::Loader )
|
140
|
+
expect( loader ).to receive( :nodes ).and_return([
|
141
|
+
testing_node('trunk'),
|
142
|
+
testing_node('branch', 'trunk'),
|
143
|
+
testing_node('leaf', 'branch')
|
144
|
+
])
|
145
|
+
|
146
|
+
expect( described_class.manager_for(loader) ).to be_a( Arborist::Manager )
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
it "can construct a MonitorRunner for all monitors provided by a Loader" do
|
151
|
+
loader = instance_double( Arborist::Loader )
|
152
|
+
expect( loader ).to receive( :monitors ).and_return([
|
153
|
+
:a_monitor,
|
154
|
+
:another_monitor
|
155
|
+
])
|
156
|
+
|
157
|
+
expect( described_class.monitor_runner_for(loader) ).to be_a( Arborist::MonitorRunner )
|
158
|
+
end
|
159
|
+
|
160
|
+
|
161
|
+
it "can construct an ObserverRunner for all observers provided by a Loader" do
|
162
|
+
loader = instance_double( Arborist::Loader )
|
163
|
+
expect( loader ).to receive( :observers ).and_return([
|
164
|
+
:an_observer,
|
165
|
+
:another_observer
|
166
|
+
])
|
167
|
+
|
168
|
+
expect( described_class.observer_runner_for(loader) ).to be_a( Arborist::ObserverRunner )
|
138
169
|
end
|
139
170
|
|
140
171
|
|