arborist 0.0.1.pre20160606141735 → 0.0.1.pre20160829140603
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
- data/ChangeLog +81 -2
- data/Events.md +11 -11
- data/Manifest.txt +2 -4
- data/TODO.md +9 -29
- data/lib/arborist.rb +6 -2
- data/lib/arborist/command/watch.rb +59 -8
- data/lib/arborist/loader/file.rb +1 -1
- data/lib/arborist/manager.rb +139 -50
- data/lib/arborist/manager/event_publisher.rb +29 -0
- data/lib/arborist/manager/tree_api.rb +16 -1
- data/lib/arborist/mixins.rb +36 -1
- data/lib/arborist/monitor.rb +12 -0
- data/lib/arborist/monitor/socket.rb +8 -9
- data/lib/arborist/monitor_runner.rb +2 -2
- data/lib/arborist/node.rb +16 -3
- data/lib/arborist/node/host.rb +25 -22
- data/lib/arborist/node/resource.rb +28 -14
- data/lib/arborist/node/service.rb +21 -2
- data/lib/arborist/observer_runner.rb +68 -7
- data/spec/arborist/client_spec.rb +3 -3
- data/spec/arborist/manager/event_publisher_spec.rb +0 -1
- data/spec/arborist/manager/tree_api_spec.rb +6 -5
- data/spec/arborist/manager_spec.rb +53 -24
- data/spec/arborist/node/resource_spec.rb +9 -0
- data/spec/arborist/node/service_spec.rb +16 -1
- data/spec/arborist/node_spec.rb +22 -12
- data/spec/arborist/observer_runner_spec.rb +58 -0
- data/spec/arborist/observer_spec.rb +15 -15
- data/spec/arborist/subscription_spec.rb +1 -1
- data/spec/data/nodes/{duir.rb → sub/duir.rb} +0 -0
- data/spec/data/observers/auditor.rb +2 -2
- data/spec/spec_helper.rb +1 -0
- metadata +30 -27
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -2
- data/lib/arborist/event/sys_node_added.rb +0 -10
- data/lib/arborist/event/sys_node_removed.rb +0 -10
- data/lib/arborist/event/sys_reloaded.rb +0 -15
- metadata.gz.sig +0 -0
@@ -25,6 +25,7 @@ class Arborist::Manager::EventPublisher < ZMQ::Handler
|
|
25
25
|
@manager = manager
|
26
26
|
@reactor = reactor
|
27
27
|
@registered = true
|
28
|
+
@enabled = true
|
28
29
|
@event_queue = []
|
29
30
|
end
|
30
31
|
|
@@ -38,9 +39,18 @@ class Arborist::Manager::EventPublisher < ZMQ::Handler
|
|
38
39
|
# to write published events).
|
39
40
|
attr_predicate :registered
|
40
41
|
|
42
|
+
##
|
43
|
+
# The Queue of pending events to publish
|
44
|
+
attr_reader :event_queue
|
45
|
+
|
46
|
+
##
|
47
|
+
# A on/off toggle for publishing events
|
48
|
+
attr_predicate :enabled
|
49
|
+
|
41
50
|
|
42
51
|
### Publish the specified +event+.
|
43
52
|
def publish( identifier, event )
|
53
|
+
return self unless self.enabled?
|
44
54
|
@event_queue << [ identifier, MessagePack.pack(event.to_h) ]
|
45
55
|
self.register
|
46
56
|
return self
|
@@ -62,6 +72,24 @@ class Arborist::Manager::EventPublisher < ZMQ::Handler
|
|
62
72
|
end
|
63
73
|
|
64
74
|
|
75
|
+
### Stop accepting events to be published
|
76
|
+
def shutdown
|
77
|
+
@enabled = false
|
78
|
+
return if @event_queue.empty?
|
79
|
+
|
80
|
+
start = Time.now
|
81
|
+
timeout = start + (@manager.linger.to_f / 2000)
|
82
|
+
|
83
|
+
self.log.warn "Waiting to empty the event queue..."
|
84
|
+
until @event_queue.empty?
|
85
|
+
sleep 0.1
|
86
|
+
break if Time.now > timeout
|
87
|
+
end
|
88
|
+
self.log.warn " ... waited %0.1f seconds" % [ Time.now - start ]
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
|
65
93
|
#########
|
66
94
|
protected
|
67
95
|
#########
|
@@ -70,6 +98,7 @@ class Arborist::Manager::EventPublisher < ZMQ::Handler
|
|
70
98
|
def register
|
71
99
|
count ||= 0
|
72
100
|
@reactor.register( self.pollitem ) unless @registered
|
101
|
+
# self.log.info "Publisher socket is now registered!"
|
73
102
|
@registered = true
|
74
103
|
rescue => err
|
75
104
|
# :TODO:
|
@@ -8,7 +8,8 @@ require 'arborist/manager' unless defined?( Arborist::Manager )
|
|
8
8
|
|
9
9
|
|
10
10
|
class Arborist::Manager::TreeAPI < ZMQ::Handler
|
11
|
-
extend Loggability
|
11
|
+
extend Loggability,
|
12
|
+
Arborist::MethodUtilities
|
12
13
|
|
13
14
|
|
14
15
|
# Loggability API -- log to the arborist logger
|
@@ -20,10 +21,16 @@ class Arborist::Manager::TreeAPI < ZMQ::Handler
|
|
20
21
|
def initialize( pollable, manager )
|
21
22
|
self.log.debug "Setting up a %p" % [ self.class ]
|
22
23
|
@pollitem = pollable
|
24
|
+
@enabled = true
|
23
25
|
@manager = manager
|
24
26
|
end
|
25
27
|
|
26
28
|
|
29
|
+
##
|
30
|
+
# True if the Tree API is accepting commands
|
31
|
+
attr_predicate :enabled
|
32
|
+
|
33
|
+
|
27
34
|
### ZMQ::Handler API -- Read and handle an incoming request.
|
28
35
|
def on_readable
|
29
36
|
request = self.recv
|
@@ -36,6 +43,8 @@ class Arborist::Manager::TreeAPI < ZMQ::Handler
|
|
36
43
|
def handle_request( raw_request )
|
37
44
|
self.log.debug "Handling request: %p" % [ raw_request ]
|
38
45
|
|
46
|
+
raise "Manager is shutting down" unless self.enabled?
|
47
|
+
|
39
48
|
header, body = self.parse_request( raw_request )
|
40
49
|
return self.dispatch_request( header, body )
|
41
50
|
|
@@ -282,5 +291,11 @@ class Arborist::Manager::TreeAPI < ZMQ::Handler
|
|
282
291
|
return successful_response( nil )
|
283
292
|
end
|
284
293
|
|
294
|
+
|
295
|
+
### Disable the API, returning errors for any future requests.
|
296
|
+
def shutdown
|
297
|
+
@enabled = false
|
298
|
+
end
|
299
|
+
|
285
300
|
end # class Arborist::Manager::TreeAPI
|
286
301
|
|
data/lib/arborist/mixins.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# -*- ruby -*-
|
2
2
|
#encoding: utf-8
|
3
3
|
|
4
|
+
require 'ipaddr'
|
5
|
+
|
4
6
|
# A collection of generically-useful mixins
|
5
7
|
module Arborist
|
6
8
|
|
@@ -280,7 +282,7 @@ module Arborist
|
|
280
282
|
end # module TimeRefinements
|
281
283
|
|
282
284
|
|
283
|
-
|
285
|
+
# A collection of utilities for working with Hashes.
|
284
286
|
module HashUtilities
|
285
287
|
|
286
288
|
###############
|
@@ -377,4 +379,37 @@ module Arborist
|
|
377
379
|
|
378
380
|
end # HashUtilities
|
379
381
|
|
382
|
+
|
383
|
+
# A collection of utilities for working with network addresses, names, etc.
|
384
|
+
module NetworkUtilities
|
385
|
+
|
386
|
+
# A union of IPv4 and IPv6 regular expressions.
|
387
|
+
IPADDR_RE = Regexp.union(
|
388
|
+
IPAddr::RE_IPV4ADDRLIKE,
|
389
|
+
IPAddr::RE_IPV6ADDRLIKE_COMPRESSED,
|
390
|
+
IPAddr::RE_IPV6ADDRLIKE_FULL
|
391
|
+
)
|
392
|
+
|
393
|
+
|
394
|
+
### Return the specified +address+ as one or more IPAddr objects.
|
395
|
+
def normalize_address( address )
|
396
|
+
addresses = []
|
397
|
+
case address
|
398
|
+
when IPAddr
|
399
|
+
addresses << address
|
400
|
+
when IPADDR_RE
|
401
|
+
addresses << IPAddr.new( address )
|
402
|
+
when String
|
403
|
+
ip_addr = TCPSocket.gethostbyname( address )
|
404
|
+
addresses = ip_addr[3..-1].map{|ip| IPAddr.new(ip) }
|
405
|
+
else
|
406
|
+
raise "I don't know how to parse a %p host address (%p)" %
|
407
|
+
[ address.class, address ]
|
408
|
+
end
|
409
|
+
|
410
|
+
return addresses
|
411
|
+
end
|
412
|
+
|
413
|
+
end # module NetworkUtilities
|
414
|
+
|
380
415
|
end # module Arborist
|
data/lib/arborist/monitor.rb
CHANGED
@@ -214,6 +214,18 @@ class Arborist::Monitor
|
|
214
214
|
attr_accessor :source
|
215
215
|
|
216
216
|
|
217
|
+
### Return a string representation of the object suitable for debugging.
|
218
|
+
def inspect
|
219
|
+
return "#<%p:%#x %s (every %ds ±%ds)>" % [
|
220
|
+
self.class,
|
221
|
+
self.object_id * 2,
|
222
|
+
self.description || "(no description)",
|
223
|
+
@interval,
|
224
|
+
@splay,
|
225
|
+
]
|
226
|
+
end
|
227
|
+
|
228
|
+
|
217
229
|
### Run the monitor
|
218
230
|
def run( nodes )
|
219
231
|
if self.exec_block
|
@@ -116,26 +116,23 @@ module Arborist::Monitor::Socket
|
|
116
116
|
end
|
117
117
|
|
118
118
|
# Now wait for connections to complete
|
119
|
-
|
119
|
+
wait_seconds = timeout_at - Time.now
|
120
|
+
until connections.empty? || wait_seconds <= 0
|
120
121
|
self.log.debug "Waiting on %d connections for %0.3ds..." %
|
121
|
-
[ connections.values.length,
|
122
|
+
[ connections.values.length, wait_seconds ]
|
122
123
|
|
123
124
|
# :FIXME: Race condition: errors if timeout_at - Time.now is 0
|
124
|
-
_, ready, _ = IO.select( nil, connections.keys, nil,
|
125
|
+
_, ready, _ = IO.select( nil, connections.keys, nil, wait_seconds )
|
125
126
|
|
126
|
-
|
127
|
+
now = Time.now
|
127
128
|
ready.each do |sock|
|
128
|
-
self.log.debug " %p is ready" % [ sock ]
|
129
129
|
identifier, sockaddr = *connections.delete( sock )
|
130
|
-
self.log.debug "%p became writable: testing connection state" % [ sock ]
|
131
130
|
|
132
131
|
begin
|
133
|
-
self.log.debug " trying another connection to %p" % [ sockaddr ]
|
134
132
|
sock.connect_nonblock( sockaddr )
|
135
133
|
rescue Errno::EISCONN
|
136
|
-
self.log.debug " connection successful"
|
137
134
|
results[ identifier ] = {
|
138
|
-
tcp_socket_connect: { time:
|
135
|
+
tcp_socket_connect: { time: now.to_s, duration: now - start }
|
139
136
|
}
|
140
137
|
rescue SocketError, SystemCallError => err
|
141
138
|
self.log.debug "%p during connection: %s" % [ err.class, err.message ]
|
@@ -143,8 +140,10 @@ module Arborist::Monitor::Socket
|
|
143
140
|
ensure
|
144
141
|
sock.close
|
145
142
|
end
|
143
|
+
|
146
144
|
end if ready
|
147
145
|
|
146
|
+
wait_seconds = timeout_at - Time.now
|
148
147
|
end
|
149
148
|
|
150
149
|
# Anything left is a timeout
|
@@ -194,7 +194,7 @@ class Arborist::MonitorRunner
|
|
194
194
|
### Create a repeating ZMQ::Timer that will run the specified monitor on its interval.
|
195
195
|
def interval_timer_for( monitor )
|
196
196
|
interval = monitor.interval
|
197
|
-
self.log.info "Creating timer for %
|
197
|
+
self.log.info "Creating timer for %p" % [ monitor ]
|
198
198
|
|
199
199
|
return ZMQ::Timer.new( interval, 0 ) do
|
200
200
|
self.handler.run_monitor( monitor )
|
@@ -206,7 +206,7 @@ class Arborist::MonitorRunner
|
|
206
206
|
### +monitor+ after a random number of seconds no greater than its splay.
|
207
207
|
def splay_timer_for( monitor )
|
208
208
|
delay = rand( monitor.splay )
|
209
|
-
self.log.
|
209
|
+
self.log.debug "Splaying registration of %p for %ds" % [ monitor, delay ]
|
210
210
|
|
211
211
|
return ZMQ::Timer.new( delay, 1 ) do
|
212
212
|
interval_timer = self.interval_timer_for( monitor )
|
data/lib/arborist/node.rb
CHANGED
@@ -250,6 +250,7 @@ class Arborist::Node
|
|
250
250
|
@description = nil
|
251
251
|
@tags = Set.new
|
252
252
|
@properties = {}
|
253
|
+
@config = {}
|
253
254
|
@source = nil
|
254
255
|
@children = {}
|
255
256
|
@dependencies = Arborist::Dependency.new( :all )
|
@@ -421,6 +422,13 @@ class Arborist::Node
|
|
421
422
|
end
|
422
423
|
|
423
424
|
|
425
|
+
### Get or set the node's configuration hash. This can be used to pass per-node
|
426
|
+
### information to systems using the tree (e.g., monitors, subscribers).
|
427
|
+
def config( new_config=nil )
|
428
|
+
@config = stringify_keys( new_config ) if new_config
|
429
|
+
return @config
|
430
|
+
end
|
431
|
+
|
424
432
|
|
425
433
|
#
|
426
434
|
# :section: Manager API
|
@@ -554,6 +562,8 @@ class Arborist::Node
|
|
554
562
|
when 'tag' then @tags.include?( val.to_s )
|
555
563
|
when 'tags' then Array(val).all? {|tag| @tags.include?(tag) }
|
556
564
|
when 'identifier' then @identifier == val
|
565
|
+
when 'config'
|
566
|
+
val.all? {|ikey, ival| hash_matches(@config, ikey, ival) }
|
557
567
|
else
|
558
568
|
hash_matches( @properties, key, val )
|
559
569
|
end
|
@@ -638,7 +648,7 @@ class Arborist::Node
|
|
638
648
|
self.log.debug "Handling a %s event." % [ event.type ]
|
639
649
|
self.method( handler_name ).call( event )
|
640
650
|
end
|
641
|
-
super
|
651
|
+
super # to state-machine
|
642
652
|
end
|
643
653
|
|
644
654
|
|
@@ -840,6 +850,7 @@ class Arborist::Node
|
|
840
850
|
parent: self.parent,
|
841
851
|
description: self.description,
|
842
852
|
tags: self.tags,
|
853
|
+
config: self.config,
|
843
854
|
status: self.status,
|
844
855
|
properties: self.properties.dup,
|
845
856
|
ack: self.ack ? self.ack.to_h : nil,
|
@@ -861,16 +872,19 @@ class Arborist::Node
|
|
861
872
|
### Marshal API -- set up the object's state using the +hash+ from a
|
862
873
|
### previously-marshalled node.
|
863
874
|
def marshal_load( hash )
|
875
|
+
self.log.debug "Restoring from serialized hash: %p" % [ hash ]
|
864
876
|
@identifier = hash[:identifier]
|
865
877
|
@properties = hash[:properties]
|
866
878
|
|
867
879
|
@parent = hash[:parent]
|
868
880
|
@description = hash[:description]
|
869
881
|
@tags = Set.new( hash[:tags] )
|
882
|
+
@config = hash[:config]
|
870
883
|
@children = {}
|
871
884
|
|
872
|
-
@status =
|
885
|
+
@status = hash[:status]
|
873
886
|
@status_changed = Time.parse( hash[:status_changed] )
|
887
|
+
@ack = Arborist::Node::Ack.from_hash( hash[:ack] ) if hash[:ack]
|
874
888
|
|
875
889
|
@error = hash[:error]
|
876
890
|
@properties = hash[:properties] || {}
|
@@ -886,7 +900,6 @@ class Arborist::Node
|
|
886
900
|
@pending_update_events = []
|
887
901
|
@subscriptions = {}
|
888
902
|
|
889
|
-
self.ack = hash[:ack]
|
890
903
|
end
|
891
904
|
|
892
905
|
|
data/lib/arborist/node/host.rb
CHANGED
@@ -2,21 +2,36 @@
|
|
2
2
|
#encoding: utf-8
|
3
3
|
|
4
4
|
require 'etc'
|
5
|
-
require 'ipaddr'
|
6
5
|
|
7
6
|
require 'arborist/node'
|
7
|
+
require 'arborist/mixins'
|
8
8
|
|
9
9
|
|
10
10
|
# A node type for Arborist trees that represent network-connected hosts.
|
11
|
+
#
|
12
|
+
# host_node = Arborist::Node.create( :host, 'acme' ) do
|
13
|
+
# description "Public-facing webserver"
|
14
|
+
# address '93.184.216.34'
|
15
|
+
#
|
16
|
+
# tags :public, :dmz
|
17
|
+
#
|
18
|
+
# resource 'disk'
|
19
|
+
# resource 'memory' do
|
20
|
+
# config hwm: '3.4G'
|
21
|
+
# end
|
22
|
+
# resource 'loadavg'
|
23
|
+
# resource 'processes' do
|
24
|
+
# config expect: { nginx: 2 }
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# service 'ssh'
|
28
|
+
# service 'www'
|
29
|
+
#
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
#
|
11
33
|
class Arborist::Node::Host < Arborist::Node
|
12
|
-
|
13
|
-
# A union of IPv4 and IPv6 regular expressions.
|
14
|
-
IPADDR_RE = Regexp.union(
|
15
|
-
IPAddr::RE_IPV4ADDRLIKE,
|
16
|
-
IPAddr::RE_IPV6ADDRLIKE_COMPRESSED,
|
17
|
-
IPAddr::RE_IPV6ADDRLIKE_FULL
|
18
|
-
)
|
19
|
-
|
34
|
+
include Arborist::NetworkUtilities
|
20
35
|
|
21
36
|
### Create a new Host node.
|
22
37
|
def initialize( identifier, attributes={}, &block )
|
@@ -60,19 +75,7 @@ class Arborist::Node::Host < Arborist::Node
|
|
60
75
|
### Set an IP address of the host.
|
61
76
|
def address( new_address )
|
62
77
|
self.log.debug "Adding address %p to %p" % [ new_address, self ]
|
63
|
-
|
64
|
-
when IPAddr
|
65
|
-
@addresses << new_address
|
66
|
-
when IPADDR_RE
|
67
|
-
@addresses << IPAddr.new( new_address )
|
68
|
-
when String
|
69
|
-
ip_addr = TCPSocket.gethostbyname( new_address )
|
70
|
-
@addresses << IPAddr.new( ip_addr[3] )
|
71
|
-
@addresses << IPAddr.new( ip_addr[4] ) if ip_addr[4]
|
72
|
-
else
|
73
|
-
raise "I don't know how to parse a %p host address (%p)" %
|
74
|
-
[ new_address.class, new_address ]
|
75
|
-
end
|
78
|
+
@addresses += normalize_address( new_address )
|
76
79
|
end
|
77
80
|
|
78
81
|
|
@@ -19,20 +19,16 @@ class Arborist::Node::Resource < Arborist::Node
|
|
19
19
|
|
20
20
|
@host = host
|
21
21
|
|
22
|
+
attributes[ :category ] ||= identifier
|
22
23
|
super( qualified_identifier, host, attributes, &block )
|
23
24
|
end
|
24
25
|
|
25
26
|
|
26
|
-
###
|
27
|
-
def
|
28
|
-
|
29
|
-
return case key
|
30
|
-
when 'address'
|
31
|
-
search_addr = IPAddr.new( val )
|
32
|
-
self.addresses.any? {|a| search_addr.include?(a) }
|
33
|
-
else
|
27
|
+
### Set service +attributes+.
|
28
|
+
def modify( attributes )
|
29
|
+
attributes = stringify_keys( attributes )
|
34
30
|
super
|
35
|
-
|
31
|
+
self.category( attributes['category'] )
|
36
32
|
end
|
37
33
|
|
38
34
|
|
@@ -45,6 +41,13 @@ class Arborist::Node::Resource < Arborist::Node
|
|
45
41
|
end
|
46
42
|
|
47
43
|
|
44
|
+
### Get/set the resource category.
|
45
|
+
def category( new_category=nil )
|
46
|
+
return @category unless new_category
|
47
|
+
@category = new_category
|
48
|
+
end
|
49
|
+
|
50
|
+
|
48
51
|
### Delegate the resources's address to its host.
|
49
52
|
def addresses
|
50
53
|
return @host.addresses
|
@@ -59,15 +62,26 @@ class Arborist::Node::Resource < Arborist::Node
|
|
59
62
|
end
|
60
63
|
|
61
64
|
|
62
|
-
|
63
|
-
# Serialization
|
64
|
-
#
|
65
|
-
|
66
|
-
### Return a Hash of the host node's state.
|
65
|
+
### Serialize the resource node. Return a Hash of the host node's state.
|
67
66
|
def to_h
|
68
67
|
return super.merge(
|
69
68
|
addresses: self.addresses.map( &:to_s )
|
70
69
|
)
|
71
70
|
end
|
72
71
|
|
72
|
+
|
73
|
+
### Returns +true+ if the node matches the specified +key+ and +val+ criteria.
|
74
|
+
def match_criteria?( key, val )
|
75
|
+
self.log.debug "Matching %p: %p against %p" % [ key, val, self ]
|
76
|
+
return case key
|
77
|
+
when 'address'
|
78
|
+
search_addr = IPAddr.new( val )
|
79
|
+
self.addresses.any? {|a| search_addr.include?(a) }
|
80
|
+
when 'category'
|
81
|
+
self.category == val
|
82
|
+
else
|
83
|
+
super
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
73
87
|
end # class Arborist::Node::Resource
|