arborist 0.0.1.pre20160606141735 → 0.0.1.pre20160829140603
Sign up to get free protection for your applications and to get access to all the features.
- 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
|