arborist 0.1.0 → 0.2.0.pre20170519125456
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 +5 -5
- data/ChangeLog +46 -2
- data/Manifest.txt +4 -4
- data/Rakefile +6 -4
- data/TODO.md +16 -0
- data/lib/arborist.rb +12 -51
- data/lib/arborist/client.rb +23 -46
- data/lib/arborist/command/client.rb +1 -0
- data/lib/arborist/command/watch.rb +4 -5
- data/lib/arborist/event_api.rb +35 -0
- data/lib/arborist/exceptions.rb +2 -2
- data/lib/arborist/manager.rb +432 -212
- data/lib/arborist/mixins.rb +9 -9
- data/lib/arborist/monitor_runner.rb +174 -137
- data/lib/arborist/node.rb +11 -4
- data/lib/arborist/observer/summarize.rb +1 -1
- data/lib/arborist/observer_runner.rb +163 -126
- data/lib/arborist/tree_api.rb +113 -0
- data/spec/arborist/client_spec.rb +63 -64
- data/spec/arborist/event_api_spec.rb +23 -0
- data/spec/arborist/manager_spec.rb +842 -66
- data/spec/arborist/monitor_runner_spec.rb +45 -85
- data/spec/arborist/observer_runner_spec.rb +86 -23
- data/spec/arborist/tree_api_spec.rb +30 -0
- data/spec/arborist_spec.rb +0 -5
- data/spec/spec_helper.rb +0 -13
- metadata +47 -45
- checksums.yaml.gz.sig +0 -3
- data.tar.gz.sig +0 -0
- data/lib/arborist/manager/event_publisher.rb +0 -126
- data/lib/arborist/manager/tree_api.rb +0 -302
- data/spec/arborist/manager/event_publisher_spec.rb +0 -65
- data/spec/arborist/manager/tree_api_spec.rb +0 -791
- metadata.gz.sig +0 -0
@@ -66,7 +66,7 @@ class Arborist::Observer::Summarize
|
|
66
66
|
|
67
67
|
|
68
68
|
### Handle a timing event by calling the block with any events in the history.
|
69
|
-
def on_timer
|
69
|
+
def on_timer( * )
|
70
70
|
self.log.debug "Timer event: %d pending event/s" % [ self.event_history.size ]
|
71
71
|
self.call_block unless self.event_history.empty?
|
72
72
|
end
|
@@ -1,7 +1,9 @@
|
|
1
1
|
# -*- ruby -*-
|
2
2
|
#encoding: utf-8
|
3
3
|
|
4
|
-
require '
|
4
|
+
require 'cztop'
|
5
|
+
require 'cztop/reactor'
|
6
|
+
require 'cztop/reactor/signal_handling'
|
5
7
|
require 'loggability'
|
6
8
|
|
7
9
|
require 'arborist' unless defined?( Arborist )
|
@@ -9,119 +11,29 @@ require 'arborist/client'
|
|
9
11
|
require 'arborist/observer'
|
10
12
|
|
11
13
|
|
12
|
-
# Undo the useless scoping
|
13
|
-
class ZMQ::Loop
|
14
|
-
public_class_method :instance
|
15
|
-
end
|
16
|
-
|
17
|
-
|
18
14
|
# An event-driven runner for Arborist::Observers.
|
19
15
|
class Arborist::ObserverRunner
|
20
16
|
extend Loggability
|
17
|
+
include CZTop::Reactor::SignalHandling
|
21
18
|
|
22
|
-
log_to :arborist
|
23
|
-
|
24
|
-
|
25
|
-
# A ZMQ::Handler object for managing IO for all running observers.
|
26
|
-
class Handler < ZMQ::Handler
|
27
|
-
extend Loggability,
|
28
|
-
Arborist::MethodUtilities
|
29
|
-
|
30
|
-
log_to :arborist
|
31
|
-
|
32
|
-
### Create a ZMQ::Handler that acts as the agent that runs the specified
|
33
|
-
### +observer+.
|
34
|
-
def initialize( runner, reactor )
|
35
|
-
@runner = runner
|
36
|
-
@client = Arborist::Client.new
|
37
|
-
@pollitem = ZMQ::Pollitem.new( @client.event_api, ZMQ::POLLIN )
|
38
|
-
@pollitem.handler = self
|
39
|
-
@subscriptions = {}
|
40
|
-
|
41
|
-
reactor.register( @pollitem )
|
42
|
-
end
|
43
|
-
|
44
|
-
|
45
|
-
######
|
46
|
-
public
|
47
|
-
######
|
48
|
-
|
49
|
-
# The Arborist::ObserverRunner that owns this handler.
|
50
|
-
attr_reader :runner
|
51
|
-
|
52
|
-
# The Arborist::Client that will be used for creating and tearing down subscriptions
|
53
|
-
attr_reader :client
|
54
|
-
|
55
|
-
# The map of subscription IDs to the Observer which it was created for.
|
56
|
-
attr_reader :subscriptions
|
57
|
-
|
58
|
-
|
59
|
-
### Unsubscribe from and clear all current subscriptions.
|
60
|
-
def reset
|
61
|
-
self.log.warn "Resetting the observer handler."
|
62
|
-
self.subscriptions.keys.each do |subid|
|
63
|
-
self.client.event_api.unsubscribe( subid )
|
64
|
-
end
|
65
|
-
self.subscriptions.clear
|
66
|
-
end
|
67
|
-
|
68
|
-
|
69
|
-
### Add the specified +observer+ and subscribe to the events it wishes to receive.
|
70
|
-
def add_observer( observer )
|
71
|
-
self.log.info "Adding observer: %s" % [ observer.description ]
|
72
|
-
observer.subscriptions.each do |sub|
|
73
|
-
subid = self.client.subscribe( sub )
|
74
|
-
self.subscriptions[ subid ] = observer
|
75
|
-
self.client.event_api.subscribe( subid )
|
76
|
-
self.log.debug " subscribed to %p with subscription %s" % [ sub, subid ]
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
|
81
|
-
### Remove the specified +observer+ after unsubscribing from its events.
|
82
|
-
def remove_observer( observer )
|
83
|
-
self.log.info "Removing observer: %s" % [ observer.description ]
|
84
|
-
|
85
|
-
self.subscriptions.keys.each do |subid|
|
86
|
-
next unless self.subscriptions[ subid ] == observer
|
87
|
-
|
88
|
-
self.client.unsubscribe( subid )
|
89
|
-
self.subscriptions.delete( subid )
|
90
|
-
self.client.event_api.unsubscribe( subid )
|
91
|
-
self.log.debug " unsubscribed from %p" % [ subid ]
|
92
|
-
end
|
93
|
-
end
|
94
19
|
|
20
|
+
# Signals the observer runner responds to
|
21
|
+
QUEUE_SIGS = [
|
22
|
+
:INT, :TERM, :HUP,
|
23
|
+
# :TODO: :QUIT, :WINCH, :USR1, :USR2, :TTIN, :TTOU
|
24
|
+
] & Signal.list.keys.map( &:to_sym )
|
95
25
|
|
96
|
-
### Read events from the event socket when it becomes readable, and dispatch them to
|
97
|
-
### the correct observer.
|
98
|
-
def on_readable
|
99
|
-
subid = self.recv
|
100
|
-
raise "Partial write?!" unless self.pollitem.pollable.rcvmore?
|
101
|
-
raw_event = self.recv
|
102
|
-
event = MessagePack.unpack( raw_event )
|
103
26
|
|
104
|
-
|
105
|
-
observer.handle_event( subid, event )
|
106
|
-
elsif subid.start_with?( 'sys.' )
|
107
|
-
self.log.debug "System event! %p" % [ event ]
|
108
|
-
self.runner.handle_system_event( subid, event )
|
109
|
-
else
|
110
|
-
self.log.warn "Ignoring event %p for which we have no observer." % [ subid ]
|
111
|
-
end
|
112
|
-
|
113
|
-
return true
|
114
|
-
end
|
115
|
-
|
116
|
-
end # class Handler
|
27
|
+
log_to :arborist
|
117
28
|
|
118
29
|
|
119
30
|
### Create a new Arborist::ObserverRunner
|
120
31
|
def initialize
|
121
|
-
@observers
|
122
|
-
@timers
|
123
|
-
@
|
124
|
-
@reactor
|
32
|
+
@observers = []
|
33
|
+
@timers = []
|
34
|
+
@subscriptions = {}
|
35
|
+
@reactor = CZTop::Reactor.new
|
36
|
+
@client = Arborist::Client.new
|
125
37
|
@manager_last_runid = nil
|
126
38
|
end
|
127
39
|
|
@@ -136,49 +48,69 @@ class Arborist::ObserverRunner
|
|
136
48
|
# The Array of registered ZMQ::Timers
|
137
49
|
attr_reader :timers
|
138
50
|
|
139
|
-
# The
|
140
|
-
attr_accessor :handler
|
141
|
-
|
142
|
-
# The reactor (a ZMQ::Loop) the runner uses to drive everything
|
51
|
+
# The reactor (a CZTop::Reactor) the runner uses to drive everything
|
143
52
|
attr_accessor :reactor
|
144
53
|
|
54
|
+
# The Arborist::Client that will be used for creating and tearing down subscriptions
|
55
|
+
attr_reader :client
|
56
|
+
|
57
|
+
# The map of subscription IDs to the Observer which it was created for.
|
58
|
+
attr_reader :subscriptions
|
59
|
+
|
145
60
|
|
146
61
|
### Load observers from the specified +enumerator+.
|
147
62
|
def load_observers( enumerator )
|
148
|
-
|
63
|
+
self.observers.concat( enumerator.to_a )
|
149
64
|
end
|
150
65
|
|
151
66
|
|
152
67
|
### Run the specified +observers+
|
153
68
|
def run
|
154
|
-
self.
|
155
|
-
|
69
|
+
self.log.info "Starting!"
|
156
70
|
self.register_observers
|
157
71
|
self.register_observer_timers
|
158
72
|
self.subscribe_to_system_events
|
159
73
|
|
160
|
-
self.reactor.
|
161
|
-
|
162
|
-
|
163
|
-
|
74
|
+
self.reactor.register( self.client.event_api, :read, &self.method(:on_subscription_event) )
|
75
|
+
|
76
|
+
self.with_signal_handler( self.reactor, *QUEUE_SIGS ) do
|
77
|
+
self.reactor.start_polling( ignore_interrupts: true )
|
78
|
+
end
|
164
79
|
end
|
165
80
|
|
166
81
|
|
167
82
|
### Stop the observer
|
168
83
|
def stop
|
169
|
-
self.
|
170
|
-
|
171
|
-
|
172
|
-
|
84
|
+
self.log.info "Stopping!"
|
85
|
+
self.remove_timers
|
86
|
+
self.unregister_observers
|
87
|
+
self.reactor.stop_polling
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
### Restart the observer, resetting all of its observers' subscriptions.
|
92
|
+
def restart
|
93
|
+
self.log.info "Restarting!"
|
94
|
+
self.reactor.timers.pause
|
95
|
+
self.unregister_observers
|
96
|
+
|
97
|
+
self.register_observers
|
98
|
+
self.reactor.timers.resume
|
99
|
+
end
|
173
100
|
|
174
|
-
|
101
|
+
|
102
|
+
### Returns true if the ObserverRunner is running.
|
103
|
+
def running?
|
104
|
+
return self.reactor &&
|
105
|
+
self.client &&
|
106
|
+
self.reactor.registered?( self.client.event_api )
|
175
107
|
end
|
176
108
|
|
177
109
|
|
178
|
-
###
|
110
|
+
### Add subscriptions for all of the observers loaded into the runner.
|
179
111
|
def register_observers
|
180
112
|
self.observers.each do |observer|
|
181
|
-
self.
|
113
|
+
self.add_observer( observer )
|
182
114
|
end
|
183
115
|
end
|
184
116
|
|
@@ -191,9 +123,17 @@ class Arborist::ObserverRunner
|
|
191
123
|
end
|
192
124
|
|
193
125
|
|
126
|
+
### Remove the subscriptions belonging to the loaded observers.
|
127
|
+
def unregister_observers
|
128
|
+
self.observers.each do |observer|
|
129
|
+
self.remove_observer( observer )
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
|
194
134
|
### Subscribe the runner to system events published by the Manager.
|
195
135
|
def subscribe_to_system_events
|
196
|
-
self.
|
136
|
+
self.client.event_api.subscribe( 'sys.' )
|
197
137
|
end
|
198
138
|
|
199
139
|
|
@@ -202,8 +142,7 @@ class Arborist::ObserverRunner
|
|
202
142
|
observer.timers.each do |interval, callback|
|
203
143
|
self.log.info "Creating timer for %s observer to run %p every %ds" %
|
204
144
|
[ observer.description, callback, interval ]
|
205
|
-
timer =
|
206
|
-
self.reactor.register_timer( timer )
|
145
|
+
timer = self.reactor.add_periodic_timer( interval, &callback )
|
207
146
|
self.timers << timer
|
208
147
|
end
|
209
148
|
end
|
@@ -212,7 +151,65 @@ class Arborist::ObserverRunner
|
|
212
151
|
### Remove any registered timers.
|
213
152
|
def remove_timers
|
214
153
|
self.timers.each do |timer|
|
215
|
-
self.reactor.
|
154
|
+
self.reactor.remove_timer( timer )
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
|
159
|
+
### Unsubscribe from and clear all current subscriptions.
|
160
|
+
def reset
|
161
|
+
self.log.warn "Resetting observer subscriptions."
|
162
|
+
self.subscriptions.keys.each do |subid|
|
163
|
+
self.client.event_api.unsubscribe( subid )
|
164
|
+
end
|
165
|
+
self.subscriptions.clear
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
### Add the specified +observer+ and subscribe to the events it wishes to receive.
|
170
|
+
def add_observer( observer )
|
171
|
+
self.log.info "Adding observer: %s" % [ observer.description ]
|
172
|
+
observer.subscriptions.each do |sub|
|
173
|
+
subid = self.client.subscribe( sub )
|
174
|
+
self.subscriptions[ subid ] = observer
|
175
|
+
self.client.event_api.subscribe( subid )
|
176
|
+
self.log.debug " subscribed to %p with subscription %s" % [ sub, subid ]
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
|
181
|
+
### Remove the specified +observer+ after unsubscribing from its events.
|
182
|
+
def remove_observer( observer )
|
183
|
+
self.log.info "Removing observer: %s" % [ observer.description ]
|
184
|
+
|
185
|
+
self.subscriptions.keys.each do |subid|
|
186
|
+
next unless self.subscriptions[ subid ] == observer
|
187
|
+
|
188
|
+
self.client.unsubscribe( subid )
|
189
|
+
self.subscriptions.delete( subid )
|
190
|
+
self.client.event_api.unsubscribe( subid )
|
191
|
+
self.log.debug " unsubscribed from %p" % [ subid ]
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
|
196
|
+
### Handle IO events from the reactor.
|
197
|
+
def on_subscription_event( event )
|
198
|
+
if event.readable?
|
199
|
+
msg = event.socket.receive
|
200
|
+
subid, event = Arborist::EventAPI.decode( msg )
|
201
|
+
|
202
|
+
if (( observer = self.subscriptions[subid] ))
|
203
|
+
self.log.debug "Got %p event for %p" % [ subid, observer ]
|
204
|
+
observer.handle_event( subid, event )
|
205
|
+
elsif subid.start_with?( 'sys.' )
|
206
|
+
self.log.debug "System event! %p" % [ event ]
|
207
|
+
self.handle_system_event( subid, event )
|
208
|
+
else
|
209
|
+
self.log.warn "Ignoring event %p for which we have no observer." % [ subid ]
|
210
|
+
end
|
211
|
+
else
|
212
|
+
raise "Unhandled event %p on the event socket" % [ event ]
|
216
213
|
end
|
217
214
|
end
|
218
215
|
|
@@ -226,7 +223,7 @@ class Arborist::ObserverRunner
|
|
226
223
|
this_runid = event['run_id']
|
227
224
|
if @manager_last_runid && this_runid != @manager_last_runid
|
228
225
|
self.log.warn "Manager run ID changed: re-subscribing"
|
229
|
-
self.
|
226
|
+
self.reset
|
230
227
|
self.register_observers
|
231
228
|
end
|
232
229
|
|
@@ -238,5 +235,45 @@ class Arborist::ObserverRunner
|
|
238
235
|
end
|
239
236
|
end
|
240
237
|
|
238
|
+
|
239
|
+
#
|
240
|
+
# :section: Signal Handling
|
241
|
+
# These methods set up some behavior for starting, restarting, and stopping
|
242
|
+
# the runner when a signal is received.
|
243
|
+
#
|
244
|
+
|
245
|
+
### Handle signals.
|
246
|
+
def handle_signal( sig )
|
247
|
+
self.log.debug "Handling signal %s" % [ sig ]
|
248
|
+
case sig
|
249
|
+
when :INT, :TERM
|
250
|
+
self.on_termination_signal( sig )
|
251
|
+
|
252
|
+
when :HUP
|
253
|
+
self.on_hangup_signal( sig )
|
254
|
+
|
255
|
+
else
|
256
|
+
self.log.warn "Unhandled signal %s" % [ sig ]
|
257
|
+
end
|
258
|
+
|
259
|
+
end
|
260
|
+
|
261
|
+
|
262
|
+
### Handle a TERM signal. Shuts the handler down after handling any current request/s. Also
|
263
|
+
### aliased to #on_interrupt_signal.
|
264
|
+
def on_termination_signal( signo )
|
265
|
+
self.log.warn "Terminated (%p)" % [ signo ]
|
266
|
+
self.stop
|
267
|
+
end
|
268
|
+
alias_method :on_interrupt_signal, :on_termination_signal
|
269
|
+
|
270
|
+
|
271
|
+
### Handle a HUP signal. The default is to restart the handler.
|
272
|
+
def on_hangup_signal( signo )
|
273
|
+
self.log.warn "Hangup (%p)" % [ signo ]
|
274
|
+
self.restart
|
275
|
+
end
|
276
|
+
|
277
|
+
|
241
278
|
end # class Arborist::ObserverRunner
|
242
279
|
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'msgpack'
|
5
|
+
require 'loggability'
|
6
|
+
require 'cztop'
|
7
|
+
require 'arborist' unless defined?( Arborist )
|
8
|
+
|
9
|
+
|
10
|
+
module Arborist::TreeAPI
|
11
|
+
extend Loggability,
|
12
|
+
Arborist::MethodUtilities,
|
13
|
+
Arborist::HashUtilities
|
14
|
+
|
15
|
+
# The version of the application protocol
|
16
|
+
PROTOCOL_VERSION = 1
|
17
|
+
|
18
|
+
|
19
|
+
# Loggability API -- log to the arborist logger
|
20
|
+
log_to :arborist
|
21
|
+
|
22
|
+
|
23
|
+
|
24
|
+
### Return a CZTop::Message with a payload containing the specified +header+ and +body+.
|
25
|
+
def self::encode( header, body=nil )
|
26
|
+
header = stringify_keys( header )
|
27
|
+
header['version'] = PROTOCOL_VERSION
|
28
|
+
|
29
|
+
self.check_header( header )
|
30
|
+
self.check_body( body )
|
31
|
+
|
32
|
+
payload = MessagePack.pack([ header, body ])
|
33
|
+
|
34
|
+
return CZTop::Message.new( payload )
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
### Return the header and body from the TreeAPI request or response in the specified +msg+
|
39
|
+
### (a CZTop::Message).
|
40
|
+
def self::decode( msg )
|
41
|
+
raw_message = msg.pop or raise Arborist::MessageError, "empty message"
|
42
|
+
|
43
|
+
parts = begin
|
44
|
+
MessagePack.unpack( raw_message )
|
45
|
+
rescue => err
|
46
|
+
raise Arborist::MessageError, err.message
|
47
|
+
end
|
48
|
+
|
49
|
+
raise Arborist::MessageError, 'not an Array' unless parts.is_a?( Array )
|
50
|
+
raise Arborist::MessageError,
|
51
|
+
"malformed message: expected 1-2 parts, got %d" % [ parts.length ] unless
|
52
|
+
parts.length.between?( 1, 2 )
|
53
|
+
|
54
|
+
header = parts.shift or
|
55
|
+
raise Arborist::MessageError, "no header"
|
56
|
+
self.check_header( header )
|
57
|
+
|
58
|
+
body = parts.shift
|
59
|
+
self.check_body( body )
|
60
|
+
|
61
|
+
return header, body
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
### Return a CZTop::Message containing a TreeAPI request with the specified
|
66
|
+
### +verb+ and +data+.
|
67
|
+
def self::request( verb, *data )
|
68
|
+
header = data.shift || {}
|
69
|
+
body = data.shift
|
70
|
+
|
71
|
+
header.merge!( action: verb )
|
72
|
+
|
73
|
+
return self.encode( header, body )
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
### Build an error response message for the specified +category+ and +reason+.
|
78
|
+
def self::error_response( category, reason )
|
79
|
+
return self.encode({ category: category, reason: reason, success: false })
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
### Build a successful response with the specified +body+.
|
84
|
+
def self::successful_response( body )
|
85
|
+
return self.encode({ success: true }, body )
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
### Check the given +header+ for validity, raising an Arborist::MessageError if
|
90
|
+
### it isn't.
|
91
|
+
def self::check_header( header )
|
92
|
+
raise Arborist::MessageError, "header is not a Map" unless
|
93
|
+
header.is_a?( Hash )
|
94
|
+
version = header['version'] or
|
95
|
+
raise Arborist::MessageError, "missing required header 'version'"
|
96
|
+
raise Arborist::MessageError, "unknown protocol version %p" % [version] unless
|
97
|
+
version == PROTOCOL_VERSION
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
### Check the given +body+ for validity, raising an Arborist::MessageError if it
|
102
|
+
### isn't.
|
103
|
+
def self::check_body( body )
|
104
|
+
unless body.is_a?( Hash ) ||
|
105
|
+
body.nil? ||
|
106
|
+
( body.is_a?(Array) && body.all? {|obj| obj.is_a?(Hash) } )
|
107
|
+
self.log.error "Invalid message body: %p" % [ body]
|
108
|
+
raise Arborist::MessageError, "body must be Nil, a Map, or an Array of Maps"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
end # class Arborist::TreeAPI
|
113
|
+
|